Commit 864f113f authored by Jean-Baptiste Kempf's avatar Jean-Baptiste Kempf

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

parent 670abc33
......@@ -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
//
// 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
\ No newline at end of file
//
// 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
<!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>
\ No newline at end of file
<!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>
\ No newline at end of file
......@@ -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;
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment