VLCHTTPConnection.m 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. //
  2. // VLCHTTPConnection.m
  3. // VLC for iOS
  4. //
  5. // Created by Felix Paul Kühne on 05.11.13.
  6. // Copyright (c) 2013 VideoLAN. All rights reserved.
  7. //
  8. // Refer to the COPYING file of the official project for license.
  9. //
  10. #import "VLCAppDelegate.h"
  11. #import "VLCHTTPConnection.h"
  12. #import "HTTPConnection.h"
  13. #import "MultipartFormDataParser.h"
  14. #import "HTTPMessage.h"
  15. #import "HTTPDataResponse.h"
  16. #import "HTTPFileResponse.h"
  17. #import "MultipartMessageHeaderField.h"
  18. @interface VLCHTTPConnection()
  19. {
  20. MultipartFormDataParser *_parser;
  21. NSFileHandle *_storeFile;
  22. NSString *_filepath;
  23. NSString *_filename;
  24. UInt64 _contentLength;
  25. UInt64 _receivedContent;
  26. }
  27. @end
  28. @implementation VLCHTTPConnection
  29. - (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path
  30. {
  31. // Add support for POST
  32. if ([method isEqualToString:@"POST"]) {
  33. if ([path isEqualToString:@"/upload.json"])
  34. return YES;
  35. }
  36. return [super supportsMethod:method atPath:path];
  37. }
  38. - (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path
  39. {
  40. // Inform HTTP server that we expect a body to accompany a POST request
  41. if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"]) {
  42. // here we need to make sure, boundary is set in header
  43. NSString* contentType = [request headerField:@"Content-Type"];
  44. NSUInteger paramsSeparator = [contentType rangeOfString:@";"].location;
  45. if (NSNotFound == paramsSeparator)
  46. return NO;
  47. if (paramsSeparator >= contentType.length - 1)
  48. return NO;
  49. NSString* type = [contentType substringToIndex:paramsSeparator];
  50. if (![type isEqualToString:@"multipart/form-data"]) {
  51. // we expect multipart/form-data content type
  52. return NO;
  53. }
  54. // enumerate all params in content-type, and find boundary there
  55. NSArray* params = [[contentType substringFromIndex:paramsSeparator + 1] componentsSeparatedByString:@";"];
  56. for (NSString* param in params) {
  57. paramsSeparator = [param rangeOfString:@"="].location;
  58. if ((NSNotFound == paramsSeparator) || paramsSeparator >= param.length - 1)
  59. continue;
  60. NSString* paramName = [param substringWithRange:NSMakeRange(1, paramsSeparator-1)];
  61. NSString* paramValue = [param substringFromIndex:paramsSeparator+1];
  62. if ([paramName isEqualToString: @"boundary"])
  63. // let's separate the boundary from content-type, to make it more handy to handle
  64. [request setHeaderField:@"boundary" value:paramValue];
  65. }
  66. // check if boundary specified
  67. if (nil == [request headerField:@"boundary"])
  68. return NO;
  69. return YES;
  70. }
  71. return [super expectsRequestBodyFromMethod:method atPath:path];
  72. }
  73. - (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path
  74. {
  75. if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"]) {
  76. return [[HTTPDataResponse alloc] initWithData:[@"\"OK\"" dataUsingEncoding:NSUTF8StringEncoding]];
  77. }
  78. if ([method isEqualToString:@"GET"] && [path hasPrefix:@"/upload/"]) {
  79. // let download the uploaded files
  80. return [[HTTPFileResponse alloc] initWithFilePath: [[config documentRoot] stringByAppendingString:path] forConnection:self];
  81. }
  82. return [super httpResponseForMethod:method URI:path];
  83. }
  84. - (void)prepareForBodyWithSize:(UInt64)contentLength
  85. {
  86. // set up mime parser
  87. NSString* boundary = [request headerField:@"boundary"];
  88. _parser = [[MultipartFormDataParser alloc] initWithBoundary:boundary formEncoding:NSUTF8StringEncoding];
  89. _parser.delegate = self;
  90. APLog(@"expecting file of size %lli kB", contentLength / 1024);
  91. _contentLength = contentLength;
  92. }
  93. - (void)processBodyData:(NSData *)postDataChunk
  94. {
  95. /* append data to the parser. It will invoke callbacks to let us handle
  96. * parsed data. */
  97. [_parser appendData:postDataChunk];
  98. _receivedContent += postDataChunk.length;
  99. APLog(@"received %lli kB (%lli %%)", _receivedContent / 1024, ((_receivedContent * 100) / _contentLength));
  100. }
  101. //-----------------------------------------------------------------
  102. #pragma mark multipart form data parser delegate
  103. - (void)processStartOfPartWithHeader:(MultipartMessageHeader*) header
  104. {
  105. /* in this sample, we are not interested in parts, other then file parts.
  106. * check content disposition to find out filename */
  107. MultipartMessageHeaderField* disposition = (header.fields)[@"Content-Disposition"];
  108. NSString* filename = [(disposition.params)[@"filename"] lastPathComponent];
  109. if ((nil == filename) || [filename isEqualToString: @""]) {
  110. // it's either not a file part, or
  111. // an empty form sent. we won't handle it.
  112. return;
  113. }
  114. _filename = filename;
  115. // create the path where to store the media temporarily
  116. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDownloadsDirectory, NSUserDomainMask, YES);
  117. NSString* uploadDirPath = searchPaths[0];
  118. NSFileManager *fileManager = [NSFileManager defaultManager];
  119. BOOL isDir = YES;
  120. if (![fileManager fileExistsAtPath:uploadDirPath isDirectory:&isDir ]) {
  121. [fileManager createDirectoryAtPath:uploadDirPath withIntermediateDirectories:YES attributes:nil error:nil];
  122. }
  123. _filepath = [uploadDirPath stringByAppendingPathComponent: filename];
  124. APLog(@"Saving file to %@", _filepath);
  125. if (![fileManager createDirectoryAtPath:uploadDirPath withIntermediateDirectories:true attributes:nil error:nil])
  126. APLog(@"Could not create directory at path: %@", _filepath);
  127. if (![fileManager createFileAtPath:_filepath contents:nil attributes:nil])
  128. APLog(@"Could not create file at path: %@", _filepath);
  129. _storeFile = [NSFileHandle fileHandleForWritingAtPath:_filepath];
  130. [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
  131. [(VLCAppDelegate*)[UIApplication sharedApplication].delegate disableIdleTimer];
  132. }
  133. - (void)processContent:(NSData*)data WithHeader:(MultipartMessageHeader*) header
  134. {
  135. // here we just write the output from parser to the file.
  136. if (_storeFile)
  137. [_storeFile writeData:data];
  138. }
  139. - (void)processEndOfPartWithHeader:(MultipartMessageHeader*)header
  140. {
  141. // as the file part is over, we close the file.
  142. APLog(@"closing file");
  143. [_storeFile closeFile];
  144. _storeFile = nil;
  145. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  146. NSString *libraryPath = searchPaths[0];
  147. NSString *finalFilePath = [libraryPath stringByAppendingPathComponent:_filename];
  148. NSFileManager *fileManager = [NSFileManager defaultManager];
  149. if ([fileManager fileExistsAtPath:finalFilePath]) {
  150. /* we don't want to over-write existing files, so add an integer to the file name */
  151. NSString *potentialFilename;
  152. NSString *fileExtension = [_filename pathExtension];
  153. NSString *rawFileName = [_filename stringByDeletingPathExtension];
  154. for (NSUInteger x = 1; x < 100; x++) {
  155. potentialFilename = [NSString stringWithFormat:@"%@ %i.%@", rawFileName, x, fileExtension];
  156. if (![[NSFileManager defaultManager] fileExistsAtPath:[libraryPath stringByAppendingPathComponent:potentialFilename]])
  157. break;
  158. }
  159. finalFilePath = [libraryPath stringByAppendingPathComponent:potentialFilename];
  160. }
  161. NSError *error;
  162. [fileManager moveItemAtPath:_filepath toPath:finalFilePath error:&error];
  163. if (error) {
  164. APLog(@"Moving received media %@ to library folder failed (%i), deleting", _filename, error.code);
  165. [fileManager removeItemAtPath:_filepath error:nil];
  166. }
  167. [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
  168. [(VLCAppDelegate*)[UIApplication sharedApplication].delegate activateIdleTimer];
  169. /* update media library when file upload was completed */
  170. VLCAppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
  171. [appDelegate updateMediaList];
  172. }
  173. - (void)die
  174. {
  175. if (_storeFile) {
  176. _storeFile = nil;
  177. [[NSFileManager defaultManager] removeItemAtPath:_filepath error:nil];
  178. [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
  179. [(VLCAppDelegate*)[UIApplication sharedApplication].delegate activateIdleTimer];
  180. }
  181. [super die];
  182. }
  183. @end