VLCHTTPConnection.m 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. /*****************************************************************************
  2. * VLCHTTPConnection.m
  3. * VLC for iOS
  4. *****************************************************************************
  5. * Copyright (c) 2013 VideoLAN. All rights reserved.
  6. * $Id$
  7. *
  8. * Authors: Felix Paul Kühne <fkuehne # videolan.org>
  9. * Jean-Baptiste Kempf <jb # videolan.org>
  10. *
  11. * Refer to the COPYING file of the official project for license.
  12. *****************************************************************************/
  13. #import "VLCAppDelegate.h"
  14. #import "VLCHTTPConnection.h"
  15. #import "HTTPConnection.h"
  16. #import "MultipartFormDataParser.h"
  17. #import "HTTPMessage.h"
  18. #import "HTTPDataResponse.h"
  19. #import "HTTPFileResponse.h"
  20. #import "MultipartMessageHeaderField.h"
  21. #import "VLCHTTPUploaderController.h"
  22. #import "HTTPDynamicFileResponse.h"
  23. @interface VLCHTTPConnection()
  24. {
  25. MultipartFormDataParser *_parser;
  26. NSFileHandle *_storeFile;
  27. NSString *_filepath;
  28. UInt64 _contentLength;
  29. UInt64 _receivedContent;
  30. }
  31. @end
  32. @implementation VLCHTTPConnection
  33. - (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path
  34. {
  35. // Add support for POST
  36. if ([method isEqualToString:@"POST"]) {
  37. if ([path isEqualToString:@"/upload.json"])
  38. return YES;
  39. }
  40. return [super supportsMethod:method atPath:path];
  41. }
  42. - (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path
  43. {
  44. // Inform HTTP server that we expect a body to accompany a POST request
  45. if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"]) {
  46. // here we need to make sure, boundary is set in header
  47. NSString* contentType = [request headerField:@"Content-Type"];
  48. NSUInteger paramsSeparator = [contentType rangeOfString:@";"].location;
  49. if (NSNotFound == paramsSeparator)
  50. return NO;
  51. if (paramsSeparator >= contentType.length - 1)
  52. return NO;
  53. NSString* type = [contentType substringToIndex:paramsSeparator];
  54. if (![type isEqualToString:@"multipart/form-data"]) {
  55. // we expect multipart/form-data content type
  56. return NO;
  57. }
  58. // enumerate all params in content-type, and find boundary there
  59. NSArray* params = [[contentType substringFromIndex:paramsSeparator + 1] componentsSeparatedByString:@";"];
  60. for (NSString* param in params) {
  61. paramsSeparator = [param rangeOfString:@"="].location;
  62. if ((NSNotFound == paramsSeparator) || paramsSeparator >= param.length - 1)
  63. continue;
  64. NSString* paramName = [param substringWithRange:NSMakeRange(1, paramsSeparator-1)];
  65. NSString* paramValue = [param substringFromIndex:paramsSeparator+1];
  66. if ([paramName isEqualToString: @"boundary"])
  67. // let's separate the boundary from content-type, to make it more handy to handle
  68. [request setHeaderField:@"boundary" value:paramValue];
  69. }
  70. // check if boundary specified
  71. if (nil == [request headerField:@"boundary"])
  72. return NO;
  73. return YES;
  74. }
  75. return [super expectsRequestBodyFromMethod:method atPath:path];
  76. }
  77. - (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path
  78. {
  79. if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"]) {
  80. return [[HTTPDataResponse alloc] initWithData:[@"\"OK\"" dataUsingEncoding:NSUTF8StringEncoding]];
  81. }
  82. if ([method isEqualToString:@"GET"] && [path hasPrefix:@"/upload/"]) {
  83. // let download the uploaded files
  84. return [[HTTPFileResponse alloc] initWithFilePath: [[config documentRoot] stringByAppendingString:path] forConnection:self];
  85. }
  86. if ([path hasPrefix:@"/download/"]) {
  87. NSString *filePath = [[path stringByReplacingOccurrencesOfString:@"/download/" withString:@""]stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  88. return [[HTTPFileResponse alloc] initWithFilePath:filePath forConnection:self];
  89. }
  90. NSString *filePath = [self filePathForURI:path];
  91. NSString *documentRoot = [config documentRoot];
  92. NSString *relativePath = [filePath substringFromIndex:[documentRoot length]];
  93. if ([relativePath isEqualToString:@"/index.html"]) {
  94. NSArray *allFiles = [MLFile allFiles];
  95. NSString *fileList = @"";
  96. for (MLFile *file in allFiles) {
  97. NSString *fileHTML = [NSString stringWithFormat:@"<li><a href=\"download/%@\" download>%@</a></li>",[file.url stringByReplacingOccurrencesOfString:@"file://"withString:@""], file.title];
  98. fileList = [fileList stringByAppendingString:fileHTML];
  99. }
  100. NSDictionary *replacementDict = @{@"FILES" : fileList,
  101. @"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE", nil),
  102. @"WEBINTF_DROPFILES" : NSLocalizedString(@"WEBINTF_DROPFILES", nil),
  103. @"WEBINTF_DROPFILES_LONG" : NSLocalizedString(@"WEBINTF_DROPFILES_LONG", nil),
  104. @"WEBINTF_DOWNLOADFILES" : NSLocalizedString(@"WEBINTF_DOWNLOADFILES", nil),
  105. @"WEBINTF_DOWNLOADFILES_LONG" : NSLocalizedString(@"WEBINTF_DOWNLOADFILES_LONG", nil)};
  106. return [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  107. forConnection:self
  108. separator:@"%%"
  109. replacementDictionary:replacementDict];
  110. } else if ([relativePath isEqualToString:@"/style.css"]) {
  111. NSDictionary *replacementDict = @{@"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE", nil)};
  112. return [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  113. forConnection:self
  114. separator:@"%%"
  115. replacementDictionary:replacementDict];
  116. }
  117. return [super httpResponseForMethod:method URI:path];
  118. }
  119. - (void)prepareForBodyWithSize:(UInt64)contentLength
  120. {
  121. // set up mime parser
  122. NSString* boundary = [request headerField:@"boundary"];
  123. _parser = [[MultipartFormDataParser alloc] initWithBoundary:boundary formEncoding:NSUTF8StringEncoding];
  124. _parser.delegate = self;
  125. APLog(@"expecting file of size %lli kB", contentLength / 1024);
  126. _contentLength = contentLength;
  127. }
  128. - (void)processBodyData:(NSData *)postDataChunk
  129. {
  130. /* append data to the parser. It will invoke callbacks to let us handle
  131. * parsed data. */
  132. [_parser appendData:postDataChunk];
  133. _receivedContent += postDataChunk.length;
  134. APLog(@"received %lli kB (%lli %%)", _receivedContent / 1024, ((_receivedContent * 100) / _contentLength));
  135. }
  136. //-----------------------------------------------------------------
  137. #pragma mark multipart form data parser delegate
  138. - (void)processStartOfPartWithHeader:(MultipartMessageHeader*) header
  139. {
  140. /* in this sample, we are not interested in parts, other then file parts.
  141. * check content disposition to find out filename */
  142. MultipartMessageHeaderField* disposition = (header.fields)[@"Content-Disposition"];
  143. NSString* filename = [(disposition.params)[@"filename"] lastPathComponent];
  144. if ((nil == filename) || [filename isEqualToString: @""]) {
  145. // it's either not a file part, or
  146. // an empty form sent. we won't handle it.
  147. return;
  148. }
  149. // create the path where to store the media temporarily
  150. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  151. NSString* uploadDirPath = [searchPaths[0] stringByAppendingPathComponent:@"Upload"];
  152. NSFileManager *fileManager = [NSFileManager defaultManager];
  153. BOOL isDir = YES;
  154. if (![fileManager fileExistsAtPath:uploadDirPath isDirectory:&isDir ]) {
  155. [fileManager createDirectoryAtPath:uploadDirPath withIntermediateDirectories:YES attributes:nil error:nil];
  156. }
  157. _filepath = [uploadDirPath stringByAppendingPathComponent: filename];
  158. APLog(@"Saving file to %@", _filepath);
  159. if (![fileManager createDirectoryAtPath:uploadDirPath withIntermediateDirectories:true attributes:nil error:nil])
  160. APLog(@"Could not create directory at path: %@", _filepath);
  161. if (![fileManager createFileAtPath:_filepath contents:nil attributes:nil])
  162. APLog(@"Could not create file at path: %@", _filepath);
  163. _storeFile = [NSFileHandle fileHandleForWritingAtPath:_filepath];
  164. [(VLCAppDelegate*)[UIApplication sharedApplication].delegate networkActivityStarted];
  165. [(VLCAppDelegate*)[UIApplication sharedApplication].delegate disableIdleTimer];
  166. }
  167. - (void)processContent:(NSData*)data WithHeader:(MultipartMessageHeader*) header
  168. {
  169. // here we just write the output from parser to the file.
  170. if (_storeFile) {
  171. @try {
  172. [_storeFile writeData:data];
  173. }
  174. @catch (NSException *exception) {
  175. APLog(@"File to write further data because storage is full.");
  176. [_storeFile closeFile];
  177. _storeFile = nil;
  178. /* don't block */
  179. [self performSelector:@selector(stop) withObject:nil afterDelay:0.1];
  180. }
  181. }
  182. }
  183. - (void)processEndOfPartWithHeader:(MultipartMessageHeader*)header
  184. {
  185. // as the file part is over, we close the file.
  186. APLog(@"closing file");
  187. [_storeFile closeFile];
  188. _storeFile = nil;
  189. }
  190. - (BOOL)shouldDie
  191. {
  192. if (_filepath) {
  193. if (_filepath.length > 0)
  194. [[(VLCAppDelegate*)[UIApplication sharedApplication].delegate uploadController] moveFileFrom:_filepath];
  195. }
  196. return [super shouldDie];
  197. }
  198. @end