GCDWebServerRequest.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. /*
  2. Copyright (c) 2012-2014, Pierre-Olivier Latour
  3. All rights reserved.
  4. Redistribution and use in source and binary forms, with or without
  5. modification, are permitted provided that the following conditions are met:
  6. * Redistributions of source code must retain the above copyright
  7. notice, this list of conditions and the following disclaimer.
  8. * Redistributions in binary form must reproduce the above copyright
  9. notice, this list of conditions and the following disclaimer in the
  10. documentation and/or other materials provided with the distribution.
  11. * The name of Pierre-Olivier Latour may not be used to endorse
  12. or promote products derived from this software without specific
  13. prior written permission.
  14. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  15. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  16. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  17. DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
  18. DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  19. (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  20. LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  21. ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  22. (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  23. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  24. */
  25. #import "GCDWebServerPrivate.h"
  26. #define kMultiPartBufferSize (256 * 1024)
  27. enum {
  28. kParserState_Undefined = 0,
  29. kParserState_Start,
  30. kParserState_Headers,
  31. kParserState_Content,
  32. kParserState_End
  33. };
  34. @interface GCDWebServerRequest () {
  35. @private
  36. NSString* _method;
  37. NSURL* _url;
  38. NSDictionary* _headers;
  39. NSString* _path;
  40. NSDictionary* _query;
  41. NSString* _type;
  42. NSUInteger _length;
  43. NSRange _range;
  44. }
  45. @end
  46. @interface GCDWebServerDataRequest () {
  47. @private
  48. NSMutableData* _data;
  49. }
  50. @end
  51. @interface GCDWebServerFileRequest () {
  52. @private
  53. NSString* _filePath;
  54. int _file;
  55. }
  56. @end
  57. @interface GCDWebServerURLEncodedFormRequest () {
  58. @private
  59. NSDictionary* _arguments;
  60. }
  61. @end
  62. @interface GCDWebServerMultiPart () {
  63. @private
  64. NSString* _contentType;
  65. NSString* _mimeType;
  66. }
  67. @end
  68. @interface GCDWebServerMultiPartArgument () {
  69. @private
  70. NSData* _data;
  71. NSString* _string;
  72. }
  73. @end
  74. @interface GCDWebServerMultiPartFile () {
  75. @private
  76. NSString* _fileName;
  77. NSString* _temporaryPath;
  78. }
  79. @end
  80. @interface GCDWebServerMultiPartFormRequest () {
  81. @private
  82. NSData* _boundary;
  83. NSUInteger _parserState;
  84. NSMutableData* _parserData;
  85. NSString* _controlName;
  86. NSString* _fileName;
  87. NSString* _contentType;
  88. NSString* _tmpPath;
  89. int _tmpFile;
  90. NSMutableDictionary* _arguments;
  91. NSMutableDictionary* _files;
  92. }
  93. @end
  94. static NSData* _newlineData = nil;
  95. static NSData* _newlinesData = nil;
  96. static NSData* _dashNewlineData = nil;
  97. static NSString* _ExtractHeaderParameter(NSString* header, NSString* attribute) {
  98. NSString* value = nil;
  99. if (header) {
  100. NSScanner* scanner = [[NSScanner alloc] initWithString:header];
  101. NSString* string = [NSString stringWithFormat:@"%@=", attribute];
  102. if ([scanner scanUpToString:string intoString:NULL]) {
  103. [scanner scanString:string intoString:NULL];
  104. if ([scanner scanString:@"\"" intoString:NULL]) {
  105. [scanner scanUpToString:@"\"" intoString:&value];
  106. } else {
  107. [scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&value];
  108. }
  109. }
  110. ARC_RELEASE(scanner);
  111. }
  112. return value;
  113. }
  114. // http://www.w3schools.com/tags/ref_charactersets.asp
  115. static NSStringEncoding _StringEncodingFromCharset(NSString* charset) {
  116. NSStringEncoding encoding = kCFStringEncodingInvalidId;
  117. if (charset) {
  118. encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)charset));
  119. }
  120. return (encoding != kCFStringEncodingInvalidId ? encoding : NSUTF8StringEncoding);
  121. }
  122. @implementation GCDWebServerRequest : NSObject
  123. @synthesize method=_method, URL=_url, headers=_headers, path=_path, query=_query, contentType=_type, contentLength=_length, byteRange=_range;
  124. - (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
  125. if ((self = [super init])) {
  126. _method = [method copy];
  127. _url = ARC_RETAIN(url);
  128. _headers = ARC_RETAIN(headers);
  129. _path = [path copy];
  130. _query = ARC_RETAIN(query);
  131. _type = ARC_RETAIN([_headers objectForKey:@"Content-Type"]);
  132. NSInteger length = [[_headers objectForKey:@"Content-Length"] integerValue];
  133. if (length < 0) {
  134. DNOT_REACHED();
  135. ARC_RELEASE(self);
  136. return nil;
  137. }
  138. _length = length;
  139. if ((_length > 0) && (_type == nil)) {
  140. _type = [kGCDWebServerDefaultMimeType copy];
  141. }
  142. _range = NSMakeRange(NSNotFound, 0);
  143. NSString* rangeHeader = [[_headers objectForKey:@"Range"] lowercaseString];
  144. if (rangeHeader) {
  145. if ([rangeHeader hasPrefix:@"bytes="]) {
  146. NSArray* components = [[rangeHeader substringFromIndex:6] componentsSeparatedByString:@","];
  147. if (components.count == 1) {
  148. components = [[components firstObject] componentsSeparatedByString:@"-"];
  149. if (components.count == 2) {
  150. NSString* startString = [components objectAtIndex:0];
  151. NSInteger startValue = [startString integerValue];
  152. NSString* endString = [components objectAtIndex:1];
  153. NSInteger endValue = [endString integerValue];
  154. if (startString.length && (startValue >= 0) && endString.length && (endValue >= startValue)) { // The second 500 bytes: "500-999"
  155. _range.location = startValue;
  156. _range.length = endValue - startValue + 1;
  157. } else if (startString.length && (startValue >= 0)) { // The bytes after 9500 bytes: "9500-"
  158. _range.location = startValue;
  159. _range.length = NSUIntegerMax;
  160. } else if (endString.length && (endValue > 0)) { // The final 500 bytes: "-500"
  161. _range.location = NSNotFound;
  162. _range.length = endValue;
  163. }
  164. }
  165. }
  166. }
  167. if ((_range.location == NSNotFound) && (_range.length == 0)) { // Ignore "Range" header if syntactically invalid
  168. LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url);
  169. }
  170. }
  171. }
  172. return self;
  173. }
  174. - (void)dealloc {
  175. ARC_RELEASE(_method);
  176. ARC_RELEASE(_url);
  177. ARC_RELEASE(_headers);
  178. ARC_RELEASE(_path);
  179. ARC_RELEASE(_query);
  180. ARC_RELEASE(_type);
  181. ARC_DEALLOC(super);
  182. }
  183. - (BOOL)hasBody {
  184. return _type ? YES : NO;
  185. }
  186. @end
  187. @implementation GCDWebServerRequest (Subclassing)
  188. - (BOOL)open {
  189. [self doesNotRecognizeSelector:_cmd];
  190. return NO;
  191. }
  192. - (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
  193. [self doesNotRecognizeSelector:_cmd];
  194. return -1;
  195. }
  196. - (BOOL)close {
  197. [self doesNotRecognizeSelector:_cmd];
  198. return NO;
  199. }
  200. @end
  201. @implementation GCDWebServerDataRequest
  202. @synthesize data=_data;
  203. - (void)dealloc {
  204. DCHECK(_data != nil);
  205. ARC_RELEASE(_data);
  206. ARC_DEALLOC(super);
  207. }
  208. - (BOOL)open {
  209. DCHECK(_data == nil);
  210. _data = [[NSMutableData alloc] initWithCapacity:self.contentLength];
  211. return _data ? YES : NO;
  212. }
  213. - (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
  214. DCHECK(_data != nil);
  215. [_data appendBytes:buffer length:length];
  216. return length;
  217. }
  218. - (BOOL)close {
  219. DCHECK(_data != nil);
  220. return YES;
  221. }
  222. @end
  223. @implementation GCDWebServerFileRequest
  224. @synthesize filePath=_filePath;
  225. - (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
  226. if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) {
  227. _filePath = ARC_RETAIN([NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]);
  228. }
  229. return self;
  230. }
  231. - (void)dealloc {
  232. DCHECK(_file < 0);
  233. unlink([_filePath fileSystemRepresentation]);
  234. ARC_RELEASE(_filePath);
  235. ARC_DEALLOC(super);
  236. }
  237. - (BOOL)open {
  238. DCHECK(_file == 0);
  239. _file = open([_filePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
  240. return (_file > 0 ? YES : NO);
  241. }
  242. - (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
  243. DCHECK(_file > 0);
  244. return write(_file, buffer, length);
  245. }
  246. - (BOOL)close {
  247. DCHECK(_file > 0);
  248. int result = close(_file);
  249. _file = -1;
  250. return (result == 0 ? YES : NO);
  251. }
  252. @end
  253. @implementation GCDWebServerURLEncodedFormRequest
  254. @synthesize arguments=_arguments;
  255. + (NSString*)mimeType {
  256. return @"application/x-www-form-urlencoded";
  257. }
  258. - (void)dealloc {
  259. ARC_RELEASE(_arguments);
  260. ARC_DEALLOC(super);
  261. }
  262. - (BOOL)close {
  263. if (![super close]) {
  264. return NO;
  265. }
  266. NSString* charset = _ExtractHeaderParameter(self.contentType, @"charset");
  267. NSString* string = [[NSString alloc] initWithData:self.data encoding:_StringEncodingFromCharset(charset)];
  268. _arguments = ARC_RETAIN(GCDWebServerParseURLEncodedForm(string));
  269. ARC_RELEASE(string);
  270. return (_arguments ? YES : NO);
  271. }
  272. @end
  273. @implementation GCDWebServerMultiPart
  274. @synthesize contentType=_contentType, mimeType=_mimeType;
  275. - (id)initWithContentType:(NSString*)contentType {
  276. if ((self = [super init])) {
  277. _contentType = [contentType copy];
  278. NSArray* components = [_contentType componentsSeparatedByString:@";"];
  279. if (components.count) {
  280. _mimeType = ARC_RETAIN([[components objectAtIndex:0] lowercaseString]);
  281. }
  282. if (_mimeType == nil) {
  283. _mimeType = @"text/plain";
  284. }
  285. }
  286. return self;
  287. }
  288. - (void)dealloc {
  289. ARC_RELEASE(_contentType);
  290. ARC_RELEASE(_mimeType);
  291. ARC_DEALLOC(super);
  292. }
  293. @end
  294. @implementation GCDWebServerMultiPartArgument
  295. @synthesize data=_data, string=_string;
  296. - (id)initWithContentType:(NSString*)contentType data:(NSData*)data {
  297. if ((self = [super initWithContentType:contentType])) {
  298. _data = ARC_RETAIN(data);
  299. if ([self.mimeType hasPrefix:@"text/"]) {
  300. NSString* charset = _ExtractHeaderParameter(self.contentType, @"charset");
  301. _string = [[NSString alloc] initWithData:_data encoding:_StringEncodingFromCharset(charset)];
  302. }
  303. }
  304. return self;
  305. }
  306. - (void)dealloc {
  307. ARC_RELEASE(_data);
  308. ARC_RELEASE(_string);
  309. ARC_DEALLOC(super);
  310. }
  311. - (NSString*)description {
  312. return [NSString stringWithFormat:@"<%@ | '%@' | %i bytes>", [self class], self.mimeType, (int)_data.length];
  313. }
  314. @end
  315. @implementation GCDWebServerMultiPartFile
  316. @synthesize fileName=_fileName, temporaryPath=_temporaryPath;
  317. - (id)initWithContentType:(NSString*)contentType fileName:(NSString*)fileName temporaryPath:(NSString*)temporaryPath {
  318. if ((self = [super initWithContentType:contentType])) {
  319. _fileName = [fileName copy];
  320. _temporaryPath = [temporaryPath copy];
  321. }
  322. return self;
  323. }
  324. - (void)dealloc {
  325. unlink([_temporaryPath fileSystemRepresentation]);
  326. ARC_RELEASE(_fileName);
  327. ARC_RELEASE(_temporaryPath);
  328. ARC_DEALLOC(super);
  329. }
  330. - (NSString*)description {
  331. return [NSString stringWithFormat:@"<%@ | '%@' | '%@>'", [self class], self.mimeType, _fileName];
  332. }
  333. @end
  334. @implementation GCDWebServerMultiPartFormRequest
  335. @synthesize arguments=_arguments, files=_files;
  336. + (void)initialize {
  337. if (_newlineData == nil) {
  338. _newlineData = [[NSData alloc] initWithBytes:"\r\n" length:2];
  339. DCHECK(_newlineData);
  340. }
  341. if (_newlinesData == nil) {
  342. _newlinesData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4];
  343. DCHECK(_newlinesData);
  344. }
  345. if (_dashNewlineData == nil) {
  346. _dashNewlineData = [[NSData alloc] initWithBytes:"--\r\n" length:4];
  347. DCHECK(_dashNewlineData);
  348. }
  349. }
  350. + (NSString*)mimeType {
  351. return @"multipart/form-data";
  352. }
  353. - (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
  354. if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) {
  355. NSString* boundary = _ExtractHeaderParameter(self.contentType, @"boundary");
  356. if (boundary) {
  357. NSData* data = [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding];
  358. _boundary = ARC_RETAIN(data);
  359. }
  360. if (_boundary == nil) {
  361. DNOT_REACHED();
  362. ARC_RELEASE(self);
  363. return nil;
  364. }
  365. _arguments = [[NSMutableDictionary alloc] init];
  366. _files = [[NSMutableDictionary alloc] init];
  367. }
  368. return self;
  369. }
  370. - (BOOL)open {
  371. DCHECK(_parserData == nil);
  372. _parserData = [[NSMutableData alloc] initWithCapacity:kMultiPartBufferSize];
  373. _parserState = kParserState_Start;
  374. return YES;
  375. }
  376. // http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4
  377. - (BOOL)_parseData {
  378. BOOL success = YES;
  379. if (_parserState == kParserState_Headers) {
  380. NSRange range = [_parserData rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _parserData.length)];
  381. if (range.location != NSNotFound) {
  382. ARC_RELEASE(_controlName);
  383. _controlName = nil;
  384. ARC_RELEASE(_fileName);
  385. _fileName = nil;
  386. ARC_RELEASE(_contentType);
  387. _contentType = nil;
  388. ARC_RELEASE(_tmpPath);
  389. _tmpPath = nil;
  390. CFHTTPMessageRef message = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true);
  391. const char* temp = "GET / HTTP/1.0\r\n";
  392. CFHTTPMessageAppendBytes(message, (const UInt8*)temp, strlen(temp));
  393. CFHTTPMessageAppendBytes(message, _parserData.bytes, range.location + range.length);
  394. if (CFHTTPMessageIsHeaderComplete(message)) {
  395. NSString* controlName = nil;
  396. NSString* fileName = nil;
  397. NSDictionary* headers = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyAllHeaderFields(message));
  398. NSString* contentDisposition = [headers objectForKey:@"Content-Disposition"];
  399. if ([[contentDisposition lowercaseString] hasPrefix:@"form-data;"]) {
  400. controlName = _ExtractHeaderParameter(contentDisposition, @"name");
  401. fileName = _ExtractHeaderParameter(contentDisposition, @"filename");
  402. }
  403. _controlName = [controlName copy];
  404. _fileName = [fileName copy];
  405. _contentType = ARC_RETAIN([headers objectForKey:@"Content-Type"]);
  406. }
  407. CFRelease(message);
  408. if (_controlName) {
  409. if (_fileName) {
  410. NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
  411. _tmpFile = open([path fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
  412. if (_tmpFile > 0) {
  413. _tmpPath = [path copy];
  414. } else {
  415. DNOT_REACHED();
  416. success = NO;
  417. }
  418. }
  419. } else {
  420. DNOT_REACHED();
  421. success = NO;
  422. }
  423. [_parserData replaceBytesInRange:NSMakeRange(0, range.location + range.length) withBytes:NULL length:0];
  424. _parserState = kParserState_Content;
  425. }
  426. }
  427. if ((_parserState == kParserState_Start) || (_parserState == kParserState_Content)) {
  428. NSRange range = [_parserData rangeOfData:_boundary options:0 range:NSMakeRange(0, _parserData.length)];
  429. if (range.location != NSNotFound) {
  430. NSRange subRange = NSMakeRange(range.location + range.length, _parserData.length - range.location - range.length);
  431. NSRange subRange1 = [_parserData rangeOfData:_newlineData options:NSDataSearchAnchored range:subRange];
  432. NSRange subRange2 = [_parserData rangeOfData:_dashNewlineData options:NSDataSearchAnchored range:subRange];
  433. if ((subRange1.location != NSNotFound) || (subRange2.location != NSNotFound)) {
  434. if (_parserState == kParserState_Content) {
  435. const void* dataBytes = _parserData.bytes;
  436. NSUInteger dataLength = range.location - 2;
  437. if (_tmpPath) {
  438. ssize_t result = write(_tmpFile, dataBytes, dataLength);
  439. if (result == (ssize_t)dataLength) {
  440. if (close(_tmpFile) == 0) {
  441. _tmpFile = 0;
  442. GCDWebServerMultiPartFile* file = [[GCDWebServerMultiPartFile alloc] initWithContentType:_contentType fileName:_fileName temporaryPath:_tmpPath];
  443. [_files setObject:file forKey:_controlName];
  444. ARC_RELEASE(file);
  445. } else {
  446. DNOT_REACHED();
  447. success = NO;
  448. }
  449. } else {
  450. DNOT_REACHED();
  451. success = NO;
  452. }
  453. ARC_RELEASE(_tmpPath);
  454. _tmpPath = nil;
  455. } else {
  456. NSData* data = [[NSData alloc] initWithBytesNoCopy:(void*)dataBytes length:dataLength freeWhenDone:NO];
  457. GCDWebServerMultiPartArgument* argument = [[GCDWebServerMultiPartArgument alloc] initWithContentType:_contentType data:data];
  458. [_arguments setObject:argument forKey:_controlName];
  459. ARC_RELEASE(argument);
  460. ARC_RELEASE(data);
  461. }
  462. }
  463. if (subRange1.location != NSNotFound) {
  464. [_parserData replaceBytesInRange:NSMakeRange(0, subRange1.location + subRange1.length) withBytes:NULL length:0];
  465. _parserState = kParserState_Headers;
  466. success = [self _parseData];
  467. } else {
  468. _parserState = kParserState_End;
  469. }
  470. }
  471. } else {
  472. NSUInteger margin = 2 * _boundary.length;
  473. if (_tmpPath && (_parserData.length > margin)) {
  474. NSUInteger length = _parserData.length - margin;
  475. ssize_t result = write(_tmpFile, _parserData.bytes, length);
  476. if (result == (ssize_t)length) {
  477. [_parserData replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
  478. } else {
  479. DNOT_REACHED();
  480. success = NO;
  481. }
  482. }
  483. }
  484. }
  485. return success;
  486. }
  487. - (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
  488. DCHECK(_parserData != nil);
  489. [_parserData appendBytes:buffer length:length];
  490. return ([self _parseData] ? length : -1);
  491. }
  492. - (BOOL)close {
  493. DCHECK(_parserData != nil);
  494. ARC_RELEASE(_parserData);
  495. _parserData = nil;
  496. ARC_RELEASE(_controlName);
  497. _controlName = nil;
  498. ARC_RELEASE(_fileName);
  499. _fileName = nil;
  500. ARC_RELEASE(_contentType);
  501. _contentType = nil;
  502. if (_tmpFile > 0) {
  503. close(_tmpFile);
  504. unlink([_tmpPath fileSystemRepresentation]);
  505. _tmpFile = 0;
  506. }
  507. ARC_RELEASE(_tmpPath);
  508. _tmpPath = nil;
  509. return (_parserState == kParserState_End ? YES : NO);
  510. }
  511. - (void)dealloc {
  512. DCHECK(_parserData == nil);
  513. ARC_RELEASE(_arguments);
  514. ARC_RELEASE(_files);
  515. ARC_RELEASE(_boundary);
  516. ARC_DEALLOC(super);
  517. }
  518. @end