MultipartFormDataTests.swift 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967
  1. //
  2. // MultipartFormDataTests.swift
  3. //
  4. // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining a copy
  7. // of this software and associated documentation files (the "Software"), to deal
  8. // in the Software without restriction, including without limitation the rights
  9. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. // copies of the Software, and to permit persons to whom the Software is
  11. // furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included in
  14. // all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. // THE SOFTWARE.
  23. //
  24. import Alamofire
  25. import Foundation
  26. import XCTest
  27. enum EncodingCharacters {
  28. static let crlf = "\r\n"
  29. }
  30. enum BoundaryGenerator {
  31. enum BoundaryType {
  32. case initial, encapsulated, final
  33. }
  34. static func boundary(forBoundaryType boundaryType: BoundaryType, boundaryKey: String) -> String {
  35. switch boundaryType {
  36. case .initial:
  37. "--\(boundaryKey)\(EncodingCharacters.crlf)"
  38. case .encapsulated:
  39. "\(EncodingCharacters.crlf)--\(boundaryKey)\(EncodingCharacters.crlf)"
  40. case .final:
  41. "\(EncodingCharacters.crlf)--\(boundaryKey)--\(EncodingCharacters.crlf)"
  42. }
  43. }
  44. static func boundaryData(boundaryType: BoundaryType, boundaryKey: String) -> Data {
  45. Data(BoundaryGenerator.boundary(forBoundaryType: boundaryType,
  46. boundaryKey: boundaryKey).utf8)
  47. }
  48. }
  49. // MARK: -
  50. class MultipartFormDataPropertiesTestCase: BaseTestCase {
  51. func testThatContentTypeContainsBoundary() {
  52. // Given
  53. let multipartFormData = MultipartFormData()
  54. // When
  55. let boundary = multipartFormData.boundary
  56. // Then
  57. let expectedContentType = "multipart/form-data; boundary=\(boundary)"
  58. XCTAssertEqual(multipartFormData.contentType, expectedContentType, "contentType should match expected value")
  59. }
  60. func testThatContentLengthMatchesTotalBodyPartSize() {
  61. // Given
  62. let multipartFormData = MultipartFormData()
  63. let data1 = Data("Lorem ipsum dolor sit amet.".utf8)
  64. let data2 = Data("Vim at integre alterum.".utf8)
  65. // When
  66. multipartFormData.append(data1, withName: "data1")
  67. multipartFormData.append(data2, withName: "data2")
  68. // Then
  69. let expectedContentLength = UInt64(data1.count + data2.count)
  70. XCTAssertEqual(multipartFormData.contentLength, expectedContentLength, "content length should match expected value")
  71. }
  72. }
  73. // MARK: -
  74. class MultipartFormDataEncodingTestCase: BaseTestCase {
  75. let crlf = EncodingCharacters.crlf
  76. func testEncodingDataBodyPart() {
  77. // Given
  78. let multipartFormData = MultipartFormData()
  79. let data = Data("Lorem ipsum dolor sit amet.".utf8)
  80. multipartFormData.append(data, withName: "data")
  81. var encodedData: Data?
  82. // When
  83. do {
  84. encodedData = try multipartFormData.encode()
  85. } catch {
  86. // No-op
  87. }
  88. // Then
  89. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  90. if let encodedData {
  91. let boundary = multipartFormData.boundary
  92. let expectedString = (
  93. BoundaryGenerator.boundary(forBoundaryType: .initial, boundaryKey: boundary) +
  94. "Content-Disposition: form-data; name=\"data\"\(crlf)\(crlf)" +
  95. "Lorem ipsum dolor sit amet." +
  96. BoundaryGenerator.boundary(forBoundaryType: .final, boundaryKey: boundary)
  97. )
  98. let expectedData = Data(expectedString.utf8)
  99. XCTAssertEqual(encodedData, expectedData, "encoded data should match expected data")
  100. }
  101. }
  102. func testEncodingMultipleDataBodyParts() {
  103. // Given
  104. let multipartFormData = MultipartFormData()
  105. let frenchData = Data("français".utf8)
  106. let japaneseData = Data("日本語".utf8)
  107. let emojiData = Data("😃👍🏻🍻🎉".utf8)
  108. multipartFormData.append(frenchData, withName: "french")
  109. multipartFormData.append(japaneseData, withName: "japanese", mimeType: "text/plain")
  110. multipartFormData.append(emojiData, withName: "emoji", mimeType: "text/plain")
  111. var encodedData: Data?
  112. // When
  113. do {
  114. encodedData = try multipartFormData.encode()
  115. } catch {
  116. // No-op
  117. }
  118. // Then
  119. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  120. if let encodedData {
  121. let boundary = multipartFormData.boundary
  122. let expectedString = (
  123. BoundaryGenerator.boundary(forBoundaryType: .initial, boundaryKey: boundary) +
  124. "Content-Disposition: form-data; name=\"french\"\(crlf)\(crlf)" +
  125. "français" +
  126. BoundaryGenerator.boundary(forBoundaryType: .encapsulated, boundaryKey: boundary) +
  127. "Content-Disposition: form-data; name=\"japanese\"\(crlf)" +
  128. "Content-Type: text/plain\(crlf)\(crlf)" +
  129. "日本語" +
  130. BoundaryGenerator.boundary(forBoundaryType: .encapsulated, boundaryKey: boundary) +
  131. "Content-Disposition: form-data; name=\"emoji\"\(crlf)" +
  132. "Content-Type: text/plain\(crlf)\(crlf)" +
  133. "😃👍🏻🍻🎉" +
  134. BoundaryGenerator.boundary(forBoundaryType: .final, boundaryKey: boundary)
  135. )
  136. let expectedData = Data(expectedString.utf8)
  137. XCTAssertEqual(encodedData, expectedData, "encoded data should match expected data")
  138. }
  139. }
  140. func testEncodingFileBodyPart() {
  141. // Given
  142. let multipartFormData = MultipartFormData()
  143. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  144. multipartFormData.append(unicornImageURL, withName: "unicorn")
  145. var encodedData: Data?
  146. // When
  147. do {
  148. encodedData = try multipartFormData.encode()
  149. } catch {
  150. // No-op
  151. }
  152. // Then
  153. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  154. if let encodedData {
  155. let boundary = multipartFormData.boundary
  156. var expectedData = Data()
  157. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  158. expectedData.append(Data((
  159. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  160. "Content-Type: image/png\(crlf)\(crlf)"
  161. ).utf8))
  162. expectedData.append(try! Data(contentsOf: unicornImageURL))
  163. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  164. XCTAssertEqual(encodedData, expectedData, "data should match expected data")
  165. }
  166. }
  167. func testEncodingMultipleFileBodyParts() {
  168. // Given
  169. let multipartFormData = MultipartFormData()
  170. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  171. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  172. multipartFormData.append(unicornImageURL, withName: "unicorn")
  173. multipartFormData.append(rainbowImageURL, withName: "rainbow")
  174. var encodedData: Data?
  175. // When
  176. do {
  177. encodedData = try multipartFormData.encode()
  178. } catch {
  179. // No-op
  180. }
  181. // Then
  182. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  183. if let encodedData {
  184. let boundary = multipartFormData.boundary
  185. var expectedData = Data()
  186. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  187. expectedData.append(Data((
  188. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  189. "Content-Type: image/png\(crlf)\(crlf)"
  190. ).utf8))
  191. expectedData.append(try! Data(contentsOf: unicornImageURL))
  192. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  193. expectedData.append(Data((
  194. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)" +
  195. "Content-Type: image/jpeg\(crlf)\(crlf)"
  196. ).utf8))
  197. expectedData.append(try! Data(contentsOf: rainbowImageURL))
  198. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  199. XCTAssertEqual(encodedData, expectedData, "data should match expected data")
  200. }
  201. }
  202. func testEncodingStreamBodyPart() {
  203. // Given
  204. let multipartFormData = MultipartFormData()
  205. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  206. let unicornDataLength = UInt64((try! Data(contentsOf: unicornImageURL)).count)
  207. let unicornStream = InputStream(url: unicornImageURL)!
  208. multipartFormData.append(unicornStream,
  209. withLength: unicornDataLength,
  210. name: "unicorn",
  211. fileName: "unicorn.png",
  212. mimeType: "image/png")
  213. var encodedData: Data?
  214. // When
  215. do {
  216. encodedData = try multipartFormData.encode()
  217. } catch {
  218. // No-op
  219. }
  220. // Then
  221. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  222. if let encodedData {
  223. let boundary = multipartFormData.boundary
  224. var expectedData = Data()
  225. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  226. expectedData.append(Data((
  227. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  228. "Content-Type: image/png\(crlf)\(crlf)"
  229. ).utf8))
  230. expectedData.append(try! Data(contentsOf: unicornImageURL))
  231. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  232. XCTAssertEqual(encodedData, expectedData, "data should match expected data")
  233. }
  234. }
  235. func testEncodingMultipleStreamBodyParts() {
  236. // Given
  237. let multipartFormData = MultipartFormData()
  238. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  239. let unicornDataLength = UInt64((try! Data(contentsOf: unicornImageURL)).count)
  240. let unicornStream = InputStream(url: unicornImageURL)!
  241. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  242. let rainbowDataLength = UInt64((try! Data(contentsOf: rainbowImageURL)).count)
  243. let rainbowStream = InputStream(url: rainbowImageURL)!
  244. multipartFormData.append(unicornStream,
  245. withLength: unicornDataLength,
  246. name: "unicorn",
  247. fileName: "unicorn.png",
  248. mimeType: "image/png")
  249. multipartFormData.append(rainbowStream,
  250. withLength: rainbowDataLength,
  251. name: "rainbow",
  252. fileName: "rainbow.jpg",
  253. mimeType: "image/jpeg")
  254. var encodedData: Data?
  255. // When
  256. do {
  257. encodedData = try multipartFormData.encode()
  258. } catch {
  259. // No-op
  260. }
  261. // Then
  262. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  263. if let encodedData {
  264. let boundary = multipartFormData.boundary
  265. var expectedData = Data()
  266. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  267. expectedData.append(Data((
  268. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  269. "Content-Type: image/png\(crlf)\(crlf)"
  270. ).utf8))
  271. expectedData.append(try! Data(contentsOf: unicornImageURL))
  272. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  273. expectedData.append(Data((
  274. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)" +
  275. "Content-Type: image/jpeg\(crlf)\(crlf)"
  276. ).utf8))
  277. expectedData.append(try! Data(contentsOf: rainbowImageURL))
  278. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  279. XCTAssertEqual(encodedData, expectedData, "data should match expected data")
  280. }
  281. }
  282. func testEncodingMultipleBodyPartsWithVaryingTypes() {
  283. // Given
  284. let multipartFormData = MultipartFormData()
  285. let loremData = Data("Lorem ipsum.".utf8)
  286. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  287. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  288. let rainbowDataLength = UInt64((try! Data(contentsOf: rainbowImageURL)).count)
  289. let rainbowStream = InputStream(url: rainbowImageURL)!
  290. multipartFormData.append(loremData, withName: "lorem")
  291. multipartFormData.append(unicornImageURL, withName: "unicorn")
  292. multipartFormData.append(rainbowStream,
  293. withLength: rainbowDataLength,
  294. name: "rainbow",
  295. fileName: "rainbow.jpg",
  296. mimeType: "image/jpeg")
  297. var encodedData: Data?
  298. // When
  299. do {
  300. encodedData = try multipartFormData.encode()
  301. } catch {
  302. // No-op
  303. }
  304. // Then
  305. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  306. if let encodedData {
  307. let boundary = multipartFormData.boundary
  308. var expectedData = Data()
  309. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  310. expectedData.append(Data(
  311. "Content-Disposition: form-data; name=\"lorem\"\(crlf)\(crlf)".utf8
  312. ))
  313. expectedData.append(loremData)
  314. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  315. expectedData.append(Data((
  316. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  317. "Content-Type: image/png\(crlf)\(crlf)"
  318. ).utf8))
  319. expectedData.append(try! Data(contentsOf: unicornImageURL))
  320. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  321. expectedData.append(Data((
  322. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)" +
  323. "Content-Type: image/jpeg\(crlf)\(crlf)"
  324. ).utf8))
  325. expectedData.append(try! Data(contentsOf: rainbowImageURL))
  326. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  327. XCTAssertEqual(encodedData, expectedData, "data should match expected data")
  328. }
  329. }
  330. }
  331. // MARK: -
  332. final class MultipartFormDataWriteEncodedDataToDiskTestCase: BaseTestCase {
  333. let crlf = EncodingCharacters.crlf
  334. func testWritingEncodedDataBodyPartToDisk() {
  335. // Given
  336. let fileURL = temporaryFileURL
  337. let multipartFormData = MultipartFormData()
  338. let data = Data("Lorem ipsum dolor sit amet.".utf8)
  339. multipartFormData.append(data, withName: "data")
  340. var encodingError: (any Error)?
  341. // When
  342. do {
  343. try multipartFormData.writeEncodedData(to: fileURL)
  344. } catch {
  345. encodingError = error
  346. }
  347. // Then
  348. XCTAssertNil(encodingError, "encoding error should be nil")
  349. if let fileData = try? Data(contentsOf: fileURL) {
  350. let boundary = multipartFormData.boundary
  351. let expectedString = BoundaryGenerator.boundary(forBoundaryType: .initial, boundaryKey: boundary) +
  352. "Content-Disposition: form-data; name=\"data\"\(crlf)\(crlf)" +
  353. "Lorem ipsum dolor sit amet." +
  354. BoundaryGenerator.boundary(forBoundaryType: .final, boundaryKey: boundary)
  355. let expectedFileData = Data(expectedString.utf8)
  356. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  357. } else {
  358. XCTFail("file data should not be nil")
  359. }
  360. }
  361. func testWritingMultipleEncodedDataBodyPartsToDisk() {
  362. // Given
  363. let fileURL = temporaryFileURL
  364. let multipartFormData = MultipartFormData()
  365. let frenchData = Data("français".utf8)
  366. let japaneseData = Data("日本語".utf8)
  367. let emojiData = Data("😃👍🏻🍻🎉".utf8)
  368. multipartFormData.append(frenchData, withName: "french")
  369. multipartFormData.append(japaneseData, withName: "japanese")
  370. multipartFormData.append(emojiData, withName: "emoji")
  371. var encodingError: (any Error)?
  372. // When
  373. do {
  374. try multipartFormData.writeEncodedData(to: fileURL)
  375. } catch {
  376. encodingError = error
  377. }
  378. // Then
  379. XCTAssertNil(encodingError, "encoding error should be nil")
  380. if let fileData = try? Data(contentsOf: fileURL) {
  381. let boundary = multipartFormData.boundary
  382. let expectedString = (
  383. BoundaryGenerator.boundary(forBoundaryType: .initial, boundaryKey: boundary) +
  384. "Content-Disposition: form-data; name=\"french\"\(crlf)\(crlf)" +
  385. "français" +
  386. BoundaryGenerator.boundary(forBoundaryType: .encapsulated, boundaryKey: boundary) +
  387. "Content-Disposition: form-data; name=\"japanese\"\(crlf)\(crlf)" +
  388. "日本語" +
  389. BoundaryGenerator.boundary(forBoundaryType: .encapsulated, boundaryKey: boundary) +
  390. "Content-Disposition: form-data; name=\"emoji\"\(crlf)\(crlf)" +
  391. "😃👍🏻🍻🎉" +
  392. BoundaryGenerator.boundary(forBoundaryType: .final, boundaryKey: boundary)
  393. )
  394. let expectedFileData = Data(expectedString.utf8)
  395. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  396. } else {
  397. XCTFail("file data should not be nil")
  398. }
  399. }
  400. func testWritingEncodedFileBodyPartToDisk() {
  401. // Given
  402. let fileURL = temporaryFileURL
  403. let multipartFormData = MultipartFormData()
  404. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  405. multipartFormData.append(unicornImageURL, withName: "unicorn")
  406. var encodingError: (any Error)?
  407. // When
  408. do {
  409. try multipartFormData.writeEncodedData(to: fileURL)
  410. } catch {
  411. encodingError = error
  412. }
  413. // Then
  414. XCTAssertNil(encodingError, "encoding error should be nil")
  415. if let fileData = try? Data(contentsOf: fileURL) {
  416. let boundary = multipartFormData.boundary
  417. var expectedFileData = Data()
  418. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  419. expectedFileData.append(Data((
  420. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  421. "Content-Type: image/png\(crlf)\(crlf)"
  422. ).utf8))
  423. expectedFileData.append(try! Data(contentsOf: unicornImageURL))
  424. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  425. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  426. } else {
  427. XCTFail("file data should not be nil")
  428. }
  429. }
  430. func testWritingMultipleEncodedFileBodyPartsToDisk() {
  431. // Given
  432. let fileURL = temporaryFileURL
  433. let multipartFormData = MultipartFormData()
  434. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  435. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  436. multipartFormData.append(unicornImageURL, withName: "unicorn")
  437. multipartFormData.append(rainbowImageURL, withName: "rainbow")
  438. var encodingError: (any Error)?
  439. // When
  440. do {
  441. try multipartFormData.writeEncodedData(to: fileURL)
  442. } catch {
  443. encodingError = error
  444. }
  445. // Then
  446. XCTAssertNil(encodingError, "encoding error should be nil")
  447. if let fileData = try? Data(contentsOf: fileURL) {
  448. let boundary = multipartFormData.boundary
  449. var expectedFileData = Data()
  450. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  451. expectedFileData.append(Data((
  452. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  453. "Content-Type: image/png\(crlf)\(crlf)"
  454. ).utf8))
  455. expectedFileData.append(try! Data(contentsOf: unicornImageURL))
  456. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  457. expectedFileData.append(Data((
  458. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)" +
  459. "Content-Type: image/jpeg\(crlf)\(crlf)"
  460. ).utf8))
  461. expectedFileData.append(try! Data(contentsOf: rainbowImageURL))
  462. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  463. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  464. } else {
  465. XCTFail("file data should not be nil")
  466. }
  467. }
  468. func testWritingEncodedStreamBodyPartToDiskRespectingBodyLength() {
  469. // Given
  470. let destinationFileURL = temporaryFileURL
  471. let multipartFormData = MultipartFormData()
  472. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  473. let unicornImageData = try! Data(contentsOf: unicornImageURL)
  474. let unicornDataLength = unicornImageData.count
  475. // Write only PART of the file
  476. let expectedFileStreamUploadLength = UInt64(unicornDataLength / 2)
  477. multipartFormData.append(InputStream(url: unicornImageURL)!,
  478. withLength: expectedFileStreamUploadLength,
  479. name: "unicorn",
  480. fileName: "unicorn.png",
  481. mimeType: "image/png")
  482. var encodingError: (any Error)?
  483. // When
  484. do {
  485. try multipartFormData.writeEncodedData(to: destinationFileURL)
  486. } catch {
  487. encodingError = error
  488. }
  489. // Then
  490. XCTAssertNil(encodingError, "encoding error should be nil")
  491. if let destinationFileData = try? Data(contentsOf: destinationFileURL) {
  492. let boundary = multipartFormData.boundary
  493. var expectedFileData = Data()
  494. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  495. expectedFileData.append(Data((
  496. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  497. "Content-Type: image/png\(crlf)\(crlf)"
  498. ).utf8))
  499. expectedFileData.append(unicornImageData.prefix(Int(expectedFileStreamUploadLength)))
  500. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  501. XCTAssertEqual(destinationFileData, expectedFileData, "file data should match expected file data")
  502. } else {
  503. XCTFail("file data should not be nil")
  504. }
  505. }
  506. func testWritingEncodedStreamBodyPartToDisk() {
  507. // Given
  508. let fileURL = temporaryFileURL
  509. let multipartFormData = MultipartFormData()
  510. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  511. let unicornDataLength = UInt64((try! Data(contentsOf: unicornImageURL)).count)
  512. let unicornStream = InputStream(url: unicornImageURL)!
  513. multipartFormData.append(unicornStream,
  514. withLength: unicornDataLength,
  515. name: "unicorn",
  516. fileName: "unicorn.png",
  517. mimeType: "image/png")
  518. var encodingError: (any Error)?
  519. // When
  520. do {
  521. try multipartFormData.writeEncodedData(to: fileURL)
  522. } catch {
  523. encodingError = error
  524. }
  525. // Then
  526. XCTAssertNil(encodingError, "encoding error should be nil")
  527. if let fileData = try? Data(contentsOf: fileURL) {
  528. let boundary = multipartFormData.boundary
  529. var expectedFileData = Data()
  530. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  531. expectedFileData.append(Data((
  532. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  533. "Content-Type: image/png\(crlf)\(crlf)"
  534. ).utf8))
  535. expectedFileData.append(try! Data(contentsOf: unicornImageURL))
  536. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  537. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  538. } else {
  539. XCTFail("file data should not be nil")
  540. }
  541. }
  542. func testWritingMultipleEncodedStreamBodyPartsToDisk() {
  543. // Given
  544. let fileURL = temporaryFileURL
  545. let multipartFormData = MultipartFormData()
  546. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  547. let unicornDataLength = UInt64((try! Data(contentsOf: unicornImageURL)).count)
  548. let unicornStream = InputStream(url: unicornImageURL)!
  549. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  550. let rainbowDataLength = UInt64((try! Data(contentsOf: rainbowImageURL)).count)
  551. let rainbowStream = InputStream(url: rainbowImageURL)!
  552. multipartFormData.append(unicornStream,
  553. withLength: unicornDataLength,
  554. name: "unicorn",
  555. fileName: "unicorn.png",
  556. mimeType: "image/png")
  557. multipartFormData.append(rainbowStream,
  558. withLength: rainbowDataLength,
  559. name: "rainbow",
  560. fileName: "rainbow.jpg",
  561. mimeType: "image/jpeg")
  562. var encodingError: (any Error)?
  563. // When
  564. do {
  565. try multipartFormData.writeEncodedData(to: fileURL)
  566. } catch {
  567. encodingError = error
  568. }
  569. // Then
  570. XCTAssertNil(encodingError, "encoding error should be nil")
  571. if let fileData = try? Data(contentsOf: fileURL) {
  572. let boundary = multipartFormData.boundary
  573. var expectedFileData = Data()
  574. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  575. expectedFileData.append(Data((
  576. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  577. "Content-Type: image/png\(crlf)\(crlf)"
  578. ).utf8))
  579. expectedFileData.append(try! Data(contentsOf: unicornImageURL))
  580. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  581. expectedFileData.append(Data((
  582. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)" +
  583. "Content-Type: image/jpeg\(crlf)\(crlf)"
  584. ).utf8))
  585. expectedFileData.append(try! Data(contentsOf: rainbowImageURL))
  586. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  587. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  588. } else {
  589. XCTFail("file data should not be nil")
  590. }
  591. }
  592. func testWritingMultipleEncodedBodyPartsWithVaryingTypesToDisk() {
  593. // Given
  594. let fileURL = temporaryFileURL
  595. let multipartFormData = MultipartFormData()
  596. let loremData = Data("Lorem ipsum.".utf8)
  597. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  598. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  599. let rainbowDataLength = UInt64((try! Data(contentsOf: rainbowImageURL)).count)
  600. let rainbowStream = InputStream(url: rainbowImageURL)!
  601. multipartFormData.append(loremData, withName: "lorem")
  602. multipartFormData.append(unicornImageURL, withName: "unicorn")
  603. multipartFormData.append(rainbowStream,
  604. withLength: rainbowDataLength,
  605. name: "rainbow",
  606. fileName: "rainbow.jpg",
  607. mimeType: "image/jpeg")
  608. var encodingError: (any Error)?
  609. // When
  610. do {
  611. try multipartFormData.writeEncodedData(to: fileURL)
  612. } catch {
  613. encodingError = error
  614. }
  615. // Then
  616. XCTAssertNil(encodingError, "encoding error should be nil")
  617. if let fileData = try? Data(contentsOf: fileURL) {
  618. let boundary = multipartFormData.boundary
  619. var expectedFileData = Data()
  620. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  621. expectedFileData.append(Data(
  622. "Content-Disposition: form-data; name=\"lorem\"\(crlf)\(crlf)".utf8
  623. ))
  624. expectedFileData.append(loremData)
  625. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  626. expectedFileData.append(Data((
  627. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  628. "Content-Type: image/png\(crlf)\(crlf)"
  629. ).utf8))
  630. expectedFileData.append(try! Data(contentsOf: unicornImageURL))
  631. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  632. expectedFileData.append(Data((
  633. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)" +
  634. "Content-Type: image/jpeg\(crlf)\(crlf)"
  635. ).utf8))
  636. expectedFileData.append(try! Data(contentsOf: rainbowImageURL))
  637. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  638. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  639. } else {
  640. XCTFail("file data should not be nil")
  641. }
  642. }
  643. }
  644. // MARK: -
  645. class MultipartFormDataFailureTestCase: BaseTestCase {
  646. func testThatAppendingFileBodyPartWithInvalidLastPathComponentReturnsError() {
  647. // Given
  648. let fileURL = NSURL(string: "")! as URL
  649. let multipartFormData = MultipartFormData()
  650. multipartFormData.append(fileURL, withName: "empty_data")
  651. var encodingError: (any Error)?
  652. // When
  653. do {
  654. _ = try multipartFormData.encode()
  655. } catch {
  656. encodingError = error
  657. }
  658. // Then
  659. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  660. XCTAssertEqual(encodingError?.asAFError?.isBodyPartFilenameInvalid, true)
  661. XCTAssertEqual(encodingError?.asAFError?.url, fileURL)
  662. }
  663. func testThatAppendingFileBodyPartThatIsNotFileURLReturnsError() {
  664. // Given
  665. let fileURL = URL(string: "https://example.com/image.jpg")!
  666. let multipartFormData = MultipartFormData()
  667. multipartFormData.append(fileURL, withName: "empty_data")
  668. var encodingError: (any Error)?
  669. // When
  670. do {
  671. _ = try multipartFormData.encode()
  672. } catch {
  673. encodingError = error
  674. }
  675. // Then
  676. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  677. XCTAssertEqual(encodingError?.asAFError?.isBodyPartURLInvalid, true)
  678. XCTAssertEqual(encodingError?.asAFError?.url, fileURL)
  679. }
  680. func testThatAppendingFileBodyPartThatIsNotReachableReturnsError() {
  681. // Given
  682. let filePath = (NSTemporaryDirectory() as NSString).appendingPathComponent("does_not_exist.jpg")
  683. let fileURL = URL(fileURLWithPath: filePath)
  684. let multipartFormData = MultipartFormData()
  685. multipartFormData.append(fileURL, withName: "empty_data")
  686. var encodingError: (any Error)?
  687. // When
  688. do {
  689. _ = try multipartFormData.encode()
  690. } catch {
  691. encodingError = error
  692. }
  693. // Then
  694. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  695. XCTAssertEqual(encodingError?.asAFError?.isBodyPartFileNotReachableWithError, true)
  696. XCTAssertEqual(encodingError?.asAFError?.url, fileURL)
  697. }
  698. func testThatAppendingFileBodyPartThatIsDirectoryReturnsError() {
  699. // Given
  700. let directoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
  701. let multipartFormData = MultipartFormData()
  702. multipartFormData.append(directoryURL, withName: "empty_data", fileName: "empty", mimeType: "application/octet")
  703. var encodingError: (any Error)?
  704. // When
  705. do {
  706. _ = try multipartFormData.encode()
  707. } catch {
  708. encodingError = error
  709. }
  710. // Then
  711. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  712. XCTAssertEqual(encodingError?.asAFError?.isBodyPartFileIsDirectory, true)
  713. XCTAssertEqual(encodingError?.asAFError?.url, directoryURL)
  714. }
  715. func testThatWritingEncodedDataToExistingFileURLFails() {
  716. // Given
  717. let fileURL = temporaryFileURL
  718. var writerError: (any Error)?
  719. do {
  720. try "dummy data".write(to: fileURL, atomically: true, encoding: .utf8)
  721. } catch {
  722. writerError = error
  723. }
  724. let multipartFormData = MultipartFormData()
  725. let data = Data("Lorem ipsum dolor sit amet.".utf8)
  726. multipartFormData.append(data, withName: "data")
  727. var encodingError: (any Error)?
  728. // When
  729. do {
  730. try multipartFormData.writeEncodedData(to: fileURL)
  731. } catch {
  732. encodingError = error
  733. }
  734. // Then
  735. XCTAssertNil(writerError, "writer error should be nil")
  736. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  737. XCTAssertEqual(encodingError?.asAFError?.isOutputStreamFileAlreadyExists, true)
  738. }
  739. func testThatWritingEncodedDataToBadURLFails() {
  740. // Given
  741. let fileURL = URL(string: "/this/is/not/a/valid/url")!
  742. let multipartFormData = MultipartFormData()
  743. let data = Data("Lorem ipsum dolor sit amet.".utf8)
  744. multipartFormData.append(data, withName: "data")
  745. var encodingError: (any Error)?
  746. // When
  747. do {
  748. try multipartFormData.writeEncodedData(to: fileURL)
  749. } catch {
  750. encodingError = error
  751. }
  752. // Then
  753. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  754. XCTAssertEqual(encodingError?.asAFError?.isOutputStreamURLInvalid, true)
  755. }
  756. func testThatStreamBodyPartHasUnexpectedLength() {
  757. // Given
  758. let multipartFormData = MultipartFormData()
  759. let data = Data("Lorem ipsum dolor sit amet.".utf8)
  760. multipartFormData.append(data, withName: "data")
  761. var firstError: (any Error)?
  762. var secondError: (any Error)?
  763. // When
  764. do {
  765. _ = try multipartFormData.encode()
  766. } catch {
  767. firstError = error
  768. }
  769. do {
  770. _ = try multipartFormData.encode()
  771. } catch {
  772. secondError = error
  773. }
  774. XCTAssertNil(firstError, "firstError should be nil")
  775. XCTAssertNotNil(secondError, "secondError should not be nil")
  776. XCTAssertEqual(secondError?.asAFError?.isInputStreamReadFailed, true)
  777. XCTAssert(secondError?.asAFError?.underlyingError is AFError.UnexpectedInputStreamLength)
  778. }
  779. }