VLCHTTPConnection.m 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826
  1. /*****************************************************************************
  2. * VLCHTTPConnection.m
  3. * VLC for iOS
  4. *****************************************************************************
  5. * Copyright (c) 2013-2018 VideoLAN. All rights reserved.
  6. * $Id$
  7. *
  8. * Authors: Felix Paul Kühne <fkuehne # videolan.org>
  9. * Pierre Sagaspe <pierre.sagaspe # me.com>
  10. * Carola Nitz <caro # videolan.org>
  11. * Jean-Baptiste Kempf <jb # videolan.org>
  12. *
  13. * Refer to the COPYING file of the official project for license.
  14. *****************************************************************************/
  15. #import "VLCActivityManager.h"
  16. #import "VLCHTTPConnection.h"
  17. #import "MultipartFormDataParser.h"
  18. #import "HTTPMessage.h"
  19. #import "HTTPDataResponse.h"
  20. #import "HTTPFileResponse.h"
  21. #import "MultipartMessageHeaderField.h"
  22. #import "HTTPDynamicFileResponse.h"
  23. #import "HTTPErrorResponse.h"
  24. #import "NSString+SupportedMedia.h"
  25. #import "UIDevice+VLC.h"
  26. #import "VLCHTTPUploaderController.h"
  27. #import "VLCMetaData.h"
  28. #if TARGET_OS_IOS
  29. #import "VLC-Swift.h"
  30. #import "VLCThumbnailsCache.h"
  31. #endif
  32. #if TARGET_OS_TV
  33. #import "VLCPlayerControlWebSocket.h"
  34. #endif
  35. @interface VLCHTTPConnection()
  36. {
  37. MultipartFormDataParser *_parser;
  38. NSFileHandle *_storeFile;
  39. NSString *_filepath;
  40. UInt64 _contentLength;
  41. UInt64 _receivedContent;
  42. #if TARGET_OS_TV
  43. NSMutableArray *_receivedFiles;
  44. #endif
  45. }
  46. @end
  47. @implementation VLCHTTPConnection
  48. - (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path
  49. {
  50. // Add support for POST
  51. if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"])
  52. return YES;
  53. return [super supportsMethod:method atPath:path];
  54. }
  55. - (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path
  56. {
  57. // Inform HTTP server that we expect a body to accompany a POST request
  58. if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"]) {
  59. // here we need to make sure, boundary is set in header
  60. NSString* contentType = [request headerField:@"Content-Type"];
  61. NSUInteger paramsSeparator = [contentType rangeOfString:@";"].location;
  62. if (NSNotFound == paramsSeparator)
  63. return NO;
  64. if (paramsSeparator >= contentType.length - 1)
  65. return NO;
  66. NSString* type = [contentType substringToIndex:paramsSeparator];
  67. if (![type isEqualToString:@"multipart/form-data"]) {
  68. // we expect multipart/form-data content type
  69. return NO;
  70. }
  71. // enumerate all params in content-type, and find boundary there
  72. NSArray* params = [[contentType substringFromIndex:paramsSeparator + 1] componentsSeparatedByString:@";"];
  73. NSUInteger count = params.count;
  74. for (NSUInteger i = 0; i < count; i++) {
  75. NSString *param = params[i];
  76. paramsSeparator = [param rangeOfString:@"="].location;
  77. if ((NSNotFound == paramsSeparator) || paramsSeparator >= param.length - 1)
  78. continue;
  79. NSString* paramName = [param substringWithRange:NSMakeRange(1, paramsSeparator-1)];
  80. NSString* paramValue = [param substringFromIndex:paramsSeparator+1];
  81. if ([paramName isEqualToString: @"boundary"])
  82. // let's separate the boundary from content-type, to make it more handy to handle
  83. [request setHeaderField:@"boundary" value:paramValue];
  84. }
  85. // check if boundary specified
  86. if (nil == [request headerField:@"boundary"])
  87. return NO;
  88. return YES;
  89. }
  90. return [super expectsRequestBodyFromMethod:method atPath:path];
  91. }
  92. - (NSObject<HTTPResponse> *)_httpPOSTresponseUploadJSON
  93. {
  94. return [[HTTPDataResponse alloc] initWithData:[@"\"OK\"" dataUsingEncoding:NSUTF8StringEncoding]];
  95. }
  96. - (BOOL)fileIsInDocumentFolder:(NSString*)filepath
  97. {
  98. if (!filepath) return NO;
  99. NSError *error;
  100. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  101. NSString *directoryPath = [searchPaths firstObject];
  102. NSArray *array = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:directoryPath error:&error];
  103. if (error != nil) {
  104. APLog(@"checking filerelationship failed %@", error);
  105. return NO;
  106. }
  107. return [array containsObject:filepath.lastPathComponent];
  108. }
  109. #if TARGET_OS_IOS
  110. - (NSObject<HTTPResponse> *)_httpGETDownloadForPath:(NSString *)path
  111. {
  112. NSString *filePath = [[path stringByReplacingOccurrencesOfString:@"/download/" withString:@""] stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLFragmentAllowedCharacterSet];
  113. if (![self fileIsInDocumentFolder:filePath]) {
  114. //return nil which gets handled as resource not found
  115. return nil;
  116. }
  117. HTTPFileResponse *fileResponse = [[HTTPFileResponse alloc] initWithFilePath:filePath forConnection:self];
  118. fileResponse.contentType = @"application/octet-stream";
  119. return fileResponse;
  120. }
  121. - (NSObject<HTTPResponse> *)_httpGETThumbnailForPath:(NSString *)path
  122. {
  123. NSString *filePath = [[path stringByReplacingOccurrencesOfString:@"/thumbnail/" withString:@""] stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLFragmentAllowedCharacterSet];
  124. filePath = [filePath stringByReplacingOccurrencesOfString:@".png" withString:@""];
  125. NSManagedObjectContext *moc = [[MLMediaLibrary sharedMediaLibrary] managedObjectContext];
  126. if (moc) {
  127. NSPersistentStoreCoordinator *psc = [moc persistentStoreCoordinator];
  128. if (psc) {
  129. NSManagedObject *mo = nil;
  130. @try {
  131. mo = [moc existingObjectWithID:[psc managedObjectIDForURIRepresentation:[NSURL URLWithString:filePath]] error:nil];
  132. }@catch (NSException *exeption) {
  133. return [[HTTPErrorResponse alloc] initWithErrorCode:404];
  134. }
  135. NSData *theData = UIImageJPEGRepresentation([VLCThumbnailsCache thumbnailForManagedObject:mo], .9);
  136. NSString *contentType = @"image/jpg";
  137. if (theData) {
  138. HTTPDataResponse *dataResponse = [[HTTPDataResponse alloc] initWithData:theData];
  139. dataResponse.contentType = contentType;
  140. return dataResponse;
  141. }
  142. }
  143. }
  144. return [[HTTPErrorResponse alloc] initWithErrorCode:404];
  145. }
  146. - (NSObject<HTTPResponse> *)_httpGETLibraryForPath:(NSString *)path
  147. {
  148. NSString *filePath = [self filePathForURI:path];
  149. NSString *documentRoot = [config documentRoot];
  150. NSString *relativePath = [filePath substringFromIndex:[documentRoot length]];
  151. BOOL shouldReturnLibVLCXML = [relativePath isEqualToString:@"/libMediaVLC.xml"];
  152. NSMutableArray *allMedia = [[NSMutableArray alloc] init];
  153. /* add all albums */
  154. NSArray *allAlbums = [MLAlbum allAlbums];
  155. for (MLAlbum *album in allAlbums) {
  156. if (album.name.length > 0 && album.tracks.count > 1)
  157. [allMedia addObject:album];
  158. }
  159. /* add all shows */
  160. NSArray *allShows = [MLShow allShows];
  161. for (MLShow *show in allShows) {
  162. if (show.name.length > 0 && show.episodes.count > 1)
  163. [allMedia addObject:show];
  164. }
  165. /* add all folders*/
  166. NSArray *allFolders = [MLLabel allLabels];
  167. for (MLLabel *folder in allFolders)
  168. [allMedia addObject:folder];
  169. /* add all remaining files */
  170. NSArray *allFiles = [MLFile allFiles];
  171. for (MLFile *file in allFiles) {
  172. if (file.labels.count > 0) continue;
  173. if (!file.isShowEpisode && !file.isAlbumTrack)
  174. [allMedia addObject:file];
  175. else if (file.isShowEpisode) {
  176. if (file.showEpisode.show.episodes.count < 2)
  177. [allMedia addObject:file];
  178. } else if (file.isAlbumTrack) {
  179. if (file.albumTrack.album.tracks.count < 2)
  180. [allMedia addObject:file];
  181. }
  182. }
  183. NSUInteger mediaCount = allMedia.count;
  184. NSMutableArray *mediaInHtml = [[NSMutableArray alloc] initWithCapacity:mediaCount];
  185. NSMutableArray *mediaInXml = [[NSMutableArray alloc] initWithCapacity:mediaCount];
  186. NSString *hostName = [NSString stringWithFormat:@"%@:%@", [[VLCHTTPUploaderController sharedInstance] hostname], [[VLCHTTPUploaderController sharedInstance] hostnamePort]];
  187. NSString *duration;
  188. for (NSManagedObject *mo in allMedia) {
  189. if ([mo isKindOfClass:[MLFile class]]) {
  190. MLFile *file = (MLFile *)mo;
  191. duration = [[VLCTime timeWithNumber:file.duration] stringValue];
  192. [mediaInHtml addObject:[NSString stringWithFormat:
  193. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  194. <a href=\"download/%@\" class=\"inner\"> \
  195. <div class=\"down icon\"></div> \
  196. <div class=\"infos\"> \
  197. <span class=\"first-line\">%@</span> \
  198. <span class=\"second-line\">%@ - %0.2f MB</span> \
  199. </div> \
  200. </a> \
  201. </div>",
  202. file.objectID.URIRepresentation,
  203. [file.url.path stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLFragmentAllowedCharacterSet],
  204. file.title,
  205. duration, (float)(file.fileSizeInBytes / 1e6)]];
  206. if (shouldReturnLibVLCXML) {
  207. NSString *pathSub = [self _checkIfSubtitleWasFound:file.path];
  208. if (pathSub)
  209. pathSub = [NSString stringWithFormat:@"http://%@/download/%@", hostName, pathSub];
  210. [mediaInXml addObject:[NSString stringWithFormat:@"<Media title=\"%@\" thumb=\"http://%@/thumbnail/%@.png\" duration=\"%@\" size=\"%li\" pathfile=\"http://%@/download/%@\" pathSubtitle=\"%@\"/>", file.title, hostName, file.objectID.URIRepresentation.absoluteString, duration, file.fileSizeInBytes, hostName, [file.url.path stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLFragmentAllowedCharacterSet], pathSub]];
  211. }
  212. }
  213. else if ([mo isKindOfClass:[MLShow class]]) {
  214. MLShow *show = (MLShow *)mo;
  215. NSArray *episodes = [show sortedEpisodes];
  216. [mediaInHtml addObject:[NSString stringWithFormat:
  217. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  218. <a href=\"#\" class=\"inner folder\"> \
  219. <div class=\"open icon\"></div> \
  220. <div class=\"infos\"> \
  221. <span class=\"first-line\">%@</span> \
  222. <span class=\"second-line\">%lu items</span> \
  223. </div> \
  224. </a> \
  225. <div class=\"content\">",
  226. mo.objectID.URIRepresentation,
  227. show.name,
  228. (unsigned long)[episodes count]]];
  229. for (MLShowEpisode *showEp in episodes) {
  230. MLFile *anyFileFromEpisode = (MLFile *)[[showEp files] anyObject];
  231. duration = [[VLCTime timeWithNumber:[anyFileFromEpisode duration]] stringValue];
  232. [mediaInHtml addObject:[NSString stringWithFormat:
  233. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  234. <a href=\"download/%@\" class=\"inner\"> \
  235. <div class=\"down icon\"></div> \
  236. <div class=\"infos\"> \
  237. <span class=\"first-line\">S%@E%@ - %@</span> \
  238. <span class=\"second-line\">%@ - %0.2f MB</span> \
  239. </div> \
  240. </a> \
  241. </div>",
  242. showEp.objectID.URIRepresentation,
  243. [anyFileFromEpisode.url.path stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLFragmentAllowedCharacterSet],
  244. showEp.seasonNumber,
  245. showEp.episodeNumber,
  246. showEp.name,
  247. duration, (float)([anyFileFromEpisode fileSizeInBytes] / 1e6)]];
  248. if (shouldReturnLibVLCXML) {
  249. NSString *pathSub = [self _checkIfSubtitleWasFound:[anyFileFromEpisode path]];
  250. if (![pathSub isEqualToString:@""])
  251. pathSub = [NSString stringWithFormat:@"http://%@/download/%@", hostName, pathSub];
  252. [mediaInXml addObject:[NSString stringWithFormat:@"<Media title=\"%@ - S%@E%@\" thumb=\"http://%@/thumbnail/%@.png\" duration=\"%@\" size=\"%li\" pathfile=\"http://%@/download/%@\" pathSubtitle=\"%@\"/>", show.name, showEp.seasonNumber, showEp.episodeNumber, hostName, showEp.objectID.URIRepresentation, duration, [anyFileFromEpisode fileSizeInBytes], hostName, [anyFileFromEpisode.url.path stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLFragmentAllowedCharacterSet], pathSub]];
  253. }
  254. }
  255. [mediaInHtml addObject:@"</div></div>"];
  256. } else if ([mo isKindOfClass:[MLLabel class]]) {
  257. MLLabel *label = (MLLabel *)mo;
  258. NSArray *folderItems = [label sortedFolderItems];
  259. [mediaInHtml addObject:[NSString stringWithFormat:
  260. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  261. <a href=\"#\" class=\"inner folder\"> \
  262. <div class=\"open icon\"></div> \
  263. <div class=\"infos\"> \
  264. <span class=\"first-line\">%@</span> \
  265. <span class=\"second-line\">%lu items</span> \
  266. </div> \
  267. </a> \
  268. <div class=\"content\">",
  269. label.objectID.URIRepresentation,
  270. label.name,
  271. (unsigned long)folderItems.count]];
  272. for (MLFile *file in folderItems) {
  273. duration = [[VLCTime timeWithNumber:[file duration]] stringValue];
  274. [mediaInHtml addObject:[NSString stringWithFormat:
  275. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  276. <a href=\"download/%@\" class=\"inner\"> \
  277. <div class=\"down icon\"></div> \
  278. <div class=\"infos\"> \
  279. <span class=\"first-line\">%@</span> \
  280. <span class=\"second-line\">%@ - %0.2f MB</span> \
  281. </div> \
  282. </a> \
  283. </div>",
  284. file.objectID.URIRepresentation,
  285. [file.url.path stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLFragmentAllowedCharacterSet],
  286. file.title,
  287. duration, (float)(file.fileSizeInBytes / 1e6)]];
  288. if (shouldReturnLibVLCXML) {
  289. NSString *pathSub = [self _checkIfSubtitleWasFound:file.path];
  290. if (pathSub)
  291. pathSub = [NSString stringWithFormat:@"http://%@/download/%@", hostName, pathSub];
  292. [mediaInXml addObject:[NSString stringWithFormat:@"<Media title=\"%@\" thumb=\"http://%@/thumbnail/%@.png\" duration=\"%@\" size=\"%li\" pathfile=\"http://%@/download/%@\" pathSubtitle=\"%@\"/>", file.title, hostName, file.objectID.URIRepresentation, duration, file.fileSizeInBytes, hostName, [file.url.path stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLFragmentAllowedCharacterSet], pathSub]];
  293. }
  294. }
  295. [mediaInHtml addObject:@"</div></div>"];
  296. } else if ([mo isKindOfClass:[MLAlbum class]]) {
  297. MLAlbum *album = (MLAlbum *)mo;
  298. NSArray *albumTracks = [album sortedTracks];
  299. [mediaInHtml addObject:[NSString stringWithFormat:
  300. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  301. <a href=\"#\" class=\"inner folder\"> \
  302. <div class=\"open icon\"></div> \
  303. <div class=\"infos\"> \
  304. <span class=\"first-line\">%@</span> \
  305. <span class=\"second-line\">%lu items</span> \
  306. </div> \
  307. </a> \
  308. <div class=\"content\">",
  309. album.objectID.URIRepresentation,
  310. album.name,
  311. (unsigned long)albumTracks.count]];
  312. for (MLAlbumTrack *track in albumTracks) {
  313. MLFile *anyFileFromTrack = [track anyFileFromTrack];
  314. duration = [[VLCTime timeWithNumber:[anyFileFromTrack duration]] stringValue];
  315. [mediaInHtml addObject:[NSString stringWithFormat:
  316. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  317. <a href=\"download/%@\" class=\"inner\"> \
  318. <div class=\"down icon\"></div> \
  319. <div class=\"infos\"> \
  320. <span class=\"first-line\">%@</span> \
  321. <span class=\"second-line\">%@ - %0.2f MB</span> \
  322. </div> \
  323. </a> \
  324. </div>",
  325. track.objectID.URIRepresentation,
  326. [anyFileFromTrack.url.path stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLFragmentAllowedCharacterSet],
  327. track.title,
  328. duration, (float)([anyFileFromTrack fileSizeInBytes] / 1e6)]];
  329. if (shouldReturnLibVLCXML)
  330. [mediaInXml addObject:[NSString stringWithFormat:@"<Media title=\"%@\" thumb=\"http://%@/thumbnail/%@.png\" duration=\"%@\" size=\"%li\" pathfile=\"http://%@/download/%@\" pathSubtitle=\"\"/>", track.title, hostName, track.objectID.URIRepresentation, duration, [anyFileFromTrack fileSizeInBytes], hostName, [anyFileFromTrack.url.path stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLFragmentAllowedCharacterSet]]];
  331. }
  332. [mediaInHtml addObject:@"</div></div>"];
  333. }
  334. }
  335. UIDevice *currentDevice = [UIDevice currentDevice];
  336. NSString *deviceModel = [currentDevice model];
  337. NSDictionary *replacementDict;
  338. HTTPDynamicFileResponse *fileResponse;
  339. if (shouldReturnLibVLCXML) {
  340. replacementDict = @{@"FILES" : [mediaInXml componentsJoinedByString:@" "],
  341. @"NB_FILE" : [NSString stringWithFormat:@"%li", (unsigned long)mediaInXml.count],
  342. @"LIB_TITLE" : [currentDevice name]};
  343. fileResponse = [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  344. forConnection:self
  345. separator:@"%%"
  346. replacementDictionary:replacementDict];
  347. fileResponse.contentType = @"application/xml";
  348. } else {
  349. replacementDict = @{@"FILES" : [mediaInHtml componentsJoinedByString:@" "],
  350. @"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE", nil),
  351. @"WEBINTF_DROPFILES" : NSLocalizedString(@"WEBINTF_DROPFILES", nil),
  352. @"WEBINTF_DROPFILES_LONG" : [NSString stringWithFormat:NSLocalizedString(@"WEBINTF_DROPFILES_LONG", nil), deviceModel],
  353. @"WEBINTF_DOWNLOADFILES" : NSLocalizedString(@"WEBINTF_DOWNLOADFILES", nil),
  354. @"WEBINTF_DOWNLOADFILES_LONG" : [NSString stringWithFormat: NSLocalizedString(@"WEBINTF_DOWNLOADFILES_LONG", nil), deviceModel]};
  355. fileResponse = [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  356. forConnection:self
  357. separator:@"%%"
  358. replacementDictionary:replacementDict];
  359. fileResponse.contentType = @"text/html";
  360. }
  361. return fileResponse;
  362. }
  363. #else
  364. - (NSObject<HTTPResponse> *)_httpGETLibraryForPath:(NSString *)path
  365. {
  366. UIDevice *currentDevice = [UIDevice currentDevice];
  367. NSString *deviceModel = [currentDevice model];
  368. NSString *filePath = [self filePathForURI:path];
  369. NSString *documentRoot = [config documentRoot];
  370. NSString *relativePath = [filePath substringFromIndex:[documentRoot length]];
  371. NSDictionary *replacementDict = @{@"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE_ATV", nil),
  372. @"WEBINTF_DROPFILES" : NSLocalizedString(@"WEBINTF_DROPFILES", nil),
  373. @"WEBINTF_DROPFILES_LONG" : [NSString stringWithFormat:NSLocalizedString(@"WEBINTF_DROPFILES_LONG_ATV", nil), deviceModel],
  374. @"WEBINTF_OPEN_URL" : NSLocalizedString(@"ENTER_URL", nil)};
  375. HTTPDynamicFileResponse *fileResponse;
  376. if ([relativePath isEqualToString:@"/index.html"]) {
  377. fileResponse = [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  378. forConnection:self
  379. separator:@"%%"
  380. replacementDictionary:replacementDict];
  381. fileResponse.contentType = @"text/html";
  382. }
  383. return fileResponse;
  384. }
  385. #endif
  386. - (NSObject<HTTPResponse> *)_httpGETCSSForPath:(NSString *)path
  387. {
  388. #if TARGET_OS_IOS
  389. NSDictionary *replacementDict = @{@"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE", nil)};
  390. #else
  391. NSDictionary *replacementDict = @{@"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE_ATV", nil)};
  392. #endif
  393. HTTPDynamicFileResponse *fileResponse = [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  394. forConnection:self
  395. separator:@"%%"
  396. replacementDictionary:replacementDict];
  397. fileResponse.contentType = @"text/css";
  398. return fileResponse;
  399. }
  400. #if TARGET_OS_TV
  401. - (NSObject <HTTPResponse> *)_HTTPGETPlaying
  402. {
  403. /* JSON response:
  404. {
  405. "currentTime": 42,
  406. "media": {
  407. "id": "some id",
  408. "title": "some title",
  409. "duration": 120000
  410. }
  411. }
  412. */
  413. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  414. if (!vpc.isPlaying) {
  415. return [[HTTPErrorResponse alloc] initWithErrorCode:404];
  416. }
  417. VLCMedia *media = [vpc currentlyPlayingMedia];
  418. if (!media) {
  419. return [[HTTPErrorResponse alloc] initWithErrorCode:404];
  420. }
  421. NSString *mediaTitle = vpc.metadata.title;
  422. if (!mediaTitle)
  423. mediaTitle = @"";
  424. NSDictionary *mediaDict = @{ @"id" : media.url.absoluteString,
  425. @"title" : mediaTitle,
  426. @"duration" : @([vpc mediaDuration])};
  427. NSDictionary *returnDict = @{ @"currentTime" : @([vpc playedTime].intValue),
  428. @"media" : mediaDict };
  429. NSError *error;
  430. NSData *returnData = [NSJSONSerialization dataWithJSONObject:returnDict options:0 error:&error];
  431. if (error != nil) {
  432. APLog(@"JSON serialization failed %@", error);
  433. return [[HTTPErrorResponse alloc] initWithErrorCode:500];
  434. }
  435. return [[HTTPDataResponse alloc] initWithData:returnData];
  436. }
  437. - (NSObject <HTTPResponse> *)_HTTPGETwebResources
  438. {
  439. /* JS response
  440. {
  441. "WEBINTF_URL_SENT" : "URL sent successfully.",
  442. "WEBINTF_URL_EMPTY" :"'URL cannot be empty.",
  443. "WEBINTF_URL_INVALID" : "Not a valid URL."
  444. }
  445. */
  446. NSString *returnString = [NSString stringWithFormat:
  447. @"var LOCALES = {\n" \
  448. "PLAYER_CONTROL: {\n" \
  449. "URL: {\n" \
  450. "EMPTY: \"%@\",\n" \
  451. "NOT_VALID: \"%@\",\n" \
  452. "SENT_SUCCESSFULLY: \"%@\"\n" \
  453. "}\n" \
  454. "}\n" \
  455. "}",
  456. NSLocalizedString(@"WEBINTF_URL_EMPTY", nil),
  457. NSLocalizedString(@"WEBINTF_URL_INVALID", nil),
  458. NSLocalizedString(@"WEBINTF_URL_SENT", nil)];
  459. NSData *returnData = [returnString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
  460. return [[HTTPDataResponse alloc] initWithData:returnData];
  461. }
  462. - (NSObject <HTTPResponse> *)_HTTPGETPlaylist
  463. {
  464. /* JSON response:
  465. [
  466. {
  467. "media": {
  468. "id": "some id 1",
  469. "title": "some title 1",
  470. "duration": 120000
  471. }
  472. },
  473. ...]
  474. */
  475. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  476. if (!vpc.isPlaying || !vpc.mediaList) {
  477. return [[HTTPErrorResponse alloc] initWithErrorCode:404];
  478. }
  479. VLCMediaList *mediaList = vpc.mediaList;
  480. [mediaList lock];
  481. NSUInteger mediaCount = mediaList.count;
  482. NSMutableArray *retArray = [NSMutableArray array];
  483. for (NSUInteger x = 0; x < mediaCount; x++) {
  484. VLCMedia *media = [mediaList mediaAtIndex:x];
  485. NSString *mediaTitle;
  486. if (media.parsedStatus == VLCMediaParsedStatusDone) {
  487. mediaTitle = [media metadataForKey:VLCMetaInformationTitle];
  488. } else {
  489. mediaTitle = media.url.lastPathComponent;
  490. }
  491. NSDictionary *mediaDict = @{ @"id" : media.url.absoluteString,
  492. @"title" : mediaTitle,
  493. @"duration" : @(media.length.intValue) };
  494. [retArray addObject:@{ @"media" : mediaDict }];
  495. }
  496. [mediaList unlock];
  497. NSError *error;
  498. NSData *returnData = [NSJSONSerialization dataWithJSONObject:retArray options:0 error:&error];
  499. if (error != nil) {
  500. APLog(@"JSON serialization failed %@", error);
  501. return [[HTTPErrorResponse alloc] initWithErrorCode:500];
  502. }
  503. return [[HTTPDataResponse alloc] initWithData:returnData];
  504. }
  505. #endif
  506. - (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path
  507. {
  508. if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"])
  509. return [self _httpPOSTresponseUploadJSON];
  510. #if TARGET_OS_IOS
  511. if ([path hasPrefix:@"/download/"]) {
  512. return [self _httpGETDownloadForPath:path];
  513. }
  514. if ([path hasPrefix:@"/thumbnail"]) {
  515. return [self _httpGETThumbnailForPath:path];
  516. }
  517. #else
  518. if ([path hasPrefix:@"/playing"]) {
  519. return [self _HTTPGETPlaying];
  520. }
  521. if ([path hasPrefix:@"/playlist"]) {
  522. return [self _HTTPGETPlaylist];
  523. }
  524. if ([path hasPrefix:@"/web_resources.js"]) {
  525. return [self _HTTPGETwebResources];
  526. }
  527. #endif
  528. NSString *filePath = [self filePathForURI:path];
  529. NSString *documentRoot = [config documentRoot];
  530. NSString *relativePath = [filePath substringFromIndex:[documentRoot length]];
  531. if ([relativePath isEqualToString:@"/index.html"] || [relativePath isEqualToString:@"/libMediaVLC.xml"]) {
  532. return [self _httpGETLibraryForPath:path];
  533. } else if ([relativePath isEqualToString:@"/style.css"]) {
  534. return [self _httpGETCSSForPath:path];
  535. }
  536. return [super httpResponseForMethod:method URI:path];
  537. }
  538. #if TARGET_OS_TV
  539. - (WebSocket *)webSocketForURI:(NSString *)path
  540. {
  541. return [[VLCPlayerControlWebSocket alloc] initWithRequest:request socket:asyncSocket];
  542. }
  543. #endif
  544. - (void)prepareForBodyWithSize:(UInt64)contentLength
  545. {
  546. // set up mime parser
  547. NSString* boundary = [request headerField:@"boundary"];
  548. _parser = [[MultipartFormDataParser alloc] initWithBoundary:boundary formEncoding:NSUTF8StringEncoding];
  549. _parser.delegate = self;
  550. APLog(@"expecting file of size %lli kB", contentLength / 1024);
  551. _contentLength = contentLength;
  552. }
  553. - (void)processBodyData:(NSData *)postDataChunk
  554. {
  555. /* append data to the parser. It will invoke callbacks to let us handle
  556. * parsed data. */
  557. [_parser appendData:postDataChunk];
  558. _receivedContent += postDataChunk.length;
  559. long long percentage = ((_receivedContent * 100) / _contentLength);
  560. APLog(@"received %lli kB (%lli %%)", _receivedContent / 1024, percentage);
  561. #if TARGET_OS_TV
  562. if (percentage >= 10) {
  563. [self performSelectorOnMainThread:@selector(startPlaybackOfPath:) withObject:_filepath waitUntilDone:NO];
  564. }
  565. #endif
  566. }
  567. #if TARGET_OS_TV
  568. - (void)startPlaybackOfPath:(NSString *)path
  569. {
  570. APLog(@"Starting playback of %@", path);
  571. if (_receivedFiles == nil)
  572. _receivedFiles = [[NSMutableArray alloc] init];
  573. if ([_receivedFiles containsObject:path])
  574. return;
  575. [_receivedFiles addObject:path];
  576. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  577. VLCMediaList *mediaList = vpc.mediaList;
  578. if (!mediaList) {
  579. mediaList = [[VLCMediaList alloc] init];
  580. }
  581. [mediaList addMedia:[VLCMedia mediaWithURL:[NSURL fileURLWithPath:path]]];
  582. if (!vpc.mediaList) {
  583. [vpc playMediaList:mediaList firstIndex:0 subtitlesFilePath:nil];
  584. }
  585. VLCFullscreenMovieTVViewController *movieVC = [VLCFullscreenMovieTVViewController fullscreenMovieTVViewController];
  586. if (![movieVC isBeingPresented]) {
  587. [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:movieVC
  588. animated:YES
  589. completion:nil];
  590. }
  591. }
  592. #endif
  593. //-----------------------------------------------------------------
  594. #pragma mark multipart form data parser delegate
  595. - (void)processStartOfPartWithHeader:(MultipartMessageHeader*) header
  596. {
  597. /* in this sample, we are not interested in parts, other then file parts.
  598. * check content disposition to find out filename */
  599. MultipartMessageHeaderField* disposition = (header.fields)[@"Content-Disposition"];
  600. NSString* filename = [(disposition.params)[@"filename"] lastPathComponent];
  601. if ((nil == filename) || [filename isEqualToString: @""]) {
  602. // it's either not a file part, or
  603. // an empty form sent. we won't handle it.
  604. return;
  605. }
  606. // create the path where to store the media temporarily
  607. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  608. NSString *uploadDirPath = [searchPaths[0] stringByAppendingPathComponent:@"Upload"];
  609. NSFileManager *fileManager = [NSFileManager defaultManager];
  610. BOOL isDir = YES;
  611. if (![fileManager fileExistsAtPath:uploadDirPath isDirectory:&isDir])
  612. [fileManager createDirectoryAtPath:uploadDirPath withIntermediateDirectories:YES attributes:nil error:nil];
  613. _filepath = [uploadDirPath stringByAppendingPathComponent: filename];
  614. NSNumber *freeSpace = [[UIDevice currentDevice] VLCFreeDiskSpace];
  615. if (_contentLength >= freeSpace.longLongValue) {
  616. /* avoid deadlock since we are on a background thread */
  617. [self performSelectorOnMainThread:@selector(notifyUserAboutEndOfFreeStorage:) withObject:filename waitUntilDone:NO];
  618. [self handleResourceNotFound];
  619. [self stop];
  620. return;
  621. }
  622. APLog(@"Saving file to %@", _filepath);
  623. if (![fileManager createDirectoryAtPath:uploadDirPath withIntermediateDirectories:true attributes:nil error:nil])
  624. APLog(@"Could not create directory at path: %@", _filepath);
  625. if (![fileManager createFileAtPath:_filepath contents:nil attributes:nil])
  626. APLog(@"Could not create file at path: %@", _filepath);
  627. _storeFile = [NSFileHandle fileHandleForWritingAtPath:_filepath];
  628. VLCActivityManager *activityManager = [VLCActivityManager defaultManager];
  629. [activityManager networkActivityStarted];
  630. [activityManager disableIdleTimer];
  631. }
  632. - (void)notifyUserAboutEndOfFreeStorage:(NSString *)filename
  633. {
  634. #if TARGET_OS_IOS
  635. [VLCAlertViewController alertViewManagerWithTitle:NSLocalizedString(@"DISK_FULL", nil)
  636. errorMessage:[NSString stringWithFormat:
  637. NSLocalizedString(@"DISK_FULL_FORMAT", nil),
  638. filename,
  639. [[UIDevice currentDevice] model]]
  640. viewController:[UIApplication sharedApplication].keyWindow.rootViewController];
  641. #else
  642. UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"DISK_FULL", nil)
  643. message:[NSString stringWithFormat:
  644. NSLocalizedString(@"DISK_FULL_FORMAT", nil),
  645. filename,
  646. [[UIDevice currentDevice] model]]
  647. preferredStyle:UIAlertControllerStyleAlert];
  648. [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_CANCEL", nil)
  649. style:UIAlertActionStyleCancel
  650. handler:nil]];
  651. [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
  652. #endif
  653. }
  654. - (void)processContent:(NSData*)data WithHeader:(MultipartMessageHeader*) header
  655. {
  656. // here we just write the output from parser to the file.
  657. if (_storeFile) {
  658. @try {
  659. [_storeFile writeData:data];
  660. }
  661. @catch (NSException *exception) {
  662. APLog(@"File to write further data because storage is full.");
  663. [_storeFile closeFile];
  664. _storeFile = nil;
  665. /* don't block */
  666. [self performSelector:@selector(stop) withObject:nil afterDelay:0.1];
  667. }
  668. }
  669. }
  670. - (void)processEndOfPartWithHeader:(MultipartMessageHeader*)header
  671. {
  672. // as the file part is over, we close the file.
  673. APLog(@"closing file");
  674. [_storeFile closeFile];
  675. _storeFile = nil;
  676. }
  677. - (BOOL)shouldDie
  678. {
  679. if (_filepath) {
  680. if (_filepath.length > 0) {
  681. [[VLCHTTPUploaderController sharedInstance] moveFileFrom:_filepath];
  682. #if TARGET_OS_TV
  683. [_receivedFiles removeObject:_filepath];
  684. #endif
  685. }
  686. }
  687. return [super shouldDie];
  688. }
  689. #pragma mark subtitle
  690. - (NSMutableArray *)_listOfSubtitles
  691. {
  692. NSMutableArray *listOfSubtitles = [[NSMutableArray alloc] init];
  693. NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
  694. NSArray *allFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:nil];
  695. NSString *filePath;
  696. NSUInteger count = allFiles.count;
  697. for (NSUInteger i = 0; i < count; i++) {
  698. filePath = [[NSString stringWithFormat:@"%@/%@", documentsDirectory, allFiles[i]] stringByReplacingOccurrencesOfString:@"file://"withString:@""];
  699. if ([filePath isSupportedSubtitleFormat])
  700. [listOfSubtitles addObject:filePath];
  701. }
  702. return listOfSubtitles;
  703. }
  704. - (NSString *)_checkIfSubtitleWasFound:(NSString *)filePath
  705. {
  706. NSString *subtitlePath;
  707. NSString *fileSub;
  708. NSString *currentPath;
  709. NSString *fileName = [[filePath lastPathComponent] stringByDeletingPathExtension];
  710. if (fileName == nil)
  711. return nil;
  712. NSMutableArray *listOfSubtitles = [self _listOfSubtitles];
  713. NSUInteger count = listOfSubtitles.count;
  714. for (NSUInteger i = 0; i < count; i++) {
  715. currentPath = listOfSubtitles[i];
  716. fileSub = [NSString stringWithFormat:@"%@", currentPath];
  717. if ([fileSub rangeOfString:fileName].location != NSNotFound)
  718. subtitlePath = currentPath;
  719. }
  720. return subtitlePath;
  721. }
  722. @end