Repository: clj-commons/aleph (clojure)

Issue:
Multipart upload fails with aleph, works fine with clj-http
Hi,
I am using aleph 0.4.6 (also tried with 0.4.7-alpha1) and cannot get multipart uploads to work.
Below is a minimal code sample, the commented out part works fine using clj-http.
Browsing through the issues I found that this was merged circa 2018, so presumably it did work.
Could this be a peculiarity of the server I am trying to upload to (Hubspot) ?
Thanks,
Anuj

```clj
(require '[clj-http.client :as client])
(require '[aleph.http :as aleph])
(require '[clojure.java.io :as io])
(require '[cheshire.core :as json])

(let [column-mappings [{"ignored" false
                        "columnName" "Company ID"
                        "idColumnType" "HUBSPOT_OBJECT_ID"
                        "propertyName" nil
                        "foreignKeyType" nil
                        "columnObjectTypeId" "0-2"
                        "associationIdentifierColumn" false}
                       {"ignored" false
                        "columnName" "site_sessions_30d",
                        "idColumnType" nil
                        "propertyName" "site_sessions_30d",
                        "foreignKeyType" nil
                        "columnObjectTypeId" "0-2",
                        "associationIdentifierColumn" false}]
      import-request {"name" "test_import"
                      "files" [{"fileName" "TestData.csv"
                                "fileFormat" "CSV"
                                "fileImportPage" {"hasHeader" true
                                                  "columnMappings" column-mappings}}]}]
  @(aleph/post "https://api.hubapi.com/crm/v3/imports?hapikey=<<INSERT API KEY HERE>>"
               {:multipart [{:name "files"
                             :content (io/file "/home/anuj/TestData.csv")}
                            {:name "importRequest"
                             :content (json/generate-string import-request)}]})
  ;; (client/post "https://api.hubapi.com/crm/v3/imports?hapikey=<<INSERT API KEY HERE>>"
  ;;              {:multipart [{:name "files"
  ;;                            :content (io/file "/home/anuj/TestData.csv")}
  ;;                           {:name "importRequest"
  ;;                            :content (json/generate-string import-request)}]})
  )
```

Pull request:
## Description

This PR fixes #566 issue which defined a behavior mismatch between `clj-http` and `aleph`.
We are now always transmitting the `filename` of the file we want to send and not the `name` of the part.

## Testing done

I reproduced the exact test scenario as mentioned on the issue.
It has been tested over `https://api.hubapi.com/crm/v3/imports` and both `clj-http` and `aleph` return the exact same output from it.

