Selaa lähdekoodia

HTTP Uploader: very simple version that is not fully functional yet

Jean-Baptiste Kempf 12 vuotta sitten
vanhempi
commit
864f113fa0

+ 7 - 2
AspenProject/VLCAddMediaViewController.m

@@ -11,8 +11,11 @@
 #import "VLCPlaylistViewController.h"
 #import "VLCAboutViewController.h"
 #import "VLCMovieViewController.h"
+#import "VLCHTTPUploaderController.h"
 
-@interface VLCAddMediaViewController ()
+@interface VLCAddMediaViewController () {
+    VLCHTTPUploaderController *_uploadController;
+}
 
 @end
 
@@ -106,8 +109,10 @@
 {
 }
 
-- (IBAction)toggleHTTPServer:(id)sender
+- (IBAction)toggleHTTPServer:(UISwitch *)sender
 {
+    _uploadController = [[VLCHTTPUploaderController alloc] init];
+    [_uploadController changeHTTPServerState: sender.on];
 }
 
 @end

+ 31 - 0
AspenProject/VLCHTTPUploaderController.h

@@ -0,0 +1,31 @@
+//
+//  VLCHTTPUploaderViewController.h
+//  VLC for iOS
+//
+//  Created by Jean-Baptiste Kempf on 19/05/13.
+//  Copyright (c) 2013 VideoLAN. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+#import "HTTPConnection.h"
+
+@class HTTPServer;
+
+@interface VLCHTTPUploaderController : NSObject {
+    HTTPServer *httpServer;
+}
+
+-(BOOL)changeHTTPServerState:(BOOL)state;
+
+@end
+
+@class MultipartFormDataParser;
+
+@interface VLCHTTPConnection : HTTPConnection  {
+    MultipartFormDataParser*        parser;
+	NSFileHandle*					storeFile;
+	
+	NSMutableArray*					uploadedFiles;
+}
+
+@end

+ 246 - 0
AspenProject/VLCHTTPUploaderController.m

@@ -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

+ 15 - 0
Resources/web/index.html

@@ -0,0 +1,15 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+	<head>
+		<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">
+			</head>
+	<body>
+		<form action="upload.html" method="post" enctype="multipart/form-data" accept-charset="utf-8">
+			<input type="file" name="upload1"><br/>
+                <input type="file" name="upload2"><br/>
+				<input type="submit" value="Submit">
+					</form>
+		
+
+	</body>
+</html>

+ 9 - 0
Resources/web/upload.html

@@ -0,0 +1,9 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+	<head>
+		<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">
+			</head>
+	<body>
+		%MyFiles%
+	</body>
+</html>

+ 22 - 0
VLC for iOS.xcodeproj/project.pbxproj

@@ -7,6 +7,8 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		29125E5617492219003F03E5 /* index.html in Resources */ = {isa = PBXBuildFile; fileRef = 29125E5417492219003F03E5 /* index.html */; };
+		29125E5717492219003F03E5 /* upload.html in Resources */ = {isa = PBXBuildFile; fileRef = 29125E5517492219003F03E5 /* upload.html */; };
 		2915540117490A1E00B86CAD /* DDData.m in Sources */ = {isa = PBXBuildFile; fileRef = 291553EB17490A1E00B86CAD /* DDData.m */; };
 		2915540217490A1E00B86CAD /* DDNumber.m in Sources */ = {isa = PBXBuildFile; fileRef = 291553ED17490A1E00B86CAD /* DDNumber.m */; };
 		2915540317490A1E00B86CAD /* DDRange.m in Sources */ = {isa = PBXBuildFile; fileRef = 291553EF17490A1E00B86CAD /* DDRange.m */; };
@@ -36,6 +38,7 @@
 		2915544017490B9C00B86CAD /* HTTPRedirectResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 2915543817490B9C00B86CAD /* HTTPRedirectResponse.m */; };
 		2915544117490B9C00B86CAD /* WebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 2915543A17490B9C00B86CAD /* WebSocket.m */; };
 		2915544317490D4A00B86CAD /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2915544217490D4A00B86CAD /* Security.framework */; };
+		29CE2D44174912C600922D8F /* VLCHTTPUploaderController.m in Sources */ = {isa = PBXBuildFile; fileRef = 29CE2D42174912C600922D8F /* VLCHTTPUploaderController.m */; };
 		7D10BC761743FA0F00DA7059 /* audio.png in Resources */ = {isa = PBXBuildFile; fileRef = 7D10BC6A1743FA0F00DA7059 /* audio.png */; };
 		7D10BC771743FA0F00DA7059 /* audio@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 7D10BC6B1743FA0F00DA7059 /* audio@2x.png */; };
 		7D10BC781743FA0F00DA7059 /* backward.png in Resources */ = {isa = PBXBuildFile; fileRef = 7D10BC6C1743FA0F00DA7059 /* backward.png */; };
