├── README.md ├── .gitmodules ├── VMMediaDownloader ├── CocoaHTTPServer │ ├── Extensions │ │ └── WebDAV │ │ │ ├── DAVConnection.h │ │ │ ├── DELETEResponse.h │ │ │ ├── PUTResponse.h │ │ │ ├── DAVResponse.h │ │ │ ├── DELETEResponse.m │ │ │ ├── PUTResponse.m │ │ │ ├── DAVConnection.m │ │ │ └── DAVResponse.m │ ├── Vendor │ │ ├── CocoaAsyncSocket │ │ │ └── About.txt │ │ └── CocoaLumberjack │ │ │ ├── DDASLLogger.h │ │ │ ├── DDTTYLogger.h │ │ │ ├── DDASLLogger.m │ │ │ ├── About.txt │ │ │ ├── DDAbstractDatabaseLogger.h │ │ │ ├── DDTTYLogger.m │ │ │ ├── DDFileLogger.h │ │ │ └── DDAbstractDatabaseLogger.m │ ├── Core │ │ ├── Responses │ │ │ ├── HTTPDataResponse.h │ │ │ ├── HTTPRedirectResponse.h │ │ │ ├── HTTPFileResponse.h │ │ │ ├── HTTPRedirectResponse.m │ │ │ ├── HTTPDataResponse.m │ │ │ ├── HTTPDynamicFileResponse.h │ │ │ ├── HTTPAsyncFileResponse.h │ │ │ ├── HTTPFileResponse.m │ │ │ ├── HTTPDynamicFileResponse.m │ │ │ └── HTTPAsyncFileResponse.m │ │ ├── Categories │ │ │ ├── DDData.h │ │ │ ├── DDNumber.h │ │ │ ├── DDNumber.m │ │ │ ├── DDRange.h │ │ │ ├── DDRange.m │ │ │ └── DDData.m │ │ ├── HTTPAuthenticationRequest.h │ │ ├── HTTPMessage.h │ │ ├── HTTPMessage.m │ │ ├── WebSocket.h │ │ ├── HTTPConnection.h │ │ ├── HTTPLogging.h │ │ ├── HTTPAuthenticationRequest.m │ │ ├── HTTPServer.h │ │ ├── HTTPResponse.h │ │ └── WebSocket.m │ ├── README.markdown │ └── LICENSE.txt ├── m3u8 │ ├── M3U8SegmentInfo.m │ ├── M3U8SegmentInfo.h │ ├── M3U8Playlist.h │ ├── DownloadDelegate.h │ ├── M3U8Handler.h │ ├── M3U8Playlist.mm │ ├── VideoDownloader.h │ ├── SegmentDownloader.h │ ├── SegmentDownloader.mm │ ├── M3U8Handler.mm │ └── VideoDownloader.mm ├── main.m ├── AppDelegate.h ├── ViewController.h ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── ViewController.m ├── AppDelegate.m └── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── VMMediaDownloader.xcodeproj └── project.xcworkspace │ └── contents.xcworkspacedata └── .gitignore /README.md: -------------------------------------------------------------------------------- 1 | # VMMediaDownloader 2 | 3 | This is a demo project to download .m3u8 and save it locally for playback. 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Vendor/asi-http-request"] 2 | path = Vendor/asi-http-request 3 | url = git@github.com:pokeb/asi-http-request.git 4 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Extensions/WebDAV/DAVConnection.h: -------------------------------------------------------------------------------- 1 | #import "HTTPConnection.h" 2 | 3 | @interface DAVConnection : HTTPConnection { 4 | id requestContentBody; 5 | NSOutputStream* requestContentStream; 6 | } 7 | @end 8 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Extensions/WebDAV/DELETEResponse.h: -------------------------------------------------------------------------------- 1 | #import "HTTPResponse.h" 2 | 3 | @interface DELETEResponse : NSObject { 4 | NSInteger _status; 5 | } 6 | - (id) initWithFilePath:(NSString*)path; 7 | @end 8 | -------------------------------------------------------------------------------- /VMMediaDownloader.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Vendor/CocoaAsyncSocket/About.txt: -------------------------------------------------------------------------------- 1 | The CocoaAsyncSocket project is under Public Domain license. 2 | http://code.google.com/p/cocoaasyncsocket/ 3 | 4 | The AsyncSocket project has been around since 2001 and is used in many applications and frameworks. -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/Responses/HTTPDataResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "HTTPResponse.h" 3 | 4 | 5 | @interface HTTPDataResponse : NSObject 6 | { 7 | NSUInteger offset; 8 | NSData *data; 9 | } 10 | 11 | - (id)initWithData:(NSData *)data; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/Responses/HTTPRedirectResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "HTTPResponse.h" 3 | 4 | 5 | @interface HTTPRedirectResponse : NSObject 6 | { 7 | NSString *redirectPath; 8 | } 9 | 10 | - (id)initWithPath:(NSString *)redirectPath; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/Categories/DDData.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSData (DDData) 4 | 5 | - (NSData *)md5Digest; 6 | 7 | - (NSData *)sha1Digest; 8 | 9 | - (NSString *)hexStringValue; 10 | 11 | - (NSString *)base64Encoded; 12 | - (NSData *)base64Decoded; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Extensions/WebDAV/PUTResponse.h: -------------------------------------------------------------------------------- 1 | #import "HTTPResponse.h" 2 | 3 | @interface PUTResponse : NSObject { 4 | NSInteger _status; 5 | } 6 | - (id) initWithFilePath:(NSString*)path headers:(NSDictionary*)headers bodyData:(NSData*)body; 7 | - (id) initWithFilePath:(NSString*)path headers:(NSDictionary*)headers bodyFile:(NSString*)body; 8 | @end 9 | -------------------------------------------------------------------------------- /VMMediaDownloader/m3u8/M3U8SegmentInfo.m: -------------------------------------------------------------------------------- 1 | // 2 | // M3U8SegmentInfo.m 3 | // XB 4 | // 5 | // Created by luoxubin on 3/7/12. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import "M3U8SegmentInfo.h" 10 | 11 | @implementation M3U8SegmentInfo 12 | @synthesize locationUrl; 13 | @synthesize duration; 14 | 15 | -(void)dealloc 16 | { 17 | locationUrl = nil; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /VMMediaDownloader/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // VMMediaDownloader 4 | // 5 | // Created by Sun Peng on 11/6/14. 6 | // Copyright (c) 2014 Peng Sun. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/Categories/DDNumber.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSNumber (DDNumber) 5 | 6 | + (BOOL)parseString:(NSString *)str intoSInt64:(SInt64 *)pNum; 7 | + (BOOL)parseString:(NSString *)str intoUInt64:(UInt64 *)pNum; 8 | 9 | + (BOOL)parseString:(NSString *)str intoNSInteger:(NSInteger *)pNum; 10 | + (BOOL)parseString:(NSString *)str intoNSUInteger:(NSUInteger *)pNum; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /VMMediaDownloader/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // VMMediaDownloader 4 | // 5 | // Created by Sun Peng on 11/6/14. 6 | // Copyright (c) 2014 Peng Sun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class HTTPServer; 12 | @interface AppDelegate : UIResponder { 13 | HTTPServer* httpServer; 14 | } 15 | 16 | @property (strong, nonatomic) UIWindow *window; 17 | 18 | 19 | @end 20 | 21 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Extensions/WebDAV/DAVResponse.h: -------------------------------------------------------------------------------- 1 | #import "HTTPResponse.h" 2 | 3 | @interface DAVResponse : NSObject { 4 | @private 5 | UInt64 _offset; 6 | NSMutableDictionary* _headers; 7 | NSData* _data; 8 | NSInteger _status; 9 | } 10 | - (id) initWithMethod:(NSString*)method headers:(NSDictionary*)headers bodyData:(NSData*)body resourcePath:(NSString*)resourcePath rootPath:(NSString*)rootPath; 11 | @end 12 | -------------------------------------------------------------------------------- /VMMediaDownloader/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // VMMediaDownloader 4 | // 5 | // Created by Sun Peng on 11/6/14. 6 | // Copyright (c) 2014 Peng Sun. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "VideoDownloader.h" 11 | 12 | @class MPMoviePlayerViewController; 13 | @interface ViewController : UIViewController { 14 | VideoDownloader *_downloader; 15 | } 16 | 17 | 18 | @end 19 | 20 | -------------------------------------------------------------------------------- /VMMediaDownloader/m3u8/M3U8SegmentInfo.h: -------------------------------------------------------------------------------- 1 | // 2 | // M3U8SegmentInfo.h 3 | // XB 4 | // 5 | // Created by luoxubin on 3/7/12. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface M3U8SegmentInfo : NSObject 12 | { 13 | NSInteger duration; 14 | NSString *locationUrl; 15 | } 16 | 17 | @property(nonatomic,assign)NSInteger duration; 18 | @property(nonatomic,copy)NSString* locationUrl; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/Responses/HTTPFileResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "HTTPResponse.h" 3 | 4 | @class HTTPConnection; 5 | 6 | 7 | @interface HTTPFileResponse : NSObject 8 | { 9 | HTTPConnection *connection; 10 | 11 | NSString *filePath; 12 | UInt64 fileLength; 13 | UInt64 fileOffset; 14 | 15 | BOOL aborted; 16 | 17 | int fileFD; 18 | void *buffer; 19 | NSUInteger bufferSize; 20 | } 21 | 22 | - (id)initWithFilePath:(NSString *)filePath forConnection:(HTTPConnection *)connection; 23 | - (NSString *)filePath; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | -------------------------------------------------------------------------------- /VMMediaDownloader/m3u8/M3U8Playlist.h: -------------------------------------------------------------------------------- 1 | // 2 | // M3U8Playlist.h 3 | // XB 4 | // 5 | // Created by luoxubin on 3/7/12. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "M3U8SegmentInfo.h" 11 | 12 | @interface M3U8Playlist : NSObject 13 | { 14 | NSMutableArray *segments; 15 | NSInteger length; 16 | NSString* uuid; 17 | } 18 | 19 | @property (nonatomic, retain) NSMutableArray *segments; 20 | @property (assign) NSInteger length; 21 | @property (nonatomic,copy)NSString* uuid; 22 | 23 | - (id)initWithSegments:(NSMutableArray *)segmentList; 24 | - (M3U8SegmentInfo *)getSegment:(NSInteger)index; 25 | 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /VMMediaDownloader/m3u8/DownloadDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // DownloadObjectDelegate.h 3 | // XB 4 | // 5 | // Created by luoxubin on 3/8/12. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | #import 9 | 10 | @class SegmentDownloader; 11 | @protocol SegmentDownloadDelegate 12 | @optional 13 | -(void)segmentDownloadFinished:(SegmentDownloader *)request; 14 | -(void)segmentDownloadFailed:(SegmentDownloader *)request; 15 | 16 | @end 17 | 18 | 19 | @class VideoDownloader; 20 | @protocol VideoDownloadDelegate 21 | @optional 22 | -(void)videoDownloaderFinished:(VideoDownloader*)request; 23 | -(void)videoDownloaderFailed:(VideoDownloader*)request; 24 | @end -------------------------------------------------------------------------------- /VMMediaDownloader/m3u8/M3U8Handler.h: -------------------------------------------------------------------------------- 1 | // 2 | // M3U8Handler.h 3 | // XB 4 | // 5 | // Created by luoxubin on 3/7/12. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import 10 | @class M3U8Playlist; 11 | @class M3U8Handler; 12 | @protocol M3U8HandlerDelegate 13 | @optional 14 | -(void)praseM3U8Finished:(M3U8Handler*)handler; 15 | -(void)praseM3U8Failed:(M3U8Handler*)handler; 16 | @end 17 | 18 | 19 | @interface M3U8Handler : NSObject 20 | { 21 | id delegate; 22 | M3U8Playlist* playlist; 23 | } 24 | @property(nonatomic,retain)id delegate; 25 | @property(nonatomic,retain)M3U8Playlist* playlist; 26 | 27 | -(void)praseUrl:(NSString*)urlstr; 28 | @end 29 | -------------------------------------------------------------------------------- /VMMediaDownloader/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /VMMediaDownloader/m3u8/M3U8Playlist.mm: -------------------------------------------------------------------------------- 1 | // 2 | // M3U8Playlist.m 3 | // XB 4 | // 5 | // Created by luoxubin on 3/7/12. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import "M3U8Playlist.h" 10 | @implementation M3U8Playlist 11 | @synthesize segments; 12 | @synthesize length; 13 | @synthesize uuid; 14 | 15 | - (id)initWithSegments:(NSMutableArray *)segmentList 16 | { 17 | self = [super init]; 18 | if(self != nil) 19 | { 20 | self.segments = segmentList; 21 | self.length = [segmentList count]; 22 | } 23 | return self; 24 | } 25 | - (M3U8SegmentInfo *)getSegment:(NSInteger)index 26 | { 27 | if( index >=0 && index < self.length) 28 | { 29 | return (M3U8SegmentInfo *)[self.segments objectAtIndex:index]; 30 | } 31 | else 32 | { 33 | return nil; 34 | } 35 | } 36 | -(void)dealloc 37 | { 38 | [segments removeAllObjects]; 39 | segments = nil; 40 | } 41 | @end 42 | -------------------------------------------------------------------------------- /VMMediaDownloader/m3u8/VideoDownloader.h: -------------------------------------------------------------------------------- 1 | // 2 | // VideoDownloader.h 3 | // XB 4 | // 5 | // Created by luoxubin on 3/8/12. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SegmentDownloader.h" 11 | #import "M3U8Playlist.h" 12 | @interface VideoDownloader : NSObject 13 | { 14 | NSMutableArray *downloadArray; 15 | M3U8Playlist* playlist; 16 | float totalprogress; 17 | id delegate; 18 | BOOL bDownloading; 19 | } 20 | 21 | @property(nonatomic,retain)id delegate; 22 | @property(nonatomic,retain)M3U8Playlist* playlist; 23 | @property(nonatomic,assign)float totalprogress; 24 | 25 | -(id)initWithM3U8List:(M3U8Playlist*)list; 26 | 27 | //开始下载 28 | -(void)startDownloadVideo; 29 | 30 | //暂停下载 31 | -(void)stopDownloadVideo; 32 | 33 | //取消下载,而且清楚下载的部分文件 34 | -(void)cancelDownloadVideo; 35 | 36 | -(NSString*)createLocalM3U8file; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/HTTPAuthenticationRequest.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #if TARGET_OS_IPHONE 4 | // Note: You may need to add the CFNetwork Framework to your project 5 | #import 6 | #endif 7 | 8 | @class HTTPMessage; 9 | 10 | 11 | @interface HTTPAuthenticationRequest : NSObject 12 | { 13 | BOOL isBasic; 14 | BOOL isDigest; 15 | 16 | NSString *base64Credentials; 17 | 18 | NSString *username; 19 | NSString *realm; 20 | NSString *nonce; 21 | NSString *uri; 22 | NSString *qop; 23 | NSString *nc; 24 | NSString *cnonce; 25 | NSString *response; 26 | } 27 | - (id)initWithRequest:(HTTPMessage *)request; 28 | 29 | - (BOOL)isBasic; 30 | - (BOOL)isDigest; 31 | 32 | // Basic 33 | - (NSString *)base64Credentials; 34 | 35 | // Digest 36 | - (NSString *)username; 37 | - (NSString *)realm; 38 | - (NSString *)nonce; 39 | - (NSString *)uri; 40 | - (NSString *)qop; 41 | - (NSString *)nc; 42 | - (NSString *)cnonce; 43 | - (NSString *)response; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Extensions/WebDAV/DELETEResponse.m: -------------------------------------------------------------------------------- 1 | #import "DELETEResponse.h" 2 | #import "HTTPLogging.h" 3 | 4 | // HTTP methods: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html 5 | // HTTP headers: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html 6 | // HTTP status codes: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 7 | 8 | static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; 9 | 10 | @implementation DELETEResponse 11 | 12 | - (id) initWithFilePath:(NSString*)path { 13 | if ((self = [super init])) { 14 | BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path]; 15 | if ([[NSFileManager defaultManager] removeItemAtPath:path error:NULL]) { 16 | _status = exists ? 200 : 204; 17 | } else { 18 | HTTPLogError(@"Failed deleting \"%@\"", path); 19 | _status = 404; 20 | } 21 | } 22 | return self; 23 | } 24 | 25 | - (UInt64) contentLength { 26 | return 0; 27 | } 28 | 29 | - (UInt64) offset { 30 | return 0; 31 | } 32 | 33 | - (void)setOffset:(UInt64)offset { 34 | ; 35 | } 36 | 37 | - (NSData*) readDataOfLength:(NSUInteger)length { 38 | return nil; 39 | } 40 | 41 | - (BOOL) isDone { 42 | return YES; 43 | } 44 | 45 | - (NSInteger) status { 46 | return _status; 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/Responses/HTTPRedirectResponse.m: -------------------------------------------------------------------------------- 1 | #import "HTTPRedirectResponse.h" 2 | #import "HTTPLogging.h" 3 | 4 | // Log levels : off, error, warn, info, verbose 5 | // Other flags: trace 6 | static const int httpLogLevel = HTTP_LOG_LEVEL_OFF; // | HTTP_LOG_FLAG_TRACE; 7 | 8 | 9 | @implementation HTTPRedirectResponse 10 | 11 | - (id)initWithPath:(NSString *)path 12 | { 13 | if ((self = [super init])) 14 | { 15 | HTTPLogTrace(); 16 | 17 | redirectPath = [path copy]; 18 | } 19 | return self; 20 | } 21 | 22 | - (UInt64)contentLength 23 | { 24 | return 0; 25 | } 26 | 27 | - (UInt64)offset 28 | { 29 | return 0; 30 | } 31 | 32 | - (void)setOffset:(UInt64)offset 33 | { 34 | // Nothing to do 35 | } 36 | 37 | - (NSData *)readDataOfLength:(NSUInteger)length 38 | { 39 | HTTPLogTrace(); 40 | 41 | return nil; 42 | } 43 | 44 | - (BOOL)isDone 45 | { 46 | return YES; 47 | } 48 | 49 | - (NSDictionary *)httpHeaders 50 | { 51 | HTTPLogTrace(); 52 | 53 | return [NSDictionary dictionaryWithObject:redirectPath forKey:@"Location"]; 54 | } 55 | 56 | - (NSInteger)status 57 | { 58 | HTTPLogTrace(); 59 | 60 | return 302; 61 | } 62 | 63 | - (void)dealloc 64 | { 65 | HTTPLogTrace(); 66 | 67 | [redirectPath release]; 68 | [super dealloc]; 69 | } 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/HTTPMessage.h: -------------------------------------------------------------------------------- 1 | /** 2 | * The HTTPMessage class is a simple Objective-C wrapper around Apple's CFHTTPMessage class. 3 | **/ 4 | 5 | #import 6 | 7 | #if TARGET_OS_IPHONE 8 | // Note: You may need to add the CFNetwork Framework to your project 9 | #import 10 | #endif 11 | 12 | #define HTTPVersion1_0 ((NSString *)kCFHTTPVersion1_0) 13 | #define HTTPVersion1_1 ((NSString *)kCFHTTPVersion1_1) 14 | 15 | 16 | @interface HTTPMessage : NSObject 17 | { 18 | CFHTTPMessageRef message; 19 | } 20 | 21 | - (id)initEmptyRequest; 22 | 23 | - (id)initRequestWithMethod:(NSString *)method URL:(NSURL *)url version:(NSString *)version; 24 | 25 | - (id)initResponseWithStatusCode:(NSInteger)code description:(NSString *)description version:(NSString *)version; 26 | 27 | - (BOOL)appendData:(NSData *)data; 28 | 29 | - (BOOL)isHeaderComplete; 30 | 31 | - (NSString *)version; 32 | 33 | - (NSString *)method; 34 | - (NSURL *)url; 35 | 36 | - (NSInteger)statusCode; 37 | 38 | - (NSDictionary *)allHeaderFields; 39 | - (NSString *)headerField:(NSString *)headerField; 40 | 41 | - (void)setHeaderField:(NSString *)headerField value:(NSString *)headerFieldValue; 42 | 43 | - (NSData *)messageData; 44 | 45 | - (NSData *)body; 46 | - (void)setBody:(NSData *)body; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/README.markdown: -------------------------------------------------------------------------------- 1 | CocoaHTTPServer is a small, lightweight, embeddable HTTP server for Mac OS X or iOS applications. 2 | 3 | Sometimes developers need an embedded HTTP server in their app. Perhaps it's a server application with remote monitoring. Or perhaps it's a desktop application using HTTP for the communication backend. Or perhaps it's an iOS app providing over-the-air access to documents. Whatever your reason, CocoaHTTPServer can get the job done. It provides: 4 | 5 | - Built in support for bonjour broadcasting 6 | - IPv4 and IPv6 support 7 | - Asynchronous networking using GCD and standard sockets 8 | - Password protection support 9 | - SSL/TLS encryption support 10 | - Extremely FAST and memory efficient 11 | - Extremely scalable (built entirely upon GCD) 12 | - Heavily commented code 13 | - Very easily extensible 14 | - WebDAV is supported too! 15 | 16 |
17 | Can't find the answer to your question in any of the [wiki](https://github.com/robbiehanson/CocoaHTTPServer/wiki) articles? Try the **[mailing list](http://groups.google.com/group/cocoahttpserver)**. 18 |
19 |
20 | Love the project? Wanna buy me a coffee? (or a beer :D) [![donation](http://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/us/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9404262) 21 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Vendor/CocoaLumberjack/DDASLLogger.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import "DDLog.h" 5 | 6 | /** 7 | * Welcome to Cocoa Lumberjack! 8 | * 9 | * The Google Code page has a wealth of documentation if you have any questions. 10 | * http://code.google.com/p/cocoalumberjack/ 11 | * 12 | * If you're new to the project you may wish to read the "Getting Started" page. 13 | * http://code.google.com/p/cocoalumberjack/wiki/GettingStarted 14 | * 15 | * 16 | * This class provides a logger for the Apple System Log facility. 17 | * 18 | * As described in the "Getting Started" page, 19 | * the traditional NSLog() function directs it's output to two places: 20 | * 21 | * - Apple System Log 22 | * - StdErr (if stderr is a TTY) so log statements show up in Xcode console 23 | * 24 | * To duplicate NSLog() functionality you can simply add this logger and a tty logger. 25 | * However, if you instead choose to use file logging (for faster performance), 26 | * you may choose to use a file logger and a tty logger. 27 | **/ 28 | 29 | @interface DDASLLogger : DDAbstractLogger 30 | { 31 | aslclient client; 32 | } 33 | 34 | + (DDASLLogger *)sharedInstance; 35 | 36 | // Inherited from DDAbstractLogger 37 | 38 | // - (id )logFormatter; 39 | // - (void)setLogFormatter:(id )formatter; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /VMMediaDownloader/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.doshine.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Software License Agreement (BSD License) 2 | 3 | Copyright (c) 2011, Deusty, LLC 4 | All rights reserved. 5 | 6 | Redistribution and use of this software in source and binary forms, 7 | with or without modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above 10 | copyright notice, this list of conditions and the 11 | following disclaimer. 12 | 13 | * Neither the name of Deusty nor the names of its 14 | contributors may be used to endorse or promote products 15 | derived from this software without specific prior 16 | written permission of Deusty, LLC. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /VMMediaDownloader/m3u8/SegmentDownloader.h: -------------------------------------------------------------------------------- 1 | // 2 | // SegmentDownloader.h 3 | // XB 4 | // 5 | // Created by luoxubin on 3/8/12. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ASIHTTPRequest.h" 11 | #import "DownloadDelegate.h" 12 | #import "ASIProgressDelegate.h" 13 | 14 | typedef enum 15 | { 16 | ERUNNING = 0, 17 | ESTOPPED = 1, 18 | }eTaskStatus; 19 | 20 | #define kPathDownload @"Downloads" 21 | #define kTextDownloadingFileSuffix @"_etc" 22 | 23 | 24 | @interface SegmentDownloader : NSObject 25 | { 26 | float progress; 27 | eTaskStatus status; 28 | NSString* filePath; 29 | NSString* fileName; 30 | NSString* tmpFileName; 31 | NSString* downloadUrl; 32 | 33 | id delegate; 34 | ASIHTTPRequest* request; 35 | } 36 | 37 | @property(nonatomic,copy)NSString* fileName; 38 | @property(nonatomic,copy)NSString* filePath; 39 | @property(nonatomic,copy)NSString* tmpFileName; 40 | @property(nonatomic,copy)NSString* downloadUrl; 41 | @property(nonatomic,retain)iddelegate; 42 | @property(nonatomic,assign)eTaskStatus status; 43 | @property(nonatomic,assign)float progress; 44 | 45 | -(void)start; 46 | -(void)stop; 47 | -(void)clean; 48 | -(id)initWithUrl:(NSString*)url andFilePath:(NSString*)path andFileName:(NSString*)fileName; 49 | 50 | 51 | 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Vendor/CocoaLumberjack/DDTTYLogger.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "DDLog.h" 4 | 5 | /** 6 | * Welcome to Cocoa Lumberjack! 7 | * 8 | * The Google Code page has a wealth of documentation if you have any questions. 9 | * http://code.google.com/p/cocoalumberjack/ 10 | * 11 | * If you're new to the project you may wish to read the "Getting Started" page. 12 | * http://code.google.com/p/cocoalumberjack/wiki/GettingStarted 13 | * 14 | * 15 | * This class provides a logger for Terminal output or Xcode console output, 16 | * depending on where you are running your code. 17 | * 18 | * As described in the "Getting Started" page, 19 | * the traditional NSLog() function directs it's output to two places: 20 | * 21 | * - Apple System Log (so it shows up in Console.app) 22 | * - StdErr (if stderr is a TTY, so log statements show up in Xcode console) 23 | * 24 | * To duplicate NSLog() functionality you can simply add this logger and an asl logger. 25 | * However, if you instead choose to use file logging (for faster performance), 26 | * you may choose to use only a file logger and a tty logger. 27 | **/ 28 | 29 | @interface DDTTYLogger : DDAbstractLogger 30 | { 31 | BOOL isaTTY; 32 | 33 | NSDateFormatter *dateFormatter; 34 | 35 | char *app; // Not null terminated 36 | char *pid; // Not null terminated 37 | 38 | size_t appLen; 39 | size_t pidLen; 40 | } 41 | 42 | + (DDTTYLogger *)sharedInstance; 43 | 44 | // Inherited from DDAbstractLogger 45 | 46 | // - (id )logFormatter; 47 | // - (void)setLogFormatter:(id )formatter; 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/Categories/DDNumber.m: -------------------------------------------------------------------------------- 1 | #import "DDNumber.h" 2 | 3 | 4 | @implementation NSNumber (DDNumber) 5 | 6 | + (BOOL)parseString:(NSString *)str intoSInt64:(SInt64 *)pNum 7 | { 8 | if(str == nil) 9 | { 10 | *pNum = 0; 11 | return NO; 12 | } 13 | 14 | errno = 0; 15 | 16 | // On both 32-bit and 64-bit machines, long long = 64 bit 17 | 18 | *pNum = strtoll([str UTF8String], NULL, 10); 19 | 20 | if(errno != 0) 21 | return NO; 22 | else 23 | return YES; 24 | } 25 | 26 | + (BOOL)parseString:(NSString *)str intoUInt64:(UInt64 *)pNum 27 | { 28 | if(str == nil) 29 | { 30 | *pNum = 0; 31 | return NO; 32 | } 33 | 34 | errno = 0; 35 | 36 | // On both 32-bit and 64-bit machines, unsigned long long = 64 bit 37 | 38 | *pNum = strtoull([str UTF8String], NULL, 10); 39 | 40 | if(errno != 0) 41 | return NO; 42 | else 43 | return YES; 44 | } 45 | 46 | + (BOOL)parseString:(NSString *)str intoNSInteger:(NSInteger *)pNum 47 | { 48 | if(str == nil) 49 | { 50 | *pNum = 0; 51 | return NO; 52 | } 53 | 54 | errno = 0; 55 | 56 | // On LP64, NSInteger = long = 64 bit 57 | // Otherwise, NSInteger = int = long = 32 bit 58 | 59 | *pNum = strtol([str UTF8String], NULL, 10); 60 | 61 | if(errno != 0) 62 | return NO; 63 | else 64 | return YES; 65 | } 66 | 67 | + (BOOL)parseString:(NSString *)str intoNSUInteger:(NSUInteger *)pNum 68 | { 69 | if(str == nil) 70 | { 71 | *pNum = 0; 72 | return NO; 73 | } 74 | 75 | errno = 0; 76 | 77 | // On LP64, NSUInteger = unsigned long = 64 bit 78 | // Otherwise, NSUInteger = unsigned int = unsigned long = 32 bit 79 | 80 | *pNum = strtoul([str UTF8String], NULL, 10); 81 | 82 | if(errno != 0) 83 | return NO; 84 | else 85 | return YES; 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/Responses/HTTPDataResponse.m: -------------------------------------------------------------------------------- 1 | #import "HTTPDataResponse.h" 2 | #import "HTTPLogging.h" 3 | 4 | // Log levels : off, error, warn, info, verbose 5 | // Other flags: trace 6 | static const int httpLogLevel = HTTP_LOG_LEVEL_OFF; // | HTTP_LOG_FLAG_TRACE; 7 | 8 | 9 | @implementation HTTPDataResponse 10 | 11 | - (id)initWithData:(NSData *)dataParam 12 | { 13 | if((self = [super init])) 14 | { 15 | HTTPLogTrace(); 16 | 17 | offset = 0; 18 | data = [dataParam retain]; 19 | } 20 | return self; 21 | } 22 | 23 | - (void)dealloc 24 | { 25 | HTTPLogTrace(); 26 | 27 | [data release]; 28 | [super dealloc]; 29 | } 30 | 31 | - (UInt64)contentLength 32 | { 33 | UInt64 result = (UInt64)[data length]; 34 | 35 | HTTPLogTrace2(@"%@[%p]: contentLength - %llu", THIS_FILE, self, result); 36 | 37 | return result; 38 | } 39 | 40 | - (UInt64)offset 41 | { 42 | HTTPLogTrace(); 43 | 44 | return offset; 45 | } 46 | 47 | - (void)setOffset:(UInt64)offsetParam 48 | { 49 | HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset); 50 | 51 | offset = (NSUInteger)offsetParam; 52 | } 53 | 54 | - (NSData *)readDataOfLength:(NSUInteger)lengthParameter 55 | { 56 | HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)lengthParameter); 57 | 58 | NSUInteger remaining = [data length] - offset; 59 | NSUInteger length = lengthParameter < remaining ? lengthParameter : remaining; 60 | 61 | void *bytes = (void *)([data bytes] + offset); 62 | 63 | offset += length; 64 | 65 | return [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:NO]; 66 | } 67 | 68 | - (BOOL)isDone 69 | { 70 | BOOL result = (offset == [data length]); 71 | 72 | HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO")); 73 | 74 | return result; 75 | } 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /VMMediaDownloader/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // VMMediaDownloader 4 | // 5 | // Created by Sun Peng on 11/6/14. 6 | // Copyright (c) 2014 Peng Sun. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "M3U8Handler.h" 11 | #import 12 | 13 | @interface ViewController () 14 | 15 | @property (strong, nonatomic) IBOutlet UITextField *urlField; 16 | 17 | @end 18 | 19 | @implementation ViewController 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | 24 | self.urlField.text = @"http://pl.youku.com/playlist/m3u8?vid=118402875&type=mp4&ts=1415233731&keyframe=0&ep=diaVH0uOVckD5SPeiT8bMX3jdSUIXP8L%2FhuFg9plBdQmSuG9&sid=441523373001112fa11b7&token=2292&ctype=12&ev=1&oip=2081459012"; 25 | } 26 | 27 | - (void)didReceiveMemoryWarning { 28 | [super didReceiveMemoryWarning]; 29 | // Dispose of any resources that can be recreated. 30 | } 31 | 32 | - (IBAction)downloadVideo:(id)sender { 33 | M3U8Handler *handler = [[M3U8Handler alloc] init]; 34 | [handler praseUrl:self.urlField.text]; 35 | handler.playlist.uuid = @"movie1"; 36 | _downloader = [[VideoDownloader alloc] initWithM3U8List:handler.playlist]; 37 | _downloader.delegate = self; 38 | 39 | [_downloader startDownloadVideo]; 40 | } 41 | 42 | -(void)videoDownloaderFinished:(VideoDownloader*)request 43 | { 44 | [request createLocalM3U8file]; 45 | } 46 | 47 | - (void)videoDownloaderFailed:(VideoDownloader *)request { 48 | NSLog(@"Video Download Failed.."); 49 | } 50 | 51 | - (IBAction)playLocal:(id)sender { 52 | MPMoviePlayerViewController *playerViewController = [[MPMoviePlayerViewController alloc] initWithContentURL:[NSURL URLWithString:@"http://127.0.0.1:12345/movie1/movie.m3u8"]]; 53 | [self presentMoviePlayerViewControllerAnimated:playerViewController]; 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/Responses/HTTPDynamicFileResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "HTTPResponse.h" 3 | #import "HTTPAsyncFileResponse.h" 4 | 5 | /** 6 | * This class is designed to assist with dynamic content. 7 | * Imagine you have a file that you want to make dynamic: 8 | * 9 | * 10 | * 11 | *

ComputerName Control Panel

12 | * ... 13 | *
  • System Time: SysTime
  • 14 | * 15 | * 16 | * 17 | * Now you could generate the entire file in Objective-C, 18 | * but this would be a horribly tedious process. 19 | * Beside, you want to design the file with professional tools to make it look pretty. 20 | * 21 | * So all you have to do is escape your dynamic content like this: 22 | * 23 | * ... 24 | *

    %%ComputerName%% Control Panel

    25 | * ... 26 | *
  • System Time: %%SysTime%%
  • 27 | * 28 | * And then you create an instance of this class with: 29 | * 30 | * - separator = @"%%" 31 | * - replacementDictionary = { "ComputerName"="Black MacBook", "SysTime"="2010-04-30 03:18:24" } 32 | * 33 | * This class will then perform the replacements for you, on the fly, as it reads the file data. 34 | * This class is also asynchronous, so it will perform the file IO using its own GCD queue. 35 | * 36 | * All keys for the replacementDictionary must be NSString's. 37 | * Values for the replacementDictionary may be NSString's, or any object that 38 | * returns what you want when its description method is invoked. 39 | **/ 40 | 41 | @interface HTTPDynamicFileResponse : HTTPAsyncFileResponse 42 | { 43 | NSData *separator; 44 | NSDictionary *replacementDict; 45 | } 46 | 47 | - (id)initWithFilePath:(NSString *)filePath 48 | forConnection:(HTTPConnection *)connection 49 | separator:(NSString *)separatorStr 50 | replacementDictionary:(NSDictionary *)dictionary; 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/Categories/DDRange.h: -------------------------------------------------------------------------------- 1 | /** 2 | * DDRange is the functional equivalent of a 64 bit NSRange. 3 | * The HTTP Server is designed to support very large files. 4 | * On 32 bit architectures (ppc, i386) NSRange uses unsigned 32 bit integers. 5 | * This only supports a range of up to 4 gigabytes. 6 | * By defining our own variant, we can support a range up to 16 exabytes. 7 | * 8 | * All effort is given such that DDRange functions EXACTLY the same as NSRange. 9 | **/ 10 | 11 | #import 12 | #import 13 | 14 | @class NSString; 15 | 16 | typedef struct _DDRange { 17 | UInt64 location; 18 | UInt64 length; 19 | } DDRange; 20 | 21 | typedef DDRange *DDRangePointer; 22 | 23 | NS_INLINE DDRange DDMakeRange(UInt64 loc, UInt64 len) { 24 | DDRange r; 25 | r.location = loc; 26 | r.length = len; 27 | return r; 28 | } 29 | 30 | NS_INLINE UInt64 DDMaxRange(DDRange range) { 31 | return (range.location + range.length); 32 | } 33 | 34 | NS_INLINE BOOL DDLocationInRange(UInt64 loc, DDRange range) { 35 | return (loc - range.location < range.length); 36 | } 37 | 38 | NS_INLINE BOOL DDEqualRanges(DDRange range1, DDRange range2) { 39 | return ((range1.location == range2.location) && (range1.length == range2.length)); 40 | } 41 | 42 | FOUNDATION_EXPORT DDRange DDUnionRange(DDRange range1, DDRange range2); 43 | FOUNDATION_EXPORT DDRange DDIntersectionRange(DDRange range1, DDRange range2); 44 | FOUNDATION_EXPORT NSString *DDStringFromRange(DDRange range); 45 | FOUNDATION_EXPORT DDRange DDRangeFromString(NSString *aString); 46 | 47 | NSInteger DDRangeCompare(DDRangePointer pDDRange1, DDRangePointer pDDRange2); 48 | 49 | @interface NSValue (NSValueDDRangeExtensions) 50 | 51 | + (NSValue *)valueWithDDRange:(DDRange)range; 52 | - (DDRange)ddrangeValue; 53 | 54 | - (NSInteger)ddrangeCompare:(NSValue *)ddrangeValue; 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Extensions/WebDAV/PUTResponse.m: -------------------------------------------------------------------------------- 1 | #import "PUTResponse.h" 2 | #import "HTTPLogging.h" 3 | 4 | // HTTP methods: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html 5 | // HTTP headers: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html 6 | // HTTP status codes: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 7 | 8 | static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; 9 | 10 | @implementation PUTResponse 11 | 12 | - (id) initWithFilePath:(NSString*)path headers:(NSDictionary*)headers body:(id)body { 13 | if ((self = [super init])) { 14 | if ([headers objectForKey:@"Content-Range"]) { 15 | HTTPLogError(@"Content-Range not supported for upload to \"%@\"", path); 16 | _status = 400; 17 | } else { 18 | BOOL overwrite = [[NSFileManager defaultManager] fileExistsAtPath:path]; 19 | BOOL success; 20 | if ([body isKindOfClass:[NSString class]]) { 21 | [[NSFileManager defaultManager] removeItemAtPath:path error:NULL]; 22 | success = [[NSFileManager defaultManager] moveItemAtPath:body toPath:path error:NULL]; 23 | } else { 24 | success = [body writeToFile:path atomically:YES]; 25 | } 26 | if (success) { 27 | _status = overwrite ? 200 : 201; 28 | } else { 29 | HTTPLogError(@"Failed writing upload to \"%@\"", path); 30 | _status = 403; 31 | } 32 | } 33 | } 34 | return self; 35 | } 36 | 37 | - (id) initWithFilePath:(NSString*)path headers:(NSDictionary*)headers bodyData:(NSData*)body { 38 | return [self initWithFilePath:path headers:headers body:body]; 39 | } 40 | 41 | - (id) initWithFilePath:(NSString*)path headers:(NSDictionary*)headers bodyFile:(NSString*)body { 42 | return [self initWithFilePath:path headers:headers body:body]; 43 | } 44 | 45 | - (UInt64) contentLength { 46 | return 0; 47 | } 48 | 49 | - (UInt64) offset { 50 | return 0; 51 | } 52 | 53 | - (void) setOffset:(UInt64)offset { 54 | ; 55 | } 56 | 57 | - (NSData*) readDataOfLength:(NSUInteger)length { 58 | return nil; 59 | } 60 | 61 | - (BOOL) isDone { 62 | return YES; 63 | } 64 | 65 | - (NSInteger) status { 66 | return _status; 67 | } 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Vendor/CocoaLumberjack/DDASLLogger.m: -------------------------------------------------------------------------------- 1 | #import "DDASLLogger.h" 2 | 3 | #import 4 | 5 | 6 | @implementation DDASLLogger 7 | 8 | static DDASLLogger *sharedInstance; 9 | 10 | /** 11 | * The runtime sends initialize to each class in a program exactly one time just before the class, 12 | * or any class that inherits from it, is sent its first message from within the program. (Thus the 13 | * method may never be invoked if the class is not used.) The runtime sends the initialize message to 14 | * classes in a thread-safe manner. Superclasses receive this message before their subclasses. 15 | * 16 | * This method may also be called directly (assumably by accident), hence the safety mechanism. 17 | **/ 18 | + (void)initialize 19 | { 20 | static BOOL initialized = NO; 21 | if (!initialized) 22 | { 23 | initialized = YES; 24 | 25 | sharedInstance = [[DDASLLogger alloc] init]; 26 | } 27 | } 28 | 29 | + (DDASLLogger *)sharedInstance 30 | { 31 | return sharedInstance; 32 | } 33 | 34 | - (id)init 35 | { 36 | if (sharedInstance != nil) 37 | { 38 | [self release]; 39 | return nil; 40 | } 41 | 42 | if ((self = [super init])) 43 | { 44 | // A default asl client is provided for the main thread, 45 | // but background threads need to create their own client. 46 | 47 | client = asl_open(NULL, "com.apple.console", 0); 48 | } 49 | return self; 50 | } 51 | 52 | - (void)logMessage:(DDLogMessage *)logMessage 53 | { 54 | NSString *logMsg = logMessage->logMsg; 55 | 56 | if (formatter) 57 | { 58 | logMsg = [formatter formatLogMessage:logMessage]; 59 | } 60 | 61 | if (logMsg) 62 | { 63 | const char *msg = [logMsg UTF8String]; 64 | 65 | int aslLogLevel; 66 | switch (logMessage->logLevel) 67 | { 68 | // Note: By default ASL will filter anything above level 5 (Notice). 69 | // So our mappings shouldn't go above that level. 70 | 71 | case 1 : aslLogLevel = ASL_LEVEL_CRIT; break; 72 | case 2 : aslLogLevel = ASL_LEVEL_ERR; break; 73 | case 3 : aslLogLevel = ASL_LEVEL_WARNING; break; 74 | default : aslLogLevel = ASL_LEVEL_NOTICE; break; 75 | } 76 | 77 | asl_log(client, NULL, aslLogLevel, "%s", msg); 78 | } 79 | } 80 | 81 | - (NSString *)loggerName 82 | { 83 | return @"cocoa.lumberjack.aslLogger"; 84 | } 85 | 86 | @end 87 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Vendor/CocoaLumberjack/About.txt: -------------------------------------------------------------------------------- 1 | CocoaLumberjack is under the New BSD License. 2 | http://code.google.com/p/cocoalumberjack/ 3 | 4 | Extensive documentation, tutorials, etc are available: 5 | http://code.google.com/p/cocoalumberjack/w/list 6 | 7 | Overview of the project (copied from google code homepage): 8 | 9 | 10 | 11 | The lumberjack framework is fast & simple, yet powerful & flexible. 12 | It is similar in concept to other popular logging frameworks such as log4j, yet is designed specifically for objective-c, and takes advantage of features such as multi-threading, grand central dispatch (if available), lockless atomic operations, and the dynamic nature of the objective-c runtime. 13 | 14 | Lumberjack is fast: 15 | In most cases it is an order of magnitude faster than NSLog. 16 | 17 | Lumberjack is simple: 18 | It takes as little as a single line of code to configure lumberjack when your application launches. Then simply replace your NSLog statements with DDLog statements and that's about it. (And the DDLog macros have the exact same format and syntax as NSLog, so it's super easy.) 19 | 20 | Lumberjack is powerful: 21 | One log statement can be sent to multiple loggers, meaning you can log to a file and the console simultaneously. Want more? Create your own loggers (it's easy) and send your log statements over the network. Or to a database or distributed file system. The sky is the limit. 22 | 23 | Lumberjack is flexible: 24 | Configure your logging however you want. Change log levels per file (perfect for debugging). Change log levels per logger (verbose console, but concise log file). Change log levels per xcode configuration (verbose debug, but concise release). Have your log statements compiled out of the release build. Customize the number of log levels for your application. Add your own fine-grained logging. Dynamically change log levels during runtime. Choose how & when you want your log files to be rolled. Upload your log files to a central server. Compress archived log files to save disk space... 25 | 26 | 27 | 28 | This framework is for you if: 29 | 30 | You're looking for a way to track down that impossible-to-reproduce bug that keeps popping up in the field. 31 | You're frustrated with the super short console log on the iPhone. 32 | You're looking to take your application to the next level in terms of support and stability. 33 | You're looking for an enterprise level logging solution for your application (Mac or iPhone). -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/HTTPMessage.m: -------------------------------------------------------------------------------- 1 | #import "HTTPMessage.h" 2 | 3 | 4 | @implementation HTTPMessage 5 | 6 | - (id)initEmptyRequest 7 | { 8 | if ((self = [super init])) 9 | { 10 | message = CFHTTPMessageCreateEmpty(NULL, YES); 11 | } 12 | return self; 13 | } 14 | 15 | - (id)initRequestWithMethod:(NSString *)method URL:(NSURL *)url version:(NSString *)version 16 | { 17 | if ((self = [super init])) 18 | { 19 | message = CFHTTPMessageCreateRequest(NULL, (CFStringRef)method, (CFURLRef)url, (CFStringRef)version); 20 | } 21 | return self; 22 | } 23 | 24 | - (id)initResponseWithStatusCode:(NSInteger)code description:(NSString *)description version:(NSString *)version 25 | { 26 | if ((self = [super init])) 27 | { 28 | message = CFHTTPMessageCreateResponse(NULL, (CFIndex)code, (CFStringRef)description, (CFStringRef)version); 29 | } 30 | return self; 31 | } 32 | 33 | - (void)dealloc 34 | { 35 | if (message) 36 | { 37 | CFRelease(message); 38 | } 39 | [super dealloc]; 40 | } 41 | 42 | - (BOOL)appendData:(NSData *)data 43 | { 44 | return CFHTTPMessageAppendBytes(message, [data bytes], [data length]); 45 | } 46 | 47 | - (BOOL)isHeaderComplete 48 | { 49 | return CFHTTPMessageIsHeaderComplete(message); 50 | } 51 | 52 | - (NSString *)version 53 | { 54 | return [NSMakeCollectable(CFHTTPMessageCopyVersion(message)) autorelease]; 55 | } 56 | 57 | - (NSString *)method 58 | { 59 | return [NSMakeCollectable(CFHTTPMessageCopyRequestMethod(message)) autorelease]; 60 | } 61 | 62 | - (NSURL *)url 63 | { 64 | return [NSMakeCollectable(CFHTTPMessageCopyRequestURL(message)) autorelease]; 65 | } 66 | 67 | - (NSInteger)statusCode 68 | { 69 | return (NSInteger)CFHTTPMessageGetResponseStatusCode(message); 70 | } 71 | 72 | - (NSDictionary *)allHeaderFields 73 | { 74 | return [NSMakeCollectable(CFHTTPMessageCopyAllHeaderFields(message)) autorelease]; 75 | } 76 | 77 | - (NSString *)headerField:(NSString *)headerField 78 | { 79 | return [NSMakeCollectable(CFHTTPMessageCopyHeaderFieldValue(message, (CFStringRef)headerField)) autorelease]; 80 | } 81 | 82 | - (void)setHeaderField:(NSString *)headerField value:(NSString *)headerFieldValue 83 | { 84 | CFHTTPMessageSetHeaderFieldValue(message, (CFStringRef)headerField, (CFStringRef)headerFieldValue); 85 | } 86 | 87 | - (NSData *)messageData 88 | { 89 | return [NSMakeCollectable(CFHTTPMessageCopySerializedMessage(message)) autorelease]; 90 | } 91 | 92 | - (NSData *)body 93 | { 94 | return [NSMakeCollectable(CFHTTPMessageCopyBody(message)) autorelease]; 95 | } 96 | 97 | - (void)setBody:(NSData *)body 98 | { 99 | CFHTTPMessageSetBody(message, (CFDataRef)body); 100 | } 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/WebSocket.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class HTTPMessage; 4 | @class GCDAsyncSocket; 5 | 6 | 7 | #define WebSocketDidDieNotification @"WebSocketDidDie" 8 | 9 | @interface WebSocket : NSObject 10 | { 11 | dispatch_queue_t websocketQueue; 12 | 13 | HTTPMessage *request; 14 | GCDAsyncSocket *asyncSocket; 15 | 16 | NSData *term; 17 | 18 | BOOL isStarted; 19 | BOOL isOpen; 20 | BOOL isVersion76; 21 | 22 | id delegate; 23 | } 24 | 25 | + (BOOL)isWebSocketRequest:(HTTPMessage *)request; 26 | 27 | - (id)initWithRequest:(HTTPMessage *)request socket:(GCDAsyncSocket *)socket; 28 | 29 | /** 30 | * Delegate option. 31 | * 32 | * In most cases it will be easier to subclass WebSocket, 33 | * but some circumstances may lead one to prefer standard delegate callbacks instead. 34 | **/ 35 | @property (/* atomic */ assign) id delegate; 36 | 37 | /** 38 | * The WebSocket class is thread-safe, generally via it's GCD queue. 39 | * All public API methods are thread-safe, 40 | * and the subclass API methods are thread-safe as they are all invoked on the same GCD queue. 41 | **/ 42 | @property (nonatomic, readonly) dispatch_queue_t websocketQueue; 43 | 44 | /** 45 | * Public API 46 | * 47 | * These methods are automatically called by the HTTPServer. 48 | * You may invoke the stop method yourself to close the WebSocket manually. 49 | **/ 50 | - (void)start; 51 | - (void)stop; 52 | 53 | /** 54 | * Public API 55 | * 56 | * Sends a message over the WebSocket. 57 | * This method is thread-safe. 58 | **/ 59 | - (void)sendMessage:(NSString *)msg; 60 | 61 | /** 62 | * Subclass API 63 | * 64 | * These methods are designed to be overriden by subclasses. 65 | **/ 66 | - (void)didOpen; 67 | - (void)didReceiveMessage:(NSString *)msg; 68 | - (void)didClose; 69 | 70 | @end 71 | 72 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 73 | #pragma mark - 74 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 75 | 76 | /** 77 | * There are two ways to create your own custom WebSocket: 78 | * 79 | * - Subclass it and override the methods you're interested in. 80 | * - Use traditional delegate paradigm along with your own custom class. 81 | * 82 | * They both exist to allow for maximum flexibility. 83 | * In most cases it will be easier to subclass WebSocket. 84 | * However some circumstances may lead one to prefer standard delegate callbacks instead. 85 | * One such example, you're already subclassing another class, so subclassing WebSocket isn't an option. 86 | **/ 87 | 88 | @protocol WebSocketDelegate 89 | @optional 90 | 91 | - (void)webSocketDidOpen:(WebSocket *)ws; 92 | 93 | - (void)webSocket:(WebSocket *)ws didReceiveMessage:(NSString *)msg; 94 | 95 | - (void)webSocketDidClose:(WebSocket *)ws; 96 | 97 | @end -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/Responses/HTTPAsyncFileResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "HTTPResponse.h" 3 | 4 | @class HTTPConnection; 5 | 6 | /** 7 | * This is an asynchronous version of HTTPFileResponse. 8 | * It reads data from the given file asynchronously via GCD. 9 | * 10 | * It may be overriden to allow custom post-processing of the data that has been read from the file. 11 | * An example of this is the HTTPDynamicFileResponse class. 12 | **/ 13 | 14 | @interface HTTPAsyncFileResponse : NSObject 15 | { 16 | HTTPConnection *connection; 17 | 18 | NSString *filePath; 19 | UInt64 fileLength; 20 | UInt64 fileOffset; // File offset as pertains to data given to connection 21 | UInt64 readOffset; // File offset as pertains to data read from file (but maybe not returned to connection) 22 | 23 | BOOL aborted; 24 | 25 | NSData *data; 26 | 27 | int fileFD; 28 | void *readBuffer; 29 | NSUInteger readBufferSize; // Malloced size of readBuffer 30 | NSUInteger readBufferOffset; // Offset within readBuffer where the end of existing data is 31 | NSUInteger readRequestLength; 32 | dispatch_queue_t readQueue; 33 | dispatch_source_t readSource; 34 | BOOL readSourceSuspended; 35 | } 36 | 37 | - (id)initWithFilePath:(NSString *)filePath forConnection:(HTTPConnection *)connection; 38 | - (NSString *)filePath; 39 | 40 | @end 41 | 42 | /** 43 | * Explanation of Variables (excluding those that are obvious) 44 | * 45 | * fileOffset 46 | * This is the number of bytes that have been returned to the connection via the readDataOfLength method. 47 | * If 1KB of data has been read from the file, but none of that data has yet been returned to the connection, 48 | * then the fileOffset variable remains at zero. 49 | * This variable is used in the calculation of the isDone method. 50 | * Only after all data has been returned to the connection are we actually done. 51 | * 52 | * readOffset 53 | * Represents the offset of the file descriptor. 54 | * In other words, the file position indidcator for our read stream. 55 | * It might be easy to think of it as the total number of bytes that have been read from the file. 56 | * However, this isn't entirely accurate, as the setOffset: method may have caused us to 57 | * jump ahead in the file (lseek). 58 | * 59 | * readBuffer 60 | * Malloc'd buffer to hold data read from the file. 61 | * 62 | * readBufferSize 63 | * Total allocation size of malloc'd buffer. 64 | * 65 | * readBufferOffset 66 | * Represents the position in the readBuffer where we should store new bytes. 67 | * 68 | * readRequestLength 69 | * The total number of bytes that were requested from the connection. 70 | * It's OK if we return a lesser number of bytes to the connection. 71 | * It's NOT OK if we return a greater number of bytes to the connection. 72 | * Doing so would disrupt proper support for range requests. 73 | * If, however, the response is chunked then we don't need to worry about this. 74 | * Chunked responses inheritly don't support range requests. 75 | **/ -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/Categories/DDRange.m: -------------------------------------------------------------------------------- 1 | #import "DDRange.h" 2 | #import "DDNumber.h" 3 | 4 | DDRange DDUnionRange(DDRange range1, DDRange range2) 5 | { 6 | DDRange result; 7 | 8 | result.location = MIN(range1.location, range2.location); 9 | result.length = MAX(DDMaxRange(range1), DDMaxRange(range2)) - result.location; 10 | 11 | return result; 12 | } 13 | 14 | DDRange DDIntersectionRange(DDRange range1, DDRange range2) 15 | { 16 | DDRange result; 17 | 18 | if((DDMaxRange(range1) < range2.location) || (DDMaxRange(range2) < range1.location)) 19 | { 20 | return DDMakeRange(0, 0); 21 | } 22 | 23 | result.location = MAX(range1.location, range2.location); 24 | result.length = MIN(DDMaxRange(range1), DDMaxRange(range2)) - result.location; 25 | 26 | return result; 27 | } 28 | 29 | NSString *DDStringFromRange(DDRange range) 30 | { 31 | return [NSString stringWithFormat:@"{%qu, %qu}", range.location, range.length]; 32 | } 33 | 34 | DDRange DDRangeFromString(NSString *aString) 35 | { 36 | DDRange result = DDMakeRange(0, 0); 37 | 38 | // NSRange will ignore '-' characters, but not '+' characters 39 | NSCharacterSet *cset = [NSCharacterSet characterSetWithCharactersInString:@"+0123456789"]; 40 | 41 | NSScanner *scanner = [NSScanner scannerWithString:aString]; 42 | [scanner setCharactersToBeSkipped:[cset invertedSet]]; 43 | 44 | NSString *str1 = nil; 45 | NSString *str2 = nil; 46 | 47 | BOOL found1 = [scanner scanCharactersFromSet:cset intoString:&str1]; 48 | BOOL found2 = [scanner scanCharactersFromSet:cset intoString:&str2]; 49 | 50 | if(found1) [NSNumber parseString:str1 intoUInt64:&result.location]; 51 | if(found2) [NSNumber parseString:str2 intoUInt64:&result.length]; 52 | 53 | return result; 54 | } 55 | 56 | NSInteger DDRangeCompare(DDRangePointer pDDRange1, DDRangePointer pDDRange2) 57 | { 58 | // Comparison basis: 59 | // Which range would you encouter first if you started at zero, and began walking towards infinity. 60 | // If you encouter both ranges at the same time, which range would end first. 61 | 62 | if(pDDRange1->location < pDDRange2->location) 63 | { 64 | return NSOrderedAscending; 65 | } 66 | if(pDDRange1->location > pDDRange2->location) 67 | { 68 | return NSOrderedDescending; 69 | } 70 | if(pDDRange1->length < pDDRange2->length) 71 | { 72 | return NSOrderedAscending; 73 | } 74 | if(pDDRange1->length > pDDRange2->length) 75 | { 76 | return NSOrderedDescending; 77 | } 78 | 79 | return NSOrderedSame; 80 | } 81 | 82 | @implementation NSValue (NSValueDDRangeExtensions) 83 | 84 | + (NSValue *)valueWithDDRange:(DDRange)range 85 | { 86 | return [NSValue valueWithBytes:&range objCType:@encode(DDRange)]; 87 | } 88 | 89 | - (DDRange)ddrangeValue 90 | { 91 | DDRange result; 92 | [self getValue:&result]; 93 | return result; 94 | } 95 | 96 | - (NSInteger)ddrangeCompare:(NSValue *)other 97 | { 98 | DDRange r1 = [self ddrangeValue]; 99 | DDRange r2 = [other ddrangeValue]; 100 | 101 | return DDRangeCompare(&r1, &r2); 102 | } 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /VMMediaDownloader/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // VMMediaDownloader 4 | // 5 | // Created by Sun Peng on 11/6/14. 6 | // Copyright (c) 2014 Peng Sun. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "HTTPServer.h" 11 | 12 | #define kPathDownload @"Downloads" 13 | 14 | @interface AppDelegate () 15 | 16 | @end 17 | 18 | @implementation AppDelegate 19 | 20 | 21 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 22 | // Create server using our custom MyHTTPServer class 23 | httpServer = [[HTTPServer alloc] init]; 24 | 25 | // Tell the server to broadcast its presence via Bonjour. 26 | // This allows browsers such as Safari to automatically discover our service. 27 | [httpServer setType:@"_http._tcp."]; 28 | 29 | // Normally there's no need to run our server on any specific port. 30 | // Technologies like Bonjour allow clients to dynamically discover the server's port at runtime. 31 | // However, for easy testing you may want force a certain port so you can just hit the refresh button. 32 | [httpServer setPort:12345]; 33 | 34 | // Serve files from our embedded Web folder 35 | 36 | 37 | NSString *pathPrefix = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) objectAtIndex:0]; 38 | NSString *webPath = [pathPrefix stringByAppendingPathComponent:kPathDownload]; 39 | NSLog(@"Setting document root: %@", webPath); 40 | 41 | [httpServer setDocumentRoot:webPath]; 42 | 43 | // Start the server (and check for problems) 44 | 45 | NSError *error; 46 | if(![httpServer start:&error]) 47 | { 48 | NSLog(@"Error starting HTTP Server: %@", error); 49 | } 50 | return YES; 51 | } 52 | 53 | - (void)applicationWillResignActive:(UIApplication *)application { 54 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 55 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 56 | } 57 | 58 | - (void)applicationDidEnterBackground:(UIApplication *)application { 59 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 60 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 61 | } 62 | 63 | - (void)applicationWillEnterForeground:(UIApplication *)application { 64 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 65 | } 66 | 67 | - (void)applicationDidBecomeActive:(UIApplication *)application { 68 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 69 | } 70 | 71 | - (void)applicationWillTerminate:(UIApplication *)application { 72 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /VMMediaDownloader/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/HTTPConnection.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class GCDAsyncSocket; 4 | @class HTTPMessage; 5 | @class HTTPServer; 6 | @class WebSocket; 7 | @protocol HTTPResponse; 8 | 9 | 10 | #define HTTPConnectionDidDieNotification @"HTTPConnectionDidDie" 11 | 12 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 13 | #pragma mark - 14 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 15 | 16 | @interface HTTPConfig : NSObject 17 | { 18 | HTTPServer *server; 19 | NSString *documentRoot; 20 | dispatch_queue_t queue; 21 | } 22 | 23 | - (id)initWithServer:(HTTPServer *)server documentRoot:(NSString *)documentRoot; 24 | - (id)initWithServer:(HTTPServer *)server documentRoot:(NSString *)documentRoot queue:(dispatch_queue_t)q; 25 | 26 | @property (nonatomic, readonly) HTTPServer *server; 27 | @property (nonatomic, readonly) NSString *documentRoot; 28 | @property (nonatomic, readonly) dispatch_queue_t queue; 29 | 30 | @end 31 | 32 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 33 | #pragma mark - 34 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 35 | 36 | @interface HTTPConnection : NSObject 37 | { 38 | dispatch_queue_t connectionQueue; 39 | GCDAsyncSocket *asyncSocket; 40 | HTTPConfig *config; 41 | 42 | BOOL started; 43 | 44 | HTTPMessage *request; 45 | unsigned int numHeaderLines; 46 | 47 | BOOL sentResponseHeaders; 48 | 49 | NSString *nonce; 50 | long lastNC; 51 | 52 | NSObject *httpResponse; 53 | 54 | NSMutableArray *ranges; 55 | NSMutableArray *ranges_headers; 56 | NSString *ranges_boundry; 57 | int rangeIndex; 58 | 59 | UInt64 requestContentLength; 60 | UInt64 requestContentLengthReceived; 61 | UInt64 requestChunkSize; 62 | UInt64 requestChunkSizeReceived; 63 | 64 | NSMutableArray *responseDataSizes; 65 | } 66 | 67 | - (id)initWithAsyncSocket:(GCDAsyncSocket *)newSocket configuration:(HTTPConfig *)aConfig; 68 | 69 | - (void)start; 70 | - (void)stop; 71 | 72 | - (void)startConnection; 73 | 74 | - (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path; 75 | - (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path; 76 | 77 | - (BOOL)isSecureServer; 78 | - (NSArray *)sslIdentityAndCertificates; 79 | 80 | - (BOOL)isPasswordProtected:(NSString *)path; 81 | - (BOOL)useDigestAccessAuthentication; 82 | - (NSString *)realm; 83 | - (NSString *)passwordForUser:(NSString *)username; 84 | 85 | - (NSDictionary *)parseParams:(NSString *)query; 86 | - (NSDictionary *)parseGetParams; 87 | 88 | - (NSString *)requestURI; 89 | 90 | - (NSArray *)directoryIndexFileNames; 91 | - (NSString *)filePathForURI:(NSString *)path; 92 | - (NSString *)filePathForURI:(NSString *)path allowDirectory:(BOOL)allowDirectory; 93 | - (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path; 94 | - (WebSocket *)webSocketForURI:(NSString *)path; 95 | 96 | - (void)prepareForBodyWithSize:(UInt64)contentLength; 97 | - (void)processBodyData:(NSData *)postDataChunk; 98 | - (void)finishBody; 99 | 100 | - (void)handleVersionNotSupported:(NSString *)version; 101 | - (void)handleAuthenticationFailed; 102 | - (void)handleResourceNotFound; 103 | - (void)handleInvalidRequest:(NSData *)data; 104 | - (void)handleUnknownMethod:(NSString *)method; 105 | 106 | - (NSData *)preprocessResponse:(HTTPMessage *)response; 107 | - (NSData *)preprocessErrorResponse:(HTTPMessage *)response; 108 | 109 | - (void)finishResponse; 110 | 111 | - (BOOL)shouldDie; 112 | - (void)die; 113 | 114 | @end 115 | 116 | @interface HTTPConnection (AsynchronousHTTPResponse) 117 | - (void)responseHasAvailableData:(NSObject *)sender; 118 | - (void)responseDidAbort:(NSObject *)sender; 119 | @end 120 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "DDLog.h" 4 | 5 | /** 6 | * Welcome to Cocoa Lumberjack! 7 | * 8 | * The Google Code page has a wealth of documentation if you have any questions. 9 | * http://code.google.com/p/cocoalumberjack/ 10 | * 11 | * If you're new to the project you may wish to read the "Getting Started" page. 12 | * http://code.google.com/p/cocoalumberjack/wiki/GettingStarted 13 | * 14 | * 15 | * This class provides an abstract implementation of a database logger. 16 | * 17 | * That is, it provides the base implementation for a database logger to build atop of. 18 | * All that is needed for a concrete database logger is to extend this class 19 | * and override the methods in the implementation file that are prefixed with "db_". 20 | **/ 21 | 22 | @interface DDAbstractDatabaseLogger : DDAbstractLogger { 23 | @protected 24 | NSUInteger saveThreshold; 25 | NSTimeInterval saveInterval; 26 | NSTimeInterval maxAge; 27 | NSTimeInterval deleteInterval; 28 | BOOL deleteOnEverySave; 29 | 30 | BOOL saveTimerSuspended; 31 | NSUInteger unsavedCount; 32 | dispatch_time_t unsavedTime; 33 | dispatch_source_t saveTimer; 34 | dispatch_time_t lastDeleteTime; 35 | dispatch_source_t deleteTimer; 36 | } 37 | 38 | /** 39 | * Specifies how often to save the data to disk. 40 | * Since saving is an expensive operation (disk io) it is not done after every log statement. 41 | * These properties allow you to configure how/when the logger saves to disk. 42 | * 43 | * A save is done when either (whichever happens first): 44 | * 45 | * - The number of unsaved log entries reaches saveThreshold 46 | * - The amount of time since the oldest unsaved log entry was created reaches saveInterval 47 | * 48 | * You can optionally disable the saveThreshold by setting it to zero. 49 | * If you disable the saveThreshold you are entirely dependent on the saveInterval. 50 | * 51 | * You can optionally disable the saveInterval by setting it to zero (or a negative value). 52 | * If you disable the saveInterval you are entirely dependent on the saveThreshold. 53 | * 54 | * It's not wise to disable both saveThreshold and saveInterval. 55 | * 56 | * The default saveThreshold is 500. 57 | * The default saveInterval is 60 seconds. 58 | **/ 59 | @property (assign, readwrite) NSUInteger saveThreshold; 60 | @property (assign, readwrite) NSTimeInterval saveInterval; 61 | 62 | /** 63 | * It is likely you don't want the log entries to persist forever. 64 | * Doing so would allow the database to grow infinitely large over time. 65 | * 66 | * The maxAge property provides a way to specify how old a log statement can get 67 | * before it should get deleted from the database. 68 | * 69 | * The deleteInterval specifies how often to sweep for old log entries. 70 | * Since deleting is an expensive operation (disk io) is is done on a fixed interval. 71 | * 72 | * An alternative to the deleteInterval is the deleteOnEverySave option. 73 | * This specifies that old log entries should be deleted during every save operation. 74 | * 75 | * You can optionally disable the maxAge by setting it to zero (or a negative value). 76 | * If you disable the maxAge then old log statements are not deleted. 77 | * 78 | * You can optionally disable the deleteInterval by setting it to zero (or a negative value). 79 | * 80 | * If you disable both deleteInterval and deleteOnEverySave then old log statements are not deleted. 81 | * 82 | * It's not wise to enable both deleteInterval and deleteOnEverySave. 83 | * 84 | * The default maxAge is 7 days. 85 | * The default deleteInterval is 5 minutes. 86 | * The default deleteOnEverySave is NO. 87 | **/ 88 | @property (assign, readwrite) NSTimeInterval maxAge; 89 | @property (assign, readwrite) NSTimeInterval deleteInterval; 90 | @property (assign, readwrite) BOOL deleteOnEverySave; 91 | 92 | /** 93 | * Forces a save of any pending log entries (flushes log entries to disk). 94 | **/ 95 | - (void)savePendingLogEntries; 96 | 97 | /** 98 | * Removes any log entries that are older than maxAge. 99 | **/ 100 | - (void)deleteOldLogEntries; 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /VMMediaDownloader/m3u8/SegmentDownloader.mm: -------------------------------------------------------------------------------- 1 | // 2 | // SegmentDownloader.m 3 | // XB 4 | // 5 | // Created by luoxubin on 3/8/12. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import "SegmentDownloader.h" 10 | 11 | @implementation SegmentDownloader 12 | @synthesize fileName,tmpFileName,delegate,downloadUrl,filePath,status,progress; 13 | 14 | 15 | -(void)start 16 | { 17 | NSLog(@"download segment start, fileName = %@,url = %@",self.fileName,self.downloadUrl); 18 | request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:[self.downloadUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]; 19 | [request setTemporaryFileDownloadPath: self.tmpFileName]; 20 | NSString *pathPrefix = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) objectAtIndex:0]; 21 | NSString *saveTo = [[pathPrefix stringByAppendingPathComponent:kPathDownload] stringByAppendingPathComponent:self.filePath]; 22 | [request setDownloadDestinationPath:[saveTo stringByAppendingPathComponent:self.fileName]]; 23 | [request setDelegate:self]; 24 | [request setDownloadProgressDelegate:self]; 25 | request.allowResumeForFileDownloads = YES; 26 | [request setNumberOfTimesToRetryOnTimeout:2]; 27 | [request startAsynchronous]; 28 | status = ERUNNING; 29 | } 30 | 31 | -(void)stop 32 | { 33 | NSLog(@"download stoped"); 34 | if(request && status == ERUNNING) 35 | { 36 | request.delegate = nil; 37 | [request cancelAuthentication]; 38 | } 39 | status = ESTOPPED; 40 | } 41 | 42 | -(void)clean 43 | { 44 | NSLog(@"download clean"); 45 | if(request && status == ERUNNING) 46 | { 47 | request.delegate = nil; 48 | [request cancelAuthentication]; 49 | [request removeTemporaryDownloadFile]; 50 | NSError *Error = nil; 51 | if (![ASIHTTPRequest removeFileAtPath:[request downloadDestinationPath] error:&Error]) { 52 | NSLog(@"clean file err:%@",Error); 53 | } 54 | } 55 | status = ESTOPPED; 56 | progress = 0.0; 57 | } 58 | 59 | -(id)initWithUrl:(NSString *)url andFilePath:(NSString *)path andFileName:(NSString *)_fileName{ 60 | self = [super init]; 61 | if(self != nil) 62 | { 63 | self.downloadUrl = url; 64 | self.fileName = _fileName; 65 | self.filePath = path; 66 | 67 | NSString *pathPrefix = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) objectAtIndex:0]; 68 | NSString *saveTo = [[pathPrefix stringByAppendingPathComponent:kPathDownload] stringByAppendingPathComponent:self.filePath]; 69 | NSString *downloadingFileName = [[[NSString alloc] initWithString:[saveTo stringByAppendingPathComponent:[fileName stringByAppendingString:kTextDownloadingFileSuffix]]] autorelease]; 70 | self.tmpFileName = downloadingFileName; 71 | BOOL isDir = NO; 72 | NSFileManager *fm = [NSFileManager defaultManager]; 73 | if(!([fm fileExistsAtPath:saveTo isDirectory:&isDir] && isDir)) 74 | { 75 | [fm createDirectoryAtPath:saveTo withIntermediateDirectories:YES attributes:nil error:nil]; 76 | } 77 | self.progress = 0.0; 78 | status = ESTOPPED; 79 | 80 | } 81 | return self; 82 | } 83 | 84 | -(void)dealloc 85 | { 86 | [self stop]; 87 | [fileName release]; 88 | [tmpFileName release]; 89 | [delegate release]; 90 | [downloadUrl release]; 91 | [super dealloc]; 92 | } 93 | 94 | 95 | - (void)requestFinished:(ASIHTTPRequest *)request 96 | { 97 | NSLog(@"download finished!"); 98 | if(delegate && [delegate respondsToSelector:@selector(segmentDownloadFinished:)]) 99 | { 100 | [delegate segmentDownloadFinished:self]; 101 | } 102 | } 103 | 104 | - (void)requestFailed:(ASIHTTPRequest *)aRequest 105 | { 106 | NSError *err = aRequest.error; 107 | if (err.code != 3) 108 | { 109 | [self stop]; 110 | NSLog(@"Download failed."); 111 | if(delegate && [delegate respondsToSelector:@selector(segmentDownloadFailed:)]) 112 | { 113 | [delegate segmentDownloadFailed:self]; 114 | } 115 | } 116 | } 117 | 118 | - (void)setProgress:(float)newProgress 119 | { 120 | progress = newProgress; 121 | NSLog(@"newprogress :%f",newProgress); 122 | } 123 | 124 | @end 125 | -------------------------------------------------------------------------------- /VMMediaDownloader/m3u8/M3U8Handler.mm: -------------------------------------------------------------------------------- 1 | // 2 | // M3U8Handler.m 3 | // XB 4 | // 5 | // Created by luoxubin on 3/7/12. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import "M3U8Handler.h" 10 | #import "M3U8Playlist.h" 11 | @implementation M3U8Handler 12 | @synthesize delegate,playlist; 13 | 14 | 15 | -(void)dealloc 16 | { 17 | delegate = nil; 18 | playlist = nil; 19 | } 20 | 21 | 22 | //解析m3u8的内容 23 | -(void)praseUrl:(NSString *)urlstr 24 | { 25 | // if([urlstr hasSuffix:@".m3u8"] == FALSE) 26 | // { 27 | // NSLog(@" Invalid url"); 28 | // if(self.delegate != nil && [self.delegate respondsToSelector:@selector(praseM3U8Failed:)]) 29 | // { 30 | // [self.delegate praseM3U8Failed:self]; 31 | // } 32 | // return; 33 | // } 34 | 35 | NSURL *url = [[NSURL alloc] initWithString:urlstr]; 36 | NSError *error = nil; 37 | NSStringEncoding encoding; 38 | NSString *data = [[NSString alloc] initWithContentsOfURL:url 39 | usedEncoding:&encoding 40 | error:&error]; 41 | 42 | if(data == nil) 43 | { 44 | NSLog(@"data is nil"); 45 | if(self.delegate != nil && [self.delegate respondsToSelector:@selector(praseM3U8Failed:)]) 46 | { 47 | [self.delegate praseM3U8Failed:self]; 48 | } 49 | return; 50 | } 51 | 52 | NSMutableArray *segments = [[NSMutableArray alloc] init]; 53 | NSString* remainData =data; 54 | NSRange segmentRange = [remainData rangeOfString:@"#EXTINF:"]; 55 | while (segmentRange.location != NSNotFound) 56 | { 57 | M3U8SegmentInfo * segment = [[M3U8SegmentInfo alloc]init]; 58 | // 读取片段时长 59 | NSRange commaRange = [remainData rangeOfString:@","]; 60 | NSString* value = [remainData substringWithRange:NSMakeRange(segmentRange.location + [@"#EXTINF:" length], commaRange.location -(segmentRange.location + [@"#EXTINF:" length]))]; 61 | segment.duration = [value intValue]; 62 | 63 | remainData = [remainData substringFromIndex:commaRange.location]; 64 | // 读取片段url 65 | NSRange linkRangeBegin = [remainData rangeOfString:@"http"]; 66 | NSRange linkRangeEnd = [remainData rangeOfString:@"#"]; 67 | NSString* linkurl = [remainData substringWithRange:NSMakeRange(linkRangeBegin.location, linkRangeEnd.location - linkRangeBegin.location)]; 68 | segment.locationUrl = linkurl; 69 | 70 | [segments addObject:segment]; 71 | remainData = [remainData substringFromIndex:linkRangeEnd.location]; 72 | segmentRange = [remainData rangeOfString:@"#EXTINF:"]; 73 | } 74 | 75 | M3U8Playlist * thePlaylist = [[M3U8Playlist alloc] initWithSegments:segments]; 76 | segments = nil; 77 | self.playlist = thePlaylist; 78 | thePlaylist = nil; 79 | 80 | if(self.delegate != nil && [self.delegate respondsToSelector:@selector(praseM3U8Finished:)]) 81 | { 82 | [self.delegate praseM3U8Finished:self]; 83 | } 84 | } 85 | 86 | 87 | /* m3u8文件格式示例 88 | 89 | #EXTM3U 90 | #EXT-X-TARGETDURATION:30 91 | #EXT-X-VERSION:2 92 | #EXT-X-DISCONTINUITY 93 | #EXTINF:10, 94 | http://f.youku.com/player/getMpegtsPath/st/flv/fileid/03000201004F4BC6AFD0C202E26EEEB41666A0-C93C-D6C9-9FFA-33424A776707/ipad0_0.ts?KM=14eb49fe4969126c6&start=0&end=10&ts=10&html5=1&seg_no=0&seg_time=0 95 | #EXTINF:20, 96 | http://f.youku.com/player/getMpegtsPath/st/flv/fileid/03000201004F4BC6AFD0C202E26EEEB41666A0-C93C-D6C9-9FFA-33424A776707/ipad0_1.ts?KM=14eb49fe4969126c6&start=10&end=30&ts=20&html5=1&seg_no=1&seg_time=0 97 | #EXTINF:20, 98 | http://f.youku.com/player/getMpegtsPath/st/flv/fileid/03000201004F4BC6AFD0C202E26EEEB41666A0-C93C-D6C9-9FFA-33424A776707/ipad0_2.ts?KM=14eb49fe4969126c6&start=30&end=50&ts=20&html5=1&seg_no=2&seg_time=0 99 | #EXTINF:20, 100 | http://f.youku.com/player/getMpegtsPath/st/flv/fileid/03000201004F4BC6AFD0C202E26EEEB41666A0-C93C-D6C9-9FFA-33424A776707/ipad0_3.ts?KM=14eb49fe4969126c6&start=50&end=70&ts=20&html5=1&seg_no=3&seg_time=0 101 | #EXTINF:24, 102 | http://f.youku.com/player/getMpegtsPath/st/flv/fileid/03000201004F4BC6AFD0C202E26EEEB41666A0-C93C-D6C9-9FFA-33424A776707/ipad0_4.ts?KM=14eb49fe4969126c6&start=70&end=98&ts=24&html5=1&seg_no=4&seg_time=0 103 | #EXT-X-ENDLIST 104 | */ 105 | 106 | 107 | @end 108 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/Categories/DDData.m: -------------------------------------------------------------------------------- 1 | #import "DDData.h" 2 | #import 3 | 4 | 5 | @implementation NSData (DDData) 6 | 7 | static char encodingTable[64] = { 8 | 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', 9 | 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', 10 | 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', 11 | 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' }; 12 | 13 | - (NSData *)md5Digest 14 | { 15 | unsigned char result[CC_MD5_DIGEST_LENGTH]; 16 | 17 | CC_MD5([self bytes], (CC_LONG)[self length], result); 18 | return [NSData dataWithBytes:result length:CC_MD5_DIGEST_LENGTH]; 19 | } 20 | 21 | - (NSData *)sha1Digest 22 | { 23 | unsigned char result[CC_SHA1_DIGEST_LENGTH]; 24 | 25 | CC_SHA1([self bytes], (CC_LONG)[self length], result); 26 | return [NSData dataWithBytes:result length:CC_SHA1_DIGEST_LENGTH]; 27 | } 28 | 29 | - (NSString *)hexStringValue 30 | { 31 | NSMutableString *stringBuffer = [NSMutableString stringWithCapacity:([self length] * 2)]; 32 | 33 | const unsigned char *dataBuffer = [self bytes]; 34 | int i; 35 | 36 | for (i = 0; i < [self length]; ++i) 37 | { 38 | [stringBuffer appendFormat:@"%02x", (unsigned long)dataBuffer[i]]; 39 | } 40 | 41 | return [[stringBuffer copy] autorelease]; 42 | } 43 | 44 | - (NSString *)base64Encoded 45 | { 46 | const unsigned char *bytes = [self bytes]; 47 | NSMutableString *result = [NSMutableString stringWithCapacity:[self length]]; 48 | unsigned long ixtext = 0; 49 | unsigned long lentext = [self length]; 50 | long ctremaining = 0; 51 | unsigned char inbuf[3], outbuf[4]; 52 | unsigned short i = 0; 53 | unsigned short charsonline = 0, ctcopy = 0; 54 | unsigned long ix = 0; 55 | 56 | while( YES ) 57 | { 58 | ctremaining = lentext - ixtext; 59 | if( ctremaining <= 0 ) break; 60 | 61 | for( i = 0; i < 3; i++ ) { 62 | ix = ixtext + i; 63 | if( ix < lentext ) inbuf[i] = bytes[ix]; 64 | else inbuf [i] = 0; 65 | } 66 | 67 | outbuf [0] = (inbuf [0] & 0xFC) >> 2; 68 | outbuf [1] = ((inbuf [0] & 0x03) << 4) | ((inbuf [1] & 0xF0) >> 4); 69 | outbuf [2] = ((inbuf [1] & 0x0F) << 2) | ((inbuf [2] & 0xC0) >> 6); 70 | outbuf [3] = inbuf [2] & 0x3F; 71 | ctcopy = 4; 72 | 73 | switch( ctremaining ) 74 | { 75 | case 1: 76 | ctcopy = 2; 77 | break; 78 | case 2: 79 | ctcopy = 3; 80 | break; 81 | } 82 | 83 | for( i = 0; i < ctcopy; i++ ) 84 | [result appendFormat:@"%c", encodingTable[outbuf[i]]]; 85 | 86 | for( i = ctcopy; i < 4; i++ ) 87 | [result appendString:@"="]; 88 | 89 | ixtext += 3; 90 | charsonline += 4; 91 | } 92 | 93 | return [NSString stringWithString:result]; 94 | } 95 | 96 | - (NSData *)base64Decoded 97 | { 98 | const unsigned char *bytes = [self bytes]; 99 | NSMutableData *result = [NSMutableData dataWithCapacity:[self length]]; 100 | 101 | unsigned long ixtext = 0; 102 | unsigned long lentext = [self length]; 103 | unsigned char ch = 0; 104 | unsigned char inbuf[4], outbuf[3]; 105 | short i = 0, ixinbuf = 0; 106 | BOOL flignore = NO; 107 | BOOL flendtext = NO; 108 | 109 | while( YES ) 110 | { 111 | if( ixtext >= lentext ) break; 112 | ch = bytes[ixtext++]; 113 | flignore = NO; 114 | 115 | if( ( ch >= 'A' ) && ( ch <= 'Z' ) ) ch = ch - 'A'; 116 | else if( ( ch >= 'a' ) && ( ch <= 'z' ) ) ch = ch - 'a' + 26; 117 | else if( ( ch >= '0' ) && ( ch <= '9' ) ) ch = ch - '0' + 52; 118 | else if( ch == '+' ) ch = 62; 119 | else if( ch == '=' ) flendtext = YES; 120 | else if( ch == '/' ) ch = 63; 121 | else flignore = YES; 122 | 123 | if( ! flignore ) 124 | { 125 | short ctcharsinbuf = 3; 126 | BOOL flbreak = NO; 127 | 128 | if( flendtext ) 129 | { 130 | if( ! ixinbuf ) break; 131 | if( ( ixinbuf == 1 ) || ( ixinbuf == 2 ) ) ctcharsinbuf = 1; 132 | else ctcharsinbuf = 2; 133 | ixinbuf = 3; 134 | flbreak = YES; 135 | } 136 | 137 | inbuf [ixinbuf++] = ch; 138 | 139 | if( ixinbuf == 4 ) 140 | { 141 | ixinbuf = 0; 142 | outbuf [0] = ( inbuf[0] << 2 ) | ( ( inbuf[1] & 0x30) >> 4 ); 143 | outbuf [1] = ( ( inbuf[1] & 0x0F ) << 4 ) | ( ( inbuf[2] & 0x3C ) >> 2 ); 144 | outbuf [2] = ( ( inbuf[2] & 0x03 ) << 6 ) | ( inbuf[3] & 0x3F ); 145 | 146 | for( i = 0; i < ctcharsinbuf; i++ ) 147 | [result appendBytes:&outbuf[i] length:1]; 148 | } 149 | 150 | if( flbreak ) break; 151 | } 152 | } 153 | 154 | return [NSData dataWithData:result]; 155 | } 156 | 157 | @end 158 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Vendor/CocoaLumberjack/DDTTYLogger.m: -------------------------------------------------------------------------------- 1 | #import "DDTTYLogger.h" 2 | 3 | #import 4 | #import 5 | 6 | 7 | @implementation DDTTYLogger 8 | 9 | static DDTTYLogger *sharedInstance; 10 | 11 | /** 12 | * The runtime sends initialize to each class in a program exactly one time just before the class, 13 | * or any class that inherits from it, is sent its first message from within the program. (Thus the 14 | * method may never be invoked if the class is not used.) The runtime sends the initialize message to 15 | * classes in a thread-safe manner. Superclasses receive this message before their subclasses. 16 | * 17 | * This method may also be called directly (assumably by accident), hence the safety mechanism. 18 | **/ 19 | + (void)initialize 20 | { 21 | static BOOL initialized = NO; 22 | if (!initialized) 23 | { 24 | initialized = YES; 25 | 26 | sharedInstance = [[DDTTYLogger alloc] init]; 27 | } 28 | } 29 | 30 | + (DDTTYLogger *)sharedInstance 31 | { 32 | return sharedInstance; 33 | } 34 | 35 | - (id)init 36 | { 37 | if (sharedInstance != nil) 38 | { 39 | [self release]; 40 | return nil; 41 | } 42 | 43 | if ((self = [super init])) 44 | { 45 | isaTTY = isatty(STDERR_FILENO); 46 | 47 | if (isaTTY) 48 | { 49 | dateFormatter = [[NSDateFormatter alloc] init]; 50 | [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; 51 | [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss:SSS"]; 52 | 53 | // Initialze 'app' variable (char *) 54 | 55 | NSString *appNStr = [[NSProcessInfo processInfo] processName]; 56 | const char *appCStr = [appNStr UTF8String]; 57 | 58 | appLen = strlen(appCStr); 59 | 60 | app = (char *)malloc(appLen); 61 | strncpy(app, appCStr, appLen); // Not null terminated 62 | 63 | // Initialize 'pid' variable (char *) 64 | 65 | NSString *pidNStr = [NSString stringWithFormat:@"%i", (int)getpid()]; 66 | const char *pidCStr = [pidNStr UTF8String]; 67 | 68 | pidLen = strlen(pidCStr); 69 | 70 | pid = (char *)malloc(pidLen); 71 | strncpy(pid, pidCStr, pidLen); // Not null terminated 72 | } 73 | } 74 | return self; 75 | } 76 | 77 | - (void)logMessage:(DDLogMessage *)logMessage 78 | { 79 | if (!isaTTY) return; 80 | 81 | NSString *logMsg = logMessage->logMsg; 82 | BOOL isFormatted = NO; 83 | 84 | if (formatter) 85 | { 86 | logMsg = [formatter formatLogMessage:logMessage]; 87 | isFormatted = logMsg != logMessage->logMsg; 88 | } 89 | 90 | if (logMsg) 91 | { 92 | const char *msg = [logMsg UTF8String]; 93 | size_t msgLen = strlen(msg); 94 | 95 | if (isFormatted) 96 | { 97 | struct iovec v[2]; 98 | 99 | v[0].iov_base = (char *)msg; 100 | v[0].iov_len = msgLen; 101 | 102 | v[1].iov_base = "\n"; 103 | v[1].iov_len = (msg[msgLen] == '\n') ? 0 : 1; 104 | 105 | writev(STDERR_FILENO, v, 2); 106 | } 107 | else 108 | { 109 | // The following is a highly optimized verion of file output to std err. 110 | 111 | // ts = timestamp 112 | 113 | NSString *tsNStr = [dateFormatter stringFromDate:(logMessage->timestamp)]; 114 | 115 | const char *tsCStr = [tsNStr UTF8String]; 116 | size_t tsLen = strlen(tsCStr); 117 | 118 | // tid = thread id 119 | // 120 | // How many characters do we need for the thread id? 121 | // logMessage->machThreadID is of type mach_port_t, which is an unsigned int. 122 | // 123 | // 1 hex char = 4 bits 124 | // 8 hex chars for 32 bit, plus ending '\0' = 9 125 | 126 | char tidCStr[9]; 127 | int tidLen = snprintf(tidCStr, 9, "%x", logMessage->machThreadID); 128 | 129 | // Here is our format: "%s %s[%i:%s] %s", timestamp, appName, processID, threadID, logMsg 130 | 131 | struct iovec v[10]; 132 | 133 | v[0].iov_base = (char *)tsCStr; 134 | v[0].iov_len = tsLen; 135 | 136 | v[1].iov_base = " "; 137 | v[1].iov_len = 1; 138 | 139 | v[2].iov_base = app; 140 | v[2].iov_len = appLen; 141 | 142 | v[3].iov_base = "["; 143 | v[3].iov_len = 1; 144 | 145 | v[4].iov_base = pid; 146 | v[4].iov_len = pidLen; 147 | 148 | v[5].iov_base = ":"; 149 | v[5].iov_len = 1; 150 | 151 | v[6].iov_base = tidCStr; 152 | v[6].iov_len = MIN((size_t)8, tidLen); // snprintf doesn't return what you might think 153 | 154 | v[7].iov_base = "] "; 155 | v[7].iov_len = 2; 156 | 157 | v[8].iov_base = (char *)msg; 158 | v[8].iov_len = msgLen; 159 | 160 | v[9].iov_base = "\n"; 161 | v[9].iov_len = (msg[msgLen] == '\n') ? 0 : 1; 162 | 163 | writev(STDERR_FILENO, v, 10); 164 | } 165 | } 166 | } 167 | 168 | - (NSString *)loggerName 169 | { 170 | return @"cocoa.lumberjack.ttyLogger"; 171 | } 172 | 173 | @end 174 | -------------------------------------------------------------------------------- /VMMediaDownloader/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 36 | 45 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/Responses/HTTPFileResponse.m: -------------------------------------------------------------------------------- 1 | #import "HTTPFileResponse.h" 2 | #import "HTTPConnection.h" 3 | #import "HTTPLogging.h" 4 | 5 | #import 6 | #import 7 | 8 | // Log levels : off, error, warn, info, verbose 9 | // Other flags: trace 10 | static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE; 11 | 12 | #define NULL_FD -1 13 | 14 | 15 | @implementation HTTPFileResponse 16 | 17 | - (id)initWithFilePath:(NSString *)fpath forConnection:(HTTPConnection *)parent 18 | { 19 | if((self = [super init])) 20 | { 21 | HTTPLogTrace(); 22 | 23 | connection = parent; // Parents retain children, children do NOT retain parents 24 | 25 | fileFD = NULL_FD; 26 | filePath = [fpath copy]; 27 | if (filePath == nil) 28 | { 29 | HTTPLogWarn(@"%@: Init failed - Nil filePath", THIS_FILE); 30 | 31 | [self release]; 32 | return nil; 33 | } 34 | 35 | NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; 36 | if (fileAttributes == nil) 37 | { 38 | HTTPLogWarn(@"%@: Init failed - Unable to get file attributes. filePath: %@", THIS_FILE, filePath); 39 | 40 | [self release]; 41 | return nil; 42 | } 43 | 44 | fileLength = (UInt64)[[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue]; 45 | fileOffset = 0; 46 | 47 | aborted = NO; 48 | 49 | // We don't bother opening the file here. 50 | // If this is a HEAD request we only need to know the fileLength. 51 | } 52 | return self; 53 | } 54 | 55 | - (void)abort 56 | { 57 | HTTPLogTrace(); 58 | 59 | [connection responseDidAbort:self]; 60 | aborted = YES; 61 | } 62 | 63 | - (BOOL)openFile 64 | { 65 | HTTPLogTrace(); 66 | 67 | fileFD = open([filePath UTF8String], O_RDONLY); 68 | if (fileFD == NULL_FD) 69 | { 70 | HTTPLogError(@"%@[%p]: Unable to open file. filePath: %@", THIS_FILE, self, filePath); 71 | 72 | [self abort]; 73 | return NO; 74 | } 75 | 76 | HTTPLogVerbose(@"%@[%p]: Open fd[%i] -> %@", THIS_FILE, self, fileFD, filePath); 77 | 78 | return YES; 79 | } 80 | 81 | - (BOOL)openFileIfNeeded 82 | { 83 | if (aborted) 84 | { 85 | // The file operation has been aborted. 86 | // This could be because we failed to open the file, 87 | // or the reading process failed. 88 | return NO; 89 | } 90 | 91 | if (fileFD != NULL_FD) 92 | { 93 | // File has already been opened. 94 | return YES; 95 | } 96 | 97 | return [self openFile]; 98 | } 99 | 100 | - (UInt64)contentLength 101 | { 102 | HTTPLogTrace(); 103 | 104 | return fileLength; 105 | } 106 | 107 | - (UInt64)offset 108 | { 109 | HTTPLogTrace(); 110 | 111 | return fileOffset; 112 | } 113 | 114 | - (void)setOffset:(UInt64)offset 115 | { 116 | HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset); 117 | 118 | if (![self openFileIfNeeded]) 119 | { 120 | // File opening failed, 121 | // or response has been aborted due to another error. 122 | return; 123 | } 124 | 125 | fileOffset = offset; 126 | 127 | off_t result = lseek(fileFD, (off_t)offset, SEEK_SET); 128 | if (result == -1) 129 | { 130 | HTTPLogError(@"%@[%p]: lseek failed - errno(%i) filePath(%@)", THIS_FILE, self, errno, filePath); 131 | 132 | [self abort]; 133 | } 134 | } 135 | 136 | - (NSData *)readDataOfLength:(NSUInteger)length 137 | { 138 | HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)length); 139 | 140 | if (![self openFileIfNeeded]) 141 | { 142 | // File opening failed, 143 | // or response has been aborted due to another error. 144 | return nil; 145 | } 146 | 147 | // Determine how much data we should read. 148 | // 149 | // It is OK if we ask to read more bytes than exist in the file. 150 | // It is NOT OK to over-allocate the buffer. 151 | 152 | UInt64 bytesLeftInFile = fileLength - fileOffset; 153 | 154 | NSUInteger bytesToRead = (NSUInteger)MIN(length, bytesLeftInFile); 155 | 156 | // Make sure buffer is big enough for read request. 157 | // Do not over-allocate. 158 | 159 | if (buffer == NULL || bufferSize < bytesToRead) 160 | { 161 | bufferSize = bytesToRead; 162 | buffer = reallocf(buffer, (size_t)bufferSize); 163 | 164 | if (buffer == NULL) 165 | { 166 | HTTPLogError(@"%@[%p]: Unable to allocate buffer", THIS_FILE, self); 167 | 168 | [self abort]; 169 | return nil; 170 | } 171 | } 172 | 173 | // Perform the read 174 | 175 | HTTPLogVerbose(@"%@[%p]: Attempting to read %lu bytes from file", THIS_FILE, self, bytesToRead); 176 | 177 | ssize_t result = read(fileFD, buffer, bytesToRead); 178 | 179 | // Check the results 180 | 181 | if (result < 0) 182 | { 183 | HTTPLogError(@"%@: Error(%i) reading file(%@)", THIS_FILE, errno, filePath); 184 | 185 | [self abort]; 186 | return nil; 187 | } 188 | else if (result == 0) 189 | { 190 | HTTPLogError(@"%@: Read EOF on file(%@)", THIS_FILE, filePath); 191 | 192 | [self abort]; 193 | return nil; 194 | } 195 | else // (result > 0) 196 | { 197 | HTTPLogVerbose(@"%@[%p]: Read %d bytes from file", THIS_FILE, self, result); 198 | 199 | fileOffset += result; 200 | 201 | return [NSData dataWithBytes:buffer length:result]; 202 | } 203 | } 204 | 205 | - (BOOL)isDone 206 | { 207 | BOOL result = (fileOffset == fileLength); 208 | 209 | HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO")); 210 | 211 | return result; 212 | } 213 | 214 | - (NSString *)filePath 215 | { 216 | return filePath; 217 | } 218 | 219 | - (void)dealloc 220 | { 221 | HTTPLogTrace(); 222 | 223 | if (fileFD != NULL_FD) 224 | { 225 | HTTPLogVerbose(@"%@[%p]: Close fd[%i]", THIS_FILE, self, fileFD); 226 | 227 | close(fileFD); 228 | } 229 | 230 | if (buffer) 231 | free(buffer); 232 | 233 | [filePath release]; 234 | [super dealloc]; 235 | } 236 | 237 | @end 238 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Extensions/WebDAV/DAVConnection.m: -------------------------------------------------------------------------------- 1 | #import "DAVConnection.h" 2 | #import "HTTPMessage.h" 3 | #import "HTTPFileResponse.h" 4 | #import "HTTPAsyncFileResponse.h" 5 | #import "PUTResponse.h" 6 | #import "DELETEResponse.h" 7 | #import "DAVResponse.h" 8 | #import "HTTPLogging.h" 9 | 10 | #define HTTP_BODY_MAX_MEMORY_SIZE (1024 * 1024) 11 | #define HTTP_ASYNC_FILE_RESPONSE_THRESHOLD (16 * 1024 * 1024) 12 | 13 | static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; 14 | 15 | @implementation DAVConnection 16 | 17 | - (void) dealloc { 18 | [requestContentStream close]; 19 | [requestContentStream release]; 20 | [requestContentBody release]; 21 | 22 | [super dealloc]; 23 | } 24 | 25 | - (BOOL) supportsMethod:(NSString*)method atPath:(NSString*)path { 26 | // HTTPFileResponse & HTTPAsyncFileResponse 27 | if ([method isEqualToString:@"GET"]) return YES; 28 | if ([method isEqualToString:@"HEAD"]) return YES; 29 | 30 | // PUTResponse 31 | if ([method isEqualToString:@"PUT"]) return YES; 32 | 33 | // DELETEResponse 34 | if ([method isEqualToString:@"DELETE"]) return YES; 35 | 36 | // DAVResponse 37 | if ([method isEqualToString:@"OPTIONS"]) return YES; 38 | if ([method isEqualToString:@"PROPFIND"]) return YES; 39 | if ([method isEqualToString:@"MKCOL"]) return YES; 40 | if ([method isEqualToString:@"MOVE"]) return YES; 41 | if ([method isEqualToString:@"COPY"]) return YES; 42 | if ([method isEqualToString:@"LOCK"]) return YES; 43 | if ([method isEqualToString:@"UNLOCK"]) return YES; 44 | 45 | return NO; 46 | } 47 | 48 | - (BOOL) expectsRequestBodyFromMethod:(NSString*)method atPath:(NSString*)path { 49 | // PUTResponse 50 | if ([method isEqualToString:@"PUT"]) { 51 | return YES; 52 | } 53 | 54 | // DAVResponse 55 | if ([method isEqual:@"PROPFIND"] || [method isEqual:@"MKCOL"]) { 56 | return [request headerField:@"Content-Length"] ? YES : NO; 57 | } 58 | if ([method isEqual:@"LOCK"]) { 59 | return YES; 60 | } 61 | 62 | return NO; 63 | } 64 | 65 | - (void) prepareForBodyWithSize:(UInt64)contentLength { 66 | NSAssert(requestContentStream == nil, @"requestContentStream should be nil"); 67 | NSAssert(requestContentBody == nil, @"requestContentBody should be nil"); 68 | 69 | if (contentLength > HTTP_BODY_MAX_MEMORY_SIZE) { 70 | requestContentBody = [[NSTemporaryDirectory() stringByAppendingString:[[NSProcessInfo processInfo] globallyUniqueString]] copy]; 71 | requestContentStream = [[NSOutputStream alloc] initToFileAtPath:requestContentBody append:NO]; 72 | [requestContentStream open]; 73 | } else { 74 | requestContentBody = [[NSMutableData alloc] initWithCapacity:(NSUInteger)contentLength]; 75 | requestContentStream = nil; 76 | } 77 | } 78 | 79 | - (void) processBodyData:(NSData*)postDataChunk { 80 | NSAssert(requestContentBody != nil, @"requestContentBody should not be nil"); 81 | if (requestContentStream) { 82 | [requestContentStream write:[postDataChunk bytes] maxLength:[postDataChunk length]]; 83 | } else { 84 | [(NSMutableData*)requestContentBody appendData:postDataChunk]; 85 | } 86 | } 87 | 88 | - (void) finishBody { 89 | NSAssert(requestContentBody != nil, @"requestContentBody should not be nil"); 90 | if (requestContentStream) { 91 | [requestContentStream close]; 92 | [requestContentStream release]; 93 | requestContentStream = nil; 94 | } 95 | } 96 | 97 | - (void)finishResponse { 98 | NSAssert(requestContentStream == nil, @"requestContentStream should be nil"); 99 | [requestContentBody release]; 100 | requestContentBody = nil; 101 | 102 | [super finishResponse]; 103 | } 104 | 105 | - (NSObject*) httpResponseForMethod:(NSString*)method URI:(NSString*)path { 106 | if ([method isEqualToString:@"HEAD"] || [method isEqualToString:@"GET"]) { 107 | NSString* filePath = [self filePathForURI:path allowDirectory:NO]; 108 | if (filePath) { 109 | NSDictionary* fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL]; 110 | if (fileAttributes) { 111 | if ([[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue] > HTTP_ASYNC_FILE_RESPONSE_THRESHOLD) { 112 | return [[[HTTPAsyncFileResponse alloc] initWithFilePath:filePath forConnection:self] autorelease]; 113 | } else { 114 | return [[[HTTPFileResponse alloc] initWithFilePath:filePath forConnection:self] autorelease]; 115 | } 116 | } 117 | } 118 | } 119 | 120 | if ([method isEqualToString:@"PUT"]) { 121 | NSString* filePath = [self filePathForURI:path allowDirectory:YES]; 122 | if (filePath) { 123 | if ([requestContentBody isKindOfClass:[NSString class]]) { 124 | return [[[PUTResponse alloc] initWithFilePath:filePath headers:[request allHeaderFields] bodyFile:requestContentBody] autorelease]; 125 | } else if ([requestContentBody isKindOfClass:[NSData class]]) { 126 | return [[[PUTResponse alloc] initWithFilePath:filePath headers:[request allHeaderFields] bodyData:requestContentBody] autorelease]; 127 | } else { 128 | HTTPLogError(@"Internal error"); 129 | } 130 | } 131 | } 132 | 133 | if ([method isEqualToString:@"DELETE"]) { 134 | NSString* filePath = [self filePathForURI:path allowDirectory:YES]; 135 | if (filePath) { 136 | return [[[DELETEResponse alloc] initWithFilePath:filePath] autorelease]; 137 | } 138 | } 139 | 140 | if ([method isEqualToString:@"OPTIONS"] || [method isEqualToString:@"PROPFIND"] || [method isEqualToString:@"MKCOL"] || 141 | [method isEqualToString:@"MOVE"] || [method isEqualToString:@"COPY"] || [method isEqualToString:@"LOCK"] || [method isEqualToString:@"UNLOCK"]) { 142 | NSString* filePath = [self filePathForURI:path allowDirectory:YES]; 143 | if (filePath) { 144 | NSString* rootPath = [config documentRoot]; 145 | NSString* resourcePath = [filePath substringFromIndex:([rootPath length] + 1)]; 146 | if (requestContentBody) { 147 | if ([requestContentBody isKindOfClass:[NSString class]]) { 148 | requestContentBody = [NSData dataWithContentsOfFile:requestContentBody]; 149 | } else if (![requestContentBody isKindOfClass:[NSData class]]) { 150 | HTTPLogError(@"Internal error"); 151 | return nil; 152 | } 153 | } 154 | return [[[DAVResponse alloc] initWithMethod:method 155 | headers:[request allHeaderFields] 156 | bodyData:requestContentBody 157 | resourcePath:resourcePath 158 | rootPath:rootPath] autorelease]; 159 | } 160 | } 161 | 162 | return nil; 163 | } 164 | 165 | @end 166 | -------------------------------------------------------------------------------- /VMMediaDownloader/m3u8/VideoDownloader.mm: -------------------------------------------------------------------------------- 1 | // 2 | // VideoDownloader.m 3 | // XB 4 | // 5 | // Created by luoxubin on 3/8/12. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import "VideoDownloader.h" 10 | 11 | @implementation VideoDownloader 12 | @synthesize totalprogress,playlist,delegate; 13 | 14 | 15 | -(id)initWithM3U8List:(M3U8Playlist *)list 16 | { 17 | self = [super init]; 18 | if(self != nil) 19 | { 20 | self.playlist = list; 21 | totalprogress = 0.0; 22 | } 23 | return self; 24 | } 25 | 26 | -(void)startDownloadVideo 27 | { 28 | NSLog(@"start download video"); 29 | if(downloadArray == nil) 30 | { 31 | downloadArray = [[NSMutableArray alloc]init]; 32 | for(int i = 0;i< self.playlist.length;i++) 33 | { 34 | NSString* filename = [NSString stringWithFormat:@"id%d",i]; 35 | M3U8SegmentInfo* segment = [self.playlist getSegment:i]; 36 | SegmentDownloader* sgDownloader = [[SegmentDownloader alloc]initWithUrl:segment.locationUrl andFilePath:self.playlist.uuid andFileName:filename]; 37 | sgDownloader.delegate = self; 38 | [downloadArray addObject:sgDownloader]; 39 | sgDownloader = nil; 40 | } 41 | } 42 | for(SegmentDownloader* obj in downloadArray) 43 | { 44 | [obj start]; 45 | } 46 | bDownloading = YES; 47 | } 48 | 49 | -(void)cleanDownloadFiles 50 | { 51 | NSLog(@"cleanDownloadFiles"); 52 | 53 | 54 | for(int i = 0;i< self.playlist.length;i++) 55 | { 56 | NSString* filename = [NSString stringWithFormat:@"id%d",i]; 57 | NSString* tmpfilename = [filename stringByAppendingString:kTextDownloadingFileSuffix]; 58 | NSString *pathPrefix = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) objectAtIndex:0]; 59 | NSString *savePath = [[pathPrefix stringByAppendingPathComponent:kPathDownload] stringByAppendingPathComponent:self.playlist.uuid]; 60 | NSString* fullpath = [savePath stringByAppendingPathComponent:filename]; 61 | NSString* fullpath_tmp = [savePath stringByAppendingPathComponent:tmpfilename]; 62 | 63 | NSFileManager *fileManager = [[NSFileManager alloc] init]; 64 | 65 | if ([fileManager fileExistsAtPath:fullpath]) { 66 | NSError *removeError = nil; 67 | [fileManager removeItemAtPath:fullpath error:&removeError]; 68 | if (removeError) 69 | { 70 | NSLog(@"delete file=%@ err, err is %@",fullpath,removeError); 71 | } 72 | } 73 | 74 | if ([fileManager fileExistsAtPath:fullpath_tmp]) { 75 | NSError *removeError = nil; 76 | [fileManager removeItemAtPath:fullpath_tmp error:&removeError]; 77 | if (removeError) 78 | { 79 | NSLog(@"delete file=%@ err, err is %@",fullpath_tmp,removeError); 80 | } 81 | } 82 | 83 | } 84 | 85 | } 86 | 87 | 88 | -(void)stopDownloadVideo 89 | { 90 | NSLog(@"stop Download Video"); 91 | if(bDownloading && downloadArray != nil) 92 | { 93 | for(SegmentDownloader *obj in downloadArray) 94 | { 95 | [obj stop]; 96 | } 97 | bDownloading = NO; 98 | } 99 | } 100 | 101 | -(void)cancelDownloadVideo 102 | { 103 | NSLog(@"cancel download video"); 104 | if(bDownloading && downloadArray != nil) 105 | { 106 | for(SegmentDownloader *obj in downloadArray) 107 | { 108 | [obj clean]; 109 | } 110 | } 111 | [self cleanDownloadFiles]; 112 | } 113 | 114 | 115 | -(void)dealloc 116 | { 117 | playlist = nil; 118 | delegate = nil; 119 | [downloadArray removeAllObjects]; 120 | downloadArray = nil; 121 | } 122 | 123 | 124 | #pragma mark - SegmentDownloadDelegate 125 | -(void)segmentDownloadFailed:(SegmentDownloader *)request 126 | { 127 | NSLog(@"a segment Download Failed"); 128 | 129 | if(delegate && [delegate respondsToSelector:@selector(videoDownloaderFailed:)]) 130 | { 131 | [delegate videoDownloaderFailed:self]; 132 | } 133 | } 134 | 135 | -(void)segmentDownloadFinished:(SegmentDownloader *)request 136 | { 137 | NSLog(@"a segment Download Finished"); 138 | 139 | [downloadArray removeObject:request]; 140 | if([downloadArray count] == 0) 141 | { 142 | totalprogress = 1; 143 | NSLog(@"all the segments downloaded. video download finished"); 144 | if(delegate && [delegate respondsToSelector:@selector(videoDownloaderFinished:)]) 145 | { 146 | [delegate videoDownloaderFinished:self]; 147 | } 148 | } 149 | } 150 | 151 | 152 | -(NSString*)createLocalM3U8file 153 | { 154 | if(playlist !=nil) 155 | { 156 | NSString *pathPrefix = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) objectAtIndex:0]; 157 | NSString *saveTo = [[pathPrefix stringByAppendingPathComponent:kPathDownload] stringByAppendingPathComponent:playlist.uuid]; 158 | NSString *fullpath = [saveTo stringByAppendingPathComponent:@"movie.m3u8"]; 159 | NSLog(@"createLocalM3U8file:%@",fullpath); 160 | 161 | //创建文件头部 162 | NSString* head = @"#EXTM3U\n#EXT-X-TARGETDURATION:30\n#EXT-X-VERSION:2\n#EXT-X-DISCONTINUITY\n"; 163 | 164 | NSString* segmentPrefix = [NSString stringWithFormat:@"http://127.0.0.1:12345/%@/",playlist.uuid]; 165 | //填充片段数据 166 | for(int i = 0;i< self.playlist.length;i++) 167 | { 168 | NSString* filename = [NSString stringWithFormat:@"id%d",i]; 169 | M3U8SegmentInfo* segInfo = [self.playlist getSegment:i]; 170 | NSString* length = [NSString stringWithFormat:@"#EXTINF:%ld,\n",(long)segInfo.duration]; 171 | NSString* url = [segmentPrefix stringByAppendingString:filename]; 172 | head = [NSString stringWithFormat:@"%@%@%@\n",head,length,url]; 173 | } 174 | //创建尾部 175 | NSString* end = @"#EXT-X-ENDLIST"; 176 | head = [head stringByAppendingString:end]; 177 | NSMutableData *writer = [[NSMutableData alloc] init]; 178 | [writer appendData:[head dataUsingEncoding:NSUTF8StringEncoding]]; 179 | 180 | BOOL bSucc =[writer writeToFile:fullpath atomically:YES]; 181 | if(bSucc) 182 | { 183 | NSLog(@"create m3u8file succeed; fullpath:%@, content:%@",fullpath,head); 184 | return fullpath; 185 | } 186 | else 187 | { 188 | NSLog(@"create m3u8file failed"); 189 | return nil; 190 | } 191 | } 192 | return nil; 193 | } 194 | 195 | 196 | 197 | @end 198 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/HTTPLogging.h: -------------------------------------------------------------------------------- 1 | /** 2 | * In order to provide fast and flexible logging, this project uses Cocoa Lumberjack. 3 | * 4 | * The Google Code page has a wealth of documentation if you have any questions. 5 | * http://code.google.com/p/cocoalumberjack/ 6 | * 7 | * Here's what you need to know concerning how logging is setup for CocoaHTTPServer: 8 | * 9 | * There are 4 log levels: 10 | * - Error 11 | * - Warning 12 | * - Info 13 | * - Verbose 14 | * 15 | * In addition to this, there is a Trace flag that can be enabled. 16 | * When tracing is enabled, it spits out the methods that are being called. 17 | * 18 | * Please note that tracing is separate from the log levels. 19 | * For example, one could set the log level to warning, and enable tracing. 20 | * 21 | * All logging is asynchronous, except errors. 22 | * To use logging within your own custom files, follow the steps below. 23 | * 24 | * Step 1: 25 | * Import this header in your implementation file: 26 | * 27 | * #import "HTTPLogging.h" 28 | * 29 | * Step 2: 30 | * Define your logging level in your implementation file: 31 | * 32 | * // Log levels: off, error, warn, info, verbose 33 | * static const int httpLogLevel = HTTP_LOG_LEVEL_VERBOSE; 34 | * 35 | * If you wish to enable tracing, you could do something like this: 36 | * 37 | * // Debug levels: off, error, warn, info, verbose 38 | * static const int httpLogLevel = HTTP_LOG_LEVEL_INFO | HTTP_LOG_FLAG_TRACE; 39 | * 40 | * Step 3: 41 | * Replace your NSLog statements with HTTPLog statements according to the severity of the message. 42 | * 43 | * NSLog(@"Fatal error, no dohickey found!"); -> HTTPLogError(@"Fatal error, no dohickey found!"); 44 | * 45 | * HTTPLog works exactly the same as NSLog. 46 | * This means you can pass it multiple variables just like NSLog. 47 | **/ 48 | 49 | #import "DDLog.h" 50 | 51 | // Define logging context for every log message coming from the HTTP server. 52 | // The logging context can be extracted from the DDLogMessage from within the logging framework, 53 | // which gives loggers, formatters, and filters the ability to optionally process them differently. 54 | 55 | #define HTTP_LOG_CONTEXT 80 56 | 57 | // Configure log levels. 58 | 59 | #define HTTP_LOG_FLAG_ERROR (1 << 0) // 0...00001 60 | #define HTTP_LOG_FLAG_WARN (1 << 1) // 0...00010 61 | #define HTTP_LOG_FLAG_INFO (1 << 2) // 0...00100 62 | #define HTTP_LOG_FLAG_VERBOSE (1 << 3) // 0...01000 63 | 64 | #define HTTP_LOG_LEVEL_OFF 0 // 0...00000 65 | #define HTTP_LOG_LEVEL_ERROR (HTTP_LOG_LEVEL_OFF | HTTP_LOG_FLAG_ERROR) // 0...00001 66 | #define HTTP_LOG_LEVEL_WARN (HTTP_LOG_LEVEL_ERROR | HTTP_LOG_FLAG_WARN) // 0...00011 67 | #define HTTP_LOG_LEVEL_INFO (HTTP_LOG_LEVEL_WARN | HTTP_LOG_FLAG_INFO) // 0...00111 68 | #define HTTP_LOG_LEVEL_VERBOSE (HTTP_LOG_LEVEL_INFO | HTTP_LOG_FLAG_VERBOSE) // 0...01111 69 | 70 | // Setup fine grained logging. 71 | // The first 4 bits are being used by the standard log levels (0 - 3) 72 | // 73 | // We're going to add tracing, but NOT as a log level. 74 | // Tracing can be turned on and off independently of log level. 75 | 76 | #define HTTP_LOG_FLAG_TRACE (1 << 4) // 0...10000 77 | 78 | // Setup the usual boolean macros. 79 | 80 | #define HTTP_LOG_ERROR (httpLogLevel & HTTP_LOG_FLAG_ERROR) 81 | #define HTTP_LOG_WARN (httpLogLevel & HTTP_LOG_FLAG_WARN) 82 | #define HTTP_LOG_INFO (httpLogLevel & HTTP_LOG_FLAG_INFO) 83 | #define HTTP_LOG_VERBOSE (httpLogLevel & HTTP_LOG_FLAG_VERBOSE) 84 | #define HTTP_LOG_TRACE (httpLogLevel & HTTP_LOG_FLAG_TRACE) 85 | 86 | // Configure asynchronous logging. 87 | // We follow the default configuration, 88 | // but we reserve a special macro to easily disable asynchronous logging for debugging purposes. 89 | 90 | #define HTTP_LOG_ASYNC_ENABLED YES 91 | 92 | #define HTTP_LOG_ASYNC_ERROR ( NO && HTTP_LOG_ASYNC_ENABLED) 93 | #define HTTP_LOG_ASYNC_WARN (YES && HTTP_LOG_ASYNC_ENABLED) 94 | #define HTTP_LOG_ASYNC_INFO (YES && HTTP_LOG_ASYNC_ENABLED) 95 | #define HTTP_LOG_ASYNC_VERBOSE (YES && HTTP_LOG_ASYNC_ENABLED) 96 | #define HTTP_LOG_ASYNC_TRACE (YES && HTTP_LOG_ASYNC_ENABLED) 97 | 98 | // Define logging primitives. 99 | 100 | #define HTTPLogError(frmt, ...) LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_ERROR, httpLogLevel, HTTP_LOG_FLAG_ERROR, \ 101 | HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) 102 | 103 | #define HTTPLogWarn(frmt, ...) LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_WARN, httpLogLevel, HTTP_LOG_FLAG_WARN, \ 104 | HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) 105 | 106 | #define HTTPLogInfo(frmt, ...) LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_INFO, httpLogLevel, HTTP_LOG_FLAG_INFO, \ 107 | HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) 108 | 109 | #define HTTPLogVerbose(frmt, ...) LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_VERBOSE, httpLogLevel, HTTP_LOG_FLAG_VERBOSE, \ 110 | HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) 111 | 112 | #define HTTPLogTrace() LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_TRACE, httpLogLevel, HTTP_LOG_FLAG_TRACE, \ 113 | HTTP_LOG_CONTEXT, @"%@[%p]: %@", THIS_FILE, self, THIS_METHOD) 114 | 115 | #define HTTPLogTrace2(frmt, ...) LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_TRACE, httpLogLevel, HTTP_LOG_FLAG_TRACE, \ 116 | HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) 117 | 118 | 119 | #define HTTPLogCError(frmt, ...) LOG_C_MAYBE(HTTP_LOG_ASYNC_ERROR, httpLogLevel, HTTP_LOG_FLAG_ERROR, \ 120 | HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) 121 | 122 | #define HTTPLogCWarn(frmt, ...) LOG_C_MAYBE(HTTP_LOG_ASYNC_WARN, httpLogLevel, HTTP_LOG_FLAG_WARN, \ 123 | HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) 124 | 125 | #define HTTPLogCInfo(frmt, ...) LOG_C_MAYBE(HTTP_LOG_ASYNC_INFO, httpLogLevel, HTTP_LOG_FLAG_INFO, \ 126 | HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) 127 | 128 | #define HTTPLogCVerbose(frmt, ...) LOG_C_MAYBE(HTTP_LOG_ASYNC_VERBOSE, httpLogLevel, HTTP_LOG_FLAG_VERBOSE, \ 129 | HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) 130 | 131 | #define HTTPLogCTrace() LOG_C_MAYBE(HTTP_LOG_ASYNC_TRACE, httpLogLevel, HTTP_LOG_FLAG_TRACE, \ 132 | HTTP_LOG_CONTEXT, @"%@[%p]: %@", THIS_FILE, self, __FUNCTION__) 133 | 134 | #define HTTPLogCTrace2(frmt, ...) LOG_C_MAYBE(HTTP_LOG_ASYNC_TRACE, httpLogLevel, HTTP_LOG_FLAG_TRACE, \ 135 | HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) 136 | 137 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/HTTPAuthenticationRequest.m: -------------------------------------------------------------------------------- 1 | #import "HTTPAuthenticationRequest.h" 2 | #import "HTTPMessage.h" 3 | 4 | @interface HTTPAuthenticationRequest (PrivateAPI) 5 | - (NSString *)quotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header; 6 | - (NSString *)nonquotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header; 7 | @end 8 | 9 | 10 | @implementation HTTPAuthenticationRequest 11 | 12 | - (id)initWithRequest:(HTTPMessage *)request 13 | { 14 | if ((self = [super init])) 15 | { 16 | NSString *authInfo = [request headerField:@"Authorization"]; 17 | 18 | isBasic = NO; 19 | if ([authInfo length] >= 6) 20 | { 21 | isBasic = [[authInfo substringToIndex:6] caseInsensitiveCompare:@"Basic "] == NSOrderedSame; 22 | } 23 | 24 | isDigest = NO; 25 | if ([authInfo length] >= 7) 26 | { 27 | isDigest = [[authInfo substringToIndex:7] caseInsensitiveCompare:@"Digest "] == NSOrderedSame; 28 | } 29 | 30 | if (isBasic) 31 | { 32 | NSMutableString *temp = [[[authInfo substringFromIndex:6] mutableCopy] autorelease]; 33 | CFStringTrimWhitespace((CFMutableStringRef)temp); 34 | 35 | base64Credentials = [temp copy]; 36 | } 37 | 38 | if (isDigest) 39 | { 40 | username = [[self quotedSubHeaderFieldValue:@"username" fromHeaderFieldValue:authInfo] retain]; 41 | realm = [[self quotedSubHeaderFieldValue:@"realm" fromHeaderFieldValue:authInfo] retain]; 42 | nonce = [[self quotedSubHeaderFieldValue:@"nonce" fromHeaderFieldValue:authInfo] retain]; 43 | uri = [[self quotedSubHeaderFieldValue:@"uri" fromHeaderFieldValue:authInfo] retain]; 44 | 45 | // It appears from RFC 2617 that the qop is to be given unquoted 46 | // Tests show that Firefox performs this way, but Safari does not 47 | // Thus we'll attempt to retrieve the value as nonquoted, but we'll verify it doesn't start with a quote 48 | qop = [self nonquotedSubHeaderFieldValue:@"qop" fromHeaderFieldValue:authInfo]; 49 | if(qop && ([qop characterAtIndex:0] == '"')) 50 | { 51 | qop = [self quotedSubHeaderFieldValue:@"qop" fromHeaderFieldValue:authInfo]; 52 | } 53 | [qop retain]; 54 | 55 | nc = [[self nonquotedSubHeaderFieldValue:@"nc" fromHeaderFieldValue:authInfo] retain]; 56 | cnonce = [[self quotedSubHeaderFieldValue:@"cnonce" fromHeaderFieldValue:authInfo] retain]; 57 | response = [[self quotedSubHeaderFieldValue:@"response" fromHeaderFieldValue:authInfo] retain]; 58 | } 59 | } 60 | return self; 61 | } 62 | 63 | - (void)dealloc 64 | { 65 | [base64Credentials release]; 66 | [username release]; 67 | [realm release]; 68 | [nonce release]; 69 | [uri release]; 70 | [qop release]; 71 | [nc release]; 72 | [cnonce release]; 73 | [response release]; 74 | [super dealloc]; 75 | } 76 | 77 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 78 | #pragma mark Accessors: 79 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 80 | 81 | - (BOOL)isBasic { 82 | return isBasic; 83 | } 84 | 85 | - (BOOL)isDigest { 86 | return isDigest; 87 | } 88 | 89 | - (NSString *)base64Credentials { 90 | return base64Credentials; 91 | } 92 | 93 | - (NSString *)username { 94 | return username; 95 | } 96 | 97 | - (NSString *)realm { 98 | return realm; 99 | } 100 | 101 | - (NSString *)nonce { 102 | return nonce; 103 | } 104 | 105 | - (NSString *)uri { 106 | return uri; 107 | } 108 | 109 | - (NSString *)qop { 110 | return qop; 111 | } 112 | 113 | - (NSString *)nc { 114 | return nc; 115 | } 116 | 117 | - (NSString *)cnonce { 118 | return cnonce; 119 | } 120 | 121 | - (NSString *)response { 122 | return response; 123 | } 124 | 125 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 126 | #pragma mark Private API: 127 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 128 | 129 | /** 130 | * Retrieves a "Sub Header Field Value" from a given header field value. 131 | * The sub header field is expected to be quoted. 132 | * 133 | * In the following header field: 134 | * Authorization: Digest username="Mufasa", qop=auth, response="6629fae4939" 135 | * The sub header field titled 'username' is quoted, and this method would return the value @"Mufasa". 136 | **/ 137 | - (NSString *)quotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header 138 | { 139 | NSRange startRange = [header rangeOfString:[NSString stringWithFormat:@"%@=\"", param]]; 140 | if(startRange.location == NSNotFound) 141 | { 142 | // The param was not found anywhere in the header 143 | return nil; 144 | } 145 | 146 | NSUInteger postStartRangeLocation = startRange.location + startRange.length; 147 | NSUInteger postStartRangeLength = [header length] - postStartRangeLocation; 148 | NSRange postStartRange = NSMakeRange(postStartRangeLocation, postStartRangeLength); 149 | 150 | NSRange endRange = [header rangeOfString:@"\"" options:0 range:postStartRange]; 151 | if(endRange.location == NSNotFound) 152 | { 153 | // The ending double-quote was not found anywhere in the header 154 | return nil; 155 | } 156 | 157 | NSRange subHeaderRange = NSMakeRange(postStartRangeLocation, endRange.location - postStartRangeLocation); 158 | return [header substringWithRange:subHeaderRange]; 159 | } 160 | 161 | /** 162 | * Retrieves a "Sub Header Field Value" from a given header field value. 163 | * The sub header field is expected to not be quoted. 164 | * 165 | * In the following header field: 166 | * Authorization: Digest username="Mufasa", qop=auth, response="6629fae4939" 167 | * The sub header field titled 'qop' is nonquoted, and this method would return the value @"auth". 168 | **/ 169 | - (NSString *)nonquotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header 170 | { 171 | NSRange startRange = [header rangeOfString:[NSString stringWithFormat:@"%@=", param]]; 172 | if(startRange.location == NSNotFound) 173 | { 174 | // The param was not found anywhere in the header 175 | return nil; 176 | } 177 | 178 | NSUInteger postStartRangeLocation = startRange.location + startRange.length; 179 | NSUInteger postStartRangeLength = [header length] - postStartRangeLocation; 180 | NSRange postStartRange = NSMakeRange(postStartRangeLocation, postStartRangeLength); 181 | 182 | NSRange endRange = [header rangeOfString:@"," options:0 range:postStartRange]; 183 | if(endRange.location == NSNotFound) 184 | { 185 | // The ending comma was not found anywhere in the header 186 | // However, if the nonquoted param is at the end of the string, there would be no comma 187 | // This is only possible if there are no spaces anywhere 188 | NSRange endRange2 = [header rangeOfString:@" " options:0 range:postStartRange]; 189 | if(endRange2.location != NSNotFound) 190 | { 191 | return nil; 192 | } 193 | else 194 | { 195 | return [header substringWithRange:postStartRange]; 196 | } 197 | } 198 | else 199 | { 200 | NSRange subHeaderRange = NSMakeRange(postStartRangeLocation, endRange.location - postStartRangeLocation); 201 | return [header substringWithRange:subHeaderRange]; 202 | } 203 | } 204 | 205 | @end 206 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/HTTPServer.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class GCDAsyncSocket; 4 | @class WebSocket; 5 | 6 | #if TARGET_OS_IPHONE 7 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000 // iPhone 4.0 8 | #define IMPLEMENTED_PROTOCOLS 9 | #else 10 | #define IMPLEMENTED_PROTOCOLS 11 | #endif 12 | #else 13 | #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 // Mac OS X 10.6 14 | #define IMPLEMENTED_PROTOCOLS 15 | #else 16 | #define IMPLEMENTED_PROTOCOLS 17 | #endif 18 | #endif 19 | 20 | 21 | @interface HTTPServer : NSObject IMPLEMENTED_PROTOCOLS 22 | { 23 | // Underlying asynchronous TCP/IP socket 24 | dispatch_queue_t serverQueue; 25 | dispatch_queue_t connectionQueue; 26 | GCDAsyncSocket *asyncSocket; 27 | 28 | // HTTP server configuration 29 | NSString *documentRoot; 30 | Class connectionClass; 31 | NSString *interface; 32 | UInt16 port; 33 | 34 | // NSNetService and related variables 35 | NSNetService *netService; 36 | NSString *domain; 37 | NSString *type; 38 | NSString *name; 39 | NSString *publishedName; 40 | NSDictionary *txtRecordDictionary; 41 | 42 | // Connection management 43 | NSMutableArray *connections; 44 | NSMutableArray *webSockets; 45 | NSLock *connectionsLock; 46 | NSLock *webSocketsLock; 47 | 48 | BOOL isRunning; 49 | } 50 | 51 | /** 52 | * Specifies the document root to serve files from. 53 | * For example, if you set this to "/Users//Sites", 54 | * then it will serve files out of the local Sites directory (including subdirectories). 55 | * 56 | * The default value is nil. 57 | * The default server configuration will not serve any files until this is set. 58 | * 59 | * If you change the documentRoot while the server is running, 60 | * the change will affect future incoming http connections. 61 | **/ 62 | - (NSString *)documentRoot; 63 | - (void)setDocumentRoot:(NSString *)value; 64 | 65 | /** 66 | * The connection class is the class used to handle incoming HTTP connections. 67 | * 68 | * The default value is [HTTPConnection class]. 69 | * You can override HTTPConnection, and then set this to [MyHTTPConnection class]. 70 | * 71 | * If you change the connectionClass while the server is running, 72 | * the change will affect future incoming http connections. 73 | **/ 74 | - (Class)connectionClass; 75 | - (void)setConnectionClass:(Class)value; 76 | 77 | /** 78 | * Set what interface you'd like the server to listen on. 79 | * By default this is nil, which causes the server to listen on all available interfaces like en1, wifi etc. 80 | * 81 | * The interface may be specified by name (e.g. "en1" or "lo0") or by IP address (e.g. "192.168.4.34"). 82 | * You may also use the special strings "localhost" or "loopback" to specify that 83 | * the socket only accept connections from the local machine. 84 | **/ 85 | - (NSString *)interface; 86 | - (void)setInterface:(NSString *)value; 87 | 88 | /** 89 | * The port number to run the HTTP server on. 90 | * 91 | * The default port number is zero, meaning the server will automatically use any available port. 92 | * This is the recommended port value, as it avoids possible port conflicts with other applications. 93 | * Technologies such as Bonjour can be used to allow other applications to automatically discover the port number. 94 | * 95 | * Note: As is common on most OS's, you need root privledges to bind to port numbers below 1024. 96 | * 97 | * You can change the port property while the server is running, but it won't affect the running server. 98 | * To actually change the port the server is listening for connections on you'll need to restart the server. 99 | * 100 | * The listeningPort method will always return the port number the running server is listening for connections on. 101 | * If the server is not running this method returns 0. 102 | **/ 103 | - (UInt16)port; 104 | - (UInt16)listeningPort; 105 | - (void)setPort:(UInt16)value; 106 | 107 | /** 108 | * Bonjour domain for publishing the service. 109 | * The default value is "local.". 110 | * 111 | * Note: Bonjour publishing requires you set a type. 112 | * 113 | * If you change the domain property after the bonjour service has already been published (server already started), 114 | * you'll need to invoke the republishBonjour method to update the broadcasted bonjour service. 115 | **/ 116 | - (NSString *)domain; 117 | - (void)setDomain:(NSString *)value; 118 | 119 | /** 120 | * Bonjour name for publishing the service. 121 | * The default value is "". 122 | * 123 | * If using an empty string ("") for the service name when registering, 124 | * the system will automatically use the "Computer Name". 125 | * Using an empty string will also handle name conflicts 126 | * by automatically appending a digit to the end of the name. 127 | * 128 | * Note: Bonjour publishing requires you set a type. 129 | * 130 | * If you change the name after the bonjour service has already been published (server already started), 131 | * you'll need to invoke the republishBonjour method to update the broadcasted bonjour service. 132 | * 133 | * The publishedName method will always return the actual name that was published via the bonjour service. 134 | * If the service is not running this method returns nil. 135 | **/ 136 | - (NSString *)name; 137 | - (NSString *)publishedName; 138 | - (void)setName:(NSString *)value; 139 | 140 | /** 141 | * Bonjour type for publishing the service. 142 | * The default value is nil. 143 | * The service will not be published via bonjour unless the type is set. 144 | * 145 | * If you wish to publish the service as a traditional HTTP server, you should set the type to be "_http._tcp.". 146 | * 147 | * If you change the type after the bonjour service has already been published (server already started), 148 | * you'll need to invoke the republishBonjour method to update the broadcasted bonjour service. 149 | **/ 150 | - (NSString *)type; 151 | - (void)setType:(NSString *)value; 152 | 153 | /** 154 | * Republishes the service via bonjour if the server is running. 155 | * If the service was not previously published, this method will publish it (if the server is running). 156 | **/ 157 | - (void)republishBonjour; 158 | 159 | /** 160 | * 161 | **/ 162 | - (NSDictionary *)TXTRecordDictionary; 163 | - (void)setTXTRecordDictionary:(NSDictionary *)dict; 164 | 165 | /** 166 | * Attempts to starts the server on the configured port, interface, etc. 167 | * 168 | * If an error occurs, this method returns NO and sets the errPtr (if given). 169 | * Otherwise returns YES on success. 170 | * 171 | * Some examples of errors that might occur: 172 | * - You specified the server listen on a port which is already in use by another application. 173 | * - You specified the server listen on a port number below 1024, which requires root priviledges. 174 | * 175 | * Code Example: 176 | * 177 | * NSError *err = nil; 178 | * if (![httpServer start:&err]) 179 | * { 180 | * NSLog(@"Error starting http server: %@", err); 181 | * } 182 | **/ 183 | - (BOOL)start:(NSError **)errPtr; 184 | 185 | /** 186 | * Stops the server, preventing it from accepting any new connections. 187 | * You may specify whether or not you want to close the existing client connections. 188 | * 189 | * The default stop method (with no arguments) will close any existing connections. (It invokes [self stop:NO]) 190 | **/ 191 | - (void)stop; 192 | - (void)stop:(BOOL)keepExistingConnections; 193 | 194 | - (BOOL)isRunning; 195 | 196 | - (void)addWebSocket:(WebSocket *)ws; 197 | 198 | - (NSUInteger)numberOfHTTPConnections; 199 | - (NSUInteger)numberOfWebSocketConnections; 200 | 201 | @end 202 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/HTTPResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @protocol HTTPResponse 5 | 6 | /** 7 | * Returns the length of the data in bytes. 8 | * If you don't know the length in advance, implement the isChunked method and have it return YES. 9 | **/ 10 | - (UInt64)contentLength; 11 | 12 | /** 13 | * The HTTP server supports range requests in order to allow things like 14 | * file download resumption and optimized streaming on mobile devices. 15 | **/ 16 | - (UInt64)offset; 17 | - (void)setOffset:(UInt64)offset; 18 | 19 | /** 20 | * Returns the data for the response. 21 | * You do not have to return data of the exact length that is given. 22 | * You may optionally return data of a lesser length. 23 | * However, you must never return data of a greater length than requested. 24 | * Doing so could disrupt proper support for range requests. 25 | * 26 | * To support asynchronous responses, read the discussion at the bottom of this header. 27 | **/ 28 | - (NSData *)readDataOfLength:(NSUInteger)length; 29 | 30 | /** 31 | * Should only return YES after the HTTPConnection has read all available data. 32 | * That is, all data for the response has been returned to the HTTPConnection via the readDataOfLength method. 33 | **/ 34 | - (BOOL)isDone; 35 | 36 | @optional 37 | 38 | /** 39 | * If you need time to calculate any part of the HTTP response headers (status code or header fields), 40 | * this method allows you to delay sending the headers so that you may asynchronously execute the calculations. 41 | * Simply implement this method and return YES until you have everything you need concerning the headers. 42 | * 43 | * This method ties into the asynchronous response architecture of the HTTPConnection. 44 | * You should read the full discussion at the bottom of this header. 45 | * 46 | * If you return YES from this method, 47 | * the HTTPConnection will wait for you to invoke the responseHasAvailableData method. 48 | * After you do, the HTTPConnection will again invoke this method to see if the response is ready to send the headers. 49 | * 50 | * You should only delay sending the headers until you have everything you need concerning just the headers. 51 | * Asynchronously generating the body of the response is not an excuse to delay sending the headers. 52 | * Instead you should tie into the asynchronous response architecture, and use techniques such as the isChunked method. 53 | * 54 | * Important: You should read the discussion at the bottom of this header. 55 | **/ 56 | - (BOOL)delayResponeHeaders; 57 | 58 | /** 59 | * Status code for response. 60 | * Allows for responses such as redirect (301), etc. 61 | **/ 62 | - (NSInteger)status; 63 | 64 | /** 65 | * If you want to add any extra HTTP headers to the response, 66 | * simply return them in a dictionary in this method. 67 | **/ 68 | - (NSDictionary *)httpHeaders; 69 | 70 | /** 71 | * If you don't know the content-length in advance, 72 | * implement this method in your custom response class and return YES. 73 | * 74 | * Important: You should read the discussion at the bottom of this header. 75 | **/ 76 | - (BOOL)isChunked; 77 | 78 | /** 79 | * This method is called from the HTTPConnection class when the connection is closed, 80 | * or when the connection is finished with the response. 81 | * If your response is asynchronous, you should implement this method so you know not to 82 | * invoke any methods on the HTTPConnection after this method is called (as the connection may be deallocated). 83 | **/ 84 | - (void)connectionDidClose; 85 | 86 | @end 87 | 88 | 89 | /** 90 | * Important notice to those implementing custom asynchronous and/or chunked responses: 91 | * 92 | * HTTPConnection supports asynchronous responses. All you have to do in your custom response class is 93 | * asynchronously generate the response, and invoke HTTPConnection's responseHasAvailableData method. 94 | * You don't have to wait until you have all of the response ready to invoke this method. For example, if you 95 | * generate the response in incremental chunks, you could call responseHasAvailableData after generating 96 | * each chunk. Please see the HTTPAsyncFileResponse class for an example of how to do this. 97 | * 98 | * The normal flow of events for an HTTPConnection while responding to a request is like this: 99 | * - Send http resopnse headers 100 | * - Get data from response via readDataOfLength method. 101 | * - Add data to asyncSocket's write queue. 102 | * - Wait for asyncSocket to notify it that the data has been sent. 103 | * - Get more data from response via readDataOfLength method. 104 | * - ... continue this cycle until the entire response has been sent. 105 | * 106 | * With an asynchronous response, the flow is a little different. 107 | * 108 | * First the HTTPResponse is given the opportunity to postpone sending the HTTP response headers. 109 | * This allows the response to asynchronously execute any code needed to calculate a part of the header. 110 | * An example might be the response needs to generate some custom header fields, 111 | * or perhaps the response needs to look for a resource on network-attached storage. 112 | * Since the network-attached storage may be slow, the response doesn't know whether to send a 200 or 404 yet. 113 | * In situations such as this, the HTTPResponse simply implements the delayResponseHeaders method and returns YES. 114 | * After returning YES from this method, the HTTPConnection will wait until the response invokes its 115 | * responseHasAvailableData method. After this occurs, the HTTPConnection will again query the delayResponseHeaders 116 | * method to see if the response is ready to send the headers. 117 | * This cycle will continue until the delayResponseHeaders method returns NO. 118 | * 119 | * You should only delay sending the response headers until you have everything you need concerning just the headers. 120 | * Asynchronously generating the body of the response is not an excuse to delay sending the headers. 121 | * 122 | * After the response headers have been sent, the HTTPConnection calls your readDataOfLength method. 123 | * You may or may not have any available data at this point. If you don't, then simply return nil. 124 | * You should later invoke HTTPConnection's responseHasAvailableData when you have data to send. 125 | * 126 | * You don't have to keep track of when you return nil in the readDataOfLength method, or how many times you've invoked 127 | * responseHasAvailableData. Just simply call responseHasAvailableData whenever you've generated new data, and 128 | * return nil in your readDataOfLength whenever you don't have any available data in the requested range. 129 | * HTTPConnection will automatically detect when it should be requesting new data and will act appropriately. 130 | * 131 | * It's important that you also keep in mind that the HTTP server supports range requests. 132 | * The setOffset method is mandatory, and should not be ignored. 133 | * Make sure you take into account the offset within the readDataOfLength method. 134 | * You should also be aware that the HTTPConnection automatically sorts any range requests. 135 | * So if your setOffset method is called with a value of 100, then you can safely release bytes 0-99. 136 | * 137 | * HTTPConnection can also help you keep your memory footprint small. 138 | * Imagine you're dynamically generating a 10 MB response. You probably don't want to load all this data into 139 | * RAM, and sit around waiting for HTTPConnection to slowly send it out over the network. All you need to do 140 | * is pay attention to when HTTPConnection requests more data via readDataOfLength. This is because HTTPConnection 141 | * will never allow asyncSocket's write queue to get much bigger than READ_CHUNKSIZE bytes. You should 142 | * consider how you might be able to take advantage of this fact to generate your asynchronous response on demand, 143 | * while at the same time keeping your memory footprint small, and your application lightning fast. 144 | * 145 | * If you don't know the content-length in advanced, you should also implement the isChunked method. 146 | * This means the response will not include a Content-Length header, and will instead use "Transfer-Encoding: chunked". 147 | * There's a good chance that if your response is asynchronous and dynamic, it's also chunked. 148 | * If your response is chunked, you don't need to worry about range requests. 149 | **/ 150 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/Responses/HTTPDynamicFileResponse.m: -------------------------------------------------------------------------------- 1 | #import "HTTPDynamicFileResponse.h" 2 | #import "HTTPConnection.h" 3 | #import "HTTPLogging.h" 4 | 5 | // Log levels : off, error, warn, info, verbose 6 | // Other flags: trace 7 | static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE; 8 | 9 | #define NULL_FD -1 10 | 11 | 12 | @implementation HTTPDynamicFileResponse 13 | 14 | - (id)initWithFilePath:(NSString *)fpath 15 | forConnection:(HTTPConnection *)parent 16 | separator:(NSString *)separatorStr 17 | replacementDictionary:(NSDictionary *)dict 18 | { 19 | if ((self = [super initWithFilePath:fpath forConnection:parent])) 20 | { 21 | HTTPLogTrace(); 22 | 23 | separator = [[separatorStr dataUsingEncoding:NSUTF8StringEncoding] retain]; 24 | replacementDict = [dict retain]; 25 | } 26 | return self; 27 | } 28 | 29 | - (BOOL)isChunked 30 | { 31 | HTTPLogTrace(); 32 | 33 | return YES; 34 | } 35 | 36 | - (UInt64)contentLength 37 | { 38 | // This method shouldn't be called since we're using a chunked response. 39 | // We override it just to be safe. 40 | 41 | HTTPLogTrace(); 42 | 43 | return 0; 44 | } 45 | 46 | - (void)setOffset:(UInt64)offset 47 | { 48 | // This method shouldn't be called since we're using a chunked response. 49 | // We override it just to be safe. 50 | 51 | HTTPLogTrace(); 52 | } 53 | 54 | - (BOOL)isDone 55 | { 56 | BOOL result = (readOffset == fileLength) && (readBufferOffset == 0); 57 | 58 | HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO")); 59 | 60 | return result; 61 | } 62 | 63 | - (void)processReadBuffer 64 | { 65 | HTTPLogTrace(); 66 | 67 | // At this point, the readBuffer has readBufferOffset bytes available. 68 | // This method is in charge of updating the readBufferOffset. 69 | 70 | NSUInteger bufLen = readBufferOffset; 71 | NSUInteger sepLen = [separator length]; 72 | 73 | // We're going to start looking for the separator at the beginning of the buffer, 74 | // and stop when we get to the point where the separator would no longer fit in the buffer. 75 | 76 | NSUInteger offset = 0; 77 | NSUInteger stopOffset = (bufLen > sepLen) ? bufLen - sepLen + 1 : 0; 78 | 79 | // In order to do the replacement, we need to find the starting and ending separator. 80 | // For example: 81 | // 82 | // %%USER_NAME%% 83 | // 84 | // Where "%%" is the separator. 85 | 86 | BOOL found1 = NO; 87 | BOOL found2 = NO; 88 | 89 | NSUInteger s1 = 0; 90 | NSUInteger s2 = 0; 91 | 92 | const void *sep = [separator bytes]; 93 | 94 | while (offset < stopOffset) 95 | { 96 | const void *subBuffer = readBuffer + offset; 97 | 98 | if (memcmp(subBuffer, sep, sepLen) == 0) 99 | { 100 | if (!found1) 101 | { 102 | // Found the first separator 103 | 104 | found1 = YES; 105 | s1 = offset; 106 | offset += sepLen; 107 | 108 | HTTPLogVerbose(@"%@[%p]: Found s1 at %lu", THIS_FILE, self, (unsigned long)s1); 109 | } 110 | else 111 | { 112 | // Found the second separator 113 | 114 | found2 = YES; 115 | s2 = offset; 116 | offset += sepLen; 117 | 118 | HTTPLogVerbose(@"%@[%p]: Found s2 at %lu", THIS_FILE, self, (unsigned long)s2); 119 | } 120 | 121 | if (found1 && found2) 122 | { 123 | // We found our separators. 124 | // Now extract the string between the two separators. 125 | 126 | NSRange fullRange = NSMakeRange(s1, (s2 - s1 + sepLen)); 127 | NSRange strRange = NSMakeRange(s1 + sepLen, (s2 - s1 - sepLen)); 128 | 129 | // Wish we could use the simple subdataWithRange method. 130 | // But that method copies the bytes... 131 | // So for performance reasons, we need to use the methods that don't copy the bytes. 132 | 133 | void *strBuf = readBuffer + strRange.location; 134 | NSUInteger strLen = strRange.length; 135 | 136 | NSString *key = [[NSString alloc] initWithBytes:strBuf length:strLen encoding:NSUTF8StringEncoding]; 137 | if (key) 138 | { 139 | // Is there a given replacement for this key? 140 | 141 | id value = [replacementDict objectForKey:key]; 142 | if (value) 143 | { 144 | // Found the replacement value. 145 | // Now perform the replacement in the buffer. 146 | 147 | HTTPLogVerbose(@"%@[%p]: key(%@) -> value(%@)", THIS_FILE, self, key, value); 148 | 149 | NSData *v = [[value description] dataUsingEncoding:NSUTF8StringEncoding]; 150 | NSUInteger vLength = [v length]; 151 | 152 | if (fullRange.length == vLength) 153 | { 154 | // Replacement is exactly the same size as what it is replacing 155 | 156 | // memcpy(void *restrict dst, const void *restrict src, size_t n); 157 | 158 | memcpy(readBuffer + fullRange.location, [v bytes], vLength); 159 | } 160 | else // (fullRange.length != vLength) 161 | { 162 | NSInteger diff = (NSInteger)vLength - (NSInteger)fullRange.length; 163 | 164 | if (diff > 0) 165 | { 166 | // Replacement is bigger than what it is replacing. 167 | // Make sure there is room in the buffer for the replacement. 168 | 169 | if (diff > (readBufferSize - bufLen)) 170 | { 171 | NSUInteger inc = MAX(diff, 256); 172 | 173 | readBufferSize += inc; 174 | readBuffer = reallocf(readBuffer, readBufferSize); 175 | } 176 | } 177 | 178 | // Move the data that comes after the replacement. 179 | // 180 | // If replacement is smaller than what it is replacing, 181 | // then we are shifting the data toward the beginning of the buffer. 182 | // 183 | // If replacement is bigger than what it is replacing, 184 | // then we are shifting the data toward the end of the buffer. 185 | // 186 | // memmove(void *dst, const void *src, size_t n); 187 | // 188 | // The memmove() function copies n bytes from src to dst. 189 | // The two areas may overlap; the copy is always done in a non-destructive manner. 190 | 191 | void *src = readBuffer + fullRange.location + fullRange.length; 192 | void *dst = readBuffer + fullRange.location + vLength; 193 | 194 | NSUInteger remaining = bufLen - (fullRange.location + fullRange.length); 195 | 196 | memmove(dst, src, remaining); 197 | 198 | // Now copy the replacement into its location. 199 | // 200 | // memcpy(void *restrict dst, const void *restrict src, size_t n) 201 | // 202 | // The memcpy() function copies n bytes from src to dst. 203 | // If the two areas overlap, behavior is undefined. 204 | 205 | memcpy(readBuffer + fullRange.location, [v bytes], vLength); 206 | 207 | // And don't forget to update our indices. 208 | 209 | bufLen += diff; 210 | offset += diff; 211 | stopOffset += diff; 212 | } 213 | } 214 | 215 | [key release]; 216 | } 217 | 218 | found1 = found2 = NO; 219 | } 220 | } 221 | else 222 | { 223 | offset++; 224 | } 225 | } 226 | 227 | // We've gone through our buffer now, and performed all the replacements that we could. 228 | // It's now time to update the amount of available data we have. 229 | 230 | if (readOffset == fileLength) 231 | { 232 | // We've read in the entire file. 233 | // So there can be no more replacements. 234 | 235 | data = [[NSData alloc] initWithBytes:readBuffer length:bufLen]; 236 | readBufferOffset = 0; 237 | } 238 | else 239 | { 240 | // There are a couple different situations that we need to take into account here. 241 | // 242 | // Imagine the following file: 243 | // My name is %%USER_NAME%% 244 | // 245 | // Situation 1: 246 | // The first chunk of data we read was "My name is %%". 247 | // So we found the first separator, but not the second. 248 | // In this case we can only return the data that precedes the first separator. 249 | // 250 | // Situation 2: 251 | // The first chunk of data we read was "My name is %". 252 | // So we didn't find any separators, but part of a separator may be included in our buffer. 253 | 254 | NSUInteger available; 255 | if (found1) 256 | { 257 | // Situation 1 258 | available = s1; 259 | } 260 | else 261 | { 262 | // Situation 2 263 | available = stopOffset; 264 | } 265 | 266 | // Copy available data 267 | 268 | data = [[NSData alloc] initWithBytes:readBuffer length:available]; 269 | 270 | // Remove the copied data from the buffer. 271 | // We do this by shifting the remaining data toward the beginning of the buffer. 272 | 273 | NSUInteger remaining = bufLen - available; 274 | 275 | memmove(readBuffer, readBuffer + available, remaining); 276 | readBufferOffset = remaining; 277 | } 278 | 279 | [connection responseHasAvailableData:self]; 280 | } 281 | 282 | - (void)dealloc 283 | { 284 | HTTPLogTrace(); 285 | 286 | [separator release]; 287 | [replacementDict release]; 288 | 289 | [super dealloc]; 290 | } 291 | 292 | @end 293 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/Responses/HTTPAsyncFileResponse.m: -------------------------------------------------------------------------------- 1 | #import "HTTPAsyncFileResponse.h" 2 | #import "HTTPConnection.h" 3 | #import "HTTPLogging.h" 4 | 5 | #import 6 | #import 7 | 8 | // Log levels : off, error, warn, info, verbose 9 | // Other flags: trace 10 | static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE; 11 | 12 | #define NULL_FD -1 13 | 14 | /** 15 | * Architecure overview: 16 | * 17 | * HTTPConnection will invoke our readDataOfLength: method to fetch data. 18 | * We will return nil, and then proceed to read the data via our readSource on our readQueue. 19 | * Once the requested amount of data has been read, we then pause our readSource, 20 | * and inform the connection of the available data. 21 | * 22 | * While our read is in progress, we don't have to worry about the connection calling any other methods, 23 | * except the connectionDidClose method, which would be invoked if the remote end closed the socket connection. 24 | * To safely handle this, we do a synchronous dispatch on the readQueue, 25 | * and nilify the connection as well as cancel our readSource. 26 | * 27 | * In order to minimize resource consumption during a HEAD request, 28 | * we don't open the file until we have to (until the connection starts requesting data). 29 | **/ 30 | 31 | @implementation HTTPAsyncFileResponse 32 | 33 | - (id)initWithFilePath:(NSString *)fpath forConnection:(HTTPConnection *)parent 34 | { 35 | if ((self = [super init])) 36 | { 37 | HTTPLogTrace(); 38 | 39 | connection = parent; // Parents retain children, children do NOT retain parents 40 | 41 | fileFD = NULL_FD; 42 | filePath = [fpath copy]; 43 | if (filePath == nil) 44 | { 45 | HTTPLogWarn(@"%@: Init failed - Nil filePath", THIS_FILE); 46 | 47 | [self release]; 48 | return nil; 49 | } 50 | 51 | NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL]; 52 | if (fileAttributes == nil) 53 | { 54 | HTTPLogWarn(@"%@: Init failed - Unable to get file attributes. filePath: %@", THIS_FILE, filePath); 55 | 56 | [self release]; 57 | return nil; 58 | } 59 | 60 | fileLength = (UInt64)[[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue]; 61 | fileOffset = 0; 62 | 63 | aborted = NO; 64 | 65 | // We don't bother opening the file here. 66 | // If this is a HEAD request we only need to know the fileLength. 67 | } 68 | return self; 69 | } 70 | 71 | - (void)abort 72 | { 73 | HTTPLogTrace(); 74 | 75 | [connection responseDidAbort:self]; 76 | aborted = YES; 77 | } 78 | 79 | - (void)processReadBuffer 80 | { 81 | // This method is here to allow superclasses to perform post-processing of the data. 82 | // For an example, see the HTTPDynamicFileResponse class. 83 | // 84 | // At this point, the readBuffer has readBufferOffset bytes available. 85 | // This method is in charge of updating the readBufferOffset. 86 | // Failure to do so will cause the readBuffer to grow to fileLength. (Imagine a 1 GB file...) 87 | 88 | // Copy the data out of the temporary readBuffer. 89 | data = [[NSData alloc] initWithBytes:readBuffer length:readBufferOffset]; 90 | 91 | // Reset the read buffer. 92 | readBufferOffset = 0; 93 | 94 | // Notify the connection that we have data available for it. 95 | [connection responseHasAvailableData:self]; 96 | } 97 | 98 | - (void)pauseReadSource 99 | { 100 | if (!readSourceSuspended) 101 | { 102 | HTTPLogVerbose(@"%@[%p]: Suspending readSource", THIS_FILE, self); 103 | 104 | readSourceSuspended = YES; 105 | dispatch_suspend(readSource); 106 | } 107 | } 108 | 109 | - (void)resumeReadSource 110 | { 111 | if (readSourceSuspended) 112 | { 113 | HTTPLogVerbose(@"%@[%p]: Resuming readSource", THIS_FILE, self); 114 | 115 | readSourceSuspended = NO; 116 | dispatch_resume(readSource); 117 | } 118 | } 119 | 120 | - (void)cancelReadSource 121 | { 122 | HTTPLogVerbose(@"%@[%p]: Canceling readSource", THIS_FILE, self); 123 | 124 | dispatch_source_cancel(readSource); 125 | 126 | // Cancelling a dispatch source doesn't 127 | // invoke the cancel handler if the dispatch source is paused. 128 | 129 | if (readSourceSuspended) 130 | { 131 | readSourceSuspended = NO; 132 | dispatch_resume(readSource); 133 | } 134 | } 135 | 136 | - (BOOL)openFileAndSetupReadSource 137 | { 138 | HTTPLogTrace(); 139 | 140 | fileFD = open([filePath UTF8String], (O_RDONLY | O_NONBLOCK)); 141 | if (fileFD == NULL_FD) 142 | { 143 | HTTPLogError(@"%@: Unable to open file. filePath: %@", THIS_FILE, filePath); 144 | 145 | return NO; 146 | } 147 | 148 | HTTPLogVerbose(@"%@[%p]: Open fd[%i] -> %@", THIS_FILE, self, fileFD, filePath); 149 | 150 | readQueue = dispatch_queue_create("HTTPAsyncFileResponse", NULL); 151 | readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fileFD, 0, readQueue); 152 | 153 | 154 | dispatch_source_set_event_handler(readSource, ^{ 155 | 156 | HTTPLogTrace2(@"%@: eventBlock - fd[%i]", THIS_FILE, fileFD); 157 | 158 | // Determine how much data we should read. 159 | // 160 | // It is OK if we ask to read more bytes than exist in the file. 161 | // It is NOT OK to over-allocate the buffer. 162 | 163 | unsigned long long _bytesAvailableOnFD = dispatch_source_get_data(readSource); 164 | 165 | UInt64 _bytesLeftInFile = fileLength - readOffset; 166 | 167 | NSUInteger bytesAvailableOnFD; 168 | NSUInteger bytesLeftInFile; 169 | 170 | bytesAvailableOnFD = (_bytesAvailableOnFD > NSUIntegerMax) ? NSUIntegerMax : (NSUInteger)_bytesAvailableOnFD; 171 | bytesLeftInFile = (_bytesLeftInFile > NSUIntegerMax) ? NSUIntegerMax : (NSUInteger)_bytesLeftInFile; 172 | 173 | NSUInteger bytesLeftInRequest = readRequestLength - readBufferOffset; 174 | 175 | NSUInteger bytesLeft = MIN(bytesLeftInRequest, bytesLeftInFile); 176 | 177 | NSUInteger bytesToRead = MIN(bytesAvailableOnFD, bytesLeft); 178 | 179 | // Make sure buffer is big enough for read request. 180 | // Do not over-allocate. 181 | 182 | if (readBuffer == NULL || bytesToRead > (readBufferSize - readBufferOffset)) 183 | { 184 | readBufferSize = bytesToRead; 185 | readBuffer = reallocf(readBuffer, (size_t)bytesToRead); 186 | 187 | if (readBuffer == NULL) 188 | { 189 | HTTPLogError(@"%@[%p]: Unable to allocate buffer", THIS_FILE, self); 190 | 191 | [self pauseReadSource]; 192 | [self abort]; 193 | 194 | return; 195 | } 196 | } 197 | 198 | // Perform the read 199 | 200 | HTTPLogVerbose(@"%@[%p]: Attempting to read %lu bytes from file", THIS_FILE, self, bytesToRead); 201 | 202 | ssize_t result = read(fileFD, readBuffer + readBufferOffset, (size_t)bytesToRead); 203 | 204 | // Check the results 205 | if (result < 0) 206 | { 207 | HTTPLogError(@"%@: Error(%i) reading file(%@)", THIS_FILE, errno, filePath); 208 | 209 | [self pauseReadSource]; 210 | [self abort]; 211 | } 212 | else if (result == 0) 213 | { 214 | HTTPLogError(@"%@: Read EOF on file(%@)", THIS_FILE, filePath); 215 | 216 | [self pauseReadSource]; 217 | [self abort]; 218 | } 219 | else // (result > 0) 220 | { 221 | HTTPLogVerbose(@"%@[%p]: Read %d bytes from file", THIS_FILE, self, result); 222 | 223 | readOffset += result; 224 | readBufferOffset += result; 225 | 226 | [self pauseReadSource]; 227 | [self processReadBuffer]; 228 | } 229 | 230 | }); 231 | 232 | int theFileFD = fileFD; 233 | dispatch_source_t theReadSource = readSource; 234 | 235 | dispatch_source_set_cancel_handler(readSource, ^{ 236 | 237 | // Do not access self from within this block in any way, shape or form. 238 | // 239 | // Note: You access self if you reference an iVar. 240 | 241 | HTTPLogTrace2(@"%@: cancelBlock - Close fd[%i]", THIS_FILE, theFileFD); 242 | 243 | dispatch_release(theReadSource); 244 | close(theFileFD); 245 | }); 246 | 247 | readSourceSuspended = YES; 248 | 249 | return YES; 250 | } 251 | 252 | - (BOOL)openFileIfNeeded 253 | { 254 | if (aborted) 255 | { 256 | // The file operation has been aborted. 257 | // This could be because we failed to open the file, 258 | // or the reading process failed. 259 | return NO; 260 | } 261 | 262 | if (fileFD != NULL_FD) 263 | { 264 | // File has already been opened. 265 | return YES; 266 | } 267 | 268 | return [self openFileAndSetupReadSource]; 269 | } 270 | 271 | - (UInt64)contentLength 272 | { 273 | HTTPLogTrace2(@"%@[%p]: contentLength - %llu", THIS_FILE, self, fileLength); 274 | 275 | return fileLength; 276 | } 277 | 278 | - (UInt64)offset 279 | { 280 | HTTPLogTrace(); 281 | 282 | return fileOffset; 283 | } 284 | 285 | - (void)setOffset:(UInt64)offset 286 | { 287 | HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset); 288 | 289 | if (![self openFileIfNeeded]) 290 | { 291 | // File opening failed, 292 | // or response has been aborted due to another error. 293 | return; 294 | } 295 | 296 | fileOffset = offset; 297 | readOffset = offset; 298 | 299 | off_t result = lseek(fileFD, (off_t)offset, SEEK_SET); 300 | if (result == -1) 301 | { 302 | HTTPLogError(@"%@[%p]: lseek failed - errno(%i) filePath(%@)", THIS_FILE, self, errno, filePath); 303 | 304 | [self abort]; 305 | } 306 | } 307 | 308 | - (NSData *)readDataOfLength:(NSUInteger)length 309 | { 310 | HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)length); 311 | 312 | if (data) 313 | { 314 | NSUInteger dataLength = [data length]; 315 | 316 | HTTPLogVerbose(@"%@[%p]: Returning data of length %lu", THIS_FILE, self, dataLength); 317 | 318 | fileOffset += dataLength; 319 | 320 | NSData *result = data; 321 | data = nil; 322 | 323 | return [result autorelease]; 324 | } 325 | else 326 | { 327 | if (![self openFileIfNeeded]) 328 | { 329 | // File opening failed, 330 | // or response has been aborted due to another error. 331 | return nil; 332 | } 333 | 334 | dispatch_sync(readQueue, ^{ 335 | 336 | NSAssert(readSourceSuspended, @"Invalid logic - perhaps HTTPConnection has changed."); 337 | 338 | readRequestLength = length; 339 | [self resumeReadSource]; 340 | }); 341 | 342 | return nil; 343 | } 344 | } 345 | 346 | - (BOOL)isDone 347 | { 348 | BOOL result = (fileOffset == fileLength); 349 | 350 | HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO")); 351 | 352 | return result; 353 | } 354 | 355 | - (NSString *)filePath 356 | { 357 | return filePath; 358 | } 359 | 360 | - (BOOL)isAsynchronous 361 | { 362 | HTTPLogTrace(); 363 | 364 | return YES; 365 | } 366 | 367 | - (void)connectionDidClose 368 | { 369 | HTTPLogTrace(); 370 | 371 | if (fileFD != NULL_FD) 372 | { 373 | dispatch_sync(readQueue, ^{ 374 | 375 | // Prevent any further calls to the connection 376 | connection = nil; 377 | 378 | // Cancel the readSource. 379 | // We do this here because the readSource's eventBlock has retained self. 380 | // In other words, if we don't cancel the readSource, we will never get deallocated. 381 | 382 | [self cancelReadSource]; 383 | }); 384 | } 385 | } 386 | 387 | - (void)dealloc 388 | { 389 | HTTPLogTrace(); 390 | 391 | if (readQueue) 392 | dispatch_release(readQueue); 393 | 394 | if (readBuffer) 395 | free(readBuffer); 396 | 397 | [filePath release]; 398 | [data release]; 399 | 400 | [super dealloc]; 401 | } 402 | 403 | @end 404 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Vendor/CocoaLumberjack/DDFileLogger.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "DDLog.h" 3 | 4 | @class DDLogFileInfo; 5 | 6 | /** 7 | * Welcome to Cocoa Lumberjack! 8 | * 9 | * The Google Code page has a wealth of documentation if you have any questions. 10 | * http://code.google.com/p/cocoalumberjack/ 11 | * 12 | * If you're new to the project you may wish to read the "Getting Started" page. 13 | * http://code.google.com/p/cocoalumberjack/wiki/GettingStarted 14 | * 15 | * 16 | * This class provides a logger to write log statements to a file. 17 | **/ 18 | 19 | 20 | // Default configuration and safety/sanity values. 21 | // 22 | // maximumFileSize -> DEFAULT_LOG_MAX_FILE_SIZE 23 | // rollingFrequency -> DEFAULT_LOG_ROLLING_FREQUENCY 24 | // maximumNumberOfLogFiles -> DEFAULT_LOG_MAX_NUM_LOG_FILES 25 | // 26 | // You should carefully consider the proper configuration values for your application. 27 | 28 | #define DEFAULT_LOG_MAX_FILE_SIZE (1024 * 1024) // 1 MB 29 | #define DEFAULT_LOG_ROLLING_FREQUENCY (60 * 60 * 24) // 24 Hours 30 | #define DEFAULT_LOG_MAX_NUM_LOG_FILES (5) // 5 Files 31 | 32 | 33 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 34 | #pragma mark - 35 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 36 | 37 | // The LogFileManager protocol is designed to allow you to control all aspects of your log files. 38 | // 39 | // The primary purpose of this is to allow you to do something with the log files after they have been rolled. 40 | // Perhaps you want to compress them to save disk space. 41 | // Perhaps you want to upload them to an FTP server. 42 | // Perhaps you want to run some analytics on the file. 43 | // 44 | // A default LogFileManager is, of course, provided. 45 | // The default LogFileManager simply deletes old log files according to the maximumNumberOfLogFiles property. 46 | // 47 | // This protocol provides various methods to fetch the list of log files. 48 | // 49 | // There are two variants: sorted and unsorted. 50 | // If sorting is not necessary, the unsorted variant is obviously faster. 51 | // The sorted variant will return an array sorted by when the log files were created, 52 | // with the most recently created log file at index 0, and the oldest log file at the end of the array. 53 | // 54 | // You can fetch only the log file paths (full path including name), log file names (name only), 55 | // or an array of DDLogFileInfo objects. 56 | // The DDLogFileInfo class is documented below, and provides a handy wrapper that 57 | // gives you easy access to various file attributes such as the creation date or the file size. 58 | 59 | @protocol DDLogFileManager 60 | @required 61 | 62 | // Public properties 63 | 64 | @property (readwrite, assign) NSUInteger maximumNumberOfLogFiles; 65 | 66 | // Public methods 67 | 68 | - (NSString *)logsDirectory; 69 | 70 | - (NSArray *)unsortedLogFilePaths; 71 | - (NSArray *)unsortedLogFileNames; 72 | - (NSArray *)unsortedLogFileInfos; 73 | 74 | - (NSArray *)sortedLogFilePaths; 75 | - (NSArray *)sortedLogFileNames; 76 | - (NSArray *)sortedLogFileInfos; 77 | 78 | // Private methods (only to be used by DDFileLogger) 79 | 80 | - (NSString *)createNewLogFile; 81 | 82 | @optional 83 | 84 | // Notifications from DDFileLogger 85 | 86 | - (void)didArchiveLogFile:(NSString *)logFilePath; 87 | - (void)didRollAndArchiveLogFile:(NSString *)logFilePath; 88 | 89 | @end 90 | 91 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 92 | #pragma mark - 93 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 94 | 95 | // Default log file manager. 96 | // 97 | // All log files are placed inside the logsDirectory. 98 | // If a specific logsDirectory isn't specified, the default directory is used. 99 | // On Mac, this is in ~/Library/Application Support//Logs. 100 | // On iPhone, this is in ~/Documents/Logs. 101 | // 102 | // Log files are named "log-.txt", 103 | // where uuid is a 6 character hexadecimal consisting of the set [0123456789ABCDEF]. 104 | // 105 | // Archived log files are automatically deleted according to the maximumNumberOfLogFiles property. 106 | 107 | @interface DDLogFileManagerDefault : NSObject 108 | { 109 | NSUInteger maximumNumberOfLogFiles; 110 | NSString *_logsDirectory; 111 | } 112 | 113 | - (id)init; 114 | - (id)initWithLogsDirectory:(NSString *)logsDirectory; 115 | 116 | @end 117 | 118 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 119 | #pragma mark - 120 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 121 | 122 | // Most users will want file log messages to be prepended with the date and time. 123 | // Rather than forcing the majority of users to write their own formatter, 124 | // we will supply a logical default formatter. 125 | // Users can easily replace this formatter with their own by invoking the setLogFormatter method. 126 | // It can also be removed by calling setLogFormatter, and passing a nil parameter. 127 | // 128 | // In addition to the convenience of having a logical default formatter, 129 | // it will also provide a template that makes it easy for developers to copy and change. 130 | 131 | @interface DDLogFileFormatterDefault : NSObject 132 | { 133 | NSDateFormatter *dateFormatter; 134 | } 135 | 136 | @end 137 | 138 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 139 | #pragma mark - 140 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 141 | 142 | @interface DDFileLogger : DDAbstractLogger 143 | { 144 | id logFileManager; 145 | 146 | DDLogFileInfo *currentLogFileInfo; 147 | NSFileHandle *currentLogFileHandle; 148 | 149 | NSTimer *rollingTimer; 150 | 151 | unsigned long long maximumFileSize; 152 | NSTimeInterval rollingFrequency; 153 | } 154 | 155 | - (id)init; 156 | - (id)initWithLogFileManager:(id )logFileManager; 157 | 158 | // Configuration 159 | // 160 | // maximumFileSize: 161 | // The approximate maximum size to allow log files to grow. 162 | // If a log file is larger than this value after a write, 163 | // then the log file is rolled. 164 | // 165 | // rollingFrequency 166 | // How often to roll the log file. 167 | // The frequency is given as an NSTimeInterval, which is a double that specifies the interval in seconds. 168 | // Once the log file gets to be this old, it is rolled. 169 | // 170 | // Both the maximumFileSize and the rollingFrequency are used to manage rolling. 171 | // Whichever occurs first will cause the log file to be rolled. 172 | // 173 | // For example: 174 | // The rollingFrequency is 24 hours, 175 | // but the log file surpasses the maximumFileSize after only 20 hours. 176 | // The log file will be rolled at that 20 hour mark. 177 | // A new log file will be created, and the 24 hour timer will be restarted. 178 | // 179 | // logFileManager 180 | // Allows you to retrieve the list of log files, 181 | // and configure the maximum number of archived log files to keep. 182 | 183 | @property (readwrite, assign) unsigned long long maximumFileSize; 184 | 185 | @property (readwrite, assign) NSTimeInterval rollingFrequency; 186 | 187 | @property (nonatomic, readonly) id logFileManager; 188 | 189 | 190 | // You can optionally force the current log file to be rolled with this method. 191 | 192 | - (void)rollLogFile; 193 | 194 | // Inherited from DDAbstractLogger 195 | 196 | // - (id )logFormatter; 197 | // - (void)setLogFormatter:(id )formatter; 198 | 199 | @end 200 | 201 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 202 | #pragma mark - 203 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 204 | 205 | // DDLogFileInfo is a simple class that provides access to various file attributes. 206 | // It provides good performance as it only fetches the information if requested, 207 | // and it caches the information to prevent duplicate fetches. 208 | // 209 | // It was designed to provide quick snapshots of the current state of log files, 210 | // and to help sort log files in an array. 211 | // 212 | // This class does not monitor the files, or update it's cached attribute values if the file changes on disk. 213 | // This is not what the class was designed for. 214 | // 215 | // If you absolutely must get updated values, 216 | // you can invoke the reset method which will clear the cache. 217 | 218 | @interface DDLogFileInfo : NSObject 219 | { 220 | NSString *filePath; 221 | NSString *fileName; 222 | 223 | NSDictionary *fileAttributes; 224 | 225 | NSDate *creationDate; 226 | NSDate *modificationDate; 227 | 228 | unsigned long long fileSize; 229 | } 230 | 231 | @property (nonatomic, readonly) NSString *filePath; 232 | @property (nonatomic, readonly) NSString *fileName; 233 | 234 | @property (nonatomic, readonly) NSDictionary *fileAttributes; 235 | 236 | @property (nonatomic, readonly) NSDate *creationDate; 237 | @property (nonatomic, readonly) NSDate *modificationDate; 238 | 239 | @property (nonatomic, readonly) unsigned long long fileSize; 240 | 241 | @property (nonatomic, readonly) NSTimeInterval age; 242 | 243 | @property (nonatomic, readwrite) BOOL isArchived; 244 | 245 | + (id)logFileWithPath:(NSString *)filePath; 246 | 247 | - (id)initWithFilePath:(NSString *)filePath; 248 | 249 | - (void)reset; 250 | - (void)renameFile:(NSString *)newFileName; 251 | 252 | #if TARGET_IPHONE_SIMULATOR 253 | 254 | // So here's the situation. 255 | // Extended attributes are perfect for what we're trying to do here (marking files as archived). 256 | // This is exactly what extended attributes were designed for. 257 | // 258 | // But Apple screws us over on the simulator. 259 | // Everytime you build-and-go, they copy the application into a new folder on the hard drive, 260 | // and as part of the process they strip extended attributes from our log files. 261 | // Normally, a copy of a file preserves extended attributes. 262 | // So obviously Apple has gone to great lengths to piss us off. 263 | // 264 | // Thus we use a slightly different tactic for marking log files as archived in the simulator. 265 | // That way it "just works" and there's no confusion when testing. 266 | // 267 | // The difference in method names is indicative of the difference in functionality. 268 | // On the simulator we add an attribute by appending a filename extension. 269 | // 270 | // For example: 271 | // log-ABC123.txt -> log-ABC123.archived.txt 272 | 273 | - (BOOL)hasExtensionAttributeWithName:(NSString *)attrName; 274 | 275 | - (void)addExtensionAttributeWithName:(NSString *)attrName; 276 | - (void)removeExtensionAttributeWithName:(NSString *)attrName; 277 | 278 | #else 279 | 280 | // Normal use of extended attributes used everywhere else, 281 | // such as on Macs and on iPhone devices. 282 | 283 | - (BOOL)hasExtendedAttributeWithName:(NSString *)attrName; 284 | 285 | - (void)addExtendedAttributeWithName:(NSString *)attrName; 286 | - (void)removeExtendedAttributeWithName:(NSString *)attrName; 287 | 288 | #endif 289 | 290 | - (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another; 291 | - (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another; 292 | 293 | @end 294 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.m: -------------------------------------------------------------------------------- 1 | #import "DDAbstractDatabaseLogger.h" 2 | 3 | @interface DDAbstractDatabaseLogger () 4 | - (void)destroySaveTimer; 5 | - (void)destroyDeleteTimer; 6 | @end 7 | 8 | #pragma mark - 9 | 10 | @implementation DDAbstractDatabaseLogger 11 | 12 | - (id)init 13 | { 14 | if ((self = [super init])) 15 | { 16 | saveThreshold = 500; 17 | saveInterval = 60; // 60 seconds 18 | maxAge = (60 * 60 * 24 * 7); // 7 days 19 | deleteInterval = (60 * 5); // 5 minutes 20 | } 21 | return self; 22 | } 23 | 24 | - (void)dealloc 25 | { 26 | [self destroySaveTimer]; 27 | [self destroyDeleteTimer]; 28 | 29 | [super dealloc]; 30 | } 31 | 32 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 33 | #pragma mark Override Me 34 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 35 | 36 | - (BOOL)db_log:(DDLogMessage *)logMessage 37 | { 38 | // Override me and add your implementation. 39 | // 40 | // Return YES if an item was added to the buffer. 41 | // Return NO if the logMessage was ignored. 42 | 43 | return NO; 44 | } 45 | 46 | - (void)db_save 47 | { 48 | // Override me and add your implementation. 49 | } 50 | 51 | - (void)db_delete 52 | { 53 | // Override me and add your implementation. 54 | } 55 | 56 | - (void)db_saveAndDelete 57 | { 58 | // Override me and add your implementation. 59 | } 60 | 61 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 62 | #pragma mark Private API 63 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 64 | 65 | - (void)performSaveAndSuspendSaveTimer 66 | { 67 | if (unsavedCount > 0) 68 | { 69 | if (deleteOnEverySave) 70 | [self db_saveAndDelete]; 71 | else 72 | [self db_save]; 73 | } 74 | 75 | unsavedCount = 0; 76 | unsavedTime = 0; 77 | 78 | if (saveTimer && !saveTimerSuspended) 79 | { 80 | dispatch_suspend(saveTimer); 81 | saveTimerSuspended = YES; 82 | } 83 | } 84 | 85 | - (void)performDelete 86 | { 87 | if (maxAge > 0.0) 88 | { 89 | [self db_delete]; 90 | 91 | lastDeleteTime = dispatch_time(DISPATCH_TIME_NOW, 0); 92 | } 93 | } 94 | 95 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 96 | #pragma mark Timers 97 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 98 | 99 | - (void)destroySaveTimer 100 | { 101 | if (saveTimer) 102 | { 103 | dispatch_source_cancel(saveTimer); 104 | dispatch_release(saveTimer); 105 | saveTimer = NULL; 106 | } 107 | } 108 | 109 | - (void)updateAndResumeSaveTimer 110 | { 111 | if ((saveTimer != NULL) && (saveInterval > 0.0) && (unsavedTime > 0.0)) 112 | { 113 | uint64_t interval = saveInterval * NSEC_PER_SEC; 114 | dispatch_time_t startTime = dispatch_time(unsavedTime, interval); 115 | 116 | dispatch_source_set_timer(saveTimer, startTime, interval, 1.0); 117 | 118 | if (saveTimerSuspended) 119 | { 120 | dispatch_resume(saveTimer); 121 | saveTimerSuspended = NO; 122 | } 123 | } 124 | } 125 | 126 | - (void)createSuspendedSaveTimer 127 | { 128 | if ((saveTimer == NULL) && (saveInterval > 0.0)) 129 | { 130 | saveTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, loggerQueue); 131 | 132 | dispatch_source_set_event_handler(saveTimer, ^{ 133 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 134 | 135 | [self performSaveAndSuspendSaveTimer]; 136 | 137 | [pool drain]; 138 | }); 139 | 140 | saveTimerSuspended = YES; 141 | } 142 | } 143 | 144 | - (void)destroyDeleteTimer 145 | { 146 | if (deleteTimer) 147 | { 148 | dispatch_source_cancel(deleteTimer); 149 | dispatch_release(deleteTimer); 150 | deleteTimer = NULL; 151 | } 152 | } 153 | 154 | - (void)updateDeleteTimer 155 | { 156 | if ((deleteTimer != NULL) && (deleteInterval > 0.0) && (maxAge > 0.0)) 157 | { 158 | uint64_t interval = deleteInterval * NSEC_PER_SEC; 159 | dispatch_time_t startTime; 160 | 161 | if (lastDeleteTime > 0) 162 | startTime = dispatch_time(lastDeleteTime, interval); 163 | else 164 | startTime = dispatch_time(DISPATCH_TIME_NOW, interval); 165 | 166 | dispatch_source_set_timer(deleteTimer, startTime, interval, 1.0); 167 | } 168 | } 169 | 170 | - (void)createAndStartDeleteTimer 171 | { 172 | if ((deleteTimer == NULL) && (deleteInterval > 0.0) && (maxAge > 0.0)) 173 | { 174 | deleteTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, loggerQueue); 175 | 176 | dispatch_source_set_event_handler(deleteTimer, ^{ 177 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 178 | 179 | [self performDelete]; 180 | 181 | [pool drain]; 182 | }); 183 | 184 | [self updateDeleteTimer]; 185 | 186 | dispatch_resume(deleteTimer); 187 | } 188 | } 189 | 190 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 191 | #pragma mark Configuration 192 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 193 | 194 | - (NSUInteger)saveThreshold 195 | { 196 | if (dispatch_get_current_queue() == loggerQueue) 197 | { 198 | return saveThreshold; 199 | } 200 | else 201 | { 202 | __block NSUInteger result; 203 | 204 | dispatch_sync(loggerQueue, ^{ 205 | result = saveThreshold; 206 | }); 207 | 208 | return result; 209 | } 210 | } 211 | 212 | - (void)setSaveThreshold:(NSUInteger)threshold 213 | { 214 | dispatch_block_t block = ^{ 215 | 216 | if (saveThreshold != threshold) 217 | { 218 | saveThreshold = threshold; 219 | 220 | // Since the saveThreshold has changed, 221 | // we check to see if the current unsavedCount has surpassed the new threshold. 222 | // 223 | // If it has, we immediately save the log. 224 | 225 | if ((unsavedCount >= saveThreshold) && (saveThreshold > 0)) 226 | { 227 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 228 | 229 | [self performSaveAndSuspendSaveTimer]; 230 | 231 | [pool drain]; 232 | } 233 | } 234 | }; 235 | 236 | if (dispatch_get_current_queue() == loggerQueue) 237 | block(); 238 | else 239 | dispatch_async(loggerQueue, block); 240 | } 241 | 242 | - (NSTimeInterval)saveInterval 243 | { 244 | if (dispatch_get_current_queue() == loggerQueue) 245 | { 246 | return saveInterval; 247 | } 248 | else 249 | { 250 | __block NSTimeInterval result; 251 | 252 | dispatch_sync(loggerQueue, ^{ 253 | result = saveInterval; 254 | }); 255 | 256 | return result; 257 | } 258 | } 259 | 260 | - (void)setSaveInterval:(NSTimeInterval)interval 261 | { 262 | dispatch_block_t block = ^{ 263 | 264 | if (saveInterval != interval) 265 | { 266 | saveInterval = interval; 267 | 268 | // There are several cases we need to handle here. 269 | // 270 | // 1. If the saveInterval was previously enabled and it just got disabled, 271 | // then we need to stop the saveTimer. (And we might as well release it.) 272 | // 273 | // 2. If the saveInterval was previously disabled and it just got enabled, 274 | // then we need to setup the saveTimer. (Plus we might need to do an immediate save.) 275 | // 276 | // 3. If the saveInterval increased, then we need to reset the timer so that it fires at the later date. 277 | // 278 | // 4. If the saveInterval decreased, then we need to reset the timer so that it fires at an earlier date. 279 | // (Plus we might need to do an immediate save.) 280 | 281 | if (saveInterval > 0.0) 282 | { 283 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 284 | 285 | if (saveTimer == NULL) 286 | { 287 | // Handles #2 288 | // 289 | // Since the saveTimer uses the unsavedTime to calculate it's first fireDate, 290 | // if a save is needed the timer will fire immediately. 291 | 292 | [self createSuspendedSaveTimer]; 293 | [self updateAndResumeSaveTimer]; 294 | } 295 | else 296 | { 297 | // Handles #3 298 | // Handles #4 299 | // 300 | // Since the saveTimer uses the unsavedTime to calculate it's first fireDate, 301 | // if a save is needed the timer will fire immediately. 302 | 303 | [self updateAndResumeSaveTimer]; 304 | } 305 | 306 | [pool drain]; 307 | } 308 | else if (saveTimer) 309 | { 310 | // Handles #1 311 | 312 | [self destroySaveTimer]; 313 | } 314 | } 315 | }; 316 | 317 | if (dispatch_get_current_queue() == loggerQueue) 318 | block(); 319 | else 320 | dispatch_async(loggerQueue, block); 321 | } 322 | 323 | - (NSTimeInterval)maxAge 324 | { 325 | if (dispatch_get_current_queue() == loggerQueue) 326 | { 327 | return maxAge; 328 | } 329 | else 330 | { 331 | __block NSTimeInterval result; 332 | 333 | dispatch_sync(loggerQueue, ^{ 334 | result = maxAge; 335 | }); 336 | 337 | return result; 338 | } 339 | } 340 | 341 | - (void)setMaxAge:(NSTimeInterval)interval 342 | { 343 | dispatch_block_t block = ^{ 344 | 345 | if (maxAge != interval) 346 | { 347 | NSTimeInterval oldMaxAge = maxAge; 348 | NSTimeInterval newMaxAge = interval; 349 | 350 | maxAge = interval; 351 | 352 | // There are several cases we need to handle here. 353 | // 354 | // 1. If the maxAge was previously enabled and it just got disabled, 355 | // then we need to stop the deleteTimer. (And we might as well release it.) 356 | // 357 | // 2. If the maxAge was previously disabled and it just got enabled, 358 | // then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.) 359 | // 360 | // 3. If the maxAge was increased, 361 | // then we don't need to do anything. 362 | // 363 | // 4. If the maxAge was decreased, 364 | // then we should do an immediate delete. 365 | 366 | BOOL shouldDeleteNow = NO; 367 | 368 | if (oldMaxAge > 0.0) 369 | { 370 | if (newMaxAge <= 0.0) 371 | { 372 | // Handles #1 373 | 374 | [self destroyDeleteTimer]; 375 | } 376 | else if (oldMaxAge > newMaxAge) 377 | { 378 | // Handles #4 379 | shouldDeleteNow = YES; 380 | } 381 | } 382 | else if (newMaxAge > 0.0) 383 | { 384 | // Handles #2 385 | shouldDeleteNow = YES; 386 | } 387 | 388 | if (shouldDeleteNow) 389 | { 390 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 391 | 392 | [self performDelete]; 393 | 394 | if (deleteTimer) 395 | [self updateDeleteTimer]; 396 | else 397 | [self createAndStartDeleteTimer]; 398 | 399 | [pool drain]; 400 | } 401 | } 402 | }; 403 | 404 | if (dispatch_get_current_queue() == loggerQueue) 405 | block(); 406 | else 407 | dispatch_async(loggerQueue, block); 408 | } 409 | 410 | - (NSTimeInterval)deleteInterval 411 | { 412 | if (dispatch_get_current_queue() == loggerQueue) 413 | { 414 | return deleteInterval; 415 | } 416 | else 417 | { 418 | __block NSTimeInterval result; 419 | 420 | dispatch_sync(loggerQueue, ^{ 421 | result = deleteInterval; 422 | }); 423 | 424 | return result; 425 | } 426 | } 427 | 428 | - (void)setDeleteInterval:(NSTimeInterval)interval 429 | { 430 | dispatch_block_t block = ^{ 431 | 432 | if (deleteInterval != interval) 433 | { 434 | deleteInterval = interval; 435 | 436 | // There are several cases we need to handle here. 437 | // 438 | // 1. If the deleteInterval was previously enabled and it just got disabled, 439 | // then we need to stop the deleteTimer. (And we might as well release it.) 440 | // 441 | // 2. If the deleteInterval was previously disabled and it just got enabled, 442 | // then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.) 443 | // 444 | // 3. If the deleteInterval increased, then we need to reset the timer so that it fires at the later date. 445 | // 446 | // 4. If the deleteInterval decreased, then we need to reset the timer so that it fires at an earlier date. 447 | // (Plus we might need to do an immediate delete.) 448 | 449 | if (deleteInterval > 0.0) 450 | { 451 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 452 | 453 | if (deleteTimer == NULL) 454 | { 455 | // Handles #2 456 | // 457 | // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate, 458 | // if a delete is needed the timer will fire immediately. 459 | 460 | [self createAndStartDeleteTimer]; 461 | } 462 | else 463 | { 464 | // Handles #3 465 | // Handles #4 466 | // 467 | // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate, 468 | // if a save is needed the timer will fire immediately. 469 | 470 | [self updateDeleteTimer]; 471 | } 472 | 473 | [pool drain]; 474 | } 475 | else if (deleteTimer) 476 | { 477 | // Handles #1 478 | 479 | [self destroyDeleteTimer]; 480 | } 481 | } 482 | }; 483 | 484 | if (dispatch_get_current_queue() == loggerQueue) 485 | block(); 486 | else 487 | dispatch_async(loggerQueue, block); 488 | } 489 | 490 | - (BOOL)deleteOnEverySave 491 | { 492 | if (dispatch_get_current_queue() == loggerQueue) 493 | { 494 | return deleteOnEverySave; 495 | } 496 | else 497 | { 498 | __block BOOL result; 499 | 500 | dispatch_sync(loggerQueue, ^{ 501 | result = deleteOnEverySave; 502 | }); 503 | 504 | return result; 505 | } 506 | } 507 | 508 | - (void)setDeleteOnEverySave:(BOOL)flag 509 | { 510 | dispatch_block_t block = ^{ 511 | 512 | deleteOnEverySave = flag; 513 | }; 514 | 515 | if (dispatch_get_current_queue() == loggerQueue) 516 | block(); 517 | else 518 | dispatch_async(loggerQueue, block); 519 | } 520 | 521 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 522 | #pragma mark Public API 523 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 524 | 525 | - (void)savePendingLogEntries 526 | { 527 | dispatch_block_t block = ^{ 528 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 529 | 530 | [self performSaveAndSuspendSaveTimer]; 531 | 532 | [pool drain]; 533 | }; 534 | 535 | if (dispatch_get_current_queue() == loggerQueue) 536 | block(); 537 | else 538 | dispatch_async(loggerQueue, block); 539 | } 540 | 541 | - (void)deleteOldLogEntries 542 | { 543 | dispatch_block_t block = ^{ 544 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 545 | 546 | [self performDelete]; 547 | 548 | [pool drain]; 549 | }; 550 | 551 | if (dispatch_get_current_queue() == loggerQueue) 552 | block(); 553 | else 554 | dispatch_async(loggerQueue, block); 555 | } 556 | 557 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 558 | #pragma mark DDLogger 559 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 560 | 561 | - (void)didAddLogger 562 | { 563 | // If you override me be sure to invoke [super didAddLogger]; 564 | 565 | [self createSuspendedSaveTimer]; 566 | 567 | [self createAndStartDeleteTimer]; 568 | } 569 | 570 | - (void)willRemoveLogger 571 | { 572 | // If you override me be sure to invoke [super willRemoveLogger]; 573 | 574 | [self performSaveAndSuspendSaveTimer]; 575 | 576 | [self destroySaveTimer]; 577 | [self destroyDeleteTimer]; 578 | } 579 | 580 | - (void)logMessage:(DDLogMessage *)logMessage 581 | { 582 | if ([self db_log:logMessage]) 583 | { 584 | BOOL firstUnsavedEntry = (++unsavedCount == 1); 585 | 586 | if ((unsavedCount >= saveThreshold) && (saveThreshold > 0)) 587 | { 588 | [self performSaveAndSuspendSaveTimer]; 589 | } 590 | else if (firstUnsavedEntry) 591 | { 592 | unsavedTime = dispatch_time(DISPATCH_TIME_NOW, 0); 593 | [self updateAndResumeSaveTimer]; 594 | } 595 | } 596 | } 597 | 598 | - (void)flush 599 | { 600 | // This method is invoked by DDLog's flushLog method. 601 | // 602 | // It is called automatically when the application quits, 603 | // or if the developer invokes DDLog's flushLog method prior to crashing or something. 604 | 605 | [self performSaveAndSuspendSaveTimer]; 606 | } 607 | 608 | @end 609 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Extensions/WebDAV/DAVResponse.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "DAVResponse.h" 4 | #import "HTTPLogging.h" 5 | 6 | // WebDAV specifications: http://webdav.org/specs/rfc4918.html 7 | 8 | typedef enum { 9 | kDAVProperty_ResourceType = (1 << 0), 10 | kDAVProperty_CreationDate = (1 << 1), 11 | kDAVProperty_LastModified = (1 << 2), 12 | kDAVProperty_ContentLength = (1 << 3), 13 | kDAVAllProperties = kDAVProperty_ResourceType | kDAVProperty_CreationDate | kDAVProperty_LastModified | kDAVProperty_ContentLength 14 | } DAVProperties; 15 | 16 | #define kXMLParseOptions (XML_PARSE_NONET | XML_PARSE_RECOVER | XML_PARSE_NOBLANKS | XML_PARSE_COMPACT | XML_PARSE_NOWARNING | XML_PARSE_NOERROR) 17 | 18 | static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; 19 | 20 | @implementation DAVResponse 21 | 22 | static void _AddPropertyResponse(NSString* itemPath, NSString* resourcePath, DAVProperties properties, NSMutableString* xmlString) { 23 | CFStringRef escapedPath = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)resourcePath, NULL, 24 | CFSTR("<&>?+"), kCFStringEncodingUTF8); 25 | if (escapedPath) { 26 | NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:itemPath error:NULL]; 27 | BOOL isDirectory = [[attributes fileType] isEqualToString:NSFileTypeDirectory]; 28 | [xmlString appendString:@""]; 29 | [xmlString appendFormat:@"%@", escapedPath]; 30 | [xmlString appendString:@""]; 31 | [xmlString appendString:@""]; 32 | 33 | if (properties & kDAVProperty_ResourceType) { 34 | if (isDirectory) { 35 | [xmlString appendString:@""]; 36 | } else { 37 | [xmlString appendString:@""]; 38 | } 39 | } 40 | 41 | if ((properties & kDAVProperty_CreationDate) && [attributes objectForKey:NSFileCreationDate]) { 42 | NSDateFormatter* formatter = [[NSDateFormatter alloc] init]; 43 | formatter.locale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease]; 44 | formatter.timeZone = [NSTimeZone timeZoneWithName:@"GMT"]; 45 | formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'+00:00'"; 46 | [xmlString appendFormat:@"%@", [formatter stringFromDate:[attributes fileCreationDate]]]; 47 | [formatter release]; 48 | } 49 | 50 | if ((properties & kDAVProperty_LastModified) && [attributes objectForKey:NSFileModificationDate]) { 51 | NSDateFormatter* formatter = [[NSDateFormatter alloc] init]; 52 | formatter.locale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease]; 53 | formatter.timeZone = [NSTimeZone timeZoneWithName:@"GMT"]; 54 | formatter.dateFormat = @"EEE', 'd' 'MMM' 'yyyy' 'HH:mm:ss' GMT'"; 55 | [xmlString appendFormat:@"%@", [formatter stringFromDate:[attributes fileModificationDate]]]; 56 | [formatter release]; 57 | } 58 | 59 | if ((properties & kDAVProperty_ContentLength) && !isDirectory && [attributes objectForKey:NSFileSize]) { 60 | [xmlString appendFormat:@"%qu", [attributes fileSize]]; 61 | } 62 | 63 | [xmlString appendString:@""]; 64 | [xmlString appendString:@"HTTP/1.1 200 OK"]; 65 | [xmlString appendString:@""]; 66 | [xmlString appendString:@"\n"]; 67 | CFRelease(escapedPath); 68 | } 69 | } 70 | 71 | static xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name) { 72 | while (child) { 73 | if ((child->type == XML_ELEMENT_NODE) && !xmlStrcmp(child->name, name)) { 74 | return child; 75 | } 76 | child = child->next; 77 | } 78 | return NULL; 79 | } 80 | 81 | - (id) initWithMethod:(NSString*)method headers:(NSDictionary*)headers bodyData:(NSData*)body resourcePath:(NSString*)resourcePath rootPath:(NSString*)rootPath { 82 | if ((self = [super init])) { 83 | _status = 200; 84 | _headers = [[NSMutableDictionary alloc] init]; 85 | 86 | // 10.1 DAV Header 87 | if ([method isEqualToString:@"OPTIONS"]) { 88 | if ([[headers objectForKey:@"User-Agent"] hasPrefix:@"WebDAVFS/"]) { // Mac OS X WebDAV support 89 | [_headers setObject:@"1, 2" forKey:@"DAV"]; 90 | } else { 91 | [_headers setObject:@"1" forKey:@"DAV"]; 92 | } 93 | } 94 | 95 | // 9.1 PROPFIND Method 96 | if ([method isEqualToString:@"PROPFIND"]) { 97 | NSInteger depth; 98 | NSString* depthHeader = [headers objectForKey:@"Depth"]; 99 | if ([depthHeader isEqualToString:@"0"]) { 100 | depth = 0; 101 | } else if ([depthHeader isEqualToString:@"1"]) { 102 | depth = 1; 103 | } else { 104 | HTTPLogError(@"Unsupported DAV depth \"%@\"", depthHeader); 105 | [self release]; 106 | return nil; 107 | } 108 | 109 | DAVProperties properties = 0; 110 | xmlDocPtr document = xmlReadMemory(body.bytes, (int)body.length, NULL, NULL, kXMLParseOptions); 111 | if (document) { 112 | xmlNodePtr node = _XMLChildWithName(document->children, (const xmlChar*)"propfind"); 113 | if (node) { 114 | node = _XMLChildWithName(node->children, (const xmlChar*)"prop"); 115 | } 116 | if (node) { 117 | node = node->children; 118 | while (node) { 119 | if (!xmlStrcmp(node->name, (const xmlChar*)"resourcetype")) { 120 | properties |= kDAVProperty_ResourceType; 121 | } else if (!xmlStrcmp(node->name, (const xmlChar*)"creationdate")) { 122 | properties |= kDAVProperty_CreationDate; 123 | } else if (!xmlStrcmp(node->name, (const xmlChar*)"getlastmodified")) { 124 | properties |= kDAVProperty_LastModified; 125 | } else if (!xmlStrcmp(node->name, (const xmlChar*)"getcontentlength")) { 126 | properties |= kDAVProperty_ContentLength; 127 | } else { 128 | HTTPLogWarn(@"Unknown DAV property requested \"%s\"", node->name); 129 | } 130 | node = node->next; 131 | } 132 | } else { 133 | HTTPLogWarn(@"HTTP Server: Invalid DAV properties\n%@", [[[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding] autorelease]); 134 | } 135 | xmlFreeDoc(document); 136 | } 137 | if (!properties) { 138 | properties = kDAVAllProperties; 139 | } 140 | 141 | NSString* basePath = [rootPath stringByAppendingPathComponent:resourcePath]; 142 | if (![basePath hasPrefix:rootPath] || ![[NSFileManager defaultManager] fileExistsAtPath:basePath]) { 143 | [self release]; 144 | return nil; 145 | } 146 | 147 | NSMutableString* xmlString = [NSMutableString stringWithString:@""]; 148 | [xmlString appendString:@"\n"]; 149 | if (![resourcePath hasPrefix:@"/"]) { 150 | resourcePath = [@"/" stringByAppendingString:resourcePath]; 151 | } 152 | _AddPropertyResponse(basePath, resourcePath, properties, xmlString); 153 | if (depth == 1) { 154 | if (![resourcePath hasSuffix:@"/"]) { 155 | resourcePath = [resourcePath stringByAppendingString:@"/"]; 156 | } 157 | NSDirectoryEnumerator* enumerator = [[NSFileManager defaultManager] enumeratorAtPath:basePath]; 158 | NSString* path; 159 | while ((path = [enumerator nextObject])) { 160 | _AddPropertyResponse([basePath stringByAppendingPathComponent:path], [resourcePath stringByAppendingString:path], properties, xmlString); 161 | [enumerator skipDescendents]; 162 | } 163 | } 164 | [xmlString appendString:@""]; 165 | 166 | [_headers setObject:@"application/xml; charset=\"utf-8\"" forKey:@"Content-Type"]; 167 | _data = [[xmlString dataUsingEncoding:NSUTF8StringEncoding] retain]; 168 | _status = 207; 169 | } 170 | 171 | // 9.3 MKCOL Method 172 | if ([method isEqualToString:@"MKCOL"]) { 173 | NSString* path = [rootPath stringByAppendingPathComponent:resourcePath]; 174 | if (![path hasPrefix:rootPath]) { 175 | [self release]; 176 | return nil; 177 | } 178 | 179 | if (![[NSFileManager defaultManager] fileExistsAtPath:[path stringByDeletingLastPathComponent]]) { 180 | HTTPLogError(@"Missing intermediate collection(s) at \"%@\"", path); 181 | _status = 409; 182 | } else if (![[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:NO attributes:nil error:NULL]) { 183 | HTTPLogError(@"Failed creating collection at \"%@\"", path); 184 | _status = 405; 185 | } 186 | } 187 | 188 | // 9.8 COPY Method 189 | // 9.9 MOVE Method 190 | if ([method isEqualToString:@"MOVE"] || [method isEqualToString:@"COPY"]) { 191 | if ([method isEqualToString:@"COPY"] && ![[headers objectForKey:@"Depth"] isEqualToString:@"infinity"]) { 192 | HTTPLogError(@"Unsupported DAV depth \"%@\"", [headers objectForKey:@"Depth"]); 193 | [self release]; 194 | return nil; 195 | } 196 | 197 | NSString* sourcePath = [rootPath stringByAppendingPathComponent:resourcePath]; 198 | if (![sourcePath hasPrefix:rootPath] || ![[NSFileManager defaultManager] fileExistsAtPath:sourcePath]) { 199 | [self release]; 200 | return nil; 201 | } 202 | 203 | NSString* destination = [headers objectForKey:@"Destination"]; 204 | NSRange range = [destination rangeOfString:[headers objectForKey:@"Host"]]; 205 | if (range.location == NSNotFound) { 206 | [self release]; 207 | return nil; 208 | } 209 | NSString* destinationPath = [rootPath stringByAppendingPathComponent: 210 | [[destination substringFromIndex:(range.location + range.length)] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; 211 | if (![destinationPath hasPrefix:rootPath] || [[NSFileManager defaultManager] fileExistsAtPath:destinationPath]) { 212 | [self release]; 213 | return nil; 214 | } 215 | 216 | BOOL isDirectory; 217 | if (![[NSFileManager defaultManager] fileExistsAtPath:[destinationPath stringByDeletingLastPathComponent] isDirectory:&isDirectory] || !isDirectory) { 218 | HTTPLogError(@"Invalid destination path \"%@\"", destinationPath); 219 | _status = 409; 220 | } else { 221 | BOOL existing = [[NSFileManager defaultManager] fileExistsAtPath:destinationPath]; 222 | if (existing && [[headers objectForKey:@"Overwrite"] isEqualToString:@"F"]) { 223 | HTTPLogError(@"Pre-existing destination path \"%@\"", destinationPath); 224 | _status = 412; 225 | } else { 226 | if ([method isEqualToString:@"COPY"]) { 227 | if ([[NSFileManager defaultManager] copyItemAtPath:sourcePath toPath:destinationPath error:NULL]) { 228 | _status = existing ? 204 : 201; 229 | } else { 230 | HTTPLogError(@"Failed copying \"%@\" to \"%@\"", sourcePath, destinationPath); 231 | _status = 403; 232 | } 233 | } else { 234 | if ([[NSFileManager defaultManager] moveItemAtPath:sourcePath toPath:destinationPath error:NULL]) { 235 | _status = existing ? 204 : 201; 236 | } else { 237 | HTTPLogError(@"Failed moving \"%@\" to \"%@\"", sourcePath, destinationPath); 238 | _status = 403; 239 | } 240 | } 241 | } 242 | } 243 | } 244 | 245 | // 9.10 LOCK Method - TODO: Actually lock the resource 246 | if ([method isEqualToString:@"LOCK"]) { 247 | NSString* path = [rootPath stringByAppendingPathComponent:resourcePath]; 248 | if (![path hasPrefix:rootPath]) { 249 | [self release]; 250 | return nil; 251 | } 252 | 253 | NSString* depth = [headers objectForKey:@"Depth"]; 254 | NSString* scope = nil; 255 | NSString* type = nil; 256 | NSString* owner = nil; 257 | xmlDocPtr document = xmlReadMemory(body.bytes, (int)body.length, NULL, NULL, kXMLParseOptions); 258 | if (document) { 259 | xmlNodePtr node = _XMLChildWithName(document->children, (const xmlChar*)"lockinfo"); 260 | if (node) { 261 | xmlNodePtr scopeNode = _XMLChildWithName(node->children, (const xmlChar*)"lockscope"); 262 | if (scopeNode && scopeNode->children && scopeNode->children->name) { 263 | scope = [NSString stringWithUTF8String:(const char*)scopeNode->children->name]; 264 | } 265 | xmlNodePtr typeNode = _XMLChildWithName(node->children, (const xmlChar*)"locktype"); 266 | if (typeNode && typeNode->children && typeNode->children->name) { 267 | type = [NSString stringWithUTF8String:(const char*)typeNode->children->name]; 268 | } 269 | xmlNodePtr ownerNode = _XMLChildWithName(node->children, (const xmlChar*)"owner"); 270 | if (ownerNode) { 271 | ownerNode = _XMLChildWithName(ownerNode->children, (const xmlChar*)"href"); 272 | if (ownerNode && ownerNode->children && ownerNode->children->content) { 273 | owner = [NSString stringWithUTF8String:(const char*)ownerNode->children->content]; 274 | } 275 | } 276 | } else { 277 | HTTPLogWarn(@"HTTP Server: Invalid DAV properties\n%@", [[[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding] autorelease]); 278 | } 279 | xmlFreeDoc(document); 280 | } 281 | if ([scope isEqualToString:@"exclusive"] && [type isEqualToString:@"write"] && [depth isEqualToString:@"0"] && 282 | ([[NSFileManager defaultManager] fileExistsAtPath:path] || [[NSData data] writeToFile:path atomically:YES])) { 283 | NSString* timeout = [headers objectForKey:@"Timeout"]; 284 | 285 | CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault); 286 | NSString* token = [NSString stringWithFormat:@"urn:uuid:%@", [(id)CFUUIDCreateString(kCFAllocatorDefault, uuid) autorelease]]; 287 | CFRelease(uuid); 288 | 289 | NSMutableString* xmlString = [NSMutableString stringWithString:@""]; 290 | [xmlString appendString:@"\n"]; 291 | [xmlString appendString:@"\n\n"]; 292 | [xmlString appendFormat:@"\n", type]; 293 | [xmlString appendFormat:@"\n", scope]; 294 | [xmlString appendFormat:@"%@\n", depth]; 295 | if (owner) { 296 | [xmlString appendFormat:@"%@\n", owner]; 297 | } 298 | if (timeout) { 299 | [xmlString appendFormat:@"%@\n", timeout]; 300 | } 301 | [xmlString appendFormat:@"%@\n", token]; 302 | // [xmlString appendFormat:@"%@\n", root]; 303 | [xmlString appendString:@"\n\n"]; 304 | [xmlString appendString:@""]; 305 | 306 | [_headers setObject:@"application/xml; charset=\"utf-8\"" forKey:@"Content-Type"]; 307 | _data = [[xmlString dataUsingEncoding:NSUTF8StringEncoding] retain]; 308 | _status = 200; 309 | HTTPLogVerbose(@"Pretending to lock \"%@\"", resourcePath); 310 | } else { 311 | HTTPLogError(@"Locking request \"%@/%@/%@\" for \"%@\" is not allowed", scope, type, depth, resourcePath); 312 | _status = 403; 313 | } 314 | } 315 | 316 | // 9.11 UNLOCK Method - TODO: Actually unlock the resource 317 | if ([method isEqualToString:@"UNLOCK"]) { 318 | NSString* path = [rootPath stringByAppendingPathComponent:resourcePath]; 319 | if (![path hasPrefix:rootPath] || ![[NSFileManager defaultManager] fileExistsAtPath:path]) { 320 | [self release]; 321 | return nil; 322 | } 323 | 324 | NSString* token = [headers objectForKey:@"Lock-Token"]; 325 | _status = token ? 204 : 400; 326 | HTTPLogVerbose(@"Pretending to unlock \"%@\"", resourcePath); 327 | } 328 | 329 | } 330 | return self; 331 | } 332 | 333 | - (void) dealloc { 334 | [_headers release]; 335 | [_data release]; 336 | 337 | [super dealloc]; 338 | } 339 | 340 | - (UInt64) contentLength { 341 | return _data ? _data.length : 0; 342 | } 343 | 344 | - (UInt64) offset { 345 | return _offset; 346 | } 347 | 348 | - (void) setOffset:(UInt64)offset { 349 | _offset = offset; 350 | } 351 | 352 | - (NSData*) readDataOfLength:(NSUInteger)lengthParameter { 353 | if (_data) { 354 | NSUInteger remaining = _data.length - (NSUInteger)_offset; 355 | NSUInteger length = lengthParameter < remaining ? lengthParameter : remaining; 356 | void* bytes = (void*)(_data.bytes + _offset); 357 | _offset += length; 358 | return [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:NO]; 359 | } 360 | return nil; 361 | } 362 | 363 | - (BOOL) isDone { 364 | return _data ? _offset == _data.length : YES; 365 | } 366 | 367 | - (NSInteger) status { 368 | return _status; 369 | } 370 | 371 | - (NSDictionary*) httpHeaders { 372 | return _headers; 373 | } 374 | 375 | @end 376 | -------------------------------------------------------------------------------- /VMMediaDownloader/CocoaHTTPServer/Core/WebSocket.m: -------------------------------------------------------------------------------- 1 | #import "WebSocket.h" 2 | #import "HTTPMessage.h" 3 | #import "GCDAsyncSocket.h" 4 | #import "DDNumber.h" 5 | #import "DDData.h" 6 | #import "HTTPLogging.h" 7 | 8 | // Log levels: off, error, warn, info, verbose 9 | // Other flags : trace 10 | static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE; 11 | 12 | #define TIMEOUT_NONE -1 13 | #define TIMEOUT_REQUEST_BODY 10 14 | 15 | #define TAG_HTTP_REQUEST_BODY 100 16 | #define TAG_HTTP_RESPONSE_HEADERS 200 17 | #define TAG_HTTP_RESPONSE_BODY 201 18 | 19 | #define TAG_PREFIX 300 20 | #define TAG_MSG_PLUS_SUFFIX 301 21 | 22 | 23 | @interface WebSocket (PrivateAPI) 24 | 25 | - (void)readRequestBody; 26 | - (void)sendResponseBody; 27 | - (void)sendResponseHeaders; 28 | 29 | @end 30 | 31 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 32 | #pragma mark - 33 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 34 | 35 | @implementation WebSocket 36 | 37 | + (BOOL)isWebSocketRequest:(HTTPMessage *)request 38 | { 39 | // Request (Draft 75): 40 | // 41 | // GET /demo HTTP/1.1 42 | // Upgrade: WebSocket 43 | // Connection: Upgrade 44 | // Host: example.com 45 | // Origin: http://example.com 46 | // WebSocket-Protocol: sample 47 | // 48 | // 49 | // Request (Draft 76): 50 | // 51 | // GET /demo HTTP/1.1 52 | // Upgrade: WebSocket 53 | // Connection: Upgrade 54 | // Host: example.com 55 | // Origin: http://example.com 56 | // Sec-WebSocket-Protocol: sample 57 | // Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5 58 | // Sec-WebSocket-Key2: 12998 5 Y3 1 .P00 59 | // 60 | // ^n:ds[4U 61 | 62 | // Look for Upgrade: and Connection: headers. 63 | // If we find them, and they have the proper value, 64 | // we can safely assume this is a websocket request. 65 | 66 | NSString *upgradeHeaderValue = [request headerField:@"Upgrade"]; 67 | NSString *connectionHeaderValue = [request headerField:@"Connection"]; 68 | 69 | BOOL isWebSocket = YES; 70 | 71 | if (!upgradeHeaderValue || !connectionHeaderValue) { 72 | isWebSocket = NO; 73 | } 74 | else if (![upgradeHeaderValue caseInsensitiveCompare:@"WebSocket"] == NSOrderedSame) { 75 | isWebSocket = NO; 76 | } 77 | else if (![connectionHeaderValue caseInsensitiveCompare:@"Upgrade"] == NSOrderedSame) { 78 | isWebSocket = NO; 79 | } 80 | 81 | HTTPLogTrace2(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, (isWebSocket ? @"YES" : @"NO")); 82 | 83 | return isWebSocket; 84 | } 85 | 86 | + (BOOL)isVersion76Request:(HTTPMessage *)request 87 | { 88 | NSString *key1 = [request headerField:@"Sec-WebSocket-Key1"]; 89 | NSString *key2 = [request headerField:@"Sec-WebSocket-Key2"]; 90 | 91 | BOOL isVersion76; 92 | 93 | if (!key1 || !key2) { 94 | isVersion76 = NO; 95 | } 96 | else { 97 | isVersion76 = YES; 98 | } 99 | 100 | HTTPLogTrace2(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, (isVersion76 ? @"YES" : @"NO")); 101 | 102 | return isVersion76; 103 | } 104 | 105 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 106 | #pragma mark Setup and Teardown 107 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 108 | 109 | @synthesize websocketQueue; 110 | 111 | - (id)initWithRequest:(HTTPMessage *)aRequest socket:(GCDAsyncSocket *)socket 112 | { 113 | HTTPLogTrace(); 114 | 115 | if (aRequest == nil) 116 | { 117 | [self release]; 118 | return nil; 119 | } 120 | 121 | if ((self = [super init])) 122 | { 123 | if (HTTP_LOG_VERBOSE) 124 | { 125 | NSData *requestHeaders = [aRequest messageData]; 126 | 127 | NSString *temp = [[NSString alloc] initWithData:requestHeaders encoding:NSUTF8StringEncoding]; 128 | HTTPLogVerbose(@"%@[%p] Request Headers:\n%@", THIS_FILE, self, temp); 129 | [temp release]; 130 | } 131 | 132 | websocketQueue = dispatch_queue_create("WebSocket", NULL); 133 | request = [aRequest retain]; 134 | 135 | asyncSocket = [socket retain]; 136 | [asyncSocket setDelegate:self delegateQueue:websocketQueue]; 137 | 138 | isOpen = NO; 139 | isVersion76 = [[self class] isVersion76Request:request]; 140 | 141 | term = [[NSData alloc] initWithBytes:"\xFF" length:1]; 142 | } 143 | return self; 144 | } 145 | 146 | - (void)dealloc 147 | { 148 | HTTPLogTrace(); 149 | 150 | dispatch_release(websocketQueue); 151 | 152 | [request release]; 153 | 154 | [asyncSocket setDelegate:nil delegateQueue:NULL]; 155 | [asyncSocket disconnect]; 156 | [asyncSocket release]; 157 | 158 | [super dealloc]; 159 | } 160 | 161 | - (id)delegate 162 | { 163 | __block id result = nil; 164 | 165 | dispatch_sync(websocketQueue, ^{ 166 | result = delegate; 167 | }); 168 | 169 | return result; 170 | } 171 | 172 | - (void)setDelegate:(id)newDelegate 173 | { 174 | dispatch_async(websocketQueue, ^{ 175 | delegate = newDelegate; 176 | }); 177 | } 178 | 179 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 180 | #pragma mark Start and Stop 181 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 182 | 183 | /** 184 | * Starting point for the WebSocket after it has been fully initialized (including subclasses). 185 | * This method is called by the HTTPConnection it is spawned from. 186 | **/ 187 | - (void)start 188 | { 189 | // This method is not exactly designed to be overriden. 190 | // Subclasses are encouraged to override the didOpen method instead. 191 | 192 | dispatch_async(websocketQueue, ^{ 193 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 194 | 195 | if (isStarted) return; 196 | isStarted = YES; 197 | 198 | if (isVersion76) 199 | { 200 | [self readRequestBody]; 201 | } 202 | else 203 | { 204 | [self sendResponseHeaders]; 205 | [self didOpen]; 206 | } 207 | 208 | [pool drain]; 209 | }); 210 | } 211 | 212 | /** 213 | * This method is called by the HTTPServer if it is asked to stop. 214 | * The server, in turn, invokes stop on each WebSocket instance. 215 | **/ 216 | - (void)stop 217 | { 218 | // This method is not exactly designed to be overriden. 219 | // Subclasses are encouraged to override the didClose method instead. 220 | 221 | dispatch_async(websocketQueue, ^{ 222 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 223 | 224 | [asyncSocket disconnect]; 225 | 226 | [pool drain]; 227 | }); 228 | } 229 | 230 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 231 | #pragma mark HTTP Response 232 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 233 | 234 | - (void)readRequestBody 235 | { 236 | HTTPLogTrace(); 237 | 238 | NSAssert(isVersion76, @"WebSocket version 75 doesn't contain a request body"); 239 | 240 | [asyncSocket readDataToLength:8 withTimeout:TIMEOUT_NONE tag:TAG_HTTP_REQUEST_BODY]; 241 | } 242 | 243 | - (NSString *)originResponseHeaderValue 244 | { 245 | HTTPLogTrace(); 246 | 247 | NSString *origin = [request headerField:@"Origin"]; 248 | 249 | if (origin == nil) 250 | { 251 | NSString *port = [NSString stringWithFormat:@"%hu", [asyncSocket localPort]]; 252 | 253 | return [NSString stringWithFormat:@"http://localhost:%@", port]; 254 | } 255 | else 256 | { 257 | return origin; 258 | } 259 | } 260 | 261 | - (NSString *)locationResponseHeaderValue 262 | { 263 | HTTPLogTrace(); 264 | 265 | NSString *location; 266 | 267 | NSString *scheme = [asyncSocket isSecure] ? @"wss" : @"ws"; 268 | NSString *host = [request headerField:@"Host"]; 269 | 270 | NSString *requestUri = [[request url] relativeString]; 271 | 272 | if (host == nil) 273 | { 274 | NSString *port = [NSString stringWithFormat:@"%hu", [asyncSocket localPort]]; 275 | 276 | location = [NSString stringWithFormat:@"%@://localhost:%@%@", scheme, port, requestUri]; 277 | } 278 | else 279 | { 280 | location = [NSString stringWithFormat:@"%@://%@%@", scheme, host, requestUri]; 281 | } 282 | 283 | return location; 284 | } 285 | 286 | - (void)sendResponseHeaders 287 | { 288 | HTTPLogTrace(); 289 | 290 | // Request (Draft 75): 291 | // 292 | // GET /demo HTTP/1.1 293 | // Upgrade: WebSocket 294 | // Connection: Upgrade 295 | // Host: example.com 296 | // Origin: http://example.com 297 | // WebSocket-Protocol: sample 298 | // 299 | // 300 | // Request (Draft 76): 301 | // 302 | // GET /demo HTTP/1.1 303 | // Upgrade: WebSocket 304 | // Connection: Upgrade 305 | // Host: example.com 306 | // Origin: http://example.com 307 | // Sec-WebSocket-Protocol: sample 308 | // Sec-WebSocket-Key2: 12998 5 Y3 1 .P00 309 | // Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5 310 | // 311 | // ^n:ds[4U 312 | 313 | 314 | // Response (Draft 75): 315 | // 316 | // HTTP/1.1 101 Web Socket Protocol Handshake 317 | // Upgrade: WebSocket 318 | // Connection: Upgrade 319 | // WebSocket-Origin: http://example.com 320 | // WebSocket-Location: ws://example.com/demo 321 | // WebSocket-Protocol: sample 322 | // 323 | // 324 | // Response (Draft 76): 325 | // 326 | // HTTP/1.1 101 WebSocket Protocol Handshake 327 | // Upgrade: WebSocket 328 | // Connection: Upgrade 329 | // Sec-WebSocket-Origin: http://example.com 330 | // Sec-WebSocket-Location: ws://example.com/demo 331 | // Sec-WebSocket-Protocol: sample 332 | // 333 | // 8jKS'y:G*Co,Wxa- 334 | 335 | 336 | HTTPMessage *wsResponse = [[HTTPMessage alloc] initResponseWithStatusCode:101 337 | description:@"Web Socket Protocol Handshake" 338 | version:HTTPVersion1_1]; 339 | 340 | [wsResponse setHeaderField:@"Upgrade" value:@"WebSocket"]; 341 | [wsResponse setHeaderField:@"Connection" value:@"Upgrade"]; 342 | 343 | // Note: It appears that WebSocket-Origin and WebSocket-Location 344 | // are required for Google's Chrome implementation to work properly. 345 | // 346 | // If we don't send either header, Chrome will never report the WebSocket as open. 347 | // If we only send one of the two, Chrome will immediately close the WebSocket. 348 | // 349 | // In addition to this it appears that Chrome's implementation is very picky of the values of the headers. 350 | // They have to match exactly with what Chrome sent us or it will close the WebSocket. 351 | 352 | NSString *originValue = [self originResponseHeaderValue]; 353 | NSString *locationValue = [self locationResponseHeaderValue]; 354 | 355 | NSString *originField = isVersion76 ? @"Sec-WebSocket-Origin" : @"WebSocket-Origin"; 356 | NSString *locationField = isVersion76 ? @"Sec-WebSocket-Location" : @"WebSocket-Location"; 357 | 358 | [wsResponse setHeaderField:originField value:originValue]; 359 | [wsResponse setHeaderField:locationField value:locationValue]; 360 | 361 | NSData *responseHeaders = [wsResponse messageData]; 362 | 363 | [wsResponse release]; 364 | 365 | if (HTTP_LOG_VERBOSE) 366 | { 367 | NSString *temp = [[NSString alloc] initWithData:responseHeaders encoding:NSUTF8StringEncoding]; 368 | HTTPLogVerbose(@"%@[%p] Response Headers:\n%@", THIS_FILE, self, temp); 369 | [temp release]; 370 | } 371 | 372 | [asyncSocket writeData:responseHeaders withTimeout:TIMEOUT_NONE tag:TAG_HTTP_RESPONSE_HEADERS]; 373 | } 374 | 375 | - (NSData *)processKey:(NSString *)key 376 | { 377 | HTTPLogTrace(); 378 | 379 | unichar c; 380 | NSUInteger i; 381 | NSUInteger length = [key length]; 382 | 383 | // Concatenate the digits into a string, 384 | // and count the number of spaces. 385 | 386 | NSMutableString *numStr = [NSMutableString stringWithCapacity:10]; 387 | long long numSpaces = 0; 388 | 389 | for (i = 0; i < length; i++) 390 | { 391 | c = [key characterAtIndex:i]; 392 | 393 | if (c >= '0' && c <= '9') 394 | { 395 | [numStr appendFormat:@"%C", c]; 396 | } 397 | else if (c == ' ') 398 | { 399 | numSpaces++; 400 | } 401 | } 402 | 403 | long long num = strtoll([numStr UTF8String], NULL, 10); 404 | 405 | long long resultHostNum; 406 | 407 | if (numSpaces == 0) 408 | resultHostNum = 0; 409 | else 410 | resultHostNum = num / numSpaces; 411 | 412 | HTTPLogVerbose(@"key(%@) -> %qi / %qi = %qi", key, num, numSpaces, resultHostNum); 413 | 414 | // Convert result to 4 byte big-endian (network byte order) 415 | // and then convert to raw data. 416 | 417 | UInt32 result = OSSwapHostToBigInt32((uint32_t)resultHostNum); 418 | 419 | return [NSData dataWithBytes:&result length:4]; 420 | } 421 | 422 | - (void)sendResponseBody:(NSData *)d3 423 | { 424 | HTTPLogTrace(); 425 | 426 | NSAssert(isVersion76, @"WebSocket version 75 doesn't contain a response body"); 427 | NSAssert([d3 length] == 8, @"Invalid requestBody length"); 428 | 429 | NSString *key1 = [request headerField:@"Sec-WebSocket-Key1"]; 430 | NSString *key2 = [request headerField:@"Sec-WebSocket-Key2"]; 431 | 432 | NSData *d1 = [self processKey:key1]; 433 | NSData *d2 = [self processKey:key2]; 434 | 435 | // Concatenated d1, d2 & d3 436 | 437 | NSMutableData *d0 = [NSMutableData dataWithCapacity:(4+4+8)]; 438 | [d0 appendData:d1]; 439 | [d0 appendData:d2]; 440 | [d0 appendData:d3]; 441 | 442 | // Hash the data using MD5 443 | 444 | NSData *responseBody = [d0 md5Digest]; 445 | 446 | [asyncSocket writeData:responseBody withTimeout:TIMEOUT_NONE tag:TAG_HTTP_RESPONSE_BODY]; 447 | 448 | if (HTTP_LOG_VERBOSE) 449 | { 450 | NSString *s1 = [[NSString alloc] initWithData:d1 encoding:NSASCIIStringEncoding]; 451 | NSString *s2 = [[NSString alloc] initWithData:d2 encoding:NSASCIIStringEncoding]; 452 | NSString *s3 = [[NSString alloc] initWithData:d3 encoding:NSASCIIStringEncoding]; 453 | 454 | NSString *s0 = [[NSString alloc] initWithData:d0 encoding:NSASCIIStringEncoding]; 455 | 456 | NSString *sH = [[NSString alloc] initWithData:responseBody encoding:NSASCIIStringEncoding]; 457 | 458 | HTTPLogVerbose(@"key1 result : raw(%@) str(%@)", d1, s1); 459 | HTTPLogVerbose(@"key2 result : raw(%@) str(%@)", d2, s2); 460 | HTTPLogVerbose(@"key3 passed : raw(%@) str(%@)", d3, s3); 461 | HTTPLogVerbose(@"key0 concat : raw(%@) str(%@)", d0, s0); 462 | HTTPLogVerbose(@"responseBody: raw(%@) str(%@)", responseBody, sH); 463 | 464 | [s1 release]; 465 | [s2 release]; 466 | [s3 release]; 467 | [s0 release]; 468 | [sH release]; 469 | } 470 | } 471 | 472 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 473 | #pragma mark Core Functionality 474 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 475 | 476 | - (void)didOpen 477 | { 478 | HTTPLogTrace(); 479 | 480 | // Override me to perform any custom actions once the WebSocket has been opened. 481 | // This method is invoked on the websocketQueue. 482 | // 483 | // Don't forget to invoke [super didOpen] in your method. 484 | 485 | // Start reading for messages 486 | [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PREFIX]; 487 | 488 | // Notify delegate 489 | if ([delegate respondsToSelector:@selector(webSocketDidOpen:)]) 490 | { 491 | [delegate webSocketDidOpen:self]; 492 | } 493 | } 494 | 495 | - (void)sendMessage:(NSString *)msg 496 | { 497 | HTTPLogTrace(); 498 | 499 | NSData *msgData = [msg dataUsingEncoding:NSUTF8StringEncoding]; 500 | 501 | NSMutableData *data = [NSMutableData dataWithCapacity:([msgData length] + 2)]; 502 | 503 | [data appendBytes:"\x00" length:1]; 504 | [data appendData:msgData]; 505 | [data appendBytes:"\xFF" length:1]; 506 | 507 | // Remember: GCDAsyncSocket is thread-safe 508 | 509 | [asyncSocket writeData:data withTimeout:TIMEOUT_NONE tag:0]; 510 | } 511 | 512 | - (void)didReceiveMessage:(NSString *)msg 513 | { 514 | HTTPLogTrace(); 515 | 516 | // Override me to process incoming messages. 517 | // This method is invoked on the websocketQueue. 518 | // 519 | // For completeness, you should invoke [super didReceiveMessage:msg] in your method. 520 | 521 | // Notify delegate 522 | if ([delegate respondsToSelector:@selector(webSocket:didReceiveMessage:)]) 523 | { 524 | [delegate webSocket:self didReceiveMessage:msg]; 525 | } 526 | } 527 | 528 | - (void)didClose 529 | { 530 | HTTPLogTrace(); 531 | 532 | // Override me to perform any cleanup when the socket is closed 533 | // This method is invoked on the websocketQueue. 534 | // 535 | // Don't forget to invoke [super didClose] at the end of your method. 536 | 537 | // Notify delegate 538 | if ([delegate respondsToSelector:@selector(webSocketDidClose:)]) 539 | { 540 | [delegate webSocketDidClose:self]; 541 | } 542 | 543 | // Notify HTTPServer 544 | [[NSNotificationCenter defaultCenter] postNotificationName:WebSocketDidDieNotification object:self]; 545 | } 546 | 547 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 548 | #pragma mark AsyncSocket Delegate 549 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 550 | 551 | - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag 552 | { 553 | HTTPLogTrace(); 554 | 555 | if (tag == TAG_HTTP_REQUEST_BODY) 556 | { 557 | [self sendResponseHeaders]; 558 | [self sendResponseBody:data]; 559 | [self didOpen]; 560 | } 561 | else if (tag == TAG_PREFIX) 562 | { 563 | UInt8 *pFrame = (UInt8 *)[data bytes]; 564 | UInt8 frame = *pFrame; 565 | 566 | if (frame <= 0x7F) 567 | { 568 | [asyncSocket readDataToData:term withTimeout:TIMEOUT_NONE tag:TAG_MSG_PLUS_SUFFIX]; 569 | } 570 | else 571 | { 572 | // Unsupported frame type 573 | [self didClose]; 574 | } 575 | } 576 | else 577 | { 578 | NSUInteger msgLength = [data length] - 1; // Excluding ending 0xFF frame 579 | 580 | NSString *msg = [[NSString alloc] initWithBytes:[data bytes] length:msgLength encoding:NSUTF8StringEncoding]; 581 | 582 | [self didReceiveMessage:msg]; 583 | 584 | [msg release]; 585 | 586 | // Read next message 587 | [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PREFIX]; 588 | } 589 | } 590 | 591 | - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)error 592 | { 593 | HTTPLogTrace2(@"%@[%p]: socketDidDisconnect:withError: %@", THIS_FILE, self, error); 594 | 595 | [self didClose]; 596 | } 597 | 598 | @end 599 | --------------------------------------------------------------------------------