|
@@ -0,0 +1,246 @@
|
|
|
+//
|
|
|
+// VLCHTTPUploaderViewController.m
|
|
|
+// VLC for iOS
|
|
|
+//
|
|
|
+// Created by Jean-Baptiste Kempf on 19/05/13.
|
|
|
+// Copyright (c) 2013 VideoLAN. All rights reserved.
|
|
|
+//
|
|
|
+
|
|
|
+#import "VLCHTTPUploaderController.h"
|
|
|
+
|
|
|
+#import "DDLog.h"
|
|
|
+#import "DDTTYLogger.h"
|
|
|
+#import "DDNumber.h"
|
|
|
+
|
|
|
+#import "HTTPServer.h"
|
|
|
+#import "HTTPMessage.h"
|
|
|
+#import "HTTPDataResponse.h"
|
|
|
+#import "HTTPLogging.h"
|
|
|
+#import "HTTPDynamicFileResponse.h"
|
|
|
+#import "HTTPFileResponse.h"
|
|
|
+
|
|
|
+#import "MultipartFormDataParser.h"
|
|
|
+#import "MultipartMessageHeaderField.h"
|
|
|
+
|
|
|
+
|
|
|
+static const int ddLogLevel = LOG_LEVEL_VERBOSE;
|
|
|
+static const int httpLogLevel = HTTP_LOG_LEVEL_VERBOSE; // | HTTP_LOG_FLAG_TRACE;
|
|
|
+
|
|
|
+@interface VLCHTTPUploaderController ()
|
|
|
+
|
|
|
+@end
|
|
|
+
|
|
|
+@implementation VLCHTTPUploaderController
|
|
|
+
|
|
|
+-(BOOL)changeHTTPServerState:(BOOL)state
|
|
|
+{
|
|
|
+ if(state) {
|
|
|
+ // To keep things simple and fast, we're just going to log to the Xcode console.
|
|
|
+ [DDLog addLogger:[DDTTYLogger sharedInstance]];
|
|
|
+
|
|
|
+ // Initalize our http server
|
|
|
+ httpServer = [[HTTPServer alloc] init];
|
|
|
+
|
|
|
+ // Tell the server to broadcast its presence via Bonjour.
|
|
|
+ // This allows browsers such as Safari to automatically discover our service.
|
|
|
+ [httpServer setType:@"_http._tcp."];
|
|
|
+
|
|
|
+ // Serve files from the standard Sites folder
|
|
|
+ NSString *docRoot = [[[NSBundle mainBundle] pathForResource:@"index" ofType:@"html" inDirectory:@"web"] stringByDeletingLastPathComponent];
|
|
|
+
|
|
|
+ DDLogInfo(@"Setting document root: %@", docRoot);
|
|
|
+
|
|
|
+ [httpServer setDocumentRoot:docRoot];
|
|
|
+
|
|
|
+ [httpServer setConnectionClass:[VLCHTTPConnection class]];
|
|
|
+
|
|
|
+ NSError *error = nil;
|
|
|
+ if(![httpServer start:&error])
|
|
|
+ {
|
|
|
+ DDLogError(@"Error starting HTTP Server: %@", error);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ [httpServer stop];
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@end
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * All we have to do is override appropriate methods in HTTPConnection.
|
|
|
+ **/
|
|
|
+
|
|
|
+@implementation VLCHTTPConnection
|
|
|
+
|
|
|
+- (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path
|
|
|
+{
|
|
|
+ HTTPLogTrace();
|
|
|
+
|
|
|
+ // Add support for POST
|
|
|
+ if ([method isEqualToString:@"POST"])
|
|
|
+ {
|
|
|
+ if ([path isEqualToString:@"/upload.html"])
|
|
|
+ {
|
|
|
+ return YES;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return [super supportsMethod:method atPath:path];
|
|
|
+}
|
|
|
+
|
|
|
+- (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path
|
|
|
+{
|
|
|
+ HTTPLogTrace();
|
|
|
+
|
|
|
+ // Inform HTTP server that we expect a body to accompany a POST request
|
|
|
+
|
|
|
+ if([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.html"]) {
|
|
|
+ // here we need to make sure, boundary is set in header
|
|
|
+ NSString* contentType = [request headerField:@"Content-Type"];
|
|
|
+ NSUInteger paramsSeparator = [contentType rangeOfString:@";"].location;
|
|
|
+ if( NSNotFound == paramsSeparator ) {
|
|
|
+ return NO;
|
|
|
+ }
|
|
|
+ if( paramsSeparator >= contentType.length - 1 ) {
|
|
|
+ return NO;
|
|
|
+ }
|
|
|
+ NSString* type = [contentType substringToIndex:paramsSeparator];
|
|
|
+ if( ![type isEqualToString:@"multipart/form-data"] ) {
|
|
|
+ // we expect multipart/form-data content type
|
|
|
+ return NO;
|
|
|
+ }
|
|
|
+
|
|
|
+ // enumerate all params in content-type, and find boundary there
|
|
|
+ NSArray* params = [[contentType substringFromIndex:paramsSeparator + 1] componentsSeparatedByString:@";"];
|
|
|
+ for( NSString* param in params ) {
|
|
|
+ paramsSeparator = [param rangeOfString:@"="].location;
|
|
|
+ if( (NSNotFound == paramsSeparator) || paramsSeparator >= param.length - 1 ) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ NSString* paramName = [param substringWithRange:NSMakeRange(1, paramsSeparator-1)];
|
|
|
+ NSString* paramValue = [param substringFromIndex:paramsSeparator+1];
|
|
|
+
|
|
|
+ if( [paramName isEqualToString: @"boundary"] ) {
|
|
|
+ // let's separate the boundary from content-type, to make it more handy to handle
|
|
|
+ [request setHeaderField:@"boundary" value:paramValue];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // check if boundary specified
|
|
|
+ if( nil == [request headerField:@"boundary"] ) {
|
|
|
+ return NO;
|
|
|
+ }
|
|
|
+ return YES;
|
|
|
+ }
|
|
|
+ return [super expectsRequestBodyFromMethod:method atPath:path];
|
|
|
+}
|
|
|
+
|
|
|
+- (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path
|
|
|
+{
|
|
|
+ HTTPLogTrace();
|
|
|
+
|
|
|
+ if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.html"])
|
|
|
+ {
|
|
|
+
|
|
|
+ // this method will generate response with links to uploaded file
|
|
|
+ NSMutableString* filesStr = [[NSMutableString alloc] init];
|
|
|
+
|
|
|
+ for( NSString* filePath in uploadedFiles ) {
|
|
|
+ //generate links
|
|
|
+ [filesStr appendFormat:@"<a href=\"%@\"> %@ </a><br/>",filePath, [filePath lastPathComponent]];
|
|
|
+ }
|
|
|
+ NSString* templatePath = [[config documentRoot] stringByAppendingPathComponent:@"upload.html"];
|
|
|
+ NSDictionary* replacementDict = [NSDictionary dictionaryWithObject:filesStr forKey:@"MyFiles"];
|
|
|
+ // use dynamic file response to apply our links to response template
|
|
|
+ return [[HTTPDynamicFileResponse alloc] initWithFilePath:templatePath forConnection:self separator:@"%" replacementDictionary:replacementDict];
|
|
|
+ }
|
|
|
+ if( [method isEqualToString:@"GET"] && [path hasPrefix:@"/upload/"] ) {
|
|
|
+ // let download the uploaded files
|
|
|
+ return [[HTTPFileResponse alloc] initWithFilePath: [[config documentRoot] stringByAppendingString:path] forConnection:self];
|
|
|
+ }
|
|
|
+
|
|
|
+ return [super httpResponseForMethod:method URI:path];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)prepareForBodyWithSize:(UInt64)contentLength
|
|
|
+{
|
|
|
+ HTTPLogTrace();
|
|
|
+
|
|
|
+ // set up mime parser
|
|
|
+ NSString* boundary = [request headerField:@"boundary"];
|
|
|
+ parser = [[MultipartFormDataParser alloc] initWithBoundary:boundary formEncoding:NSUTF8StringEncoding];
|
|
|
+ parser.delegate = self;
|
|
|
+
|
|
|
+ uploadedFiles = [[NSMutableArray alloc] init];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)processBodyData:(NSData *)postDataChunk
|
|
|
+{
|
|
|
+ HTTPLogTrace();
|
|
|
+ // append data to the parser. It will invoke callbacks to let us handle
|
|
|
+ // parsed data.
|
|
|
+ [parser appendData:postDataChunk];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+//-----------------------------------------------------------------
|
|
|
+#pragma mark multipart form data parser delegate
|
|
|
+
|
|
|
+
|
|
|
+- (void) processStartOfPartWithHeader:(MultipartMessageHeader*) header {
|
|
|
+ // in this sample, we are not interested in parts, other then file parts.
|
|
|
+ // check content disposition to find out filename
|
|
|
+
|
|
|
+ MultipartMessageHeaderField* disposition = [header.fields objectForKey:@"Content-Disposition"];
|
|
|
+ NSString* filename = [[disposition.params objectForKey:@"filename"] lastPathComponent];
|
|
|
+
|
|
|
+ if ( (nil == filename) || [filename isEqualToString: @""] ) {
|
|
|
+ // it's either not a file part, or
|
|
|
+ // an empty form sent. we won't handle it.
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ NSString* uploadDirPath = [[config documentRoot] stringByAppendingPathComponent:@"upload"];
|
|
|
+
|
|
|
+ BOOL isDir = YES;
|
|
|
+ if (![[NSFileManager defaultManager]fileExistsAtPath:uploadDirPath isDirectory:&isDir ]) {
|
|
|
+ [[NSFileManager defaultManager]createDirectoryAtPath:uploadDirPath withIntermediateDirectories:YES attributes:nil error:nil];
|
|
|
+ }
|
|
|
+
|
|
|
+ NSString* filePath = [uploadDirPath stringByAppendingPathComponent: filename];
|
|
|
+ if( [[NSFileManager defaultManager] fileExistsAtPath:filePath] ) {
|
|
|
+ storeFile = nil;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ HTTPLogVerbose(@"Saving file to %@", filePath);
|
|
|
+ if(![[NSFileManager defaultManager] createDirectoryAtPath:uploadDirPath withIntermediateDirectories:true attributes:nil error:nil]) {
|
|
|
+ HTTPLogError(@"Could not create directory at path: %@", filePath);
|
|
|
+ }
|
|
|
+ if(![[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil]) {
|
|
|
+ HTTPLogError(@"Could not create file at path: %@", filePath);
|
|
|
+ }
|
|
|
+ storeFile = [NSFileHandle fileHandleForWritingAtPath:filePath];
|
|
|
+ [uploadedFiles addObject: [NSString stringWithFormat:@"/upload/%@", filename]];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void) processContent:(NSData*) data WithHeader:(MultipartMessageHeader*) header
|
|
|
+{
|
|
|
+ // here we just write the output from parser to the file.
|
|
|
+ if( storeFile ) {
|
|
|
+ [storeFile writeData:data];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void) processEndOfPartWithHeader:(MultipartMessageHeader*) header
|
|
|
+{
|
|
|
+ // as the file part is over, we close the file.
|
|
|
+ [storeFile closeFile];
|
|
|
+ storeFile = nil;
|
|
|
+}
|
|
|
+
|
|
|
+@end
|