@@ -133,6 +136,8 @@
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
+		29125E5417492219003F03E5 /* index.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = index.html; sourceTree = "<group>"; };
+		29125E5517492219003F03E5 /* upload.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = upload.html; sourceTree = "<group>"; };
 		291553EA17490A1E00B86CAD /* DDData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDData.h; sourceTree = "<group>"; };
 		291553EB17490A1E00B86CAD /* DDData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDData.m; sourceTree = "<group>"; };
 		291553EC17490A1E00B86CAD /* DDNumber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDNumber.h; sourceTree = "<group>"; };
@@ -189,6 +194,8 @@
 		2915543917490B9C00B86CAD /* WebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WebSocket.h; path = ImportedSources/CocoaHTTPServer/Core/WebSocket.h; sourceTree = SOURCE_ROOT; };
 		2915543A17490B9C00B86CAD /* WebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = WebSocket.m; path = ImportedSources/CocoaHTTPServer/Core/WebSocket.m; sourceTree = SOURCE_ROOT; };
 		2915544217490D4A00B86CAD /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
+		29CE2D41174912C600922D8F /* VLCHTTPUploaderController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VLCHTTPUploaderController.h; sourceTree = "<group>"; };
+		29CE2D42174912C600922D8F /* VLCHTTPUploaderController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VLCHTTPUploaderController.m; sourceTree = "<group>"; };
 		7D10BC6A1743FA0F00DA7059 /* audio.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = audio.png; sourceTree = "<group>"; };
 		7D10BC6B1743FA0F00DA7059 /* audio@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "audio@2x.png"; sourceTree = "<group>"; };
 		7D10BC6C1743FA0F00DA7059 /* backward.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = backward.png; sourceTree = "<group>"; };
@@ -345,6 +352,15 @@
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+		29125E5317492219003F03E5 /* web */ = {
+			isa = PBXGroup;
+			children = (
+				29125E5417492219003F03E5 /* index.html */,
+				29125E5517492219003F03E5 /* upload.html */,
+			);
+			path = web;
+			sourceTree = "<group>";
+		};
 		291553E817490A0400B86CAD /* CocoaHTTPServer */ = {
 			isa = PBXGroup;
 			children = (
@@ -636,6 +652,8 @@
 				7DA6209A170A0CE500643D11 /* VLCPlaylistTableViewCell.m */,
 				7D6B07F51716D45B003280C4 /* VLCPlaylistGridView.h */,
 				7D6B07F61716D45B003280C4 /* VLCPlaylistGridView.m */,
+				29CE2D41174912C600922D8F /* VLCHTTPUploaderController.h */,
+				29CE2D42174912C600922D8F /* VLCHTTPUploaderController.m */,
 				A7DA16CF171083DF00D6FED9 /* VLCExternalDisplayController.h */,
 				A7DA16D0171083DF00D6FED9 /* VLCExternalDisplayController.m */,
 				7D6BA1141748EFE100C0E203 /* VLCAddMediaViewController.h */,
@@ -704,6 +722,7 @@
 		A7924697170F0ED20036AAF2 /* Resources */ = {
 			isa = PBXGroup;
 			children = (
+				29125E5317492219003F03E5 /* web */,
 				A7035BBD174519600057DFA7 /* iTunesArtwork */,
 				7D10BC691743F9CB00DA7059 /* Filters and stuff */,
 				7D10BC681743F9C300DA7059 /* Playback Controls */,
@@ -842,6 +861,8 @@
 				2915542B17490A9C00B86CAD /* README.txt in Resources */,
 				7D6BA1181748EFE100C0E203 /* VLCAddMediaViewController~ipad.xib in Resources */,
 				7D6BA11A174911C200C0E203 /* VLCAddMediaViewController~iphone.xib in Resources */,
+				29125E5617492219003F03E5 /* index.html in Resources */,
+				29125E5717492219003F03E5 /* upload.html in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -901,6 +922,7 @@
 				2915544017490B9C00B86CAD /* HTTPRedirectResponse.m in Sources */,
 				2915544117490B9C00B86CAD /* WebSocket.m in Sources */,
 				7D6BA1171748EFE100C0E203 /* VLCAddMediaViewController.m in Sources */,
+				29CE2D44174912C600922D8F /* VLCHTTPUploaderController.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};