diff --git a/ru/04.4.md b/ru/04.4.md
index 06bbd502..41578519 100644
--- a/ru/04.4.md
+++ b/ru/04.4.md
@@ -1,6 +1,6 @@
# 4.4 Дублирование отправки
-Не знаю, встречалось ли Вам, что на каком-либо блоге или форуме размещено несколько постов подряд с одинаковым содержимым, но я могу сказать Вам, что это происходит по причине того, что отправка постов дублируется пользователем. Это может произойти по многим причинам; иногда пользователь отправляет форму двойным щелчком, или он после отправки решает исправить содержимое поста и нажимает кнопку браузера "Назад". А иногда это - намереные действия злоумышленников. Понятно, что дублирование отправки может привести ко многим проблемам. Поэтому нам нужно принимать эффективные меры для его предотвращения.
+Не знаю, встречали ли Вы, как на каком-либо блоге или форуме размещено несколько постов подряд с одинаковым содержимым, но я могу сказать Вам, что это происходит по причине того, что отправка постов дублируется пользователем. Это может произойти по многим причинам; иногда пользователь отправляет форму двойным щелчком, или он после отправки решает исправить содержимое поста и нажимает кнопку браузера "Назад". А иногда это - намереные действия злоумышленников. Понятно, что дублирование отправки может привести ко многим проблемам. Поэтому нам нужно принимать эффективные меры для его предотвращения.
Решением этой задачи является добавление в форму скрытого поля с уникальным токеном и проверка этого токена перед перед обработкой введенных данных. А если для отправки формы Вы используете Ajax, можно после того, как данные отправлены, сделать кнопку отправки неактивной.
@@ -14,7 +14,7 @@
-Для того, чтобы сгенерировать токен, мы используем хэш MD5 (временная отметка), и добавляем его как в скрытое поле формы ввода данных на стороне клиента, так и в сессионный куки на стороне сервера (Раздел 6). Мы можем использовать этот токен для того, чтобы проверить, отправлялись ли уже с этой формы данные.
+Для того, чтобы сгенерировать токен, мы используем хэш MD5 (временная отметка), и добавляем его как в скрытое поле формы ввода данных на стороне клиента, так и в сессионный куки на стороне сервера (см. Раздел 6). Мы можем использовать этот токен для того, чтобы проверить, отправлялись ли уже данные с этой формы:
func login(w http.ResponseWriter, r *http.Request) {
fmt.Println("method:", r.Method) // получаем метод запроса
@@ -46,7 +46,7 @@
Рисунок 4.4 Содержимое браузера после добавления токена
-Если обновлять страницу, можно видеть каждый раз новый токен. Этот факт обеспечивает то, что каждая форма уникальна.
+Если обновлять страницу, можно видеть каждый раз новый токен. Это обеспечивает то, что каждая форма уникальна.
На данный момент Вы можете предотвращать множество атак на основе дублирования отправки посредством добавления в формы токенов, но все атаки такого типа предотвратить таким образом нельзя. Для этого нужно проделать еще больше работы.
diff --git a/ru/04.5.md b/ru/04.5.md
new file mode 100644
index 00000000..6aa48a09
--- /dev/null
+++ b/ru/04.5.md
@@ -0,0 +1,168 @@
+# 4.5 Загрузка файлов
+
+Предположим, у Вас есть веб-сайт наподобие Instagram, и Вы хотите, чтобы пользователи закачивали туда свои фортографии. Как можно реализовать эту функцию?
+
+Для этого нужно добавить в форму, через которую будут закачиваться фотографии, свойство `enctype`. Оно имеет три значения:
+
+```
+application/x-www-form-urlencoded Кодировать все символы перед закачкой (по умолчанию).
+multipart/form-data Не кодировать. Если в форме есть функционал закачки файлов, Вы должны использовать это значение.
+text/plain Конвертировать пробелы в "+", но не кодировать специальные символы.
+```
+
+
+Поэтому, содержимое HTML формы для загрузки файлов должно выглядеть так:
+
+```
+
+
+ Загрузка файлов
+
+
+
+
+
+```
+
+
+Для работы с этой формой мы должны добавить функцию на сервере:
+
+```
+http.HandleFunc("/upload", upload)
+
+// обработка закачки
+func upload(w http.ResponseWriter, r *http.Request) {
+ fmt.Println("Метод:", r.Method)
+ if r.Method == "GET" {
+ crutime := time.Now().Unix()
+ h := md5.New()
+ io.WriteString(h, strconv.FormatInt(crutime, 10))
+ token := fmt.Sprintf("%x", h.Sum(nil))
+
+ t, _ := template.ParseFiles("upload.gtpl")
+ t.Execute(w, token)
+ } else {
+ r.ParseMultipartForm(32 << 20)
+ file, handler, err := r.FormFile("uploadfile")
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ defer file.Close()
+ fmt.Fprintf(w, "%v", handler.Header)
+ f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ defer f.Close()
+ io.Copy(f, file)
+ }
+}
+```
+
+
+Как Вы можете видеть, для загрузки файлов нужно вызвать функцию `r.ParseMultipartForm`. Эта функция имеет аргумент `maxMemory`. После вызова `ParseMultipartForm` файл будет сохранен в памяти сервера с размером `maxMemory`. Если размер файла больше, чем `maxMemory`, остальная часть данных будет сохранена во временном файле в системе. Вы можете использовать `r.FormFile` для того, чтобы работать с файлом, и `io.Copy` для того, чтобы сохранить файл в файловой системе.
+
+Для того, чтобы получить доступ к другим полям формы, не относящимся к загрузке файлов, Вам не нужно вызывать `r.ParseForm`, так как Go вызовет эту функцию, когда понадобится. Также, вызов `ParseMultipartForm` один раз достаточен - многократные вызовы ничего не меняют.
+
+Для загрузки файлов мы используем следующие три шага:
+
+1. Добавить в форму `enctype="multipart/form-data"`.
+2. Вызвать на стороне сервера `r.ParseMultipartForm`, чтобы сохранить файл в память или во временный файл.
+3. Вызвать `r.FormFile` для обработки файла и сохранения его в файловую систему.
+
+Хэндлером для файла является `multipart.FileHeader`. У него следующая структура:
+
+```
+type FileHeader struct {
+ Filename string
+ Header textproto.MIMEHeader
+ // соджержит отфильтрованные или неэкспортируемые поля
+}
+```
+
+
+
+Рисунок 4.5 Вывод информации на сервере после получения файла
+
+## Загрузка файлов клиентом
+
+Я показал Вам пример, как можно использовать форму для загрузки файлов. Мы можем сделать так, чтобы загружать файлы через форму без участия человека:
+
+```
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "mime/multipart"
+ "net/http"
+ "os"
+)
+
+func postFile(filename string, targetUrl string) error {
+ bodyBuf := &bytes.Buffer{}
+ bodyWriter := multipart.NewWriter(bodyBuf)
+
+ // этот шаг очень важен
+ fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)
+ if err != nil {
+ fmt.Println("ошибка записи в буфер")
+ return err
+ }
+
+ // процедура открытия файла
+ fh, err := os.Open(filename)
+ if err != nil {
+ fmt.Println("ошибка открытия файла")
+ return err
+ }
+
+ //iocopy
+ _, err = io.Copy(fileWriter, fh)
+ if err != nil {
+ return err
+ }
+
+ contentType := bodyWriter.FormDataContentType()
+ bodyWriter.Close()
+
+ resp, err := http.Post(targetUrl, contentType, bodyBuf)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+ resp_body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+ fmt.Println(resp.Status)
+ fmt.Println(string(resp_body))
+ return nil
+}
+
+// пример использования
+func main() {
+ target_url := "http://localhost:9090/upload"
+ filename := "./astaxie.pdf"
+ postFile(filename, target_url)
+}
+```
+
+
+Этот пример показывает, как можно использовать клиента для загрузки файлов. Он использует `multipart.Write` для того, чтобы записывать файлы в кэш, и посылает их на сервер посредством метода POST.
+
+Если у Вас есть другие поля, которые нужно писать в данные, такие, как имя пользователя, вызывайте `multipart.WriteField` по необходимости.
+
+## Ссылки
+
+- [Содержание](preface.md)
+- Предыдущий раздел: [Дублирование отправки](04.4.md)
+- Следующий раздел: [Итоги раздела](04.6.md)
diff --git a/ru/04.6.md b/ru/04.6.md
new file mode 100644
index 00000000..9dd42932
--- /dev/null
+++ b/ru/04.6.md
@@ -0,0 +1,11 @@
+# 4.6 Итоги раздела
+
+В этом разделе мы изучили основные моменты того, как работать с данными в Go, посредством нескольких примеров, таких как вход пользователей и загрузка файлов. Мы также заострили внимание на том, что проверка данных крайне важна для безопасности сайта, а также посвятили одну секцию тому, как фильтровать входные данные посредством регулярных выражений.
+
+Я надеюсь, что теперь Вы больше знаете о процессе коммуникации между клиентом и сервером.
+
+## Ссылки
+
+- [Содержание](preface.md)
+- Предыдущий раздел: [Загрузка файлов](04.5.md)
+- Следужщий раздел: [Базы данных](05.0.md)