diff --git a/README.md b/README.md
index 4fdf243..290e580 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
1. [Основы языка C++](labs/lab01)
2. [Система контроля версий Git](labs/lab02)
3. [Структурирование программ](labs/lab03)
-4. Использование библиотек
+4. [Использование библиотек](labs/lab04)
## Ресурсы
diff --git a/labs/lab04/README.md b/labs/lab04/README.md
new file mode 100644
index 0000000..47442aa
--- /dev/null
+++ b/labs/lab04/README.md
@@ -0,0 +1,814 @@
+# Использование библиотек
+
+## Цель работы
+
+1. Уметь устанавливать и подключать к программе внешние библиотеки.
+1. Уметь использовать типовые элементы API библиотек.
+1. Уметь работать с параметрами командной строки программы.
+
+## Задание
+
+Добавить возможность построения гистограммы по данным из файла в сети.
+Адрес файла задается аргументом командной строки программы.
+Если адрес не задан, читать данные со стандартного ввода, как раньше.
+
+Пример запуска программы с загрузкой данных из файла на сайте кафедры:
+
+```
+lab34.exe http://uit.mpei.ru/study/courses/cs/lab03/marks.txt >marks.svg
+```
+
+Работу нужно вести на основе кода общего задания к ЛР № 3 в прежнем репозитарии.
+По этой причине во всех примерах используется `lab34.exe`.
+
+Защита представляет собой демонстрацию выполненного индивидуального задания,
+ответы на вопросы по коду и по релевантной теории (см. контрольные вопросы).
+
+## Указания
+
+Скачивать файл по сети будем с помощью библиотеки [cURL](https://curl.haxx.se).
+
+Получать сведения о системе будем с помощью API операционной системы.
+Это тоже внешняя библиотека, но её не требуется устанавливать.
+
+### Ввод из произвольного потока
+
+Рефакторинг (refactoring) — это изменение кода программы
+без изменения её функциональных характеристик
+(интересующего пользователя поведения).
+
+Зачем менять код, если это не добавляет возможностей и не исправляет ошибок?
+Например, для того, чтобы облегчить дальнейшее внесение изменений в него.
+В нашем случае программа жестко завязана на то,
+что данные поступают со стандартного ввода.
+Ожидается, что они смогут поступать не только оттуда.
+Код нужно переписать так, чтобы он мог работать с любым входящим потоком.
+Тогда текущее поведение будет частным случаем нового,
+когда поток — стандартный ввод (`cin`).
+
+
+
+Что такое «работать с потоком ввода»?
+Рассмотрим любой пример работы со стандартным потоком ввода:
+
+```cpp
+cin >> number_count;
+```
+
+Здесь есть переменная `cin`, которая представляет поток, и операция `>>` с ней.
+Работать с другим потоком значит использовать иную переменную вместо `cin`.
+
+Что такое «работать с любым потоком», если поток — это переменная в программе?
+Это значит, в программе есть блок кода для ввода данных,
+который принимает переменную-поток и работает с ней.
+Но параметризованный блок кода — это же функция!
+Вводом данных занимается функция `input_data()`.
+Следовательно, у нее должен появится параметр — поток, из которого читать.
+
+```cpp
+Input
+input_data(istream& in) {
+ // Прежний код, но работающий с in вместо cin.
+}
+```
+
+Это должна быть именно ссылка, потому что нельзя скопировать поток ввода —
+он объективно один во всей программе.
+Это не может быть константная ссылка, потому что функция меняет поток:
+данные после вычитывания оттуда «исчезают».
+
+Вызов функции будет выглядеть так:
+
+```cpp
+auto in = input_data(cin);
+```
+
+**Самостоятельно.**
+Перепишите функцию `input_data()` и ее вызов, как предложено.
+Убедитесь, что поведение программы не изменилось.
+Сделайте коммит.
+
+**Самостоятельно.**
+Добавьте `input_data()` новый параметр: `bool prompt`.
+Если он равен `true`, то нужно выводить в `cerr` подсказки,
+если `false`, то не нужно их выводить.
+Сделайте коммит.
+
+### Загрузка данных по сети
+
+#### Установка cURL
+
+Страница библиотеки cURL: .
+
+На этой странице есть раздел *Download* со ссылкой на страницу загрузок:
+
+
+
+Вариантов загрузки очень много, потому что cURL собирается под много
+операционных систем (Windows, Linux, различные Unix-системы), архитектур
+процессора (Intel/AMD, ARM и других) и их вариантов (32 или 64 бита).
+Windows находится в конце списка:
+
+
+
+Для Windows следует выбрать ссылку с описанием `binary the curl project`:
+Странице загрузок для Windows — ссылки, чтобы скачать архив для системы нужной
+разрядности. Как выбрать разрядность? Это зависит не от версии Windows,
+а от того, собирает компилятор 32-битные или 64-битные программы. По умолчанию
+с CodeBlocks идет компилятор 32-битных программ mingw32, он же устанавливается
+по инструкции для РПОСУ. Используемый компилятор можно увидеть в *Build log*
+при сборке программы. Если там mingw64, значит, компилятор 64-битный.
+
+
+
+В архиве находится папка `curl-7.69.1-win32-mingw`
+(версия cURL и разрядность компилятора та, которая выбрана для загрузки).
+Ее нужно извлечь,
+переименовать в `curl` для удобства
+и переместить в каталог проекта.
+
+**Самостоятельно.**
+Занесите эту папку в `.gitignore`.
+Проверьте, что сделали это правильно, с помощью команды `git status`.
+
+Из папки `curl` далее понадобятся:
+
+* `bin/libcurl.dll` — динамическая библиотека (`libcurl-x64.dll` для 64 бит);
+* `lib` — каталог со статическими библиотеками;
+* `include` — каталог с заголовочными файлами.
+
+**Примечание.**
+Есть инструменты, которые позволяют скачивать и устанавливать библиотеки
+из командной строки, для Windows это *Chocolatey* и *vcpkg.*
+Они автоматизируют и стандартизируют то,
+что в рамках этой ЛР делается вручную, чтобы разобраться.
+
+#### Подключение cURL к проекту
+
+Каждая программа должна до первого вызова функций библиотеки cURL вызвать
+функцию [`curl_global_init()`][curl_global_init].
+Например, это можно сделать первой строкой в `main()`:
+
+```cpp
+curl_global_init(CURL_GLOBAL_ALL);
+```
+
+При попытке собрать программу возникнут ошибки, текст которых означает,
+что константа `CURL_GLOBAL_ALL` и функция `curl_global_init()` не объявлены,
+то есть неизвестны компилятору:
+
+```
+error: 'CURL_GLOBAL_ALL' was not declared in this scope
+error: 'curl_global_init' was not declared in this scope
+```
+
+Если открыть документацию по ссылке, она начинается так:
+
+> SYNOPSIS
+>
+> #include
+>
+> CURLcode curl_global_init(long flags);
+
+«Synopsis» значит «сводка» (отсюда слово «синоптик»), это типовой раздел
+справки к библиотекам, в котором кратко показывается, как функция объявлена
+и какой заголовочный нужно подключить, чтобы ее использовать. В данном случае
+нужно добавить в начало `main.cpp`:
+
+```cpp
+#include
+```
+
+При компиляции все равно будет ошибка, но другая — что файл не найден:
+
+```
+fatal error: curl/curl.h: No such file or directory
+```
+
+Как известно из лекций, `#include <...>` ищет файл в неких «стандартных
+каталогах», куда папка `include` из архива не входит. Чтобы добавить её
+в список, нужно открыть диалог *Project → Build options...* и перейти
+во вкладку *Search directories.* Там три дочерних вкладки: *Compile*
+(пути для поиска `#include <...>`), *Linker* (пути для поиска библиотек)
+и *Resource compiler* (не требуется). Нужно нажать кнопку *Add,* в открывшемся
+диалоге указать `curl/include` и нажать *OK* (путь к папке можно также
+выбрать через диалог рядом с полем ввода). Каталог `curl/include` добавится
+в список, диалог можно закрыть кнопкой *OK.*
+
+**Внимание.**
+Рекомендуется вводить пути и имена файлов в настройках как текст,
+а не выбирать их с помощью диалогов.
+
+Ошибка сборки изменится:
+
+```
+undefined reference to `__imp_curl_global_init'
+```
+
+Обратите внимание, что это уже ошибка не компиляции, а компоновки (линкера).
+Исходный текст программы правильный, все заголовочные файлы найдены,
+появился `obj/Debug/main.o`. Но чтобы создать из него исполняемый файл
+не хватает машинного кода функции `curl_global_init()`.
+
+Чтобы подключить библиотеку, нужно два действия:
+
+1. Вернуться к *Search directories,* теперь на дочернюю вкладку *Linker.*
+ Там нужно добавить каталог `curl/lib` аналогично предыдущему шагу.
+
+1. Указать, какую именно библиотеку подключить. Если просмотреть каталог
+ `curl/lib`, там есть `libcurl.a` и `libcurl.dll.a`. Сейчас выберем
+ `libcurl.dll.a`, разница будет пояснена в последующих пунктах.
+ На вкладке *Linker settings* (соседняя с *Search directories)* нужно
+ нажать *Add* и ввести `libcurl.dll.a`, после чего сохранить настройки
+ и покинуть диалог нажатиями *OK.*
+
+Если все сделано правильно, сборка должна проходить успешно.
+
+[curl_global_init]: https://curl.haxx.se/libcurl/c/curl_global_init.html
+
+
+#### Установка динамических библиотек
+
+Перейдите в каталог `bin/Debug`, откройте там консоль и запустите программу:
+
+```cmd
+lab34.exe
+```
+
+Будет ошибка (главное в ней — имя библиотеки `libcurl….dll`):
+
+
+
+Дело в том, что программа собрана с использованием *динамической* библиотеки.
+В `lab34.exe` нет кода cURL, а только указание загрузить `libcurl-x64.dll`,
+где этот код есть, и вызвать его. Но библиотеки нет в тех каталогах,
+где Windows ищет библиотеки. Одним из таких каталогов является текущий.
+Скопируйте `libcurl-x64.dll` в `bin/Debug` и попробуйте запустить программу
+(`libcurl.dll` для 32 бит).
+
+**Самостоятельно.**
+Убедитесь, что программа запускается. Сделайте коммит.
+
+---
+
+**Врезка отсюда до горизонтальной черты не нужна для новых версий cURL.
+Если после размещения `libcurl….dll` в `bin/Debug` программа запускается,
+пропустите это дополнение.**
+
+Ошибка изменится, теперь не найдены будут `libcrypto-1_1.dll`
+и `libssl-1_1.dll`, но их уже нет в поставке cURL. В интернете можно узнать,
+что эти библиотеки из набора OpenSSL, нужного cURL для установки защищенных
+соединений. Откуда их взять?
+
+Разумеется, в интернете найдется всё:
+
+
+
+**Никогда не устанавливайте библиотеки из непроверенных источников!**
+
+Чтобы установить недостающие библиотеки, нужно посетить официальный сайт,
+как правило, там есть ссылки для загрузки, и им можно доверять. Кроме того,
+даже если скачанная библиотека будет подлинной (безопасной), раскладывать
+отдельные библиотеки вручную ненадежно. Через OpenSSL wiki можно найти
+[сайт с готовыми сборками](https://slproweb.com/products/Win32OpenSSL.html).
+
+Скачайте установочный пакет (MSI) по ссылке в первой или третьей строке
+в зависимости от разрядности системы:
+
+**Внимание.**
+Разрядность cURL и OpenSSL должны совпадать.
+
+В процессе установки:
+
+
+
+* При выборе *Copy OpenSSL DLLs to:* нужно выбрать *The Windows system
+ directory,* чтобы библиотеки были доступны при запуске программы
+ из любого каталога.
+* На последнем экране автор сборки предлагает поддержать его труд 10 \$. Можно
+ просто снять флажок и продолжить. OpenSSL распространяется бесплатно.
+
+---
+
+#### Статическая сборка программы
+
+Попробуем собрать программу статически. Чтобы поменять компоновку cURL
+на статическую, необходимо:
+
+1. Вернуться в *Linker settings,* выбрать в списке `libcurl.dll.a`,
+ нажать *Edit,* изменить имя на `libcurl.a` и нажать *OK.*
+
+2. Перейти на вкладку *Compiler settings* (рядом с *Linker settings),*
+ выбрать дочернюю вкладку *#defines.*
+ Там в текстовое поле нужно ввести `CURL_STATICLIB`
+ и выйти из диалога кнопкой *OK.*
+
+**Примечание.**
+Последний шаг специфичен для cURL, аналогичный шаг для других библиотек
+обычно требуется только на Windows.
+
+При сборке будет множество ошибок:
+
+```
+curl\lib\libcurl.a(connect.o):(.text+0x1d1): undefined reference to `__imp_ntohs'
+curl\lib\libcurl.a(connect.o):(.text+0x2ee): undefined reference to `__imp_getpeername'
+curl\lib\libcurl.a(connect.o):(.text+0x2f8): undefined reference to `__imp_WSAGetLastError'
+...
+```
+
+Причина в том, что код cURL использует функции из других библиотек,
+поэтому программа должна линковаться с ними всеми.
+Для этого нужно определять,
+из какой библиотеки недостающая функция (обычно поиском в интернете),
+устанавливать эту библиотеку (если она не системная),
+добавлять её в *Linker settings* и пробовать собрать снова.
+В рамках ЛР этого делать не нужно.
+На практике у зависимостей основной библиотеки могут быть другие лицензии,
+которые придется принимать во внимание.
+
+**Самостоятельно.**
+Удалите все изменения, сделанные в попытках собрать программу статически,
+при помощи git.
+
+
+#### Получение аргументов программы
+
+Из ЛР № 1 известно, что программа может запускаться с аргументами,
+которые перечисляются после ее имени через пробел:
+`fc /N expected.txt actual.txt`.
+Здесь `fc` (`fc.exe`) — исполняемый файл программы,
+а `/N`, `expected.txt` и `actual.txt` — три аргумента.
+Перенаправление ввода-вывода не является аргументом, например,
+команда `lab01.exe NUL` (скачайте `marks.txt`)
+
+**Примечание.**
+Необязательные аргументы,
+которые имеют фиксированные имена (например, `/N` у `fc`),
+иногда называют опциями.
+Имена опций принято начинать с `-`, `--` или `/` (только в Windows).
+Остальные аргументы называют *позиционными.*
+
+#### Загрузка файла по сети
+
+Весь код в этом пункте должен выполняться только в случае `argc > 1`.
+
+Для простых сценариев cURL имеет набор easy-функций, которые позволяют
+скачать файл всего в несколько строк. Первой должна быть вызвана
+[`curl_easy_init()`][curl_easy_init]:
+
+[curl_easy_init]: https://curl.haxx.se/libcurl/c/curl_easy_init.html
+
+```cpp
+CURL* curl = curl_easy_init();
+```
+
+Что за тип данных `CURL*`? Это так называемый *дескриптор (handle).*
+Очень часто библиотека оперирует сложными объектами,
+внутреннее устройство которых программе знать не нужно,
+но с другой стороны, программа должна сообщать функциям библиотеки,
+с каким объектом работать.
+В случае cURL таким объектом выступает сетевое соединение и связанные данные.
+Единственное, что можно делать с дескрипторами —
+передавать их функциям библиотеки и сравнивать между собой.
+Переменную типа `CURL*` нельзя разыменовать, хотя это и указатель.
+
+Как правило, если есть функция создания дескриптора, как `curl_easy_init()`,
+есть и функция его освобождения (закрытия).
+Действительно, документация гласит:
+
+> This call MUST have a corresponding call to curl_easy_cleanup when the
+> operation is complete.
+
+Это означает, что вся работа с `curl` должна делаться
+между написанной строкой и следующим вызовом:
+
+```cpp
+curl_easy_cleanup(curl);
+```
+
+**Самостоятельно.**
+Изучите пример (раздел *EXAMPLE*) документации на `curl_easy_init()`.
+Перенесите пример в программу, но вместо фиксированного адреса
+используйте первый аргумент командной строки (с индексом 1).
+Убедитесь, что программа работает —
+содержимое файла из сети печатается на стандартный вывод.
+Код печати `argc` и `argv` удалите.
+Сделайте коммит.
+
+Структура программы должна стать такой:
+
+```cpp
+int
+main(int argc, char* argv[]) {
+ if (argc > 1) {
+ // работа с cURL
+ return 0;
+ }
+ // прежний код построения гистограммы
+}
+```
+
+#### Обработка ошибок
+
+В переменную `res` пример сохраняет результат `curl_easy_perform()`.
+Значение `res` — числовой [код ошибки][libcurl-errors],
+а если ошибок не было, `CURLE_OK` (равно 0).
+Функция [`curl_easy_strerror()`][curl_easy_strerror]
+принимает код ошибки и возвращает строку с текстом ошибки.
+
+**Самостоятельно.**
+Проверяйте результат `curl_easy_perform()`, если произошли ошибка,
+печатайте её текст и завершайте программу вызовом `exit(1)`. Сделайте коммит.
+
+[libcurl-errors]: https://curl.haxx.se/libcurl/c/libcurl-errors.html
+[curl_easy_strerror]: https://curl.haxx.se/libcurl/c/curl_easy_strerror.html
+
+#### Работа с буфером, загруженным по сети
+
+Содержимое файла из сети нужно не печатать,
+а разбирать, считывая из него те же данные, что и со стандартного ввода.
+Проще всего сначала считать весь файл из сети в память.
+Такое промежуточное хранилище называют *буфером*.
+Затем можно организовать поток,
+который будет читать не из стандартного ввода, а из буфера.
+
+Для загрузки файла выделим функцию `download()`,
+которая принимает строку с адресом файла.
+Файл `` подключает `stringstream` —
+поток, который может читать из буфера в памяти.
+Также в переменные типа `stringstream` можно записывать данные,
+то есть заполнять буфер.
+
+```cpp
+#include
+#include
+
+// ...прежние функции...
+
+Input
+download(const string& address) {
+ stringstream buffer;
+
+ // TODO: заполнить буфер.
+
+ return input_data(buffer, false);
+}
+```
+
+Обратите внимание: `CURLOPT_URL` требует адрес в виде строки C (`const char*`).
+Чтобы получить её из `address` типа `string` можно как `address.c_str()`.
+
+В зависимости от того, переданы ли программе параметры,
+она получает исходные данные (`Input input`)
+либо из сети, либо из стандартного ввода:
+
+```cpp
+int
+main(int argc, char* argv[]) {
+ Input input;
+ if (argc > 1) {
+ input = download(argv[1]);
+ } else {
+ input = input_data(cin, true);
+ }
+
+ const auto bins = make_histogram(input);
+ show_histogram_svg(bins);
+}
+```
+
+**Самостоятельно.**
+Перенесите код работы с cURL в место, отмеченное комментарием `// TODO`.
+Убедитесь, что программа компилируется
+(запускать ее с параметром пока нельзя).
+
+#### Сохранение данных из сети в буфер
+
+Как же получить доступ к данным, которые cURL загружает и (пока) печатает?
+Каждый раз, когда cURL получает из сети очередной блок данных,
+библиотека может вызывать функцию, которую укажет программа.
+Эта функция должна добавлять полученный блок данных в буфер.
+
+Такой элемент интерфейса библиотеки называется *callback-функцией,*
+или *функцией обратного вызова.*
+
+Функция должна иметь такой вид:
+
+```cpp
+size_t
+write_data(void* items, size_t item_size, size_t item_count, void* ctx) {
+ // TODO: дописывать данные к буферу.
+ return 0;
+}
+```
+
+Имя функции и параметров может быть любым,
+но типы данных должны быть строго такими, как описано в документации.
+Ошибку может быть очень сложно отладить.
+К счастью, компилятор предупреждает о несоответствии.
+
+Параметры:
+
+* `void* items` — указатель на принятые данные: `item_count` блоков
+ по `item_size` каждый, итого `item_size * item_count` байтов.
+* `size_t item_size` — размер одного блока данных;
+* `size_t item_count` — количество блоков данных;
+* `void* ctx` — пользовательские данные (поясняется ниже).
+
+Чтобы cURL продолжила загрузку,
+функция должна возвращать размер принятых данных в байтах,
+иначе `curl_easy_perform()` остановит работу с ошибкой.
+Пока функция возвращает 0, просто чтобы возвращать какое-нибудь значение.
+
+Указать cURL, какую функцию вызывать, можно так:
+
+```cpp
+curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
+```
+
+**Самостоятельно.**
+Внутри `write_data()` вычислите количество принятых байтов (см. описание
+параметров), сохраните в переменную `data_size`.
+
+Далее необходимо внутри `write_data()` добавлять данные в буфер, для этого
+есть специальный метод [`buffer.write()`][basic_ostream/write]:
+
+```cpp
+ buffer.write(items, data_size);
+```
+
+Но это не компилируется: переменная `buffer` объявлена в `download()`, внутри
+`write_data()` она недоступна.
+Перенести `buffer` внутрь `write_data()` — не выход:
+`buffer` нужна и в `download()`.
+Объявлять в `write_data()` еще один буфер тоже неправильно:
+это должен быть *тот же самый* буфер, который затем использует `download()`.
+
+Заметим, что хотя `buffer` недоступна, она существует где-то в памяти,
+потому что `write_data()` вызывается из `curl_easy_perform()`,
+и пока этот вызов не закончен, не закончится и `download()`.
+Задача в том, чтобы получить этот адрес,
+так как имея адрес переменной (то есть указатель на нее), с ней можно работать.
+
+Это типовая необходимость и обычно библиотеки, в том числе cURL,
+предоставляют в callback-функциях *пользовательские данные (user data).*
+Перед тем, как позволить библиотеке вызывать функцию,
+программа указывает также фиксированные данные (обычно один указатель),
+которые будут передаваться библиотекой в функцию.
+Именно для этого `write_data()` принимает параметр `ctx` (контекст).
+Можно передавать в этом контексте адрес `buffer` так
+(перед `curl_easy_perform()`):
+
+```cpp
+curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
+```
+
+Контекст имеет тип `void*`, но нам известно, что это указатель на `buffer`,
+то есть реальный тип — `stringstream*`.
+Это как раз тот случай, когда программист может и должен
+принудительно указать компилятору тип данных (приведение типов из ЛР № 1):
+
+```cpp
+stringstream* buffer = reinterpret_cast(ctx);
+```
+
+Поскольку `buffer` здесь указатель,
+его нужно разыменовать перед вызовом метода:
+
+```cpp
+(*buffer).write(items, data_size);
+```
+
+Осталась последняя ошибка: если прочитать документацию на `write()`,
+она принимает первым параметром `const char*`, а `items` имеет тип `void*`.
+
+**Самостоятельно.**
+Сделайте приведение типа для `items`, чтобы программа компилировалась.
+
+Конструкция `(*buffer).write(...)` выглядит громоздко, потому в C++ введен
+специальный оператор стрелки, который позволяет вызвать метод по указателю
+красивее (параметры `write()` прежние, они опущены для краткости):
+
+```cpp
+buffer->write(...);
+```
+
+**Самостоятельно.**
+Добейтесь компиляции и проверьте корректную работу программы с примером
+из задания.
+
+[basic_ostream/write]: https://en.cppreference.com/w/cpp/io/basic_ostream/write
+
+### Индивидуальные задания
+
+Всю необходимую информацию по функциям cURL нужно изучить по официальной
+документации и на защите предъявить ссылку на конкретную страницу.
+В частности, если не указан конкретный параметр `curl_easy_setopt()`,
+нужно подобрать его по описанию в списке.
+
+Во всех вариантах нужно обрабатывать ошибки cURL,
+как это делается в основной программе.
+
+Если вариант требует ввода дополнительных данных, проверить работу с файлом
+«из сети» можно через локальный файл и адрес вида
+`file://C:/Users/kozlyuk/Desktop/marks.txt` для файла `marks.txt` на рабочем
+столе пользователя `kozlyuk`. Полный путь к файлу можно получить, щелкнув
+в адресной строке «Проводника» (так же, как перед запуском `cmd`).
+
+
+#### Вариант 1 {.unnumbered}
+
+
+С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок
+суммарное время (total), затраченное на загрузку файла по сети.
+
+
+#### Вариант 2 {.unnumbered}
+
+
+Добавьте программе опцию `-format`, которая определяет формат вывода:
+`lab34.exe -format text` — текст,
+`lab34.exe -format svg` — изображение.
+Опция может стоять до или после URL:
+`lab34.exe -format svg http://...` или `lab04.exe http://... -format svg`.
+Если после `-format` не стоит `text` или `svg`, нужно печатать сообщение
+с подсказкой, как запускать программу, и завершать работу.
+
+
+#### Вариант 3 {.unnumbered}
+
+
+С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок
+скорость загрузки файла по сети.
+
+
+#### Вариант 4 {.unnumbered}
+
+
+Добавьте программе опцию `-bins`, которая позволяет задать количество
+корзин при запуске. Считывать его в `read_input()` при этом не нужно.
+Например: `lab34.exe -bins 3`.
+Опция может стоять до или после URL:
+`lab34.exe -bins 3 http://...` или `lab04.exe http://... -bins 3`.
+Если после `-bins` не стоит числа, нужно печатать сообщение с подсказкой,
+как запускать программу, и завершать работу.
+
+
+#### Вариант 5 {.unnumbered}
+
+
+Добавьте программе опцию `-generate`, при указании которой программа будет
+генерировать числа вместо их считывания. Количество столбцов считывать
+по-прежнему нужно. Например, `lab34.exe -generate 100` генерирует 100 чисел.
+Опция может стоять до или после URL:
+`lab34.exe -generate 10 http://...` или `lab04.exe http://... -generate 10`.
+Если после `-generate` не стоит числа, нужно печатать сообщение с подсказкой,
+как запускать программу, и завершать работу.
+
+
+#### Вариант 6 {.unnumbered}
+
+
+С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок
+размер файла, загружаемого по сети.
+
+
+#### Вариант 7 {.unnumbered}
+
+
+
+При помощи `CURLOPT_PROGRESSFUNCTION` добавьте в программу отображение
+прогресса загрузки файла. Каждый раз, когда cURL рапортует прогресс,
+печатайте строку вида: `Progress: 42%`.
+
+**Указание.**
+Чтобы cURL рапортовала прогресс, нужно установить `CURLOPT_NOPROGRESS` в 0.
+
+
+#### Вариант 8 {.unnumbered}
+
+
+С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок
+IP-адрес сервера, с которого скачан файл (не local).
+
+
+#### Вариант 9 {.unnumbered}
+
+
+С помощью функции `curl_version_info()` печатайте на стандартный вывод ошибок
+версию cURL и версию OpenSSL в строковом виде.
+
+
+#### Вариант 10 {.unnumbered}
+
+
+Добавьте программе опцию `-verbose`, при указании которой нужно включать
+отладочный вывод cURL с помощью `CURLOPT_VERBOSE`.
+Опция может стоять до или после URL:
+`lab34.exe -verbose http://...` или `lab04.exe http://... -verbose`.
+Если встретился иной аргумент, начинающийся с дефиса, нужно печатать сообщение
+с подсказкой, как запускать программу, и завершать работу.
+
+
+#### Вариант 11 {.unnumbered}
+
+
+С помощью функции `curl_version_info()` печатайте на стандартный вывод ошибок
+список протоколов, которые поддерживает cURL (`protocols`).
+
+
+#### Вариант 12 {.unnumbered}
+
+
+Добавьте программе опцию `-fill` для задания цвета заливки столбцов. Например,
+`lab34.exe -fill red` или `lab04.exe -fill "#ff0000"` заливает их красным.
+Опция может стоять до или после URL:
+`lab34.exe -fill red http://...` или `lab04.exe http://... -fill red`.
+Если после `-fill` не стоит еще одного аргумента, нужно печатать сообщение
+с подсказкой, как запускать программу, и завершать работу.
+
+
+#### Вариант 13 {.unnumbered}
+
+
+Добавьте программе опцию `-stroke` для задания цвета границ столбцов.
+Например, `lab34.exe -stroke red` или `lab04.exe -stroke "#ff0000"` делает их
+красными.
+Опция может стоять до или после URL:
+`lab34.exe -stroke red http://...` или `lab04.exe http://... -fill red`.
+Если после `-stroke` не стоит еще одного аргумента, нужно печатать сообщение
+с подсказкой, как запускать программу, и завершать работу.
+
+
+#### Вариант 14 {.unnumbered}
+
+
+С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок
+размер запроса, который cURL отправила серверу (request size).
+
+
+#### Вариант 15 {.unnumbered}
+
+
+Установите callback-функцию `CURLOPT_HEADERDATA`, откуда печатайте
+полученные данные на стандартный вывод ошибок.
+
+
+#### Вариант 16 {.unnumbered}
+
+
+С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок
+время, затраченное на установку соединения с сервером (connect).
+
+
+#### Вариант 17 {.unnumbered}
+
+
+Устанавливайте опцию `CURLOPT_FAILONERROR` и анализируйте результат
+`curl_easy_perform()`, чтобы проверить результат загрузки файла,
+а не выполнения запроса. Придумайте способ тестирования.
+
+
+#### Вариант 18 {.unnumbered}
+
+
+С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок
+время, затраченное на получение IP-адреса сервера по его имени (name lookup).
+
+
+## Контрольные вопросы
+
+1. Сколько аргументов в команде `git add -p file.txt`? Чему равно `argc`?
+1. Из каких файлов и как собирается программа, использующая библиотеку?
+1. Чем отличаются статические и динамические библиотеки?
+1. Как расшифровывается термин «API» и что представляет собой API библиотеки?
+1. Что такое дескриптор (handle), для чего он предназначен?
+1. Что такое функция обратного вызова (callback)?
+1. Зачем и как применяются пользовательские данные в callback-функциях?
diff --git a/labs/lab04/assets/curl-download.png b/labs/lab04/assets/curl-download.png
new file mode 100644
index 0000000..bc91dab
Binary files /dev/null and b/labs/lab04/assets/curl-download.png differ
diff --git a/labs/lab04/assets/curl-link.png b/labs/lab04/assets/curl-link.png
new file mode 100644
index 0000000..927282e
Binary files /dev/null and b/labs/lab04/assets/curl-link.png differ
diff --git a/labs/lab04/assets/curl-windows.png b/labs/lab04/assets/curl-windows.png
new file mode 100644
index 0000000..d780f33
Binary files /dev/null and b/labs/lab04/assets/curl-windows.png differ
diff --git a/labs/lab04/assets/dll-not-found.png b/labs/lab04/assets/dll-not-found.png
new file mode 100644
index 0000000..498d5ec
Binary files /dev/null and b/labs/lab04/assets/dll-not-found.png differ
diff --git a/labs/lab04/assets/flow.svg b/labs/lab04/assets/flow.svg
new file mode 100644
index 0000000..ca88e3f
--- /dev/null
+++ b/labs/lab04/assets/flow.svg
@@ -0,0 +1,22 @@
+
\ No newline at end of file
diff --git a/labs/lab04/assets/flow.uml b/labs/lab04/assets/flow.uml
new file mode 100644
index 0000000..37337c1
--- /dev/null
+++ b/labs/lab04/assets/flow.uml
@@ -0,0 +1,10 @@
+@startuml
+(*) -d-> [текстовый поток\n(стандартный ввод)] "Чтение данных"
+ -r-> [массив чисел (numbers)\nи количество корзин (bin_count)] "Расчет гистограммы"
+ -r-> [массив количеств\nчисел в корзинах (bins)] "Отображение гистограммы"
+ -d-> [текстовый поток\n(стандартный вывод)] (*)
+@enduml
+
+(*) -right--> [текст]
+"Чтение данных" --right--> [числа]
+"Расчет гистограммы" --right--> (*)
diff --git a/labs/lab04/assets/installer.png b/labs/lab04/assets/installer.png
new file mode 100644
index 0000000..b8c528c
Binary files /dev/null and b/labs/lab04/assets/installer.png differ
diff --git a/labs/lab04/assets/search.png b/labs/lab04/assets/search.png
new file mode 100644
index 0000000..7a16c6e
Binary files /dev/null and b/labs/lab04/assets/search.png differ