MultipartFormData.swift 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
  1. // MultipartFormData.swift
  2. //
  3. // Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/)
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a copy
  6. // of this software and associated documentation files (the "Software"), to deal
  7. // in the Software without restriction, including without limitation the rights
  8. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. // copies of the Software, and to permit persons to whom the Software is
  10. // furnished to do so, subject to the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included in
  13. // all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. // THE SOFTWARE.
  22. import Foundation
  23. #if os(iOS)
  24. import MobileCoreServices
  25. #elseif os(OSX)
  26. import CoreServices
  27. #endif
  28. /**
  29. Constructs `multipart/form-data` for uploads within an HTTP or HTTPS body. There are currently two ways to encode
  30. multipart form data. The first way is to encode the data directly in memory. This is very efficient, but can lead
  31. to memory issues if the dataset is too large. The second way is designed for larger datasets and will write all the
  32. data to a single file on disk with all the proper boundary segmentation. The second approach MUST be used for
  33. larger datasets such as video content, otherwise your app may run out of memory when trying to encode the dataset.
  34. For more information on `multipart/form-data` in general, please refer to the RFC-2388 and RFC-2045 specs as well
  35. and the w3 form documentation.
  36. - http://www.ietf.org/rfc/rfc2388.txt
  37. - http://www.ietf.org/rfc/rfc2045.txt
  38. - http://www.w3.org/TR/html401/interact/forms.html#h-17.13
  39. */
  40. public class MultipartFormData {
  41. // MARK: - Helper Types
  42. /**
  43. Used to specify whether encoding was successful.
  44. */
  45. public enum EncodingResult {
  46. case Success(NSData)
  47. case Failure(NSError)
  48. }
  49. struct EncodingCharacters {
  50. static let CRLF = "\r\n"
  51. }
  52. struct BoundaryGenerator {
  53. enum BoundaryType {
  54. case Initial, Encapsulated, Final
  55. }
  56. static func randomBoundary() -> String {
  57. return String(format: "alamofire.boundary.%08x%08x", arc4random(), arc4random())
  58. }
  59. static func boundaryData(#boundaryType: BoundaryType, boundary: String) -> NSData {
  60. let boundaryText: String
  61. switch boundaryType {
  62. case .Initial:
  63. boundaryText = "--\(boundary)\(EncodingCharacters.CRLF)"
  64. case .Encapsulated:
  65. boundaryText = "\(EncodingCharacters.CRLF)--\(boundary)\(EncodingCharacters.CRLF)"
  66. case .Final:
  67. boundaryText = "\(EncodingCharacters.CRLF)--\(boundary)--\(EncodingCharacters.CRLF)"
  68. }
  69. return boundaryText.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  70. }
  71. }
  72. class BodyPart {
  73. let headers: [String: String]
  74. let bodyStream: NSInputStream
  75. let bodyContentLength: UInt64
  76. var hasInitialBoundary = false
  77. var hasFinalBoundary = false
  78. init(headers: [String: String], bodyStream: NSInputStream, bodyContentLength: UInt64) {
  79. self.headers = headers
  80. self.bodyStream = bodyStream
  81. self.bodyContentLength = bodyContentLength
  82. }
  83. }
  84. // MARK: - Properties
  85. /// The `Content-Type` header value containing the boundary used to generate the `multipart/form-data`.
  86. public var contentType: String { return "multipart/form-data; boundary=\(self.boundary)" }
  87. /// The content length of all body parts used to generate the `multipart/form-data` not including the boundaries.
  88. public var contentLength: UInt64 { return self.bodyParts.reduce(0) { $0 + $1.bodyContentLength } }
  89. /// The boundary used to separate the body parts in the encoded form data.
  90. public let boundary: String
  91. private var bodyParts: [BodyPart]
  92. private var bodyPartError: NSError?
  93. private let streamBufferSize: Int
  94. // MARK: - Lifecycle
  95. /**
  96. Creates a multipart form data object.
  97. :returns: The multipart form data object.
  98. */
  99. public init() {
  100. self.boundary = BoundaryGenerator.randomBoundary()
  101. self.bodyParts = []
  102. /**
  103. * The optimal read/write buffer size in bytes for input and output streams is 1024 (1KB). For more
  104. * information, please refer to the following article:
  105. * - https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Streams/Articles/ReadingInputStreams.html
  106. */
  107. self.streamBufferSize = 1024
  108. }
  109. // MARK: - Body Parts
  110. /**
  111. Creates a body part from the data and appends it to the multipart form data object.
  112. The body part data will be encoded using the following format:
  113. - `Content-Disposition: form-data; name=#{name}` (HTTP Header)
  114. - Encoded data
  115. - Multipart form boundary
  116. :param: data The data to encode into the multipart form data.
  117. :param: name The name to associate with the data in the `Content-Disposition` HTTP header.
  118. */
  119. public func appendBodyPart(#data: NSData, name: String) {
  120. let headers = contentHeaders(name: name)
  121. let stream = NSInputStream(data: data)
  122. let length = UInt64(data.length)
  123. appendBodyPart(stream: stream, length: length, headers: headers)
  124. }
  125. /**
  126. Creates a body part from the data and appends it to the multipart form data object.
  127. The body part data will be encoded using the following format:
  128. - `Content-Disposition: form-data; name=#{name}` (HTTP Header)
  129. - `Content-Type: #{generated mimeType}` (HTTP Header)
  130. - Encoded data
  131. - Multipart form boundary
  132. :param: data The data to encode into the multipart form data.
  133. :param: name The name to associate with the data in the `Content-Disposition` HTTP header.
  134. :param: mimeType The MIME type to associate with the data content type in the `Content-Type` HTTP header.
  135. */
  136. public func appendBodyPart(#data: NSData, name: String, mimeType: String) {
  137. let headers = contentHeaders(name: name, mimeType: mimeType)
  138. let stream = NSInputStream(data: data)
  139. let length = UInt64(data.length)
  140. appendBodyPart(stream: stream, length: length, headers: headers)
  141. }
  142. /**
  143. Creates a body part from the data and appends it to the multipart form data object.
  144. The body part data will be encoded using the following format:
  145. - `Content-Disposition: form-data; name=#{name}; filename=#{filename}` (HTTP Header)
  146. - `Content-Type: #{mimeType}` (HTTP Header)
  147. - Encoded file data
  148. - Multipart form boundary
  149. :param: data The data to encode into the multipart form data.
  150. :param: name The name to associate with the data in the `Content-Disposition` HTTP header.
  151. :param: fileName The filename to associate with the data in the `Content-Disposition` HTTP header.
  152. :param: mimeType The MIME type to associate with the data in the `Content-Type` HTTP header.
  153. */
  154. public func appendBodyPart(#data: NSData, name: String, fileName: String, mimeType: String) {
  155. let headers = contentHeaders(name: name, fileName: fileName, mimeType: mimeType)
  156. let stream = NSInputStream(data: data)
  157. let length = UInt64(data.length)
  158. appendBodyPart(stream: stream, length: length, headers: headers)
  159. }
  160. /**
  161. Creates a body part from the file and appends it to the multipart form data object.
  162. The body part data will be encoded using the following format:
  163. - `Content-Disposition: form-data; name=#{name}; filename=#{generated filename}` (HTTP Header)
  164. - `Content-Type: #{generated mimeType}` (HTTP Header)
  165. - Encoded file data
  166. - Multipart form boundary
  167. The filename in the `Content-Disposition` HTTP header is generated from the last path component of the
  168. `fileURL`. The `Content-Type` HTTP header MIME type is generated by mapping the `fileURL` extension to the
  169. system associated MIME type.
  170. :param: fileURL The URL of the file whose content will be encoded into the multipart form data.
  171. :param: name The name to associate with the file content in the `Content-Disposition` HTTP header.
  172. */
  173. public func appendBodyPart(#fileURL: NSURL, name: String) {
  174. if let
  175. fileName = fileURL.lastPathComponent,
  176. pathExtension = fileURL.pathExtension
  177. {
  178. let mimeType = mimeTypeForPathExtension(pathExtension)
  179. appendBodyPart(fileURL: fileURL, name: name, fileName: fileName, mimeType: mimeType)
  180. } else {
  181. let failureReason = "Failed to extract the fileName of the provided URL: \(fileURL)"
  182. let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason]
  183. let error = NSError(domain: AlamofireErrorDomain, code: NSURLErrorBadURL, userInfo: userInfo)
  184. setBodyPartError(error)
  185. }
  186. }
  187. /**
  188. Creates a body part from the file and appends it to the multipart form data object.
  189. The body part data will be encoded using the following format:
  190. - Content-Disposition: form-data; name=#{name}; filename=#{filename} (HTTP Header)
  191. - Content-Type: #{mimeType} (HTTP Header)
  192. - Encoded file data
  193. - Multipart form boundary
  194. :param: fileURL The URL of the file whose content will be encoded into the multipart form data.
  195. :param: name The name to associate with the file content in the `Content-Disposition` HTTP header.
  196. :param: fileName The filename to associate with the file content in the `Content-Disposition` HTTP header.
  197. :param: mimeType The MIME type to associate with the file content in the `Content-Type` HTTP header.
  198. */
  199. public func appendBodyPart(#fileURL: NSURL, name: String, fileName: String, mimeType: String) {
  200. let headers = contentHeaders(name: name, fileName: fileName, mimeType: mimeType)
  201. var isDirectory: ObjCBool = false
  202. var error: NSError?
  203. if !fileURL.fileURL {
  204. error = errorWithCode(NSURLErrorBadURL, failureReason: "The URL does not point to a file URL: \(fileURL)")
  205. } else if !fileURL.checkResourceIsReachableAndReturnError(nil) {
  206. error = errorWithCode(NSURLErrorBadURL, failureReason: "The URL is not reachable: \(fileURL)")
  207. } else if NSFileManager.defaultManager().fileExistsAtPath(fileURL.path!, isDirectory: &isDirectory) && isDirectory {
  208. error = errorWithCode(NSURLErrorBadURL, failureReason: "The URL is a directory, not a file: \(fileURL)")
  209. }
  210. if let error = error {
  211. setBodyPartError(error)
  212. return
  213. }
  214. let length: UInt64
  215. if let
  216. path = fileURL.path,
  217. attributes = NSFileManager.defaultManager().attributesOfItemAtPath(path, error: &error),
  218. fileSize = (attributes[NSFileSize] as? NSNumber)?.unsignedLongLongValue
  219. {
  220. length = fileSize
  221. } else {
  222. let failureReason = "Could not fetch attributes from the URL: \(fileURL)"
  223. let error = errorWithCode(NSURLErrorBadURL, failureReason: failureReason)
  224. setBodyPartError(error)
  225. return
  226. }
  227. if let stream = NSInputStream(URL: fileURL) {
  228. appendBodyPart(stream: stream, length: length, headers: headers)
  229. } else {
  230. let failureReason = "Failed to create an input stream from the URL: \(fileURL)"
  231. let error = errorWithCode(NSURLErrorCannotOpenFile, failureReason: failureReason)
  232. setBodyPartError(error)
  233. }
  234. }
  235. /**
  236. Creates a body part from the stream and appends it to the multipart form data object.
  237. The body part data will be encoded using the following format:
  238. - `Content-Disposition: form-data; name=#{name}; filename=#{filename}` (HTTP Header)
  239. - `Content-Type: #{mimeType}` (HTTP Header)
  240. - Encoded stream data
  241. - Multipart form boundary
  242. :param: stream The input stream to encode in the multipart form data.
  243. :param: length The content length of the stream.
  244. :param: name The name to associate with the stream content in the `Content-Disposition` HTTP header.
  245. :param: fileName The filename to associate with the stream content in the `Content-Disposition` HTTP header.
  246. :param: mimeType The MIME type to associate with the stream content in the `Content-Type` HTTP header.
  247. */
  248. public func appendBodyPart(#stream: NSInputStream, length: UInt64, name: String, fileName: String, mimeType: String) {
  249. let headers = contentHeaders(name: name, fileName: fileName, mimeType: mimeType)
  250. appendBodyPart(stream: stream, length: length, headers: headers)
  251. }
  252. /**
  253. Creates a body part with the headers, stream and length and appends it to the multipart form data object.
  254. The body part data will be encoded using the following format:
  255. - HTTP headers
  256. - Encoded stream data
  257. - Multipart form boundary
  258. :param: stream The input stream to encode in the multipart form data.
  259. :param: length The content length of the stream.
  260. :param: headers The HTTP headers for the body part.
  261. */
  262. public func appendBodyPart(#stream: NSInputStream, length: UInt64, headers: [String: String]) {
  263. let bodyPart = BodyPart(headers: headers, bodyStream: stream, bodyContentLength: length)
  264. self.bodyParts.append(bodyPart)
  265. }
  266. // MARK: - Data Encoding
  267. /**
  268. Encodes all the appended body parts into a single `NSData` object.
  269. It is important to note that this method will load all the appended body parts into memory all at the same
  270. time. This method should only be used when the encoded data will have a small memory footprint. For large data
  271. cases, please use the `writeEncodedDataToDisk(fileURL:completionHandler:)` method.
  272. :returns: EncodingResult containing an `NSData` object if the encoding succeeded, an `NSError` otherwise.
  273. */
  274. public func encode() -> EncodingResult {
  275. if let bodyPartError = self.bodyPartError {
  276. return .Failure(bodyPartError)
  277. }
  278. var encoded = NSMutableData()
  279. self.bodyParts.first?.hasInitialBoundary = true
  280. self.bodyParts.last?.hasFinalBoundary = true
  281. for bodyPart in self.bodyParts {
  282. let encodedDataResult = encodeBodyPart(bodyPart)
  283. switch encodedDataResult {
  284. case .Failure:
  285. return encodedDataResult
  286. case let .Success(data):
  287. encoded.appendData(data)
  288. }
  289. }
  290. return .Success(encoded)
  291. }
  292. /**
  293. Writes the appended body parts into the given file URL asynchronously and calls the `completionHandler`
  294. when finished.
  295. This process is facilitated by reading and writing with input and output streams, respectively. Thus,
  296. this approach is very memory efficient and should be used for large body part data.
  297. :param: fileURL The file URL to write the multipart form data into.
  298. :param: completionHandler A closure to be executed when writing is finished.
  299. */
  300. public func writeEncodedDataToDisk(fileURL: NSURL, completionHandler: (NSError?) -> Void) {
  301. if let bodyPartError = self.bodyPartError {
  302. completionHandler(bodyPartError)
  303. return
  304. }
  305. var error: NSError?
  306. if let path = fileURL.path where NSFileManager.defaultManager().fileExistsAtPath(path) {
  307. let failureReason = "A file already exists at the given file URL: \(fileURL)"
  308. error = errorWithCode(NSURLErrorBadURL, failureReason: failureReason)
  309. } else if !fileURL.fileURL {
  310. let failureReason = "The URL does not point to a valid file: \(fileURL)"
  311. error = errorWithCode(NSURLErrorBadURL, failureReason: failureReason)
  312. }
  313. if let error = error {
  314. completionHandler(error)
  315. return
  316. }
  317. let outputStream: NSOutputStream
  318. if let possibleOutputStream = NSOutputStream(URL: fileURL, append: false) {
  319. outputStream = possibleOutputStream
  320. } else {
  321. let failureReason = "Failed to create an output stream with the given URL: \(fileURL)"
  322. let error = errorWithCode(NSURLErrorCannotOpenFile, failureReason: failureReason)
  323. completionHandler(error)
  324. return
  325. }
  326. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
  327. outputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
  328. outputStream.open()
  329. self.bodyParts.first?.hasInitialBoundary = true
  330. self.bodyParts.last?.hasFinalBoundary = true
  331. var error: NSError?
  332. for bodyPart in self.bodyParts {
  333. if let writeError = self.writeBodyPart(bodyPart, toOutputStream: outputStream) {
  334. error = writeError
  335. break
  336. }
  337. }
  338. outputStream.close()
  339. outputStream.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
  340. dispatch_async(dispatch_get_main_queue()) {
  341. completionHandler(error)
  342. }
  343. }
  344. }
  345. // MARK: - Private - Body Part Encoding
  346. private func encodeBodyPart(bodyPart: BodyPart) -> EncodingResult {
  347. let encoded = NSMutableData()
  348. let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData()
  349. encoded.appendData(initialData)
  350. let headerData = encodeHeaderDataForBodyPart(bodyPart)
  351. encoded.appendData(headerData)
  352. let bodyStreamResult = encodeBodyStreamDataForBodyPart(bodyPart)
  353. switch bodyStreamResult {
  354. case .Failure:
  355. return bodyStreamResult
  356. case let .Success(data):
  357. encoded.appendData(data)
  358. }
  359. if bodyPart.hasFinalBoundary {
  360. encoded.appendData(finalBoundaryData())
  361. }
  362. return .Success(encoded)
  363. }
  364. private func encodeHeaderDataForBodyPart(bodyPart: BodyPart) -> NSData {
  365. var headerText = ""
  366. for (key, value) in bodyPart.headers {
  367. headerText += "\(key): \(value)\(EncodingCharacters.CRLF)"
  368. }
  369. headerText += EncodingCharacters.CRLF
  370. return headerText.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  371. }
  372. private func encodeBodyStreamDataForBodyPart(bodyPart: BodyPart) -> EncodingResult {
  373. let inputStream = bodyPart.bodyStream
  374. inputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
  375. inputStream.open()
  376. var error: NSError?
  377. let encoded = NSMutableData()
  378. while inputStream.hasBytesAvailable {
  379. var buffer = [UInt8](count: self.streamBufferSize, repeatedValue: 0)
  380. let bytesRead = inputStream.read(&buffer, maxLength: self.streamBufferSize)
  381. if inputStream.streamError != nil {
  382. error = inputStream.streamError
  383. break
  384. }
  385. if bytesRead > 0 {
  386. encoded.appendBytes(buffer, length: bytesRead)
  387. } else if bytesRead < 0 {
  388. let failureReason = "Failed to read from input stream: \(inputStream)"
  389. error = errorWithCode(AlamofireInputStreamReadFailed, failureReason: failureReason)
  390. break
  391. } else {
  392. break
  393. }
  394. }
  395. inputStream.close()
  396. inputStream.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
  397. if let error = error {
  398. return .Failure(error)
  399. }
  400. return .Success(encoded)
  401. }
  402. // MARK: - Private - Writing Body Part to Output Stream
  403. private func writeBodyPart(bodyPart: BodyPart, toOutputStream outputStream: NSOutputStream) -> NSError? {
  404. if let error = writeInitialBoundaryDataForBodyPart(bodyPart, toOutputStream: outputStream) {
  405. return error
  406. }
  407. if let error = writeHeaderDataForBodyPart(bodyPart, toOutputStream: outputStream) {
  408. return error
  409. }
  410. if let error = writeBodyStreamForBodyPart(bodyPart, toOutputStream: outputStream) {
  411. return error
  412. }
  413. if let error = writeFinalBoundaryDataForBodyPart(bodyPart, toOutputStream: outputStream) {
  414. return error
  415. }
  416. return nil
  417. }
  418. private func writeInitialBoundaryDataForBodyPart(bodyPart: BodyPart, toOutputStream outputStream: NSOutputStream) -> NSError? {
  419. let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData()
  420. return writeData(initialData, toOutputStream: outputStream)
  421. }
  422. private func writeHeaderDataForBodyPart(bodyPart: BodyPart, toOutputStream outputStream: NSOutputStream) -> NSError? {
  423. let headerData = encodeHeaderDataForBodyPart(bodyPart)
  424. return writeData(headerData, toOutputStream: outputStream)
  425. }
  426. private func writeBodyStreamForBodyPart(bodyPart: BodyPart, toOutputStream outputStream: NSOutputStream) -> NSError? {
  427. var error: NSError?
  428. let inputStream = bodyPart.bodyStream
  429. inputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
  430. inputStream.open()
  431. while inputStream.hasBytesAvailable {
  432. var buffer = [UInt8](count: self.streamBufferSize, repeatedValue: 0)
  433. let bytesRead = inputStream.read(&buffer, maxLength: self.streamBufferSize)
  434. if inputStream.streamError != nil {
  435. error = inputStream.streamError
  436. break
  437. }
  438. if bytesRead > 0 {
  439. if buffer.count != bytesRead {
  440. buffer = Array(buffer[0..<bytesRead])
  441. }
  442. if let writeError = writeBuffer(&buffer, toOutputStream: outputStream) {
  443. error = writeError
  444. break
  445. }
  446. } else if bytesRead < 0 {
  447. let failureReason = "Failed to read from input stream: \(inputStream)"
  448. error = errorWithCode(AlamofireInputStreamReadFailed, failureReason: failureReason)
  449. break
  450. } else {
  451. break
  452. }
  453. }
  454. inputStream.close()
  455. inputStream.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
  456. return error
  457. }
  458. private func writeFinalBoundaryDataForBodyPart(bodyPart: BodyPart, toOutputStream outputStream: NSOutputStream) -> NSError? {
  459. if bodyPart.hasFinalBoundary {
  460. return writeData(finalBoundaryData(), toOutputStream: outputStream)
  461. }
  462. return nil
  463. }
  464. // MARK: - Private - Writing Buffered Data to Output Stream
  465. private func writeData(data: NSData, toOutputStream outputStream: NSOutputStream) -> NSError? {
  466. var buffer = [UInt8](count: data.length, repeatedValue: 0)
  467. data.getBytes(&buffer, length: data.length)
  468. return writeBuffer(&buffer, toOutputStream: outputStream)
  469. }
  470. private func writeBuffer(inout buffer: [UInt8], toOutputStream outputStream: NSOutputStream) -> NSError? {
  471. var error: NSError?
  472. var bytesToWrite = buffer.count
  473. while bytesToWrite > 0 {
  474. if outputStream.hasSpaceAvailable {
  475. let bytesWritten = outputStream.write(buffer, maxLength: bytesToWrite)
  476. if outputStream.streamError != nil {
  477. error = outputStream.streamError
  478. break
  479. }
  480. if bytesWritten < 0 {
  481. let failureReason = "Failed to write to output stream: \(outputStream)"
  482. error = errorWithCode(AlamofireOutputStreamWriteFailed, failureReason: failureReason)
  483. break
  484. }
  485. bytesToWrite -= bytesWritten
  486. if bytesToWrite > 0 {
  487. buffer = Array(buffer[bytesWritten..<buffer.count])
  488. }
  489. } else if outputStream.streamError != nil {
  490. error = outputStream.streamError
  491. break
  492. }
  493. }
  494. return error
  495. }
  496. // MARK: - Private - Mime Type
  497. private func mimeTypeForPathExtension(pathExtension: String) -> String {
  498. let identifier = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension, nil).takeRetainedValue()
  499. if let contentType = UTTypeCopyPreferredTagWithClass(identifier, kUTTagClassMIMEType) {
  500. return contentType.takeRetainedValue() as String
  501. }
  502. return "application/octet-stream"
  503. }
  504. // MARK: - Private - Content Headers
  505. private func contentHeaders(#name: String) -> [String: String] {
  506. return ["Content-Disposition": "form-data; name=\"\(name)\""]
  507. }
  508. private func contentHeaders(#name: String, mimeType: String) -> [String: String] {
  509. return [
  510. "Content-Disposition": "form-data; name=\"\(name)\"",
  511. "Content-Type": "\(mimeType)"
  512. ]
  513. }
  514. private func contentHeaders(#name: String, fileName: String, mimeType: String) -> [String: String] {
  515. return [
  516. "Content-Disposition": "form-data; name=\"\(name)\"; filename=\"\(fileName)\"",
  517. "Content-Type": "\(mimeType)"
  518. ]
  519. }
  520. // MARK: - Private - Boundary Encoding
  521. private func initialBoundaryData() -> NSData {
  522. return BoundaryGenerator.boundaryData(boundaryType: .Initial, boundary: self.boundary)
  523. }
  524. private func encapsulatedBoundaryData() -> NSData {
  525. return BoundaryGenerator.boundaryData(boundaryType: .Encapsulated, boundary: self.boundary)
  526. }
  527. private func finalBoundaryData() -> NSData {
  528. return BoundaryGenerator.boundaryData(boundaryType: .Final, boundary: self.boundary)
  529. }
  530. // MARK: - Private - Errors
  531. private func setBodyPartError(error: NSError) {
  532. if self.bodyPartError == nil {
  533. self.bodyPartError = error
  534. }
  535. }
  536. private func errorWithCode(code: Int, failureReason: String) -> NSError {
  537. let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason]
  538. return NSError(domain: AlamofireErrorDomain, code: code, userInfo: userInfo)
  539. }
  540. }