GCDWebServerRequest.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  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. static NSData* _newlineData = nil;
  35. static NSData* _newlinesData = nil;
  36. static NSData* _dashNewlineData = nil;
  37. static NSString* _ExtractHeaderParameter(NSString* header, NSString* attribute) {
  38. NSString* value = nil;
  39. if (header) {
  40. NSScanner* scanner = [[NSScanner alloc] initWithString:header];
  41. NSString* string = [NSString stringWithFormat:@"%@=", attribute];
  42. if ([scanner scanUpToString:string intoString:NULL]) {
  43. [scanner scanString:string intoString:NULL];
  44. if ([scanner scanString:@"\"" intoString:NULL]) {
  45. [scanner scanUpToString:@"\"" intoString:&value];
  46. } else {
  47. [scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&value];
  48. }
  49. }
  50. ARC_RELEASE(scanner);
  51. }
  52. return value;
  53. }
  54. // http://www.w3schools.com/tags/ref_charactersets.asp
  55. static NSStringEncoding _StringEncodingFromCharset(NSString* charset) {
  56. NSStringEncoding encoding = kCFStringEncodingInvalidId;
  57. if (charset) {
  58. encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)charset));
  59. }
  60. return (encoding != kCFStringEncodingInvalidId ? encoding : NSUTF8StringEncoding);
  61. }
  62. @implementation GCDWebServerRequest : NSObject
  63. @synthesize method=_method, URL=_url, headers=_headers, path=_path, query=_query, contentType=_type, contentLength=_length;
  64. - (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
  65. if ((self = [super init])) {
  66. _method = [method copy];
  67. _url = ARC_RETAIN(url);
  68. _headers = ARC_RETAIN(headers);
  69. _path = [path copy];
  70. _query = ARC_RETAIN(query);
  71. _type = ARC_RETAIN([_headers objectForKey:@"Content-Type"]);
  72. NSInteger length = [[_headers objectForKey:@"Content-Length"] integerValue];
  73. if (length < 0) {
  74. DNOT_REACHED();
  75. ARC_RELEASE(self);
  76. return nil;
  77. }
  78. _length = length;
  79. if ((_length > 0) && (_type == nil)) {
  80. _type = [kGCDWebServerDefaultMimeType copy];
  81. }
  82. }
  83. return self;
  84. }
  85. - (void)dealloc {
  86. ARC_RELEASE(_method);
  87. ARC_RELEASE(_url);
  88. ARC_RELEASE(_headers);
  89. ARC_RELEASE(_path);
  90. ARC_RELEASE(_query);
  91. ARC_RELEASE(_type);
  92. ARC_DEALLOC(super);
  93. }
  94. - (BOOL)hasBody {
  95. return _type ? YES : NO;
  96. }
  97. @end
  98. @implementation GCDWebServerRequest (Subclassing)
  99. - (BOOL)open {
  100. [self doesNotRecognizeSelector:_cmd];
  101. return NO;
  102. }
  103. - (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
  104. [self doesNotRecognizeSelector:_cmd];
  105. return -1;
  106. }
  107. - (BOOL)close {
  108. [self doesNotRecognizeSelector:_cmd];
  109. return NO;
  110. }
  111. @end
  112. @implementation GCDWebServerDataRequest
  113. @synthesize data=_data;
  114. - (void)dealloc {
  115. DCHECK(_data != nil);
  116. ARC_RELEASE(_data);
  117. ARC_DEALLOC(super);
  118. }
  119. - (BOOL)open {
  120. DCHECK(_data == nil);
  121. _data = [[NSMutableData alloc] initWithCapacity:self.contentLength];
  122. return _data ? YES : NO;
  123. }
  124. - (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
  125. DCHECK(_data != nil);
  126. [_data appendBytes:buffer length:length];
  127. return length;
  128. }
  129. - (BOOL)close {
  130. DCHECK(_data != nil);
  131. return YES;
  132. }
  133. @end
  134. @implementation GCDWebServerFileRequest
  135. @synthesize filePath=_filePath;
  136. - (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
  137. if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) {
  138. _filePath = ARC_RETAIN([NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]);
  139. }
  140. return self;
  141. }
  142. - (void)dealloc {
  143. DCHECK(_file < 0);
  144. unlink([_filePath fileSystemRepresentation]);
  145. ARC_RELEASE(_filePath);
  146. ARC_DEALLOC(super);
  147. }
  148. - (BOOL)open {
  149. DCHECK(_file == 0);
  150. _file = open([_filePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
  151. return (_file > 0 ? YES : NO);
  152. }
  153. - (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
  154. DCHECK(_file > 0);
  155. return write(_file, buffer, length);
  156. }
  157. - (BOOL)close {
  158. DCHECK(_file > 0);
  159. int result = close(_file);
  160. _file = -1;
  161. return (result == 0 ? YES : NO);
  162. }
  163. @end
  164. @implementation GCDWebServerURLEncodedFormRequest
  165. @synthesize arguments=_arguments;
  166. + (NSString*)mimeType {
  167. return @"application/x-www-form-urlencoded";
  168. }
  169. - (void)dealloc {
  170. ARC_RELEASE(_arguments);
  171. ARC_DEALLOC(super);
  172. }
  173. - (BOOL)close {
  174. if (![super close]) {
  175. return NO;
  176. }
  177. NSString* charset = _ExtractHeaderParameter(self.contentType, @"charset");
  178. NSString* string = [[NSString alloc] initWithData:self.data encoding:_StringEncodingFromCharset(charset)];
  179. _arguments = ARC_RETAIN(GCDWebServerParseURLEncodedForm(string));
  180. ARC_RELEASE(string);
  181. return (_arguments ? YES : NO);
  182. }
  183. @end
  184. @implementation GCDWebServerMultiPart
  185. @synthesize contentType=_contentType, mimeType=_mimeType;
  186. - (id)initWithContentType:(NSString*)contentType {
  187. if ((self = [super init])) {
  188. _contentType = [contentType copy];
  189. NSArray* components = [_contentType componentsSeparatedByString:@";"];
  190. if (components.count) {
  191. _mimeType = ARC_RETAIN([[components objectAtIndex:0] lowercaseString]);
  192. }
  193. if (_mimeType == nil) {
  194. _mimeType = @"text/plain";
  195. }
  196. }
  197. return self;
  198. }
  199. - (void)dealloc {
  200. ARC_RELEASE(_contentType);
  201. ARC_RELEASE(_mimeType);
  202. ARC_DEALLOC(super);
  203. }
  204. @end
  205. @implementation GCDWebServerMultiPartArgument
  206. @synthesize data=_data, string=_string;
  207. - (id)initWithContentType:(NSString*)contentType data:(NSData*)data {
  208. if ((self = [super initWithContentType:contentType])) {
  209. _data = ARC_RETAIN(data);
  210. if ([self.mimeType hasPrefix:@"text/"]) {
  211. NSString* charset = _ExtractHeaderParameter(self.contentType, @"charset");
  212. _string = [[NSString alloc] initWithData:_data encoding:_StringEncodingFromCharset(charset)];
  213. }
  214. }
  215. return self;
  216. }
  217. - (void)dealloc {
  218. ARC_RELEASE(_data);
  219. ARC_RELEASE(_string);
  220. ARC_DEALLOC(super);
  221. }
  222. - (NSString*)description {
  223. return [NSString stringWithFormat:@"<%@ | '%@' | %i bytes>", [self class], self.mimeType, (int)_data.length];
  224. }
  225. @end
  226. @implementation GCDWebServerMultiPartFile
  227. @synthesize fileName=_fileName, temporaryPath=_temporaryPath;
  228. - (id)initWithContentType:(NSString*)contentType fileName:(NSString*)fileName temporaryPath:(NSString*)temporaryPath {
  229. if ((self = [super initWithContentType:contentType])) {
  230. _fileName = [fileName copy];
  231. _temporaryPath = [temporaryPath copy];
  232. }
  233. return self;
  234. }
  235. - (void)dealloc {
  236. unlink([_temporaryPath fileSystemRepresentation]);
  237. ARC_RELEASE(_fileName);
  238. ARC_RELEASE(_temporaryPath);
  239. ARC_DEALLOC(super);
  240. }
  241. - (NSString*)description {
  242. return [NSString stringWithFormat:@"<%@ | '%@' | '%@>'", [self class], self.mimeType, _fileName];
  243. }
  244. @end
  245. @implementation GCDWebServerMultiPartFormRequest
  246. @synthesize arguments=_arguments, files=_files;
  247. + (void)initialize {
  248. if (_newlineData == nil) {
  249. _newlineData = [[NSData alloc] initWithBytes:"\r\n" length:2];
  250. DCHECK(_newlineData);
  251. }
  252. if (_newlinesData == nil) {
  253. _newlinesData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4];
  254. DCHECK(_newlinesData);
  255. }
  256. if (_dashNewlineData == nil) {
  257. _dashNewlineData = [[NSData alloc] initWithBytes:"--\r\n" length:4];
  258. DCHECK(_dashNewlineData);
  259. }
  260. }
  261. + (NSString*)mimeType {
  262. return @"multipart/form-data";
  263. }
  264. - (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
  265. if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) {
  266. NSString* boundary = _ExtractHeaderParameter(self.contentType, @"boundary");
  267. if (boundary) {
  268. NSData* data = [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding];
  269. _boundary = ARC_RETAIN(data);
  270. }
  271. if (_boundary == nil) {
  272. DNOT_REACHED();
  273. ARC_RELEASE(self);
  274. return nil;
  275. }
  276. _arguments = [[NSMutableDictionary alloc] init];
  277. _files = [[NSMutableDictionary alloc] init];
  278. }
  279. return self;
  280. }
  281. - (BOOL)open {
  282. DCHECK(_parserData == nil);
  283. _parserData = [[NSMutableData alloc] initWithCapacity:kMultiPartBufferSize];
  284. _parserState = kParserState_Start;
  285. return YES;
  286. }
  287. // http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4
  288. - (BOOL)_parseData {
  289. BOOL success = YES;
  290. if (_parserState == kParserState_Headers) {
  291. NSRange range = [_parserData rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _parserData.length)];
  292. if (range.location != NSNotFound) {
  293. ARC_RELEASE(_controlName);
  294. _controlName = nil;
  295. ARC_RELEASE(_fileName);
  296. _fileName = nil;
  297. ARC_RELEASE(_contentType);
  298. _contentType = nil;
  299. ARC_RELEASE(_tmpPath);
  300. _tmpPath = nil;
  301. CFHTTPMessageRef message = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true);
  302. const char* temp = "GET / HTTP/1.0\r\n";
  303. CFHTTPMessageAppendBytes(message, (const UInt8*)temp, strlen(temp));
  304. CFHTTPMessageAppendBytes(message, _parserData.bytes, range.location + range.length);
  305. if (CFHTTPMessageIsHeaderComplete(message)) {
  306. NSString* controlName = nil;
  307. NSString* fileName = nil;
  308. NSDictionary* headers = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyAllHeaderFields(message));
  309. NSString* contentDisposition = [headers objectForKey:@"Content-Disposition"];
  310. if ([[contentDisposition lowercaseString] hasPrefix:@"form-data;"]) {
  311. controlName = _ExtractHeaderParameter(contentDisposition, @"name");
  312. fileName = _ExtractHeaderParameter(contentDisposition, @"filename");
  313. }
  314. _controlName = [controlName copy];
  315. _fileName = [fileName copy];
  316. _contentType = ARC_RETAIN([headers objectForKey:@"Content-Type"]);
  317. }
  318. CFRelease(message);
  319. if (_controlName) {
  320. if (_fileName) {
  321. NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
  322. _tmpFile = open([path fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
  323. if (_tmpFile > 0) {
  324. _tmpPath = [path copy];
  325. } else {
  326. DNOT_REACHED();
  327. success = NO;
  328. }
  329. }
  330. } else {
  331. DNOT_REACHED();
  332. success = NO;
  333. }
  334. [_parserData replaceBytesInRange:NSMakeRange(0, range.location + range.length) withBytes:NULL length:0];
  335. _parserState = kParserState_Content;
  336. }
  337. }
  338. if ((_parserState == kParserState_Start) || (_parserState == kParserState_Content)) {
  339. NSRange range = [_parserData rangeOfData:_boundary options:0 range:NSMakeRange(0, _parserData.length)];
  340. if (range.location != NSNotFound) {
  341. NSRange subRange = NSMakeRange(range.location + range.length, _parserData.length - range.location - range.length);
  342. NSRange subRange1 = [_parserData rangeOfData:_newlineData options:NSDataSearchAnchored range:subRange];
  343. NSRange subRange2 = [_parserData rangeOfData:_dashNewlineData options:NSDataSearchAnchored range:subRange];
  344. if ((subRange1.location != NSNotFound) || (subRange2.location != NSNotFound)) {
  345. if (_parserState == kParserState_Content) {
  346. const void* dataBytes = _parserData.bytes;
  347. NSUInteger dataLength = range.location - 2;
  348. if (_tmpPath) {
  349. ssize_t result = write(_tmpFile, dataBytes, dataLength);
  350. if (result == dataLength) {
  351. if (close(_tmpFile) == 0) {
  352. _tmpFile = 0;
  353. GCDWebServerMultiPartFile* file = [[GCDWebServerMultiPartFile alloc] initWithContentType:_contentType fileName:_fileName temporaryPath:_tmpPath];
  354. [_files setObject:file forKey:_controlName];
  355. ARC_RELEASE(file);
  356. } else {
  357. DNOT_REACHED();
  358. success = NO;
  359. }
  360. } else {
  361. DNOT_REACHED();
  362. success = NO;
  363. }
  364. ARC_RELEASE(_tmpPath);
  365. _tmpPath = nil;
  366. } else {
  367. NSData* data = [[NSData alloc] initWithBytesNoCopy:(void*)dataBytes length:dataLength freeWhenDone:NO];
  368. GCDWebServerMultiPartArgument* argument = [[GCDWebServerMultiPartArgument alloc] initWithContentType:_contentType data:data];
  369. [_arguments setObject:argument forKey:_controlName];
  370. ARC_RELEASE(argument);
  371. ARC_RELEASE(data);
  372. }
  373. }
  374. if (subRange1.location != NSNotFound) {
  375. [_parserData replaceBytesInRange:NSMakeRange(0, subRange1.location + subRange1.length) withBytes:NULL length:0];
  376. _parserState = kParserState_Headers;
  377. success = [self _parseData];
  378. } else {
  379. _parserState = kParserState_End;
  380. }
  381. }
  382. } else {
  383. NSUInteger margin = 2 * _boundary.length;
  384. if (_tmpPath && (_parserData.length > margin)) {
  385. NSUInteger length = _parserData.length - margin;
  386. ssize_t result = write(_tmpFile, _parserData.bytes, length);
  387. if (result == length) {
  388. [_parserData replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
  389. } else {
  390. DNOT_REACHED();
  391. success = NO;
  392. }
  393. }
  394. }
  395. }
  396. return success;
  397. }
  398. - (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
  399. DCHECK(_parserData != nil);
  400. [_parserData appendBytes:buffer length:length];
  401. return ([self _parseData] ? length : -1);
  402. }
  403. - (BOOL)close {
  404. DCHECK(_parserData != nil);
  405. ARC_RELEASE(_parserData);
  406. _parserData = nil;
  407. ARC_RELEASE(_controlName);
  408. _controlName = nil;
  409. ARC_RELEASE(_fileName);
  410. _fileName = nil;
  411. ARC_RELEASE(_contentType);
  412. _contentType = nil;
  413. if (_tmpFile > 0) {
  414. close(_tmpFile);
  415. unlink([_tmpPath fileSystemRepresentation]);
  416. _tmpFile = 0;
  417. }
  418. ARC_RELEASE(_tmpPath);
  419. _tmpPath = nil;
  420. return (_parserState == kParserState_End ? YES : NO);
  421. }
  422. - (void)dealloc {
  423. DCHECK(_parserData == nil);
  424. ARC_RELEASE(_arguments);
  425. ARC_RELEASE(_files);
  426. ARC_RELEASE(_boundary);
  427. ARC_DEALLOC(super);
  428. }
  429. @end