__Behavior with `clj-http`__
![Di 29 Mär 2022 23:57:54 CEST](https://user-images.githubusercontent.com/5036764/160716237-53a8f177-051d-4c7d-9951-e5a168dd6c59.png)

__Previous behavior with `aleph`__
![Di 29 Mär 2022 23:58:11 CEST](https://user-images.githubusercontent.com/5036764/160716307-9227e0ff-ef8d-4285-a479-dbe991ead6b1.png)

__New behaviour with `aleph`__
![Di 29 Mär 2022 23:58:52 CEST](https://user-images.githubusercontent.com/5036764/160716359-86133906-a392-47e2-9057-18aa3ed3acea.png)

Patch:
diff --git a/src/aleph/http/multipart.clj b/src/aleph/http/multipart.clj
index 05a1e0fe..119a2ba0 100644
--- a/src/aleph/http/multipart.clj
+++ b/src/aleph/http/multipart.clj
@@ -139,7 +139,7 @@
           (.addBodyFileUpload encoder
                               (or part-name name)
                               ;; Netty's multipart encoder ignores empty strings here
-                              (or name "")
+                              (or filename "")
                               content
                               content-type
                               false))


Test patch:
diff --git a/test/aleph/http/multipart_test.clj b/test/aleph/http/multipart_test.clj
index 26e413d3..52dae74b 100644
--- a/test/aleph/http/multipart_test.clj
+++ b/test/aleph/http/multipart_test.clj
@@ -1,15 +1,21 @@
 (ns aleph.http.multipart-test
-  (:use
-   [clojure test])
   (:require
    [aleph.http :as http]
+   [aleph.http.core :as core]
    [aleph.http.multipart :as mp]
    [byte-streams :as bs]
+   [clojure.edn :as edn]
+   [clojure.test :refer [deftest testing is]]
    [manifold.deferred :as d]
    [manifold.stream :as s]
-   [clojure.string :as str]
-   [clojure.edn :as edn])
+   [clojure.string :as str])
   (:import
+   [io.netty.buffer
+    ByteBufAllocator]
+   [io.netty.handler.codec.http
+    HttpContent]
+   [io.netty.handler.stream
+    ChunkedInput]
    [java.io
     File]))
 
@@ -84,37 +90,47 @@
 
 (deftest reject-unknown-transfer-encoding
   (is (thrown? IllegalArgumentException
-        (mp/encode-body [{:part-name "part1"
-                          :content "content1"
-                          :transfer-encoding :uknown-transfer-encoding}]))))
+      (mp/encode-body [{:part-name "part1"
+                        :content "content1"
+                        :transfer-encoding :uknown-transfer-encoding}]))))
 
 (deftest test-content-as-file
-  (let [body (mp/encode-body [{:part-name "part1"
-                               :content file-to-send}
-                              {:part-name "part2"
-                               :mime-type "application/png"
-                               :content file-to-send}
-                              {:part-name "part3"
-                               :name "text-file-to-send.txt"
-                               :content file-to-send}
-                              {:part-name "part4"
-                               :charset "UTF-8"
-                               :content file-to-send}
-                              {:content file-to-send}
-                              {:content file-to-send
-                               :transfer-encoding :base64}])
-        body-str (bs/to-string body)]
-    (is (.contains body-str "name=\"part1\""))
-    (is (.contains body-str "name=\"part2\""))
-    (is (.contains body-str "name=\"part3\""))
-    (is (.contains body-str "name=\"part4\""))
-    (is (.contains body-str "name=\"file.txt\""))
-    (is (.contains body-str "filename=\"file.txt\""))
-    (is (.contains body-str "filename=\"text-file-to-send.txt\""))
-    (is (.contains body-str "Content-Type: text/plain\r\n"))
-    (is (.contains body-str "Content-Type: text/plain; charset=UTF-8\r\n"))
-    (is (.contains body-str "Content-Type: application/png\r\n"))
-    (is (.contains body-str "Content-Transfer-Encoding: base64\r\n"))))
+  (let [parts [{:part-name "part1"
+                :content file-to-send}
+               {:part-name "part2"
+                :mime-type "application/png"
+                :content file-to-send}
+               {:part-name "part3"
+                :name "text-file-to-send.txt"
+                :content file-to-send}
+               {:part-name "part4"
+                :charset "UTF-8"
+                :content file-to-send}
+               {:content file-to-send}
+               {:content file-to-send
+                :transfer-encoding :base64}]
+        validate (fn [^String body-str]
+                   (is (.contains body-str "name=\"part1\""))
+                   (is (.contains body-str "name=\"part2\""))
+                   (is (.contains body-str "name=\"part3\""))
+                   (is (.contains body-str "name=\"part4\""))
+                   (is (.contains body-str "name=\"file.txt\""))
+                   (is (.contains body-str "filename=\"file.txt\""))
+                   (is (.contains body-str "filename=\"file.txt\""))
+                   (is (.contains (str/lower-case body-str) (str/lower-case "Content-Type: text/plain\r\n")))
+                   (is (.contains (str/lower-case body-str) (str/lower-case "Content-Type: text/plain; charset=UTF-8\r\n")))
+                   (is (.contains (str/lower-case body-str) (str/lower-case "Content-Type: application/png\r\n"))))]
+    (testing "legacy encode-body"
+      (let [body (mp/encode-body parts)
+            body-str (bs/to-string body)]
+        (validate body-str)
+        (is (.contains body-str "Content-Transfer-Encoding: base64\r\n"))))
+    (testing "encode-request"
+      (let [req (core/ring-request->netty-request {:request-method :get})
+            [_ body] (mp/encode-request req parts)
+            body-str (-> ^ChunkedInput body ^HttpContent (.readChunk ByteBufAllocator/DEFAULT) .content bs/to-string)]
+        (validate body-str)
+        (is (.contains body-str "content-transfer-encoding: binary\r\n"))))))
 
 (def port1 26023)
 (def port2 26024)
@@ -165,7 +181,7 @@
     (is (.contains resp "; charset=ISO-8859-1"))
 
     ;; explicit filename
-    (is (.contains resp "filename=\"text-file-to-send.txt\""))
+    (is (.contains resp "filename=\"file.txt\""))
 
     (.close ^java.io.Closeable s)))
 
@@ -205,7 +221,7 @@
 
     ;; filename
     (is (= "file.txt" (get-in chunks [3 :name])))
-    (is (= "text-file-to-send.txt" (get-in chunks [4 :name])))
+    (is (= "file.txt" (get-in chunks [4 :name])))
 
     ;; charset
     (is (= "ISO-8859-1" (get-in chunks [5 :charset])))