├── .gitignore ├── .travis.yml ├── Classes ├── Core │ ├── Component │ │ ├── HSDConsoleLogComponent.h │ │ ├── HSDConsoleLogComponent.m │ │ ├── HSDDBInspectComponent.h │ │ ├── HSDDBInspectComponent.m │ │ ├── HSDFileExplorerComponent.h │ │ ├── HSDFileExplorerComponent.m │ │ ├── HSDFilePreviewComponent.h │ │ ├── HSDFilePreviewComponent.m │ │ ├── HSDHostNameResolveComponent.h │ │ ├── HSDHostNameResolveComponent.m │ │ ├── HSDSendInfoComponent.h │ │ ├── HSDSendInfoComponent.m │ │ ├── HSDViewDebugComponent.h │ │ └── HSDViewDebugComponent.m │ ├── HSDDefine.h │ ├── HSDDefine.m │ ├── HSDDelegate.h │ ├── HSDHttpServerControlPannelController.h │ ├── HSDHttpServerControlPannelController.m │ ├── HSDManager+Project.h │ ├── HSDManager.h │ └── HSDManager.m ├── Handler │ ├── HSDRequestHandler.h │ ├── HSDRequestHandler.m │ ├── HSDWebSocketHandler.h │ └── HSDWebSocketHandler.m ├── HttpServerDebug.h ├── Middleware │ ├── HSDComponentMiddleware.h │ ├── HSDComponentMiddleware.m │ ├── HSDResponseInfo.h │ └── HSDResponseInfo.m └── Others │ ├── README.md │ ├── WebServer │ ├── Core │ │ ├── HSDGWebServer.h │ │ ├── HSDGWebServer.m │ │ ├── HSDGWebServerConnection.h │ │ ├── HSDGWebServerConnection.m │ │ ├── HSDGWebServerFunctions.h │ │ ├── HSDGWebServerFunctions.m │ │ ├── HSDGWebServerHTTPStatusCodes.h │ │ ├── HSDGWebServerPrivate.h │ │ ├── HSDGWebServerRequest.h │ │ ├── HSDGWebServerRequest.m │ │ ├── HSDGWebServerResponse.h │ │ ├── HSDGWebServerResponse.m │ │ ├── HSDGWebSocket.h │ │ └── HSDGWebSocket.m │ ├── Requests │ │ ├── HSDGWebServerDataRequest.h │ │ ├── HSDGWebServerDataRequest.m │ │ ├── HSDGWebServerFileRequest.h │ │ ├── HSDGWebServerFileRequest.m │ │ ├── HSDGWebServerMultiPartFormRequest.h │ │ ├── HSDGWebServerMultiPartFormRequest.m │ │ ├── HSDGWebServerURLEncodedFormRequest.h │ │ └── HSDGWebServerURLEncodedFormRequest.m │ └── Responses │ │ ├── HSDGWebServerDataResponse.h │ │ ├── HSDGWebServerDataResponse.m │ │ ├── HSDGWebServerErrorResponse.h │ │ ├── HSDGWebServerErrorResponse.m │ │ ├── HSDGWebServerFileResponse.h │ │ ├── HSDGWebServerFileResponse.m │ │ ├── HSDGWebServerStreamedResponse.h │ │ └── HSDGWebServerStreamedResponse.m │ └── ZipArchive │ ├── HSDZipArchive.h │ ├── HSDZipArchive.m │ └── minizip │ ├── HSDioapi.c │ ├── HSDioapi.h │ ├── HSDzip.c │ └── HSDzip.h ├── Documents └── Interfaces-CN.md ├── HttpServerDebug.podspec ├── HttpServerDebug.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ └── HttpServerDebug.xcscheme ├── LICENSE.txt ├── README.md ├── Resources └── HttpServerDebug.bundle │ └── web │ ├── components │ └── notification │ │ ├── notification.css │ │ └── notification.js │ ├── css │ └── default.css │ ├── index.css │ ├── index.html │ ├── index.js │ ├── libs │ ├── OrbitControls.min.js │ ├── Tween.js │ └── three.min.js │ ├── locals │ ├── enus.json │ └── zhcn.json │ ├── pages │ ├── console_log │ │ ├── console_log.css │ │ ├── console_log.html │ │ └── console_log.js │ ├── database_inspect │ │ ├── database_inspect.css │ │ ├── database_inspect.html │ │ └── database_inspect.js │ ├── file_explorer │ │ ├── file_explorer.css │ │ ├── file_explorer.html │ │ ├── file_explorer.js │ │ ├── file_explorer_context_menu.css │ │ ├── file_explorer_context_menu.js │ │ └── file_explorer_resize.js │ ├── send_info │ │ ├── send_info.css │ │ ├── send_info.html │ │ └── send_info.js │ ├── view_debug │ │ ├── view_debug.css │ │ ├── view_debug.html │ │ ├── view_debug.js │ │ ├── view_debug_canvas.js │ │ └── view_debug_sidebar.js │ └── web_upload │ │ └── web_upload.html │ ├── resources │ ├── XC_O_area_button_navigator.png │ ├── XC_O_utilities_button.png │ ├── db_icon_view_orient_2d.png │ ├── db_icon_view_orient_3d.png │ ├── db_icon_view_show_clipped.png │ ├── directory-icon.png │ ├── favicon.ico │ ├── file-icon.png │ └── loading-bubbles.svg │ └── util.js ├── Sample ├── Sample.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Sample │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Database │ ├── HSDSampleCategoryDataModel.h │ ├── HSDSampleCategoryDataModel.m │ ├── HSDSampleDBCategoryManager.h │ ├── HSDSampleDBCategoryManager.m │ ├── HSDSampleDBManager.h │ └── HSDSampleDBManager.m │ ├── HSDSampleAppDelegate.h │ ├── HSDSampleAppDelegate.m │ ├── HSDSampleRootController.h │ ├── HSDSampleRootController.m │ ├── Sample.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Sample.xcscheme │ ├── Supporting Files │ └── Info.plist │ ├── ViewController │ ├── HSDSampleCategoryEditController.h │ ├── HSDSampleCategoryEditController.m │ ├── HSDSampleDBInspectViewController.h │ ├── HSDSampleDBInspectViewController.m │ ├── HSDSampleHomeViewController.h │ ├── HSDSampleHomeViewController.m │ ├── HSDSampleViewDebugViewController.h │ └── HSDSampleViewDebugViewController.m │ └── main.m ├── ThirdParties └── FMDB │ ├── extra │ ├── InMemoryOnDiskIO │ │ ├── FMDatabase+InMemoryOnDiskIO.h │ │ └── FMDatabase+InMemoryOnDiskIO.m │ └── fts3 │ │ ├── FMDatabase+FTS3.h │ │ ├── FMDatabase+FTS3.m │ │ ├── FMTokenizers.h │ │ ├── FMTokenizers.m │ │ └── fts3_tokenizer.h │ └── fmdb │ ├── FMDB.h │ ├── FMDatabase.h │ ├── FMDatabase.m │ ├── FMDatabaseAdditions.h │ ├── FMDatabaseAdditions.m │ ├── FMDatabasePool.h │ ├── FMDatabasePool.m │ ├── FMDatabaseQueue.h │ ├── FMDatabaseQueue.m │ ├── FMResultSet.h │ └── FMResultSet.m └── archive.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | xcuserdata/ 3 | build/ 4 | output/ 5 | Pods/ 6 | tmp/ 7 | .vscode/ 8 | 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode9.3 3 | script: 4 | - xcodebuild -workspace Sample/Sample.xcworkspace -scheme Sample -sdk iphonesimulator -configuration Debug 5 | - bash archive.sh 6 | -------------------------------------------------------------------------------- /Classes/Core/Component/HSDConsoleLogComponent.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDConsoleLogComponent.h 3 | // HttpServerDebug 4 | // 5 | // Created by chenjun on 2018/4/10. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HSDConsoleLogComponent : NSObject 12 | 13 | /** 14 | * finish reading from stderr 15 | */ 16 | @property (nonatomic, copy) void(^readCompletionBlock)(NSString *); 17 | 18 | #pragma mark - state 19 | 20 | /** 21 | * is log output redirected 22 | */ 23 | - (BOOL)isRedirected; 24 | 25 | #pragma mark - behaviour control 26 | 27 | /** 28 | * redirect STDERR_FILENO 29 | */ 30 | - (void)redirectStandardErrorOutput; 31 | 32 | /** 33 | * reset STDERR_FILENO 34 | */ 35 | - (void)recoverStandardErrorOutput; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /Classes/Core/Component/HSDConsoleLogComponent.m: -------------------------------------------------------------------------------- 1 | // 2 | // HSDConsoleLogComponent.m 3 | // HttpServerDebug 4 | // 5 | // Created by chenjun on 2018/4/10. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | // produce and consume logs with producer and consumer model 9 | 10 | #import "HSDConsoleLogComponent.h" 11 | 12 | static int kStdErrIllegalFd = -1; // stderr illegal file descriptor value 13 | 14 | @interface HSDConsoleLogComponent () 15 | 16 | @property (nonatomic, assign) int stdErrFd; // saved origin stderr 17 | 18 | @end 19 | 20 | @implementation HSDConsoleLogComponent 21 | 22 | - (instancetype)init { 23 | self = [super init]; 24 | if (self) { 25 | self.stdErrFd = kStdErrIllegalFd; 26 | } 27 | return self; 28 | } 29 | 30 | - (void)dealloc { 31 | // remove notification observer 32 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 33 | 34 | // reset STDERR_FILENO 35 | if (self.stdErrFd != kStdErrIllegalFd) { 36 | dup2(self.stdErrFd, STDERR_FILENO); 37 | self.stdErrFd = kStdErrIllegalFd; 38 | } 39 | } 40 | 41 | - (void)redirectReadCompletionNotificationReceived:(NSNotification *)notification { 42 | // parse data 43 | NSData *data = [[notification userInfo] objectForKey:NSFileHandleNotificationDataItem]; 44 | NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 45 | 46 | if (self.readCompletionBlock) { 47 | self.readCompletionBlock(str); 48 | } 49 | 50 | // read 51 | [[notification object] performSelectorOnMainThread:@selector(readInBackgroundAndNotifyForModes:) withObject:@[NSRunLoopCommonModes] waitUntilDone:NO modes:@[NSRunLoopCommonModes]]; 52 | } 53 | 54 | #pragma mark - state 55 | 56 | - (BOOL)isRedirected { 57 | return self.stdErrFd != kStdErrIllegalFd; 58 | } 59 | 60 | #pragma mark - 61 | 62 | - (void)redirectStandardErrorOutput { 63 | if ([self isRedirected]) { 64 | return; 65 | } 66 | 67 | // save origin STDERR_FILENO with a new file descriptor 68 | self.stdErrFd = dup(STDERR_FILENO); 69 | 70 | // redirect standard error output 71 | NSPipe *stdErrPipe = [NSPipe pipe]; 72 | NSFileHandle *writingHandle= [stdErrPipe fileHandleForWriting]; 73 | int writingHandleFd = [writingHandle fileDescriptor]; 74 | NSFileHandle *readingHandle = [stdErrPipe fileHandleForReading]; 75 | dup2(writingHandleFd, STDERR_FILENO); 76 | 77 | // add notification observer 78 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(redirectReadCompletionNotificationReceived:) name:NSFileHandleReadCompletionNotification object:readingHandle]; 79 | 80 | // read 81 | [readingHandle performSelectorOnMainThread:@selector(readInBackgroundAndNotifyForModes:) withObject:@[NSRunLoopCommonModes] waitUntilDone:NO modes:@[NSRunLoopCommonModes]]; 82 | } 83 | 84 | -(void)recoverStandardErrorOutput { 85 | if (![self isRedirected]) { 86 | return; 87 | } 88 | 89 | // remove observer 90 | [[NSNotificationCenter defaultCenter] removeObserver:self name:NSFileHandleReadCompletionNotification object:nil]; 91 | 92 | // reset STDERR_FILENO 93 | if (self.stdErrFd != kStdErrIllegalFd) { 94 | dup2(self.stdErrFd, STDERR_FILENO); 95 | self.stdErrFd = kStdErrIllegalFd; 96 | } 97 | } 98 | 99 | @end 100 | -------------------------------------------------------------------------------- /Classes/Core/Component/HSDDBInspectComponent.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDDBInspectComponent.h 3 | // HttpServerDebug 4 | // 5 | // Created by chenjun on 2018/4/28. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | // TODO: decouple from cocoahttpserver 9 | 10 | #import 11 | 12 | @interface HSDDBInspectComponent : NSObject 13 | 14 | /** 15 | * all table names list 16 | */ 17 | + (NSString *)fetchTableNamesHTMLString:(NSString *)dbPath; 18 | 19 | /** 20 | * 21 | */ 22 | + (NSArray *)queryTableData:(NSString *)dbPath tableName:(NSString *)tableName; 23 | 24 | /** 25 | * 26 | */ 27 | + (NSDictionary *)queryDatabaseSchema:(NSString *)dbPath; 28 | 29 | /** 30 | * 31 | */ 32 | + (NSDictionary *)executeSQL:(NSString *)dbPath sql:(NSString *)sqlStr; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Classes/Core/Component/HSDFileExplorerComponent.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDFileExplorerComponent.h 3 | // HttpServerDebug 4 | // 5 | // Created by chenjun on 2018/4/28. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HSDFileExplorerComponent : NSObject 12 | 13 | /** 14 | * enumarate directory and construct json data 15 | * @param filePath the objective directory file path 16 | * @return json data 17 | */ 18 | + (NSArray *)constructFilesDataListInDirectory:(NSString *)filePath; 19 | 20 | /** 21 | * get file attribute 22 | */ 23 | + (NSDictionary *)constructFileAttribute:(NSString *)filePath; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /Classes/Core/Component/HSDFileExplorerComponent.m: -------------------------------------------------------------------------------- 1 | // 2 | // HSDFileExplorerComponent.m 3 | // HttpServerDebug 4 | // 5 | // Created by chenjun on 2018/4/28. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import "HSDFileExplorerComponent.h" 10 | #import "HSDManager+Project.h" 11 | 12 | @implementation HSDFileExplorerComponent 13 | 14 | /** 15 | * enumarate directory and construct json data 16 | * @param filePath the objective directory file path 17 | * @return json data 18 | */ 19 | + (NSArray *)constructFilesDataListInDirectory:(NSString *)filePath { 20 | NSMutableArray *itemList = [[NSMutableArray alloc] init]; 21 | NSFileManager *fileManager = [NSFileManager defaultManager]; 22 | NSArray *fileNames = [fileManager contentsOfDirectoryAtPath:filePath error:nil]; 23 | for (NSString *fileName in fileNames) { 24 | // files in filePath directory 25 | NSString *subPath = [filePath stringByAppendingPathComponent:fileName]; 26 | BOOL isExist; 27 | BOOL isDir; 28 | isExist = [fileManager fileExistsAtPath:subPath isDirectory:&isDir]; 29 | if (isExist) { 30 | // construct file item 31 | NSString *tmpFileName = fileName.length > 0 ? fileName : @""; 32 | subPath = subPath.length > 0 ? subPath : @""; 33 | NSDictionary *itemDict = 34 | @{ 35 | @"file_name": tmpFileName, 36 | @"file_path": subPath, 37 | @"is_directory": @(isDir) 38 | }; 39 | [itemList addObject:itemDict]; 40 | } 41 | } 42 | return itemList; 43 | } 44 | 45 | + (NSDictionary *)constructFileAttribute:(NSString *)filePath { 46 | NSFileManager *fileManager = [NSFileManager defaultManager]; 47 | // normal file, construct file attribute 48 | NSDictionary *attrs = [fileManager attributesOfItemAtPath:filePath error:nil]; 49 | NSString *fileType = [attrs objectForKey:NSFileType]; 50 | NSNumber *fileSize = [attrs objectForKey:NSFileSize]; // unit, Byte 51 | NSDate *fileModificationDate = [attrs objectForKey:NSFileModificationDate]; 52 | NSDate *fileCreationDate = [attrs objectForKey:NSFileCreationDate]; 53 | 54 | // file type 55 | fileType = fileType.length > 0 ? fileType : @""; 56 | 57 | // file size 58 | NSString *sizeStr; 59 | long size = fileSize.longValue; 60 | long KB = 1024; 61 | long MB = 1024 * 1024; 62 | long GB = 1024 * 1024 * 1024; 63 | if (size >= GB) { 64 | double num = (size * 1.0f) / GB; 65 | sizeStr = [NSString stringWithFormat:@"%.1fGB", num]; 66 | } else if (size >= MB) { 67 | double num = (size * 1.0f) / MB; 68 | sizeStr = [NSString stringWithFormat:@"%.1fMB", num]; 69 | } else if (size >= KB) { 70 | double num = (size * 1.0f) / KB; 71 | sizeStr = [NSString stringWithFormat:@"%.1fKB", num]; 72 | } else { 73 | sizeStr = [NSString stringWithFormat:@"%ldB", size]; 74 | } 75 | 76 | // file modification date 77 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 78 | [dateFormatter setDateFormat:@"yyyy/M/d H:mm"]; 79 | NSString *modificationTime = @""; 80 | if (fileModificationDate) { 81 | modificationTime = [dateFormatter stringFromDate:fileModificationDate]; 82 | } 83 | 84 | // file creation date 85 | NSString *creationTime = @""; 86 | if (fileCreationDate) { 87 | creationTime = [dateFormatter stringFromDate:fileCreationDate]; 88 | } 89 | 90 | // content type 91 | NSString *contentType = [HSDManager fetchContentTypeWithFilePathExtension:[filePath pathExtension]]; 92 | 93 | NSDictionary *json = 94 | @{ 95 | @"file_type" : fileType, 96 | @"file_size" : sizeStr, 97 | @"modification_time" : modificationTime, 98 | @"creation_time" : creationTime, 99 | @"content_type" : contentType 100 | }; 101 | return json; 102 | } 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /Classes/Core/Component/HSDFilePreviewComponent.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDFilePreviewComponent.h 3 | // HttpServerDebug 4 | // 5 | // Created by chenjun on 2018/4/28. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HSDFilePreviewComponent : NSObject 12 | 13 | /** 14 | * return contents of standardUserDefaults 15 | */ 16 | + (NSData *)fetchContentsOfStandardUserDefaults; 17 | 18 | /** 19 | * return contents of file 20 | */ 21 | + (NSData *)fetchContentsWithFilePath:(NSString *)filePath contentType:(NSString **)contentType; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Classes/Core/Component/HSDFilePreviewComponent.m: -------------------------------------------------------------------------------- 1 | // 2 | // HSDFilePreviewComponent.m 3 | // HttpServerDebug 4 | // 5 | // Created by chenjun on 2018/4/28. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import "HSDFilePreviewComponent.h" 10 | #import "HSDZipArchive.h" 11 | #import "HSDManager+Project.h" 12 | 13 | @implementation HSDFilePreviewComponent 14 | 15 | + (NSData *)fetchContentsOfStandardUserDefaults { 16 | NSDictionary *dict = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]; 17 | NSDictionary *serializableDict = [self fetchSerializableDictionary:dict]; 18 | NSError *error; 19 | NSData *data = [NSJSONSerialization dataWithJSONObject:serializableDict options:(NSJSONWritingPrettyPrinted) error:&error]; 20 | return data; 21 | } 22 | 23 | /** 24 | * get dictionary, that is valid for [NSJSONSerialization dataWithJSONObject:options:error:]; 25 | */ 26 | + (NSDictionary *)fetchSerializableDictionary:(NSDictionary *)dict { 27 | NSMutableDictionary *retDict = [[NSMutableDictionary alloc] init]; 28 | NSArray *allKeys = [dict allKeys]; 29 | for (id ele in allKeys) { 30 | if ([ele isKindOfClass:[NSString class]]) { 31 | // valid key 32 | id value = [dict objectForKey:ele]; 33 | id val = [self fetchSerializableObject:value]; 34 | if (val) { 35 | [retDict setObject:val forKey:ele]; 36 | } 37 | } 38 | } 39 | return retDict; 40 | } 41 | 42 | /** 43 | * get arrary, that is valid for [NSJSONSerialization dataWithJSONObject:options:error:]; 44 | */ 45 | + (NSArray *)fetchSerializableArray:(NSArray *)arr { 46 | NSMutableArray *retArr = [[NSMutableArray alloc] init]; 47 | for (id value in arr) { 48 | id val = [self fetchSerializableObject:value]; 49 | if (val) { 50 | [retArr addObject:val];; 51 | } 52 | } 53 | return retArr; 54 | } 55 | 56 | /** 57 | * get object, that is valid for [NSJSONSerialization dataWithJSONObject:options:error:]; 58 | */ 59 | + (id)fetchSerializableObject:(id)value { 60 | id retVal; 61 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 62 | [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss zzz"]; 63 | 64 | if ([value isKindOfClass:[NSString class]] 65 | || [value isKindOfClass:[NSNumber class]]) { 66 | // NSString or NSNumber value 67 | retVal = value; 68 | } else if ([value isKindOfClass:[NSData class]]) { 69 | // NSDate value 70 | NSString *str = [[NSString alloc] initWithData:value encoding:(NSASCIIStringEncoding)]; 71 | if (str) { 72 | retVal = str; 73 | } 74 | } else if ([value isKindOfClass:[NSDate class]]) { 75 | // NSDate value 76 | NSString *dateStr = [dateFormatter stringFromDate:(NSDate *)value]; 77 | retVal = dateStr; 78 | } else if ([value isKindOfClass:[NSDictionary class]]) { 79 | // NSDictionary value 80 | NSDictionary *dictVal = [self fetchSerializableDictionary:(NSDictionary *)value]; 81 | if (dictVal) { 82 | retVal = dictVal; 83 | } 84 | } else if ([value isKindOfClass:[NSArray class]]) { 85 | // NSArray value 86 | NSArray *arrVal = [self fetchSerializableArray:(NSArray *)value]; 87 | if (arrVal) { 88 | retVal = arrVal; 89 | } 90 | } 91 | return retVal; 92 | } 93 | 94 | + (NSData *)fetchContentsWithFilePath:(NSString *)filePath contentType:(NSString **)contentType { 95 | NSData *data; 96 | // generate response data 97 | if (![filePath hasPrefix:@"/"]) { 98 | // relative path, get full path 99 | NSString *firstPathComp = [[filePath pathComponents] firstObject]; 100 | NSString *remainPath = [filePath substringFromIndex:firstPathComp.length]; 101 | if ([firstPathComp isEqualToString:@"Documents"]) { 102 | NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; 103 | filePath = [documents stringByAppendingPathComponent:remainPath]; 104 | } else if ([firstPathComp isEqualToString:@"Library"]) { 105 | NSString *library = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject]; 106 | filePath = [library stringByAppendingPathComponent:remainPath]; 107 | } else if ([firstPathComp isEqualToString:@"tmp"]) { 108 | NSString *tmp = NSTemporaryDirectory(); 109 | filePath = [tmp stringByAppendingPathComponent:remainPath]; 110 | } else { 111 | filePath = @""; 112 | } 113 | } 114 | 115 | // file or directory 116 | BOOL isDirectory; 117 | BOOL isExist = [[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory]; 118 | 119 | NSString *fileContentType; 120 | 121 | if (isExist) { 122 | if (isDirectory) { 123 | // request directory, zip archive directory and response 124 | NSString *tmpFileName = [NSString stringWithFormat:@"hsd_file_preview_%@.zip", filePath.lastPathComponent]; 125 | NSString *tmpPath = [NSTemporaryDirectory() stringByAppendingPathComponent:tmpFileName]; 126 | [HSDZipArchive createZipFileAtPath:tmpPath withContentsOfDirectory:filePath keepParentDirectory:YES]; 127 | data = [[NSData alloc] initWithContentsOfFile:tmpPath]; 128 | 129 | // content type 130 | NSString *fileExtension = tmpPath.pathExtension; 131 | fileContentType = [HSDManager fetchContentTypeWithFilePathExtension:fileExtension]; 132 | 133 | // clean tmp file 134 | [[NSFileManager defaultManager] removeItemAtPath:tmpPath error:nil]; 135 | } else { 136 | // request file 137 | data = [[NSData alloc] initWithContentsOfFile:filePath]; 138 | 139 | // content type 140 | NSString *fileExtension = filePath.pathExtension; 141 | fileContentType = [HSDManager fetchContentTypeWithFilePathExtension:fileExtension]; 142 | } 143 | } 144 | *contentType = fileContentType; 145 | return data; 146 | } 147 | 148 | @end 149 | -------------------------------------------------------------------------------- /Classes/Core/Component/HSDHostNameResolveComponent.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDHostNameResolveComponent.h 3 | // HttpServerDebug 4 | // 5 | // Created by chenjun on 2018/4/27. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | // used to resolve HttpServerDebug host name 9 | 10 | #import 11 | #import "HSDManager+Project.h" 12 | 13 | @interface HSDHostNameResolveComponent : NSObject 14 | 15 | /** 16 | * 17 | */ 18 | - (void)resolveHostName:(HSDHostNameResolveBlock)block; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Classes/Core/Component/HSDHostNameResolveComponent.m: -------------------------------------------------------------------------------- 1 | // 2 | // HSDHostNameResolveComponent.m 3 | // HttpServerDebug 4 | // 5 | // Created by chenjun on 2018/4/27. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import "HSDHostNameResolveComponent.h" 10 | #import 11 | 12 | @interface HSDHostNameResolveComponent () 13 | 14 | 15 | @property (strong, nonatomic) NSNetService *netService; // used to resolve hostname 16 | @property (assign, nonatomic) BOOL isResolving; 17 | @property (strong, nonatomic) HSDHostNameResolveBlock resolveBlock; 18 | 19 | @end 20 | 21 | @implementation HSDHostNameResolveComponent 22 | 23 | - (instancetype)init { 24 | self = [super init]; 25 | if (self) { 26 | self.isResolving = NO; 27 | } 28 | return self; 29 | } 30 | 31 | - (void)resolveHostName:(HSDHostNameResolveBlock)block { 32 | if (self.isResolving) { 33 | [self.netService stop]; 34 | } 35 | self.resolveBlock = block; 36 | 37 | NSString *name = [HSDManager fetchHttpServerName]; 38 | self.netService = [[NSNetService alloc] initWithDomain:@"local." type:@"_http._tcp" name:name]; 39 | self.netService.delegate = self; 40 | [self.netService resolveWithTimeout:5]; 41 | } 42 | 43 | #pragma mark - NSNetServiceDelegate 44 | 45 | - (void)netServiceWillResolve:(NSNetService *)sender { 46 | self.isResolving = YES; 47 | if (self.resolveBlock) { 48 | self.resolveBlock(HSDHostNameResolveStateReady, nil, nil); 49 | } 50 | } 51 | 52 | - (void)netServiceDidResolveAddress:(NSNetService *)sender { 53 | if (self.resolveBlock) { 54 | NSMutableArray *results = [[NSMutableArray alloc] init]; 55 | NSInteger port = sender.port; 56 | 57 | // parse ip address 58 | NSArray *addresses = sender.addresses; 59 | char addressBuffer[INET6_ADDRSTRLEN]; 60 | for (NSData *data in addresses) { 61 | memset(addressBuffer, 0, INET6_ADDRSTRLEN); 62 | typedef union { 63 | struct sockaddr sa; 64 | struct sockaddr_in ipv4; 65 | struct sockaddr_in6 ipv6; 66 | } ip_socket_address; 67 | 68 | ip_socket_address *socketAddress = (ip_socket_address *)[data bytes]; 69 | if (socketAddress) { 70 | sa_family_t saFamily = socketAddress->sa.sa_family; 71 | if (saFamily == AF_INET) { 72 | // filter only ipv4 address 73 | void *src = (void *)&(socketAddress->ipv4.sin_addr); 74 | const char *addressCStr = inet_ntop(saFamily, src, addressBuffer, sizeof(addressBuffer)); 75 | NSString *addressStr = [[NSString alloc] initWithCString:addressCStr encoding:NSUTF8StringEncoding]; 76 | if (addressStr.length > 0) { 77 | NSString *tmp = [[NSString alloc] initWithFormat:@"http://%@:%ld", addressStr, (long)port]; 78 | [results addObject:tmp]; 79 | } 80 | } 81 | } 82 | } 83 | // parse host name 84 | NSString *hostName = sender.hostName; 85 | if (hostName.length > 0) { 86 | NSString *tmp = [[NSString alloc] initWithFormat:@"http://%@:%ld", hostName, (long)port]; 87 | [results addObject:tmp]; 88 | } 89 | self.resolveBlock(HSDHostNameResolveStateSuccess, results, nil); 90 | } 91 | } 92 | 93 | - (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict { 94 | if (self.resolveBlock) { 95 | self.resolveBlock(HSDHostNameResolveStateFail, nil, errorDict); 96 | } 97 | [sender stop]; 98 | } 99 | 100 | - (void)netServiceDidStop:(NSNetService *)sender { 101 | self.isResolving = NO; 102 | if (self.resolveBlock) { 103 | self.resolveBlock(HSDHostNameResolveStateStop, nil, nil); 104 | } 105 | 106 | self.netService = nil; 107 | } 108 | 109 | @end 110 | -------------------------------------------------------------------------------- /Classes/Core/Component/HSDSendInfoComponent.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDSendInfoComponent.h 3 | // HttpServerDebug 4 | // 5 | // Created by chenjun on 2018/4/28. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HSDSendInfoComponent : NSObject 12 | 13 | /** 14 | * 15 | */ 16 | + (NSDictionary *)fetchResultWithInfo:(NSString *)info; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Classes/Core/Component/HSDSendInfoComponent.m: -------------------------------------------------------------------------------- 1 | // 2 | // HSDSendInfoComponent.m 3 | // HttpServerDebug 4 | // 5 | // Created by chenjun on 2018/4/28. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import "HSDSendInfoComponent.h" 10 | #import "HSDManager+Project.h" 11 | #import "HSDDelegate.h" 12 | 13 | @implementation HSDSendInfoComponent 14 | 15 | + (NSDictionary *)fetchResultWithInfo:(NSString *)info { 16 | NSDictionary *responseDict; 17 | // forward to the delegate 18 | id delegate = [HSDManager fetchHSDDelegate]; 19 | if ([delegate respondsToSelector:@selector(onHSDReceiveInfo:)]) { 20 | responseDict = [delegate onHSDReceiveInfo:info]; 21 | } 22 | return responseDict; 23 | } 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /Classes/Core/Component/HSDViewDebugComponent.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDViewDebugComponent.h 3 | // HttpServerDebug 4 | // 5 | // Created by chenjun on 2018/4/28. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface HSDViewDebugComponent : NSObject 13 | 14 | /** 15 | * get all views data 16 | */ 17 | + (NSArray *)fetchAllViewsDataInHierarchy; 18 | 19 | /** 20 | * get snapshot 21 | * @param view target view 22 | * @param isSubviewsExcluding snapshot target view with or without subviews 23 | */ 24 | + (NSData *)snapshotImageData:(UIView *)view isSubviewsExcluding:(BOOL)isSubviewsExcluding clippedFrame:(CGRect)clippedFrame; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Classes/Core/HSDDefine.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDDefine.h 3 | // HttpServerDebug 4 | // 5 | // Created by chenjun on 22/07/2017. 6 | // Copyright © 2017 Baidu Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | // http request path 12 | extern NSString *const kHSDComponentFileExplorer; 13 | extern NSString *const kHSDComponentDBInspect; 14 | extern NSString *const kHSDComponentSendInfo; 15 | extern NSString *const kHSDComponentFilePreview; 16 | extern NSString *const kHSDComponentViewDebug; 17 | extern NSString *const kHSDComponentConsoleLog; 18 | 19 | extern NSString *const kHSDMarkFormatString; // mark format strings 20 | extern NSString *const kHSDMarkLocalizationString; // mark localization strings 21 | 22 | extern NSString *const kHSDUserDefaultsKeyAutoStart; // should hsd start automatically 23 | extern NSString *const kHSDUserDefaultsKeyServerPort; // server port 24 | 25 | // user setting max and min port number 26 | extern const UInt16 kHSDServerPortUserSettingMin; 27 | extern const UInt16 kHSDServerPostUserSettingMax; 28 | -------------------------------------------------------------------------------- /Classes/Core/HSDDefine.m: -------------------------------------------------------------------------------- 1 | // 2 | // HSDDefine.m 3 | // HttpServerDebug 4 | // 5 | // Created by chenjun on 22/07/2017. 6 | // Copyright © 2017 Baidu Inc. All rights reserved. 7 | // 8 | 9 | #import "HSDDefine.h" 10 | 11 | NSString *const kHSDComponentFileExplorer = @"file_explorer"; 12 | NSString *const kHSDComponentDBInspect = @"database_inspect"; 13 | NSString *const kHSDComponentSendInfo = @"send_info"; 14 | NSString *const kHSDComponentFilePreview = @"file_preview"; 15 | NSString *const kHSDComponentViewDebug = @"view_debug"; 16 | NSString *const kHSDComponentConsoleLog = @"console_log"; 17 | 18 | NSString *const kHSDMarkFormatString = @"@@"; 19 | NSString *const kHSDMarkLocalizationString = @"%%"; 20 | 21 | NSString *const kHSDUserDefaultsKeyAutoStart = @"hsd_userdefaultskey_is_started_automatically"; 22 | NSString *const kHSDUserDefaultsKeyServerPort = @"hsd_userdefaultskey_server_port"; 23 | 24 | const UInt16 kHSDServerPortUserSettingMin = 1024; 25 | const UInt16 kHSDServerPostUserSettingMax = 65535; 26 | -------------------------------------------------------------------------------- /Classes/Core/HSDDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDDelegate.h 3 | // HttpServerDebug 4 | // 5 | // Created by chenjun on 2017/12/26. 6 | // Copyright © 2017年 chenjun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol HSDDelegate 12 | 13 | @optional 14 | 15 | /** 16 | * send information to app 17 | * @param info information 18 | * @return results, returned in response data 19 | */ 20 | - (NSDictionary *)onHSDReceiveInfo:(NSString *)info; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Classes/Core/HSDHttpServerControlPannelController.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDHttpServerControlPannelController.h 3 | // HttpServerDebug 4 | // 5 | // Created by chenjun on 18/07/2017. 6 | // Copyright © 2017 Baidu Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HSDHttpServerControlPannelController : UIViewController 12 | 13 | /** 14 | * Actions when user press back button. 15 | * If not assigned, [self.navigationController popViewControllerAnimated:YES] will be executed. 16 | */ 17 | @property (nonatomic, strong) dispatch_block_t backBlock; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Classes/Core/HSDManager+Project.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDManager+Project.h 3 | // HttpServerDebug 4 | // 5 | // Created by chenjun on 2018/4/27. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import "HSDManager.h" 10 | @class HSDConsoleLogComponent; 11 | @class HSDHostNameResolveComponent; 12 | @class HSDViewDebugComponent; 13 | @class HSDDBInspectComponent; 14 | @class HSDFileExplorerComponent; 15 | @class HSDSendInfoComponent; 16 | @class HSDFilePreviewComponent; 17 | 18 | @interface HSDManager (Project) 19 | 20 | + (void)updateHttpServerName:(NSString *)name; 21 | 22 | + (NSString *)fetchHttpServerName; 23 | 24 | + (NSString *)fetchDefaultInspectDBFilePath; 25 | 26 | + (id)fetchHSDDelegate; 27 | 28 | + (NSString *)fetchWebUploadDirectoryPath; 29 | 30 | /** 31 | * get web root path 32 | */ 33 | + (NSString *)fetchDocumentRoot; 34 | 35 | /** 36 | * Content-Type according to file extension, default return value text/plain;charset=utf-8 37 | */ 38 | + (NSString *)fetchContentTypeWithFilePathExtension:(NSString *)pathExtension; 39 | 40 | /** 41 | * get the object of one specific memory address 42 | */ 43 | + (id)instanceOfMemoryAddress:(NSString *)memoryAddress; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Classes/Core/HSDManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDManager.h 3 | // HttpServerDebug 4 | // 5 | // Created by chenjun on 07/07/2017. 6 | // Copyright © 2017 Baidu Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol HSDDelegate; 12 | 13 | // notification name 14 | extern NSString *kHSDNotificationServerStarted; // hsd started 15 | extern NSString *kHSDNotificationServerStopped; // hsd stopped 16 | 17 | // host name resolving state 18 | typedef NS_ENUM(NSUInteger, HSDHostNameResolveState) { 19 | HSDHostNameResolveStateReady, 20 | HSDHostNameResolveStateSuccess, 21 | HSDHostNameResolveStateFail, 22 | HSDHostNameResolveStateStop 23 | }; 24 | 25 | /** 26 | * host name resolving callback 27 | * @param state host name resolving state 28 | * @param results all candidates 29 | */ 30 | typedef void(^HSDHostNameResolveBlock)(HSDHostNameResolveState state, NSArray *results, NSDictionary *errorDict); 31 | 32 | @interface HSDManager : NSObject 33 | 34 | /** 35 | * set the default db file path, that you can inspect when click the db inspect entrance in the index.html 36 | */ 37 | + (void)updateDefaultInspectDBFilePath:(NSString *)path; 38 | 39 | /** 40 | * set the delegate, that implements hsd's delegate protocol 41 | */ 42 | + (void)updateHSDDelegate:(id)delegate; 43 | 44 | /** 45 | * Call before starting http server, if you need to set the port. Otherwise, server serves on a random port. 46 | * User setting from control pannel have higher priority than setting with this method. 47 | * @param port port number, interval [1024, 65535]. 48 | */ 49 | + (void)updateHttpServerPort:(NSUInteger)port; 50 | 51 | /** 52 | * 1024~65535 53 | */ 54 | + (NSUInteger)fetchHttpServerPort; 55 | 56 | /** 57 | * is hsd started 58 | */ 59 | + (BOOL)isHttpServerRunning; 60 | 61 | /** 62 | * start hsd 63 | */ 64 | + (void)startHttpServer; 65 | 66 | /** 67 | * stop hsd 68 | */ 69 | + (void)stopHttpServer; 70 | 71 | /** 72 | * 73 | */ 74 | + (void)resolveHostName:(HSDHostNameResolveBlock)block; 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /Classes/Handler/HSDRequestHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDRequestHandler.h 3 | // HttpServerDebug 4 | // 5 | // Created by 陈军 on 2018/11/5. 6 | // Copyright © 2018 chenjun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class HSDGWebServerRequest; 12 | @class HSDGWebServerResponse; 13 | 14 | @interface HSDRequestHandler : NSObject 15 | 16 | + (HSDGWebServerResponse *)handleRequest:(HSDGWebServerRequest *)request; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Classes/Handler/HSDWebSocketHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDWebSocketHandler.h 3 | // HttpServerDebug 4 | // 5 | // Created by jam.chenjun on 2019/5/31. 6 | // Copyright © 2019 chenjun. All rights reserved. 7 | // 8 | 9 | #import "HSDGWebSocket.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface HSDWebSocketHandler : HSDGWebSocket 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Classes/Handler/HSDWebSocketHandler.m: -------------------------------------------------------------------------------- 1 | // 2 | // HSDWebSocketHandler.m 3 | // HttpServerDebug 4 | // 5 | // Created by jam.chenjun on 2019/5/31. 6 | // Copyright © 2019 chenjun. All rights reserved. 7 | // 8 | 9 | #import "HSDWebSocketHandler.h" 10 | #import "HSDComponentMiddleware.h" 11 | 12 | @implementation HSDWebSocketHandler 13 | 14 | - (void)didOpen { 15 | // redirect stderr 16 | [HSDComponentMiddleware consoleLogRedirectStandardErrorOutput:^(NSString *logStr) { 17 | [self sendMessage:logStr]; 18 | }]; 19 | } 20 | 21 | - (void)didReceiveMessage:(NSString *)msg { 22 | NSLog(@"HSDWEBSOCKET: didReceiveMessage: %@", msg); 23 | } 24 | 25 | - (void)didClose { 26 | // reset stderr 27 | [HSDComponentMiddleware consoleLogRecoverStandardErrorOutput]; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Classes/HttpServerDebug.h: -------------------------------------------------------------------------------- 1 | // 2 | // HttpServerDebug.h 3 | // HttpServerDebug 4 | // 5 | // Created by chenjun on 11/08/2017. 6 | // Copyright © 2017 chenjun. All rights reserved. 7 | // 8 | 9 | #ifndef HttpServerDebug_h 10 | #define HttpServerDebug_h 11 | 12 | #import "HSDManager.h" 13 | #import "HSDHttpServerControlPannelController.h" 14 | #import "HSDDelegate.h" 15 | 16 | #endif /* HttpServerDebug_h */ 17 | -------------------------------------------------------------------------------- /Classes/Middleware/HSDComponentMiddleware.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDComponentMiddleware.h 3 | // HttpServerDebug 4 | // 5 | // Created by chenjun on 2018/5/27. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | // middleware between the http server and the core ability 9 | 10 | #import 11 | 12 | @class HSDResponseInfo; 13 | 14 | @interface HSDComponentMiddleware : NSObject 15 | 16 | #pragma mark - File Explorer 17 | 18 | /** 19 | * request data 20 | */ 21 | + (HSDResponseInfo *)fetchFileExplorerAPIResponseInfo:(NSDictionary *)params; 22 | 23 | /** 24 | * move the uploaded file, from the temporary path to the target path 25 | * @param temporaryPath the uploaded file's temporay path 26 | * @param targetDirectory the target directory 27 | * @param targetFileName the target file name 28 | * @return response info 29 | */ 30 | + (HSDResponseInfo *)uploadTemporaryFile:(NSString *)temporaryPath targetDirectory:(NSString *)targetDirectory fileName:(NSString *)targetFileName; 31 | 32 | #pragma mark - Database Inspect 33 | 34 | + (NSDictionary *)fetchDatabaseAPITemplateHTMLReplacement:(NSDictionary *)params; 35 | 36 | /** 37 | * request table data, database schema; execute sql 38 | */ 39 | + (HSDResponseInfo *)fetchDatabaseAPIResponseInfo:(NSDictionary *)params; 40 | 41 | #pragma mark - View Debug 42 | 43 | /** 44 | * view debug 45 | */ 46 | + (HSDResponseInfo *)fetchViewDebugAPIResponseInfo:(NSDictionary *)params; 47 | 48 | #pragma mark - Send Info 49 | 50 | + (HSDResponseInfo *)fetchSendInfoAPIResponseInfo:(NSString *)infoStr; 51 | 52 | #pragma mark - File Preview 53 | 54 | + (HSDResponseInfo *)fetchFilePreviewResponseInfo:(NSDictionary *)params; 55 | 56 | #pragma mark - Console Log 57 | 58 | /** 59 | * redirect STDERR_FILENO 60 | */ 61 | + (void)consoleLogRedirectStandardErrorOutput:(void(^)(NSString *))readCompletionBlock; 62 | 63 | /** 64 | * reset STDERR_FILENO 65 | */ 66 | + (void)consoleLogRecoverStandardErrorOutput; 67 | 68 | #pragma mark - 69 | 70 | /** 71 | * 72 | */ 73 | + (NSString *)formatTemplateString:(NSString *)str variables:(NSDictionary *)variables; 74 | 75 | #pragma mark - localization 76 | 77 | /** 78 | * localize text, content needs to be localized is marked in style: %%LocalizedLanguageName%% 79 | */ 80 | + (NSString *)localize:(NSString *)local text:(NSString *)text; 81 | 82 | /** 83 | * get localized json 84 | */ 85 | + (NSDictionary *)localizationJSON:(NSString *)local; 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /Classes/Middleware/HSDResponseInfo.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDResponseInfo.h 3 | // HttpServerDebug 4 | // 5 | // Created by 陈军 on 2018/11/5. 6 | // Copyright © 2018 chenjun. All rights reserved. 7 | // 8 | // http server framework independent data model, representing http response information 9 | 10 | #import 11 | 12 | @interface HSDResponseInfo : NSObject 13 | 14 | @property (nonatomic, strong) NSData *data; 15 | @property (nonatomic, copy) NSString *contentType; 16 | 17 | - (instancetype)init; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Classes/Middleware/HSDResponseInfo.m: -------------------------------------------------------------------------------- 1 | // 2 | // HSDResponseInfo.m 3 | // HttpServerDebug 4 | // 5 | // Created by 陈军 on 2018/11/5. 6 | // Copyright © 2018 chenjun. All rights reserved. 7 | // 8 | 9 | #import "HSDResponseInfo.h" 10 | 11 | @implementation HSDResponseInfo 12 | 13 | - (instancetype)init { 14 | self = [super init]; 15 | if (self) { 16 | self.data = nil; 17 | self.contentType = nil; 18 | } 19 | return self; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Classes/Others/README.md: -------------------------------------------------------------------------------- 1 | This folder contains third party libraries, but has diverged from the upstream. 2 | 3 | WebServer derived from GCDWebServer 4 | 5 | ZipArchive derived from SSZipArchive 6 | -------------------------------------------------------------------------------- /Classes/Others/WebServer/Core/HSDGWebServerConnection.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "HSDGWebServer.h" 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | @class HSDGWebServerHandler; 33 | 34 | /** 35 | * The GCDWebServerConnection class is instantiated by GCDWebServer to handle 36 | * each new HTTP connection. Each instance stays alive until the connection is 37 | * closed. 38 | * 39 | * You cannot use this class directly, but it is made public so you can 40 | * subclass it to override some hooks. Use the GCDWebServerOption_ConnectionClass 41 | * option for GCDWebServer to install your custom subclass. 42 | * 43 | * @warning The GCDWebServerConnection retains the GCDWebServer until the 44 | * connection is closed. 45 | */ 46 | @interface HSDGWebServerConnection : NSObject 47 | 48 | /** 49 | * Returns the GCDWebServer that owns the connection. 50 | */ 51 | @property(nonatomic, readonly) HSDGWebServer* server; 52 | 53 | /** 54 | * Returns the address of the local peer (i.e. server) of the connection 55 | * as a raw "struct sockaddr". 56 | */ 57 | @property(nonatomic, readonly) NSData* localAddressData; 58 | 59 | /** 60 | * Returns the address of the local peer (i.e. server) of the connection 61 | * as a string. 62 | */ 63 | @property(nonatomic, readonly) NSString* localAddressString; 64 | 65 | /** 66 | * Returns the address of the remote peer (i.e. client) of the connection 67 | * as a raw "struct sockaddr". 68 | */ 69 | @property(nonatomic, readonly) NSData* remoteAddressData; 70 | 71 | /** 72 | * Returns the address of the remote peer (i.e. client) of the connection 73 | * as a string. 74 | */ 75 | @property(nonatomic, readonly) NSString* remoteAddressString; 76 | 77 | /** 78 | * Returns the total number of bytes received from the remote peer (i.e. client) 79 | * so far. 80 | */ 81 | @property(nonatomic, readonly) NSUInteger totalBytesRead; 82 | 83 | /** 84 | * Returns the total number of bytes sent to the remote peer (i.e. client) so far. 85 | */ 86 | @property(nonatomic, readonly) NSUInteger totalBytesWritten; 87 | 88 | @end 89 | 90 | /** 91 | * Hooks to customize the behavior of GCDWebServer HTTP connections. 92 | * 93 | * @warning These methods can be called on any GCD thread. 94 | * Be sure to also call "super" when overriding them. 95 | */ 96 | @interface HSDGWebServerConnection (Subclassing) 97 | 98 | /** 99 | * This method is called when the connection is opened. 100 | * 101 | * Return NO to reject the connection e.g. after validating the local 102 | * or remote address. 103 | */ 104 | - (BOOL)open; 105 | 106 | /** 107 | * This method is called whenever data has been received 108 | * from the remote peer (i.e. client). 109 | * 110 | * @warning Do not attempt to modify this data. 111 | */ 112 | - (void)didReadBytes:(const void*)bytes length:(NSUInteger)length; 113 | 114 | /** 115 | * This method is called whenever data has been sent 116 | * to the remote peer (i.e. client). 117 | * 118 | * @warning Do not attempt to modify this data. 119 | */ 120 | - (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length; 121 | 122 | /** 123 | * Assuming a valid HTTP request was received, this method is called before 124 | * the request is processed. 125 | * 126 | * Return a non-nil GCDWebServerResponse to bypass the request processing entirely. 127 | * 128 | * The default implementation checks for HTTP authentication if applicable 129 | * and returns a barebone 401 status code response if authentication failed. 130 | */ 131 | - (nullable HSDGWebServerResponse*)preflightRequest:(HSDGWebServerRequest*)request; 132 | 133 | /** 134 | * Assuming a valid HTTP request was received and -preflightRequest: returned nil, 135 | * this method is called to process the request by executing the handler's 136 | * process block. 137 | */ 138 | - (void)processRequest:(HSDGWebServerRequest*)request completion:(HSDGWebServerCompletionBlock)completion; 139 | 140 | /** 141 | * Assuming a valid HTTP request was received and either -preflightRequest: 142 | * or -processRequest:completion: returned a non-nil GCDWebServerResponse, 143 | * this method is called to override the response. 144 | * 145 | * You can either modify the current response and return it, or return a 146 | * completely new one. 147 | * 148 | * The default implementation replaces any response matching the "ETag" or 149 | * "Last-Modified-Date" header of the request by a barebone "Not-Modified" (304) 150 | * one. 151 | */ 152 | - (HSDGWebServerResponse*)overrideResponse:(HSDGWebServerResponse*)response forRequest:(HSDGWebServerRequest*)request; 153 | 154 | /** 155 | * This method is called if any error happens while validing or processing 156 | * the request or if no GCDWebServerResponse was generated during processing. 157 | * 158 | * @warning If the request was invalid (e.g. the HTTP headers were malformed), 159 | * the "request" argument will be nil. 160 | */ 161 | - (void)abortRequest:(nullable HSDGWebServerRequest*)request withStatusCode:(NSInteger)statusCode; 162 | 163 | /** 164 | * Called when the connection is closed. 165 | */ 166 | - (void)close; 167 | 168 | @end 169 | 170 | NS_ASSUME_NONNULL_END 171 | -------------------------------------------------------------------------------- /Classes/Others/WebServer/Core/HSDGWebServerFunctions.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | /** 37 | * Converts a file extension to the corresponding MIME type. 38 | * If there is no match, "application/octet-stream" is returned. 39 | * 40 | * Overrides allow to customize the built-in mapping from extensions to MIME 41 | * types. Keys of the dictionary must be lowercased file extensions without 42 | * the period, and the values must be the corresponding MIME types. 43 | */ 44 | NSString* HSDGWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary* _Nullable overrides); 45 | 46 | /** 47 | * Add percent-escapes to a string so it can be used in a URL. 48 | * The legal characters ":@/?&=+" are also escaped to ensure compatibility 49 | * with URL encoded forms and URL queries. 50 | */ 51 | NSString* _Nullable HSDGWebServerEscapeURLString(NSString* string); 52 | 53 | /** 54 | * Unescapes a URL percent-encoded string. 55 | */ 56 | NSString* _Nullable HSDGWebServerUnescapeURLString(NSString* string); 57 | 58 | /** 59 | * Extracts the unescaped names and values from an 60 | * "application/x-www-form-urlencoded" form. 61 | * http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 62 | */ 63 | NSDictionary* HSDGWebServerParseURLEncodedForm(NSString* form); 64 | 65 | /** 66 | * On OS X, returns the IPv4 or IPv6 address as a string of the primary 67 | * connected service or nil if not available. 68 | * 69 | * On iOS, returns the IPv4 or IPv6 address as a string of the WiFi 70 | * interface if connected or nil otherwise. 71 | */ 72 | NSString* _Nullable HSDGWebServerGetPrimaryIPAddress(BOOL useIPv6); 73 | 74 | /** 75 | * Converts a date into a string using RFC822 formatting. 76 | * https://tools.ietf.org/html/rfc822#section-5 77 | * https://tools.ietf.org/html/rfc1123#section-5.2.14 78 | */ 79 | NSString* HSDGWebServerFormatRFC822(NSDate* date); 80 | 81 | /** 82 | * Converts a RFC822 formatted string into a date. 83 | * https://tools.ietf.org/html/rfc822#section-5 84 | * https://tools.ietf.org/html/rfc1123#section-5.2.14 85 | * 86 | * @warning Timezones other than GMT are not supported by this function. 87 | */ 88 | NSDate* _Nullable HSDGWebServerParseRFC822(NSString* string); 89 | 90 | /** 91 | * Converts a date into a string using IOS 8601 formatting. 92 | * http://tools.ietf.org/html/rfc3339#section-5.6 93 | */ 94 | NSString* HSDGWebServerFormatISO8601(NSDate* date); 95 | 96 | /** 97 | * Converts a ISO 8601 formatted string into a date. 98 | * http://tools.ietf.org/html/rfc3339#section-5.6 99 | * 100 | * @warning Only "calendar" variant is supported at this time and timezones 101 | * other than GMT are not supported either. 102 | */ 103 | NSDate* _Nullable HSDGWebServerParseISO8601(NSString* string); 104 | 105 | #ifdef __cplusplus 106 | } 107 | #endif 108 | 109 | NS_ASSUME_NONNULL_END 110 | -------------------------------------------------------------------------------- /Classes/Others/WebServer/Core/HSDGWebServerHTTPStatusCodes.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 29 | // http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml 30 | 31 | #import 32 | 33 | /** 34 | * Convenience constants for "informational" HTTP status codes. 35 | */ 36 | typedef NS_ENUM(NSInteger, GCDWebServerInformationalHTTPStatusCode) { 37 | kGCDWebServerHTTPStatusCode_Continue = 100, 38 | kGCDWebServerHTTPStatusCode_SwitchingProtocols = 101, 39 | kGCDWebServerHTTPStatusCode_Processing = 102 40 | }; 41 | 42 | /** 43 | * Convenience constants for "successful" HTTP status codes. 44 | */ 45 | typedef NS_ENUM(NSInteger, GCDWebServerSuccessfulHTTPStatusCode) { 46 | kGCDWebServerHTTPStatusCode_OK = 200, 47 | kGCDWebServerHTTPStatusCode_Created = 201, 48 | kGCDWebServerHTTPStatusCode_Accepted = 202, 49 | kGCDWebServerHTTPStatusCode_NonAuthoritativeInformation = 203, 50 | kGCDWebServerHTTPStatusCode_NoContent = 204, 51 | kGCDWebServerHTTPStatusCode_ResetContent = 205, 52 | kGCDWebServerHTTPStatusCode_PartialContent = 206, 53 | kGCDWebServerHTTPStatusCode_MultiStatus = 207, 54 | kGCDWebServerHTTPStatusCode_AlreadyReported = 208 55 | }; 56 | 57 | /** 58 | * Convenience constants for "redirection" HTTP status codes. 59 | */ 60 | typedef NS_ENUM(NSInteger, GCDWebServerRedirectionHTTPStatusCode) { 61 | kGCDWebServerHTTPStatusCode_MultipleChoices = 300, 62 | kGCDWebServerHTTPStatusCode_MovedPermanently = 301, 63 | kGCDWebServerHTTPStatusCode_Found = 302, 64 | kGCDWebServerHTTPStatusCode_SeeOther = 303, 65 | kGCDWebServerHTTPStatusCode_NotModified = 304, 66 | kGCDWebServerHTTPStatusCode_UseProxy = 305, 67 | kGCDWebServerHTTPStatusCode_TemporaryRedirect = 307, 68 | kGCDWebServerHTTPStatusCode_PermanentRedirect = 308 69 | }; 70 | 71 | /** 72 | * Convenience constants for "client error" HTTP status codes. 73 | */ 74 | typedef NS_ENUM(NSInteger, GCDWebServerClientErrorHTTPStatusCode) { 75 | kGCDWebServerHTTPStatusCode_BadRequest = 400, 76 | kGCDWebServerHTTPStatusCode_Unauthorized = 401, 77 | kGCDWebServerHTTPStatusCode_PaymentRequired = 402, 78 | kGCDWebServerHTTPStatusCode_Forbidden = 403, 79 | kGCDWebServerHTTPStatusCode_NotFound = 404, 80 | kGCDWebServerHTTPStatusCode_MethodNotAllowed = 405, 81 | kGCDWebServerHTTPStatusCode_NotAcceptable = 406, 82 | kGCDWebServerHTTPStatusCode_ProxyAuthenticationRequired = 407, 83 | kGCDWebServerHTTPStatusCode_RequestTimeout = 408, 84 | kGCDWebServerHTTPStatusCode_Conflict = 409, 85 | kGCDWebServerHTTPStatusCode_Gone = 410, 86 | kGCDWebServerHTTPStatusCode_LengthRequired = 411, 87 | kGCDWebServerHTTPStatusCode_PreconditionFailed = 412, 88 | kGCDWebServerHTTPStatusCode_RequestEntityTooLarge = 413, 89 | kGCDWebServerHTTPStatusCode_RequestURITooLong = 414, 90 | kGCDWebServerHTTPStatusCode_UnsupportedMediaType = 415, 91 | kGCDWebServerHTTPStatusCode_RequestedRangeNotSatisfiable = 416, 92 | kGCDWebServerHTTPStatusCode_ExpectationFailed = 417, 93 | kGCDWebServerHTTPStatusCode_UnprocessableEntity = 422, 94 | kGCDWebServerHTTPStatusCode_Locked = 423, 95 | kGCDWebServerHTTPStatusCode_FailedDependency = 424, 96 | kGCDWebServerHTTPStatusCode_UpgradeRequired = 426, 97 | kGCDWebServerHTTPStatusCode_PreconditionRequired = 428, 98 | kGCDWebServerHTTPStatusCode_TooManyRequests = 429, 99 | kGCDWebServerHTTPStatusCode_RequestHeaderFieldsTooLarge = 431 100 | }; 101 | 102 | /** 103 | * Convenience constants for "server error" HTTP status codes. 104 | */ 105 | typedef NS_ENUM(NSInteger, GCDWebServerServerErrorHTTPStatusCode) { 106 | kGCDWebServerHTTPStatusCode_InternalServerError = 500, 107 | kGCDWebServerHTTPStatusCode_NotImplemented = 501, 108 | kGCDWebServerHTTPStatusCode_BadGateway = 502, 109 | kGCDWebServerHTTPStatusCode_ServiceUnavailable = 503, 110 | kGCDWebServerHTTPStatusCode_GatewayTimeout = 504, 111 | kGCDWebServerHTTPStatusCode_HTTPVersionNotSupported = 505, 112 | kGCDWebServerHTTPStatusCode_InsufficientStorage = 507, 113 | kGCDWebServerHTTPStatusCode_LoopDetected = 508, 114 | kGCDWebServerHTTPStatusCode_NotExtended = 510, 115 | kGCDWebServerHTTPStatusCode_NetworkAuthenticationRequired = 511 116 | }; 117 | -------------------------------------------------------------------------------- /Classes/Others/WebServer/Core/HSDGWebSocket.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDGCDWebSocket.h 3 | // HttpServerDebug 4 | // 5 | // Created by jam.chenjun on 2019/4/17. 6 | // Copyright © 2019 chenjun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @class HSDGWebServer; 14 | @protocol HSDGWebSocketDelegate; 15 | 16 | @interface HSDGWebSocket : NSObject 17 | 18 | @property (nonatomic, weak) id webSocketDelegate; 19 | 20 | /** 21 | * judge websocket request with header 22 | */ 23 | + (BOOL)isWebSocketRequest:(NSDictionary *)requestHeaders; 24 | 25 | /** 26 | * init method 27 | */ 28 | - (instancetype)initWithServer:(HSDGWebServer *)server requestMessage:(CFHTTPMessageRef)requestMessage socket:(CFSocketNativeHandle)socket; 29 | 30 | /** 31 | * send message from server to client 32 | */ 33 | - (void)sendMessage:(NSString *)msg; 34 | 35 | @end 36 | 37 | @protocol HSDGWebSocketDelegate 38 | 39 | - (void)webSocketDidClose; 40 | 41 | @end 42 | 43 | NS_ASSUME_NONNULL_END 44 | -------------------------------------------------------------------------------- /Classes/Others/WebServer/Requests/HSDGWebServerDataRequest.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "HSDGWebServerRequest.h" 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | /** 33 | * The GCDWebServerDataRequest subclass of GCDWebServerRequest stores the body 34 | * of the HTTP request in memory. 35 | */ 36 | @interface HSDGWebServerDataRequest : HSDGWebServerRequest 37 | 38 | /** 39 | * Returns the data for the request body. 40 | */ 41 | @property(nonatomic, readonly) NSData* data; 42 | 43 | @end 44 | 45 | @interface HSDGWebServerDataRequest (Extensions) 46 | 47 | /** 48 | * Returns the data for the request body interpreted as text. If the content 49 | * type of the body is not a text one, or if an error occurs, nil is returned. 50 | * 51 | * The text encoding used to interpret the data is extracted from the 52 | * "Content-Type" header or defaults to UTF-8. 53 | */ 54 | @property(nonatomic, readonly, nullable) NSString* text; 55 | 56 | /** 57 | * Returns the data for the request body interpreted as a JSON object. If the 58 | * content type of the body is not JSON, or if an error occurs, nil is returned. 59 | */ 60 | @property(nonatomic, readonly, nullable) id jsonObject; 61 | 62 | @end 63 | 64 | NS_ASSUME_NONNULL_END 65 | -------------------------------------------------------------------------------- /Classes/Others/WebServer/Requests/HSDGWebServerDataRequest.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error GCDWebServer requires ARC 30 | #endif 31 | 32 | #import "HSDGWebServerPrivate.h" 33 | 34 | @interface HSDGWebServerDataRequest () 35 | @property(nonatomic) NSMutableData* data; 36 | @end 37 | 38 | @implementation HSDGWebServerDataRequest { 39 | NSString* _text; 40 | id _jsonObject; 41 | } 42 | 43 | - (BOOL)open:(NSError**)error { 44 | if (self.contentLength != NSUIntegerMax) { 45 | _data = [[NSMutableData alloc] initWithCapacity:self.contentLength]; 46 | } else { 47 | _data = [[NSMutableData alloc] init]; 48 | } 49 | if (_data == nil) { 50 | if (error) { 51 | *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{ NSLocalizedDescriptionKey : @"Failed allocating memory" }]; 52 | } 53 | return NO; 54 | } 55 | return YES; 56 | } 57 | 58 | - (BOOL)writeData:(NSData*)data error:(NSError**)error { 59 | [_data appendData:data]; 60 | return YES; 61 | } 62 | 63 | - (BOOL)close:(NSError**)error { 64 | return YES; 65 | } 66 | 67 | - (NSString*)description { 68 | NSMutableString* description = [NSMutableString stringWithString:[super description]]; 69 | if (_data) { 70 | [description appendString:@"\n\n"]; 71 | [description appendString:HSDGWebServerDescribeData(_data, (NSString*)self.contentType)]; 72 | } 73 | return description; 74 | } 75 | 76 | @end 77 | 78 | @implementation HSDGWebServerDataRequest (Extensions) 79 | 80 | - (NSString*)text { 81 | if (_text == nil) { 82 | if ([self.contentType hasPrefix:@"text/"]) { 83 | NSString* charset = HSDGWebServerExtractHeaderValueParameter(self.contentType, @"charset"); 84 | _text = [[NSString alloc] initWithData:self.data encoding:HSDGWebServerStringEncodingFromCharset(charset)]; 85 | } else { 86 | GWS_DNOT_REACHED(); 87 | } 88 | } 89 | return _text; 90 | } 91 | 92 | - (id)jsonObject { 93 | if (_jsonObject == nil) { 94 | NSString* mimeType = HSDGWebServerTruncateHeaderValue(self.contentType); 95 | if ([mimeType isEqualToString:@"application/json"] || [mimeType isEqualToString:@"text/json"] || [mimeType isEqualToString:@"text/javascript"]) { 96 | _jsonObject = [NSJSONSerialization JSONObjectWithData:_data options:0 error:NULL]; 97 | } else { 98 | GWS_DNOT_REACHED(); 99 | } 100 | } 101 | return _jsonObject; 102 | } 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /Classes/Others/WebServer/Requests/HSDGWebServerFileRequest.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "HSDGWebServerRequest.h" 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | /** 33 | * The GCDWebServerFileRequest subclass of GCDWebServerRequest stores the body 34 | * of the HTTP request to a file on disk. 35 | */ 36 | @interface HSDGWebServerFileRequest : HSDGWebServerRequest 37 | 38 | /** 39 | * Returns the path to the temporary file containing the request body. 40 | * 41 | * @warning This temporary file will be automatically deleted when the 42 | * GCDWebServerFileRequest is deallocated. If you want to preserve this file, 43 | * you must move it to a different location beforehand. 44 | */ 45 | @property(nonatomic, readonly) NSString* temporaryPath; 46 | 47 | @end 48 | 49 | NS_ASSUME_NONNULL_END 50 | -------------------------------------------------------------------------------- /Classes/Others/WebServer/Requests/HSDGWebServerFileRequest.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error GCDWebServer requires ARC 30 | #endif 31 | 32 | #import "HSDGWebServerPrivate.h" 33 | 34 | @implementation HSDGWebServerFileRequest { 35 | int _file; 36 | } 37 | 38 | - (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { 39 | if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) { 40 | _temporaryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; 41 | } 42 | return self; 43 | } 44 | 45 | - (void)dealloc { 46 | unlink([_temporaryPath fileSystemRepresentation]); 47 | } 48 | 49 | - (BOOL)open:(NSError**)error { 50 | _file = open([_temporaryPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 51 | if (_file <= 0) { 52 | if (error) { 53 | *error = GCDWebServerMakePosixError(errno); 54 | } 55 | return NO; 56 | } 57 | return YES; 58 | } 59 | 60 | - (BOOL)writeData:(NSData*)data error:(NSError**)error { 61 | if (write(_file, data.bytes, data.length) != (ssize_t)data.length) { 62 | if (error) { 63 | *error = GCDWebServerMakePosixError(errno); 64 | } 65 | return NO; 66 | } 67 | return YES; 68 | } 69 | 70 | - (BOOL)close:(NSError**)error { 71 | if (close(_file) < 0) { 72 | if (error) { 73 | *error = GCDWebServerMakePosixError(errno); 74 | } 75 | return NO; 76 | } 77 | #ifdef __GCDWEBSERVER_ENABLE_TESTING__ 78 | NSString* creationDateHeader = [self.headers objectForKey:@"X-GCDWebServer-CreationDate"]; 79 | if (creationDateHeader) { 80 | NSDate* date = HSDGWebServerParseISO8601(creationDateHeader); 81 | if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileCreationDate : date} ofItemAtPath:_temporaryPath error:error]) { 82 | return NO; 83 | } 84 | } 85 | NSString* modifiedDateHeader = [self.headers objectForKey:@"X-GCDWebServer-ModifiedDate"]; 86 | if (modifiedDateHeader) { 87 | NSDate* date = HSDGWebServerParseRFC822(modifiedDateHeader); 88 | if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileModificationDate : date} ofItemAtPath:_temporaryPath error:error]) { 89 | return NO; 90 | } 91 | } 92 | #endif 93 | return YES; 94 | } 95 | 96 | - (NSString*)description { 97 | NSMutableString* description = [NSMutableString stringWithString:[super description]]; 98 | [description appendFormat:@"\n\n{%@}", _temporaryPath]; 99 | return description; 100 | } 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /Classes/Others/WebServer/Requests/HSDGWebServerMultiPartFormRequest.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "HSDGWebServerRequest.h" 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | /** 33 | * The GCDWebServerMultiPart class is an abstract class that wraps the content 34 | * of a part. 35 | */ 36 | @interface HSDGWebServerMultiPart : NSObject 37 | 38 | /** 39 | * Returns the control name retrieved from the part headers. 40 | */ 41 | @property(nonatomic, readonly) NSString* controlName; 42 | 43 | /** 44 | * Returns the content type retrieved from the part headers or "text/plain" 45 | * if not available (per HTTP specifications). 46 | */ 47 | @property(nonatomic, readonly) NSString* contentType; 48 | 49 | /** 50 | * Returns the MIME type component of the content type for the part. 51 | */ 52 | @property(nonatomic, readonly) NSString* mimeType; 53 | 54 | @end 55 | 56 | /** 57 | * The GCDWebServerMultiPartArgument subclass of GCDWebServerMultiPart wraps 58 | * the content of a part as data in memory. 59 | */ 60 | @interface HSDGWebServerMultiPartArgument : HSDGWebServerMultiPart 61 | 62 | /** 63 | * Returns the data for the part. 64 | */ 65 | @property(nonatomic, readonly) NSData* data; 66 | 67 | /** 68 | * Returns the data for the part interpreted as text. If the content 69 | * type of the part is not a text one, or if an error occurs, nil is returned. 70 | * 71 | * The text encoding used to interpret the data is extracted from the 72 | * "Content-Type" header or defaults to UTF-8. 73 | */ 74 | @property(nonatomic, readonly, nullable) NSString* string; 75 | 76 | @end 77 | 78 | /** 79 | * The GCDWebServerMultiPartFile subclass of GCDWebServerMultiPart wraps 80 | * the content of a part as a file on disk. 81 | */ 82 | @interface HSDGWebServerMultiPartFile : HSDGWebServerMultiPart 83 | 84 | /** 85 | * Returns the file name retrieved from the part headers. 86 | */ 87 | @property(nonatomic, readonly) NSString* fileName; 88 | 89 | /** 90 | * Returns the path to the temporary file containing the part data. 91 | * 92 | * @warning This temporary file will be automatically deleted when the 93 | * GCDWebServerMultiPartFile is deallocated. If you want to preserve this file, 94 | * you must move it to a different location beforehand. 95 | */ 96 | @property(nonatomic, readonly) NSString* temporaryPath; 97 | 98 | @end 99 | 100 | /** 101 | * The GCDWebServerMultiPartFormRequest subclass of GCDWebServerRequest 102 | * parses the body of the HTTP request as a multipart encoded form. 103 | */ 104 | @interface HSDGWebServerMultiPartFormRequest : HSDGWebServerRequest 105 | 106 | /** 107 | * Returns the argument parts from the multipart encoded form as 108 | * name / GCDWebServerMultiPartArgument pairs. 109 | */ 110 | @property(nonatomic, readonly) NSArray* arguments; 111 | 112 | /** 113 | * Returns the files parts from the multipart encoded form as 114 | * name / GCDWebServerMultiPartFile pairs. 115 | */ 116 | @property(nonatomic, readonly) NSArray* files; 117 | 118 | /** 119 | * Returns the MIME type for multipart encoded forms 120 | * i.e. "multipart/form-data". 121 | */ 122 | + (NSString*)mimeType; 123 | 124 | /** 125 | * Returns the first argument for a given control name or nil if not found. 126 | */ 127 | - (nullable HSDGWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name; 128 | 129 | /** 130 | * Returns the first file for a given control name or nil if not found. 131 | */ 132 | - (nullable HSDGWebServerMultiPartFile*)firstFileForControlName:(NSString*)name; 133 | 134 | @end 135 | 136 | NS_ASSUME_NONNULL_END 137 | -------------------------------------------------------------------------------- /Classes/Others/WebServer/Requests/HSDGWebServerURLEncodedFormRequest.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "HSDGWebServerDataRequest.h" 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | /** 33 | * The GCDWebServerURLEncodedFormRequest subclass of GCDWebServerRequest 34 | * parses the body of the HTTP request as a URL encoded form using 35 | * GCDWebServerParseURLEncodedForm(). 36 | */ 37 | @interface HSDGWebServerURLEncodedFormRequest : HSDGWebServerDataRequest 38 | 39 | /** 40 | * Returns the unescaped control names and values for the URL encoded form. 41 | * 42 | * The text encoding used to interpret the data is extracted from the 43 | * "Content-Type" header or defaults to UTF-8. 44 | */ 45 | @property(nonatomic, readonly) NSDictionary* arguments; 46 | 47 | /** 48 | * Returns the MIME type for URL encoded forms 49 | * i.e. "application/x-www-form-urlencoded". 50 | */ 51 | + (NSString*)mimeType; 52 | 53 | @end 54 | 55 | NS_ASSUME_NONNULL_END 56 | -------------------------------------------------------------------------------- /Classes/Others/WebServer/Requests/HSDGWebServerURLEncodedFormRequest.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error GCDWebServer requires ARC 30 | #endif 31 | 32 | #import "HSDGWebServerPrivate.h" 33 | 34 | @implementation HSDGWebServerURLEncodedFormRequest 35 | 36 | + (NSString*)mimeType { 37 | return @"application/x-www-form-urlencoded"; 38 | } 39 | 40 | - (BOOL)close:(NSError**)error { 41 | if (![super close:error]) { 42 | return NO; 43 | } 44 | 45 | NSString* charset = HSDGWebServerExtractHeaderValueParameter(self.contentType, @"charset"); 46 | NSString* string = [[NSString alloc] initWithData:self.data encoding:HSDGWebServerStringEncodingFromCharset(charset)]; 47 | _arguments = HSDGWebServerParseURLEncodedForm(string); 48 | return YES; 49 | } 50 | 51 | - (NSString*)description { 52 | NSMutableString* description = [NSMutableString stringWithString:[super description]]; 53 | [description appendString:@"\n"]; 54 | for (NSString* argument in [[_arguments allKeys] sortedArrayUsingSelector:@selector(compare:)]) { 55 | [description appendFormat:@"\n%@ = %@", argument, [_arguments objectForKey:argument]]; 56 | } 57 | return description; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /Classes/Others/WebServer/Responses/HSDGWebServerDataResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "HSDGWebServerResponse.h" 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | /** 33 | * The GCDWebServerDataResponse subclass of GCDWebServerResponse reads the body 34 | * of the HTTP response from memory. 35 | */ 36 | @interface HSDGWebServerDataResponse : HSDGWebServerResponse 37 | @property(nonatomic, copy) NSString* contentType; // Redeclare as non-null 38 | 39 | /** 40 | * Creates a response with data in memory and a given content type. 41 | */ 42 | + (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type; 43 | 44 | /** 45 | * This method is the designated initializer for the class. 46 | */ 47 | - (instancetype)initWithData:(NSData*)data contentType:(NSString*)type; 48 | 49 | @end 50 | 51 | @interface HSDGWebServerDataResponse (Extensions) 52 | 53 | /** 54 | * Creates a data response from text encoded using UTF-8. 55 | */ 56 | + (nullable instancetype)responseWithText:(NSString*)text; 57 | 58 | /** 59 | * Creates a data response from HTML encoded using UTF-8. 60 | */ 61 | + (nullable instancetype)responseWithHTML:(NSString*)html; 62 | 63 | /** 64 | * Creates a data response from an HTML template encoded using UTF-8. 65 | * See -initWithHTMLTemplate:variables: for details. 66 | */ 67 | + (nullable instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; 68 | 69 | /** 70 | * Creates a data response from a serialized JSON object and the default 71 | * "application/json" content type. 72 | */ 73 | + (nullable instancetype)responseWithJSONObject:(id)object; 74 | 75 | /** 76 | * Creates a data response from a serialized JSON object and a custom 77 | * content type. 78 | */ 79 | + (nullable instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type; 80 | 81 | /** 82 | * Initializes a data response from text encoded using UTF-8. 83 | */ 84 | - (nullable instancetype)initWithText:(NSString*)text; 85 | 86 | /** 87 | * Initializes a data response from HTML encoded using UTF-8. 88 | */ 89 | - (nullable instancetype)initWithHTML:(NSString*)html; 90 | 91 | /** 92 | * Initializes a data response from an HTML template encoded using UTF-8. 93 | * 94 | * All occurences of "%variable%" within the HTML template are replaced with 95 | * their corresponding values. 96 | */ 97 | - (nullable instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; 98 | 99 | /** 100 | * Initializes a data response from a serialized JSON object and the default 101 | * "application/json" content type. 102 | */ 103 | - (nullable instancetype)initWithJSONObject:(id)object; 104 | 105 | /** 106 | * Initializes a data response from a serialized JSON object and a custom 107 | * content type. 108 | */ 109 | - (nullable instancetype)initWithJSONObject:(id)object contentType:(NSString*)type; 110 | 111 | @end 112 | 113 | NS_ASSUME_NONNULL_END 114 | -------------------------------------------------------------------------------- /Classes/Others/WebServer/Responses/HSDGWebServerDataResponse.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error GCDWebServer requires ARC 30 | #endif 31 | 32 | #import "HSDGWebServerPrivate.h" 33 | 34 | @implementation HSDGWebServerDataResponse { 35 | NSData* _data; 36 | BOOL _done; 37 | } 38 | 39 | @dynamic contentType; 40 | 41 | + (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type { 42 | return [[[self class] alloc] initWithData:data contentType:type]; 43 | } 44 | 45 | - (instancetype)initWithData:(NSData*)data contentType:(NSString*)type { 46 | if ((self = [super init])) { 47 | _data = data; 48 | 49 | self.contentType = type; 50 | self.contentLength = data.length; 51 | } 52 | return self; 53 | } 54 | 55 | - (NSData*)readData:(NSError**)error { 56 | NSData* data; 57 | if (_done) { 58 | data = [NSData data]; 59 | } else { 60 | data = _data; 61 | _done = YES; 62 | } 63 | return data; 64 | } 65 | 66 | - (NSString*)description { 67 | NSMutableString* description = [NSMutableString stringWithString:[super description]]; 68 | [description appendString:@"\n\n"]; 69 | [description appendString:HSDGWebServerDescribeData(_data, self.contentType)]; 70 | return description; 71 | } 72 | 73 | @end 74 | 75 | @implementation HSDGWebServerDataResponse (Extensions) 76 | 77 | + (instancetype)responseWithText:(NSString*)text { 78 | return [[self alloc] initWithText:text]; 79 | } 80 | 81 | + (instancetype)responseWithHTML:(NSString*)html { 82 | return [[self alloc] initWithHTML:html]; 83 | } 84 | 85 | + (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables { 86 | return [[self alloc] initWithHTMLTemplate:path variables:variables]; 87 | } 88 | 89 | + (instancetype)responseWithJSONObject:(id)object { 90 | return [[self alloc] initWithJSONObject:object]; 91 | } 92 | 93 | + (instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type { 94 | return [[self alloc] initWithJSONObject:object contentType:type]; 95 | } 96 | 97 | - (instancetype)initWithText:(NSString*)text { 98 | NSData* data = [text dataUsingEncoding:NSUTF8StringEncoding]; 99 | if (data == nil) { 100 | GWS_DNOT_REACHED(); 101 | return nil; 102 | } 103 | return [self initWithData:data contentType:@"text/plain; charset=utf-8"]; 104 | } 105 | 106 | - (instancetype)initWithHTML:(NSString*)html { 107 | NSData* data = [html dataUsingEncoding:NSUTF8StringEncoding]; 108 | if (data == nil) { 109 | GWS_DNOT_REACHED(); 110 | return nil; 111 | } 112 | return [self initWithData:data contentType:@"text/html; charset=utf-8"]; 113 | } 114 | 115 | - (instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables { 116 | NSMutableString* html = [[NSMutableString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; 117 | [variables enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL* stop) { 118 | [html replaceOccurrencesOfString:[NSString stringWithFormat:@"%%%@%%", key] withString:value options:0 range:NSMakeRange(0, html.length)]; 119 | }]; 120 | return [self initWithHTML:html]; 121 | } 122 | 123 | - (instancetype)initWithJSONObject:(id)object { 124 | return [self initWithJSONObject:object contentType:@"application/json"]; 125 | } 126 | 127 | - (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type { 128 | NSData* data = [NSJSONSerialization dataWithJSONObject:object options:0 error:NULL]; 129 | if (data == nil) { 130 | GWS_DNOT_REACHED(); 131 | return nil; 132 | } 133 | return [self initWithData:data contentType:type]; 134 | } 135 | 136 | @end 137 | -------------------------------------------------------------------------------- /Classes/Others/WebServer/Responses/HSDGWebServerErrorResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "HSDGWebServerDataResponse.h" 29 | #import "HSDGWebServerHTTPStatusCodes.h" 30 | 31 | NS_ASSUME_NONNULL_BEGIN 32 | 33 | /** 34 | * The GCDWebServerDataResponse subclass of GCDWebServerDataResponse generates 35 | * an HTML body from an HTTP status code and an error message. 36 | */ 37 | @interface HSDGWebServerErrorResponse : HSDGWebServerDataResponse 38 | 39 | /** 40 | * Creates a client error response with the corresponding HTTP status code. 41 | */ 42 | + (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); 43 | 44 | /** 45 | * Creates a server error response with the corresponding HTTP status code. 46 | */ 47 | + (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); 48 | 49 | /** 50 | * Creates a client error response with the corresponding HTTP status code 51 | * and an underlying NSError. 52 | */ 53 | + (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4); 54 | 55 | /** 56 | * Creates a server error response with the corresponding HTTP status code 57 | * and an underlying NSError. 58 | */ 59 | + (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4); 60 | 61 | /** 62 | * Initializes a client error response with the corresponding HTTP status code. 63 | */ 64 | - (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); 65 | 66 | /** 67 | * Initializes a server error response with the corresponding HTTP status code. 68 | */ 69 | - (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); 70 | 71 | /** 72 | * Initializes a client error response with the corresponding HTTP status code 73 | * and an underlying NSError. 74 | */ 75 | - (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4); 76 | 77 | /** 78 | * Initializes a server error response with the corresponding HTTP status code 79 | * and an underlying NSError. 80 | */ 81 | - (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4); 82 | 83 | @end 84 | 85 | NS_ASSUME_NONNULL_END 86 | -------------------------------------------------------------------------------- /Classes/Others/WebServer/Responses/HSDGWebServerFileResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "HSDGWebServerResponse.h" 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | /** 33 | * The GCDWebServerFileResponse subclass of GCDWebServerResponse reads the body 34 | * of the HTTP response from a file on disk. 35 | * 36 | * It will automatically set the contentType, lastModifiedDate and eTag 37 | * properties of the GCDWebServerResponse according to the file extension and 38 | * metadata. 39 | */ 40 | @interface HSDGWebServerFileResponse : HSDGWebServerResponse 41 | @property(nonatomic, copy) NSString* contentType; // Redeclare as non-null 42 | @property(nonatomic) NSDate* lastModifiedDate; // Redeclare as non-null 43 | @property(nonatomic, copy) NSString* eTag; // Redeclare as non-null 44 | 45 | /** 46 | * Creates a response with the contents of a file. 47 | */ 48 | + (nullable instancetype)responseWithFile:(NSString*)path; 49 | 50 | /** 51 | * Creates a response like +responseWithFile: and sets the "Content-Disposition" 52 | * HTTP header for a download if the "attachment" argument is YES. 53 | */ 54 | + (nullable instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment; 55 | 56 | /** 57 | * Creates a response like +responseWithFile: but restricts the file contents 58 | * to a specific byte range. 59 | * 60 | * See -initWithFile:byteRange: for details. 61 | */ 62 | + (nullable instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range; 63 | 64 | /** 65 | * Creates a response like +responseWithFile:byteRange: and sets the 66 | * "Content-Disposition" HTTP header for a download if the "attachment" 67 | * argument is YES. 68 | */ 69 | + (nullable instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment; 70 | 71 | /** 72 | * Initializes a response with the contents of a file. 73 | */ 74 | - (nullable instancetype)initWithFile:(NSString*)path; 75 | 76 | /** 77 | * Initializes a response like +responseWithFile: and sets the 78 | * "Content-Disposition" HTTP header for a download if the "attachment" 79 | * argument is YES. 80 | */ 81 | - (nullable instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment; 82 | 83 | /** 84 | * Initializes a response like -initWithFile: but restricts the file contents 85 | * to a specific byte range. This range should be set to (NSUIntegerMax, 0) for 86 | * the full file, (offset, length) if expressed from the beginning of the file, 87 | * or (NSUIntegerMax, length) if expressed from the end of the file. The "offset" 88 | * and "length" values will be automatically adjusted to be compatible with the 89 | * actual size of the file. 90 | * 91 | * This argument would typically be set to the value of the byteRange property 92 | * of the current GCDWebServerRequest. 93 | */ 94 | - (nullable instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range; 95 | 96 | /** 97 | * This method is the designated initializer for the class. 98 | * 99 | * If MIME type overrides are specified, they allow to customize the built-in 100 | * mapping from extensions to MIME types. Keys of the dictionary must be lowercased 101 | * file extensions without the period, and the values must be the corresponding 102 | * MIME types. 103 | */ 104 | - (nullable instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment mimeTypeOverrides:(nullable NSDictionary*)overrides; 105 | 106 | @end 107 | 108 | NS_ASSUME_NONNULL_END 109 | -------------------------------------------------------------------------------- /Classes/Others/WebServer/Responses/HSDGWebServerStreamedResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "HSDGWebServerResponse.h" 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | /** 33 | * The GCDWebServerStreamBlock is called to stream the data for the HTTP body. 34 | * The block must return either a chunk of data, an empty NSData when done, or 35 | * nil on error and set the "error" argument which is guaranteed to be non-NULL. 36 | */ 37 | typedef NSData* _Nullable (^GCDWebServerStreamBlock)(NSError** error); 38 | 39 | /** 40 | * The GCDWebServerAsyncStreamBlock works like the GCDWebServerStreamBlock 41 | * except the streamed data can be returned at a later time allowing for 42 | * truly asynchronous generation of the data. 43 | * 44 | * The block must call "completionBlock" passing the new chunk of data when ready, 45 | * an empty NSData when done, or nil on error and pass a NSError. 46 | * 47 | * The block cannot call "completionBlock" more than once per invocation. 48 | */ 49 | typedef void (^GCDWebServerAsyncStreamBlock)(GCDWebServerBodyReaderCompletionBlock completionBlock); 50 | 51 | /** 52 | * The GCDWebServerStreamedResponse subclass of GCDWebServerResponse streams 53 | * the body of the HTTP response using a GCD block. 54 | */ 55 | @interface HSDGWebServerStreamedResponse : HSDGWebServerResponse 56 | @property(nonatomic, copy) NSString* contentType; // Redeclare as non-null 57 | 58 | /** 59 | * Creates a response with streamed data and a given content type. 60 | */ 61 | + (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block; 62 | 63 | /** 64 | * Creates a response with async streamed data and a given content type. 65 | */ 66 | + (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block; 67 | 68 | /** 69 | * Initializes a response with streamed data and a given content type. 70 | */ 71 | - (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block; 72 | 73 | /** 74 | * This method is the designated initializer for the class. 75 | */ 76 | - (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block; 77 | 78 | @end 79 | 80 | NS_ASSUME_NONNULL_END 81 | -------------------------------------------------------------------------------- /Classes/Others/WebServer/Responses/HSDGWebServerStreamedResponse.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error GCDWebServer requires ARC 30 | #endif 31 | 32 | #import "HSDGWebServerPrivate.h" 33 | 34 | @implementation HSDGWebServerStreamedResponse { 35 | GCDWebServerAsyncStreamBlock _block; 36 | } 37 | 38 | @dynamic contentType; 39 | 40 | + (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block { 41 | return [[[self class] alloc] initWithContentType:type streamBlock:block]; 42 | } 43 | 44 | + (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block { 45 | return [[[self class] alloc] initWithContentType:type asyncStreamBlock:block]; 46 | } 47 | 48 | - (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block { 49 | return [self initWithContentType:type 50 | asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) { 51 | 52 | NSError* error = nil; 53 | NSData* data = block(&error); 54 | completionBlock(data, error); 55 | 56 | }]; 57 | } 58 | 59 | - (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block { 60 | if ((self = [super init])) { 61 | _block = [block copy]; 62 | 63 | self.contentType = type; 64 | } 65 | return self; 66 | } 67 | 68 | - (void)asyncReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block { 69 | _block(block); 70 | } 71 | 72 | - (NSString*)description { 73 | NSMutableString* description = [NSMutableString stringWithString:[super description]]; 74 | [description appendString:@"\n\n"]; 75 | return description; 76 | } 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /Classes/Others/ZipArchive/HSDZipArchive.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDZipArchive.h 3 | // HSDZipArchive 4 | // 5 | // Created by Sam Soffes on 7/21/10. 6 | // Copyright (c) Sam Soffes 2010-2015. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface HSDZipArchive : NSObject 14 | 15 | // Zip 16 | // default compression level is Z_DEFAULT_COMPRESSION (from "zlib.h") 17 | 18 | + (BOOL)createZipFileAtPath:(NSString *)path withFilesAtPaths:(NSArray *)paths; 19 | 20 | + (BOOL)createZipFileAtPath:(NSString *)path withContentsOfDirectory:(NSString *)directoryPath; 21 | + (BOOL)createZipFileAtPath:(NSString *)path withContentsOfDirectory:(NSString *)directoryPath keepParentDirectory:(BOOL)keepParentDirectory; 22 | + (BOOL)createZipFileAtPath:(NSString *)path withContentsOfDirectory:(NSString *)directoryPath keepParentDirectory:(BOOL)keepParentDirectory andProgressHandler:(void(^ _Nullable)(NSUInteger entryNumber, NSUInteger total))progressHandler; 23 | + (BOOL)createZipFileAtPath:(NSString *)path withContentsOfDirectory:(NSString *)directoryPath keepParentDirectory:(BOOL)keepParentDirectory compressionLevel:(int)compressionLevel progressHandler:(void(^ _Nullable)(NSUInteger entryNumber, NSUInteger total))progressHandler; 24 | 25 | - (instancetype)init NS_UNAVAILABLE; 26 | - (instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER; 27 | - (BOOL)open; 28 | 29 | /// write empty folder 30 | - (BOOL)writeFolderAtPath:(NSString *)path withFolderName:(NSString *)folderName; 31 | /// write file 32 | - (BOOL)writeFile:(NSString *)path; 33 | - (BOOL)writeFileAtPath:(NSString *)path withFileName:(nullable NSString *)fileName; 34 | - (BOOL)writeFileAtPath:(NSString *)path withFileName:(nullable NSString *)fileName compressionLevel:(int)compressionLevel; 35 | 36 | - (BOOL)close; 37 | 38 | @end 39 | 40 | NS_ASSUME_NONNULL_END 41 | -------------------------------------------------------------------------------- /Documents/Interfaces-CN.md: -------------------------------------------------------------------------------- 1 | # 服务端接口规范 2 | 3 | ## 一、请求 4 | 5 | 存在两种请求方式:GET 和 POST 6 | 7 | ## 二、响应 8 | 9 | response data 为 json 格式。 10 | 11 | ``` 12 | { 13 | "errno": "0", 14 | "data": xxx 15 | } 16 | ``` 17 | 18 | |字段|类型|说明| 19 | |----|----|----| 20 | |errno|int|错误码,成功0,错误-1| 21 | |data|any|业务数据| 22 | 23 | ## 三、API 列表 24 | 25 | ### 1. file_preview 26 | 27 | 描述:获取文件内容。 28 | 29 | request,GET 方法 30 | 31 | ``` 32 | /api/file_preview?file_path=%@ 33 | ``` 34 | 35 | |参数|说明| 36 | |----|----| 37 | |file_path|使用绝对路径直接返回该路径内容。
使用相对路径必须为以下情况开头:
Documents:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject],以该目录为根目录;
Library:[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject],以该目录为根目录;
tmp:NSTemporaryDirectory(),以该目录为根目录。| 38 | 39 | response 40 | 41 | 返回指定路径的文件内容。若指向的路径为文件夹,zip压缩后返回。 42 | 43 | ### 2. send_info 44 | 45 | 描述:向 App 发送信息,HSD 通过 delegate 将信息抛给宿主 App,处理结果在 responseData 中返回。支持 GET 方法和 POST 方法。 46 | 47 | request,GET 方法 48 | 49 | ``` 50 | /api/send_info?info=%@ 51 | ``` 52 | 53 | |参数|说明| 54 | |----|----| 55 | |info|发送给 App 的信息,字符串类型。注意:需进行编码,不能包含URI保留字符(如?&)。| 56 | 57 | request,POST 方法 58 | 59 | ``` 60 | /api/send_info 61 | ``` 62 | 63 | 向 App 发送字符串信息,信息在 HTTP body 中传输,支持的 Content-Type 为 text/plain 和 application/x-www-form-urlencoded。 64 | 65 | ### 3. file_explorer 66 | 67 | 描述:获取文件信息,或修改文件。 68 | 69 | request,GET 方法 70 | 71 | ``` 72 | /api/file_explorer?file_path=xxx&action=delete 73 | ``` 74 | 75 | |参数|说明| 76 | |----|----| 77 | |file_path|文件或文件夹完整路径| 78 | |action|字段不存在或为空:对于文件,请求文件属性;对于文件夹,请求文件夹内内容信息。
delete:删除文件或文件夹。| 79 | -------------------------------------------------------------------------------- /HttpServerDebug.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "HttpServerDebug" 3 | s.version = "0.2.2" 4 | s.summary = "HSD offers debug utilities (exploring file system, inspecting " \ 5 | "database, etc.) with the help of http server." 6 | s.description = <<-DESC 7 | HSD offers debug utilities (exploring file system, inspecting 8 | database, etc.) with the help of http server. HSD will start 9 | http server in your device, and you can connect to the server 10 | through user agents in the local area network. 11 | DESC 12 | 13 | s.homepage = "https://github.com/rob2468/HttpServerDebug" 14 | s.screenshots = "https://user-images.githubusercontent.com/1450652/44396867-ca139000-a570-11e8-9a5c-80da964159ba.gif", \ 15 | "https://user-images.githubusercontent.com/1450652/44396868-ca139000-a570-11e8-8a05-871de9efeb34.gif", \ 16 | "https://user-images.githubusercontent.com/1450652/44396869-ca139000-a570-11e8-9018-dc27634ebd9d.gif" 17 | s.license = { :type => "MIT", :file => "LICENSE.txt" } 18 | s.author = { "jam.chenjun" => "jam.chenjun@gmail.com" } 19 | s.social_media_url = "https://weibo.com/rob2468" 20 | s.platform = :ios, "8.0" 21 | s.source = { :git => "https://github.com/rob2468/HttpServerDebug.git", :commit => "b71963cf29e8992ebb1ba88ad2457c040b7e3b3c" } 22 | s.source_files = "Classes/**/*.{h,m,c}" 23 | s.public_header_files = "Classes/**/{HSDDelegate,HSDHttpServerControlPannelController,HSDManager,HttpServerDebug}.h" 24 | s.resources = "Resources/HttpServerDebug.bundle" 25 | s.frameworks = "UIKit", "Foundation" 26 | s.requires_arc = true 27 | s.dependency "FMDB", "~> 2.7" 28 | end 29 | -------------------------------------------------------------------------------- /HttpServerDebug.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /HttpServerDebug.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /HttpServerDebug.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /HttpServerDebug.xcodeproj/xcshareddata/xcschemes/HttpServerDebug.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-present chenjun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HttpServerDebug (HSD) 2 | 3 | [![Build Status](https://travis-ci.org/rob2468/HttpServerDebug.svg?branch=master)](https://travis-ci.org/rob2468/HttpServerDebug) 4 | [![License](https://img.shields.io/github/license/rob2468/HttpServerDebug.svg)](https://github.com/rob2468/HttpServerDebug/blob/master/LICENSE.txt) 5 | 6 | [中文文档](https://github.com/rob2468/HttpServerDebug/wiki) 7 | 8 | ## Overview 9 | 10 | HSD offers debug utilities (exploring file system, inspecting database, etc.) with the help of http server. HSD will start http server in your device, and you can connect to the server through user agents in the local area network. 11 | 12 | Requirements: 13 | 14 | * iOS 8.0 or later (armv7, armv7s or arm64) 15 | 16 | ## Demo screenshot 17 | 18 | index 19 | 20 | file explorer 21 | 22 | view debug 23 | 24 | ## Integration 25 | 26 | ### Packaging way 27 | 28 | In the root directory, there is the "archive.sh" script. `cd` to the root directory, then `bash archive.sh`. This script will generate files in the "output" folder in the same directory. The "output" folder contains three kinds of files, headers, library and bundle. These are all files that needed. 29 | 30 | HttpServerDebug utilizes some third party libraries, e.g. FMDB. "archive.sh" script will compile all source files and integrate all contents in one static library, libHttpServerDebug.a. But sometimes you may want to exclude some third party libraries if your project has already import. You can update "archive.sh". For example, if you want to remove FMDB, set `FMDB_INCLUDE=0`. 31 | 32 | ```shell 33 | # Dependencies onfiguration 34 | FMDB_INCLUDE=0 # exclude FMDB 35 | ``` 36 | 37 | ### Source code way 38 | 39 | You can copy source code files to your project directly. `Classes/` and `Resources/` in the root directory contains HSD codes and resources. `ThirdParties/` contains dependent libraries, and you should copy as needed. 40 | 41 | ### CocoaPods way 42 | 43 | Add following statements in your Podfile. 44 | 45 | ``` 46 | pod 'HttpServerDebug', '~> 0.2' 47 | ``` 48 | 49 | ## Access HSD 50 | 51 | As HSD is started as a http server in your device, you can access it just like browsing normal websites in your favorite web browser. HSD also provides some useful server apis, you can get these apis' description from `Documents/` Directory. There are several ways you can get the HSD host name. 52 | 53 | ### HSD Control Pannel 54 | 55 | HSD provides a control pannel, where you can make HSD running in the way that you want. 56 | 57 |
control pannel
58 | 59 | ### Public Host Name Resolving Interface 60 | 61 | HSD also makes the host name resolving interface, `+[HSDManager resolveHostName:]` used in the control pannel, public. You can call this method and display host name in your custom views. 62 | 63 | ### Browse And Lookup 64 | 65 | If you start up HSD silently, the following method is another way to access HSD. 66 | 67 | When HSD is started, the builtin bonjour broadcasting of `_http._tcp` type service is also published. You can browse for instances of service type `_http._tcp` in domain `local.`. When you get the instance name, you can lookup the target hostname to contact. In the following example, we use the `dns-sd` tool to browse and lookup the target hostname. 68 | 69 | ```shell 70 | chenjundeMacBook-Pro:~ chenjun$ dns-sd -B _http 71 | Browsing for _http._tcp 72 | DATE: ---Wed 04 Apr 2018--- 73 | 10:10:14.738 ...STARTING... 74 | Timestamp A/R Flags if Domain Service Type Instance Name 75 | 10:10:14.738 Add 2 13 local. _http._tcp. 陈军的iPhone 7 76 | 77 | chenjundeMacBook-Pro:~ chenjun$ dns-sd -L "陈军的iPhone 7" _http 78 | Lookup 陈军的iPhone 7._http._tcp.local 79 | DATE: ---Wed 04 Apr 2018--- 80 | 10:10:45.715 ...STARTING... 81 | 10:10:45.879 陈军的iPhone\0327._http._tcp.local. can be reached at chenjundeiPhone-7.local.:5555 (interface 13) 82 | ``` 83 | 84 | ### Manually 85 | 86 | AS HSD runs on a http server, with your device's ip address and http server's listening port number, you can construct the complete URL. 87 | 88 | ## FAQ 89 | 90 | 1. Why does Xcode produce dupliate symbol errors. ("duplicate symbol xxx in:/xxx/libHttpServerDebug.a(xxx.o) /xxx/xxx(xxx.o) ld: xxx duplicate symbols for architecture xxx") 91 | 92 | When your project contains some same classes, the linker produces these errors. 93 | 94 | As HttpServerDebug imports some third party libraries, if your project has already import one, then exclude it in the "archive.sh" as described above. 95 | 96 | 2. How to link HttpServerDebug in specific configuration? 97 | 98 | For example, import HttpServerDebug only in Debug configuration. 99 | 100 | - Search HttpServerDebug Headers in Debug configuration. 101 | 102 | * "Build Settings -> Header Search Paths", add header searching paths for Debug configuration. 103 | 104 | - Link Binary With Libraries in Debug configuration. 105 | 106 | * "Build Settings -> Other Link Flags", add "-lHttpServerDebug" for Debug configuration. 107 | 108 | * "Build Settings -> Library Search Paths", add libHttpServerDebug.a searching path for Debug configuration. 109 | 110 | - Copy Bundle Resources in Debug configuration. 111 | 112 | * Add copy bundle resources script in "Build Phases -> Run Script". 113 | 114 | ```shell 115 | if [ "${CONFIGURATION}" == "Debug" ]; then 116 | cp -r "${PROJECT_DIR}/HttpServerDebug.bundle" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app" 117 | fi 118 | ``` 119 | 120 | ## Acknowledgments 121 | 122 | [CocoaHTTPServer](https://github.com/robbiehanson/CocoaHTTPServer), [FLEX](https://github.com/Flipboard/FLEX), [Custom-Context-Menu](https://github.com/callmenick/Custom-Context-Menu), [ZipArchive](https://github.com/ZipArchive/ZipArchive), [GCDWebServer](https://github.com/swisspol/GCDWebServer) 123 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/components/notification/notification.css: -------------------------------------------------------------------------------- 1 | #notification-group { 2 | position: absolute; 3 | top: 0; 4 | height: auto; 5 | right: 0; 6 | width: 300px; 7 | z-index: 1; 8 | overflow-y: scroll; 9 | } 10 | .notification { 11 | float: right; 12 | width: 250px; 13 | height: 70px; 14 | background-color: rgba(111, 111, 111, 0.8); 15 | margin: 10px 10px 0 10px; 16 | border-radius: 8px; 17 | color: white; 18 | display: flex; 19 | justify-content: center; 20 | align-items: center; 21 | } 22 | .notification * { 23 | margin: 0; 24 | } 25 | .notification .content { 26 | width: 90%; 27 | } 28 | .notification p { 29 | line-height: 20px; 30 | /* height: 40px; */ 31 | overflow: hidden; 32 | } -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/components/notification/notification.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @class Notification 4 | * @property {HTMLElement} view 5 | */ 6 | class Notification { 7 | constructor() { 8 | this.view = null; 9 | } 10 | } 11 | 12 | /** 13 | * show notification 14 | * @param {string} msg displaying message 15 | * @param {number} duration Millisecond unit. Dismiss after the duration time. set 0, never dismiss 16 | * @returns {Notification} 17 | */ 18 | function showNotification(msg, duration) { 19 | // notification div 20 | const notificationEle = document.createElement('div'); 21 | notificationEle.setAttribute('class', 'notification'); 22 | 23 | // content div 24 | const contentEle = document.createElement('div'); 25 | contentEle.setAttribute('class', 'content'); 26 | notificationEle.appendChild(contentEle); 27 | 28 | // p 29 | const pEle = document.createElement('p'); 30 | pEle.innerHTML = msg; 31 | pEle.style.visibility = 'hidden'; 32 | contentEle.append(pEle); 33 | 34 | // show 35 | const groupEle = document.querySelector('#notification-group'); 36 | groupEle.appendChild(notificationEle); 37 | 38 | // limit the text displaying length 39 | while (pEle.clientHeight > 40) { 40 | msg = msg.slice(0, msg.length - 1); 41 | pEle.innerHTML = msg + '...'; 42 | } 43 | pEle.style.visibility = ''; 44 | 45 | // create one instance 46 | const notification = new Notification(); 47 | notification.view = notificationEle; 48 | 49 | updateNotificationGroup(); 50 | 51 | // dismiss action 52 | if (typeof(duration) === 'undefined') { 53 | // default value 54 | duration = 3000; 55 | } 56 | 57 | if (duration > 0) { 58 | // auto dismiss 59 | setTimeout(() => { 60 | dismissNotification(notification); 61 | }, duration); 62 | } 63 | return notification; 64 | } 65 | 66 | /** 67 | * 68 | * @param {Notification} notification 69 | */ 70 | function dismissNotification(notification) { 71 | if (notification && notification.view) { 72 | // update notification 73 | notification.view.remove(); 74 | 75 | // update notification group 76 | updateNotificationGroup(); 77 | } 78 | } 79 | 80 | /** 81 | * 82 | */ 83 | function updateNotificationGroup() { 84 | const groupEle = document.querySelector('#notification-group'); 85 | const maxHeight = window.innerHeight; 86 | 87 | // fixed or dynamic height 88 | let heightVal; 89 | if (groupEle.scrollHeight > maxHeight) { 90 | heightVal = maxHeight + 'px'; 91 | } else { 92 | heightVal = 'auto'; 93 | } 94 | 95 | groupEle.style.height = heightVal; 96 | } 97 | 98 | function initNotification() { 99 | // add notification group div 100 | const groupEle = document.createElement('div'); 101 | groupEle.setAttribute('id', 'notification-group'); 102 | document.body.appendChild(groupEle); 103 | 104 | window.addEventListener('resize', function () { 105 | updateNotificationGroup(); 106 | }); 107 | 108 | // debug 109 | // showNotification('上传失败', 0); 110 | // showNotification('上传失败上传失败上传失败上传失败上传失败上传失败上传失败上传失败上传失败上传失败', 0); 111 | // showNotification('上传失败', 0); 112 | // showNotification('上传失败', 0); 113 | // showNotification('上传失败', 0); 114 | // showNotification('上传失败', 0); 115 | // showNotification('上传失败', 0); 116 | // showNotification('上传失败', 0); 117 | // showNotification('上传失败', 0); 118 | // showNotification('上传失败', 0); 119 | // showNotification('上传失败', 0); 120 | // showNotification('上传失败', 0); 121 | // showNotification('上传失败', 0); 122 | // showNotification('上传失败'); 123 | // showNotification('上传失败'); 124 | // showNotification('上传失败'); 125 | // showNotification('上传失败'); 126 | // showNotification('上传失败'); 127 | // showNotification('上传失败'); 128 | // showNotification('上传失败'); 129 | // showNotification('上传失败'); 130 | // showNotification('上传失败'); 131 | // setTimeout(() => { 132 | // showNotification('上传成功'); 133 | // }, 1000); 134 | } 135 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/css/default.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | font-size: 12px; 5 | } 6 | a { 7 | text-decoration: none; 8 | } 9 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/index.css: -------------------------------------------------------------------------------- 1 | header { 2 | height: 54px; 3 | line-height: 54px; 4 | color: #fff; 5 | background-color: #24292e; 6 | } 7 | header .container { 8 | margin: 0 14% 0 25%; 9 | } 10 | header .home { 11 | display: inline-block; 12 | font-size: 16px; 13 | } 14 | header .languages { 15 | position: relative; 16 | float: right; 17 | } 18 | header .languages .language-comp { 19 | cursor: pointer; 20 | } 21 | header .languages ul { 22 | display: none; 23 | position: absolute; 24 | top: 54px; 25 | width: 100px; 26 | color: #fff; 27 | background-color: #000; 28 | list-style-type: none; 29 | text-align: center; 30 | } 31 | header .languages ul.active { 32 | display: block; 33 | } 34 | header .languages li { 35 | height: 40px; 36 | line-height: 40px; 37 | cursor: pointer; 38 | } 39 | header .languages li:hover { 40 | color: #000; 41 | background-color: rgb(129, 129, 129); 42 | } 43 | .main { 44 | display: flex; 45 | margin-left: auto; 46 | margin-right: auto; 47 | margin-top: 100px; 48 | flex-direction: row; 49 | flex-wrap: wrap; 50 | justify-content: flex-start; 51 | align-items: flex-start; 52 | align-content: flex-start; 53 | } 54 | .main .item { 55 | display: inline-flex; 56 | width: 130px; 57 | height: 130px; 58 | border-radius: 4px; 59 | margin: 5px 5px; 60 | background-color: #000; 61 | justify-content: center; 62 | align-items: center; 63 | } 64 | .main .item:hover { 65 | background-color: #333; 66 | } 67 | .main .item .name { 68 | color: #dedede; 69 | text-align: center; 70 | word-wrap: break-word; 71 | } 72 | @media screen and (min-width: 768px) { 73 | div.main { 74 | width: 610px; 75 | } 76 | } -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | HttpServerDebug 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
HttpServerDebug
16 |
17 |
18 | Lang: %%LocalizedLanguageName%% 19 |
20 |
    21 |
  • 简体中文
  • 22 |
  • English
  • 23 |
24 |
25 |
26 |
27 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/index.js: -------------------------------------------------------------------------------- 1 | const Languages = { 2 | zhcn: { 3 | languageName: '简体中文', 4 | fileName: 'zh-cn.js', 5 | }, 6 | enus: { 7 | languageName: 'English', 8 | fileName: 'en-us.js', 9 | }, 10 | } 11 | 12 | window.onload = function () { 13 | // let languageType = getCookie('languageType'); 14 | // languageType = languageType || 'zhcn'; 15 | // const languageName = Languages[languageType].languageName; 16 | 17 | }; 18 | 19 | /** 20 | * show languages list 21 | */ 22 | function showLanguageList() { 23 | const ulEle = document.querySelector('header .languages ul'); 24 | ulEle.setAttribute('class', 'active'); 25 | } 26 | 27 | /** 28 | * hide languages list 29 | */ 30 | function hideLanguageList() { 31 | const ulEle = document.querySelector('header .languages ul'); 32 | ulEle.setAttribute('class', ''); 33 | } 34 | 35 | /** 36 | * switch language 37 | * @param {HTMLElement} element html element 38 | */ 39 | function selectLanguage(element) { 40 | const languageType = element.getAttribute('data-language-type'); 41 | setCookie('languageType', languageType); 42 | 43 | // refresh page 44 | location.reload(true); 45 | } 46 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/locals/enus.json: -------------------------------------------------------------------------------- 1 | { 2 | "LocalizedLanguageName": "English", 3 | "LocalizedFileExplorerEntryName": "File Explore", 4 | "LocalizedDatabaseInspectEntryName": "Database Inspect", 5 | "LocalizedViewDebugEntryName": "View Debug", 6 | "LocalizedSendInfoEntryName": "Send Information", 7 | "LocalizedConsoleLogEntryName": "Console Log", 8 | "LocalizedFileExplorerTitle": "File Explore", 9 | "LocalizedDatabaseInspectTitle": "Database Inspect", 10 | "LocalizedSendInfoTitle": "Send Information", 11 | "LocalizedConsoleLogTitle": "Console Log", 12 | "LocalizedContextMenuOpen": "Open", 13 | "LocalizedContextMenuDownload": "Download", 14 | "LocalizedContextMenuDelete": "Delete", 15 | "LocalizedContextMenuRename": "Rename", 16 | "LocalizedContextMenuUpload": "Upload", 17 | "LocalizedContextMenuCreateDirectory": "New Directory", 18 | "LocalizedFileAttributeCreateTime": "Create Time", 19 | "LocalizedFileAttributeModificationTime": "Modified Time", 20 | "LocalizedFileAttributeFilePath": "Path", 21 | "LocalizedDBInspectDatabaseFilePath": "Database file path: ", 22 | "LocalizedDBInspectBrowseData": "Browse Data", 23 | "LocalizedDBInspectExecuteSQL": "Execute SQL", 24 | "LocalizedDBInspectDatabaseSchema": "Schema", 25 | "LocalizedDBInspectRefresh": "Refresh", 26 | "LocalizedDBInspectExecute": "Execute", 27 | "LocalizedDBInspectInputSQLStatement": "SQL statement", 28 | "LocalizedDBInspectErrorPromptEmptyStatement": "empty sql statement", 29 | "LocalizedDBInspectDBDisconnectedPromptHtml": "

There is no database connected. This can be solved by the following method.

Search the objective database file in file explore, and open it.

", 30 | "LocalizedSendInfoTextareaPlaceholder": "input and send contents to app", 31 | "LocalizedSendInfoSendButtonTitle": "Send", 32 | "LocalizedConsoleLogButtonTitleConnect": "Connect", 33 | "LocalizedConsoleLogButtonTitleDisconnect": "Disconnect", 34 | "LocalizedConsoleLogButtonTitleRefresh": "Refresh connection state", 35 | "LocalizedConsoleLogStateName": "State: ", 36 | "LocalizedConsoleLogStateDisconnected": "Disconnected", 37 | "LocalizedConsoleLogStateConnected": "Connected" 38 | } 39 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/locals/zhcn.json: -------------------------------------------------------------------------------- 1 | { 2 | "LocalizedLanguageName": "简体中文", 3 | "LocalizedFileExplorerEntryName": "文件浏览", 4 | "LocalizedDatabaseInspectEntryName": "数据库", 5 | "LocalizedViewDebugEntryName": "视图调试", 6 | "LocalizedSendInfoEntryName": "发送信息", 7 | "LocalizedConsoleLogEntryName": "控制台日志", 8 | "LocalizedFileExplorerTitle": "文件浏览", 9 | "LocalizedDatabaseInspectTitle": "数据库", 10 | "LocalizedSendInfoTitle": "发送信息", 11 | "LocalizedConsoleLogTitle": "控制台日志", 12 | "LocalizedContextMenuOpen": "打开", 13 | "LocalizedContextMenuDownload": "下载", 14 | "LocalizedContextMenuDelete": "删除", 15 | "LocalizedContextMenuRename": "重新命名", 16 | "LocalizedContextMenuUpload": "上传", 17 | "LocalizedContextMenuCreateDirectory": "新建文件夹", 18 | "LocalizedFileAttributeCreateTime": "创建时间", 19 | "LocalizedFileAttributeModificationTime": "修改时间", 20 | "LocalizedFileAttributeFilePath": "文件路径", 21 | "LocalizedDBInspectDatabaseFilePath": "数据库文件路径:", 22 | "LocalizedDBInspectBrowseData": "浏览数据", 23 | "LocalizedDBInspectExecuteSQL": "执行 SQL", 24 | "LocalizedDBInspectDatabaseSchema": "数据库结构", 25 | "LocalizedDBInspectRefresh": "刷新", 26 | "LocalizedDBInspectExecute": "执行", 27 | "LocalizedDBInspectInputSQLStatement": "输入 SQL 语句", 28 | "LocalizedDBInspectErrorPromptEmptyStatement": "SQL 语句为空", 29 | "LocalizedDBInspectDBDisconnectedPromptHtml": "

没有连接到可用的数据库,可通过如下方法解决。

文件浏览中找到目标数据库文件,打开。

", 30 | "LocalizedSendInfoTextareaPlaceholder": "输入发送至 app 的信息", 31 | "LocalizedSendInfoSendButtonTitle": "发送", 32 | "LocalizedConsoleLogButtonTitleConnect": "连接", 33 | "LocalizedConsoleLogButtonTitleDisconnect": "断开连接", 34 | "LocalizedConsoleLogButtonTitleRefresh": "刷新连接状态", 35 | "LocalizedConsoleLogStateName": "状态:", 36 | "LocalizedConsoleLogStateDisconnected": "未连接", 37 | "LocalizedConsoleLogStateConnected": "已连接" 38 | } 39 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/pages/console_log/console_log.css: -------------------------------------------------------------------------------- 1 | #state-header { 2 | position: absolute; 3 | top: 0; 4 | height: 60px; 5 | left: 0; 6 | right: 0; 7 | } 8 | #logs-content { 9 | position: absolute; 10 | top: 60px; 11 | bottom: 0; 12 | left: 0; 13 | right: 0; 14 | overflow-y: scroll; 15 | } 16 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/pages/console_log/console_log.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 控制台日志 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

14 |
15 |
16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/pages/console_log/console_log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * localization data 3 | * @type {object} 4 | */ 5 | let localStrings; 6 | 7 | window.onload = function () { 8 | // localization 9 | requestLocalizationInfo(param => { 10 | localStrings = param; 11 | 12 | if ('WebSocket' in window) { 13 | var host = window.location.host; 14 | var ws = new WebSocket('ws://' + host); 15 | ws.onopen = function () { 16 | var stateEle = document.getElementById('connection_state'); 17 | stateEle.innerHTML = 'CONNECTED'; 18 | }; 19 | ws.onmessage = function (evt) { 20 | var needScrollToBottom = false; 21 | var logsEle = document.getElementById('logs-content'); 22 | if (logsEle.scrollTop + logsEle.clientHeight === logsEle.scrollHeight) { 23 | needScrollToBottom = true; 24 | } 25 | // show log string 26 | var htmlStr = '

' + evt.data + '

'; 27 | logsEle.insertAdjacentHTML('beforeend', htmlStr); 28 | // auto scroll to bottom 29 | if (needScrollToBottom) { 30 | logsEle.scrollTop = logsEle.scrollHeight - logsEle.clientHeight; 31 | } 32 | }; 33 | ws.onclose = function () { 34 | var stateEle = document.getElementById('connection_state'); 35 | stateEle.innerHTML = 'DISCONNECTED'; 36 | }; 37 | ws.onerror = function () { 38 | }; 39 | } else { 40 | alert('Browser doesn\'t support WebSocket!'); 41 | } 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/pages/database_inspect/database_inspect.css: -------------------------------------------------------------------------------- 1 | body, 2 | table { 3 | font-size: 12px; 4 | } 5 | table { 6 | border-collapse: collapse; 7 | } 8 | table, 9 | th, 10 | td { 11 | border: 1px solid #000; 12 | } 13 | td { 14 | text-align: center; 15 | } 16 | .nav-bar { 17 | display: block; 18 | border: 1px solid; 19 | } 20 | .nav-bar ul { 21 | list-style-type: none; 22 | margin: 0; 23 | } 24 | .nav-bar li { 25 | display: inline-block; 26 | padding: 15px 0; 27 | width: 100px; 28 | text-align: center; 29 | cursor: pointer; 30 | color: rgba(0, 0, 0, 0.8); 31 | } 32 | .nav-bar li:hover { 33 | color: #000; 34 | } 35 | .nav-bar li.active { 36 | background-color: #e7e7e7; 37 | color: #000; 38 | } 39 | .tab-pane { 40 | display: none; 41 | } 42 | .tab-pane.active { 43 | display: block; 44 | } 45 | .tab-pane .ctrl-head { 46 | margin: 15px 0; 47 | padding: 0; 48 | height: 20px; 49 | } 50 | .tab-pane .ctrl-head button { 51 | color: #000; 52 | border-width: 1px; 53 | margin: 0; 54 | padding: 0; 55 | width: 40px; 56 | height: 100%; 57 | } 58 | .tab-pane .ctrl-head button img, 59 | .tab-pane .ctrl-head button span { 60 | width: 100%; 61 | height: 100%; 62 | vertical-align: middle; 63 | } 64 | .tab-pane .ctrl-head button.reload-normal img { 65 | display: none; 66 | } 67 | .tab-pane .ctrl-head button.reload-loading img { 68 | display: inline-block; 69 | } 70 | .tab-pane .ctrl-head button.reload-normal span { 71 | display: inline-block; 72 | } 73 | .tab-pane .ctrl-head button.reload-loading span { 74 | display: none; 75 | } 76 | .tab-pane .table { 77 | overflow-x: scroll; 78 | } 79 | .tab-pane textarea { 80 | width: 100%; 81 | height: 145px; 82 | resize: vertical; 83 | } 84 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/pages/database_inspect/database_inspect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %%LocalizedDatabaseInspectTitle%% 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

%%LocalizedDBInspectDatabaseFilePath%%@@DB_FILE_PATH@@

14 | 15 | 22 | 23 |
24 |
25 | 26 | 29 |
30 |
31 | 32 |
33 |
34 | 35 |
36 |
37 | 40 |
41 |
42 |
43 | 44 |
45 |
46 |
47 | 48 |
49 |
50 |
51 | 52 |
53 |
54 |
55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/pages/file_explorer/file_explorer.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Verdana, Geneva, Tahoma, sans-serif; 3 | margin: 0; 4 | font-size: 14px; 5 | } 6 | #file-explorer, 7 | #property-sidebar { 8 | position: absolute; 9 | top: 0; 10 | bottom: 0; 11 | } 12 | #file-explorer { 13 | left: 0; 14 | right: 0; 15 | overflow-x: scroll; 16 | white-space: nowrap; 17 | } 18 | #property-sidebar { 19 | display: none; 20 | right: 0; 21 | width: 200px; 22 | } 23 | #property-sidebar.active { 24 | display: block; 25 | } 26 | .directory-container { 27 | display: inline-block; 28 | position: relative; 29 | width: 200px; 30 | height: 100%; 31 | border-right: 1px solid transparent; 32 | overflow-y: scroll; 33 | } 34 | div.split-handler { 35 | position: absolute; 36 | top: 0; 37 | bottom: 0; 38 | width: 2px; 39 | cursor: col-resize; 40 | } 41 | .directory-container ul { 42 | display: block; 43 | margin: 0 1px 0 0; 44 | padding: 0; 45 | list-style-type: none; 46 | } 47 | .directory-container li { 48 | display: block; 49 | position: relative; 50 | height: 26px; 51 | border: 1px solid transparent; 52 | color: #000; 53 | background-color: #fff; 54 | margin-bottom: 3px; 55 | } 56 | .directory-container li.selected { 57 | color: #fff; 58 | background-color: #0069D9; 59 | } 60 | .directory-container li:hover { 61 | border: 1px solid #0069D9; 62 | } 63 | .directory-container img.icon { 64 | display: inline-block; 65 | position: static; 66 | vertical-align: middle; 67 | width: 21px; 68 | height: 20px; 69 | } 70 | .directory-container img.directory-icon { 71 | content: url(../../resources/directory-icon.png); 72 | } 73 | .directory-container img.file-icon { 74 | content: url(../../resources/file-icon.png); 75 | } 76 | .directory-container li span { 77 | display: inline-block; 78 | position: absolute; 79 | vertical-align: middle; 80 | line-height: 26px; 81 | left: 21px; 82 | right: 0; 83 | text-overflow: ellipsis; 84 | white-space: nowrap; 85 | overflow: hidden; 86 | } 87 | .directory-container div.split-handler { 88 | right: 0; 89 | border-right: 1px solid #E0E0E0; 90 | } 91 | #property-sidebar div.content-container { 92 | margin: 0 0 0 1px; 93 | width: 100%; 94 | height: 100%; 95 | overflow: scroll; 96 | } 97 | #property-sidebar img.icon { 98 | display: block; 99 | margin-left: auto; 100 | margin-right: auto; 101 | width: 80%; 102 | } 103 | #property-sidebar p { 104 | text-align: center; 105 | word-wrap: break-word; 106 | overflow: hidden; 107 | } 108 | #property-sidebar p.file-name { 109 | font-size: 16px; 110 | } 111 | #property-sidebar div.split-handler { 112 | left: 0; 113 | border-left: 1px solid #E0E0E0; 114 | } 115 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/pages/file_explorer/file_explorer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %%LocalizedFileExplorerTitle%% 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | 20 |
21 |
22 |
23 |
24 | 25 | 35 | 36 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/pages/file_explorer/file_explorer_context_menu.css: -------------------------------------------------------------------------------- 1 | /* context menu */ 2 | .context-menu { 3 | display: none; 4 | position: absolute; 5 | z-index: 10; 6 | padding: 8px 0; 7 | width: 240px; 8 | background-color: #fff; 9 | border: solid 1px #dfdfdf; 10 | border-radius: 4px; 11 | box-shadow: 1px 1px 2px #cfcfcf; 12 | } 13 | 14 | .context-menu-active { 15 | display: block; 16 | } 17 | 18 | .context-menu-items { 19 | list-style: none; 20 | margin: 0; 21 | padding: 0; 22 | } 23 | 24 | .context-menu-item { 25 | display: none; 26 | padding: 4px 12px; 27 | color: #000000; 28 | } 29 | 30 | .context-menu-item-active { 31 | display: block; 32 | } 33 | 34 | .context-menu-item:hover { 35 | color: #fff; 36 | background-color: #0069d9; 37 | } 38 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/pages/file_explorer/file_explorer_resize.js: -------------------------------------------------------------------------------- 1 | /* resize column functions */ 2 | var startResizeX; 3 | var startResizeWidth; 4 | var targetResizeEle; 5 | 6 | function initResizeDrag(event) { 7 | targetResizeEle = event.currentTarget.parentElement; 8 | 9 | // initial value 10 | startResizeX = event.clientX; 11 | startResizeWidth = parseInt(document.defaultView.getComputedStyle(targetResizeEle).width, 10); 12 | 13 | document.documentElement.addEventListener('mousemove', doResizeDrag, false); 14 | document.documentElement.addEventListener('mouseup', stopResizeDrag, false); 15 | } 16 | 17 | function doResizeDrag(event) { 18 | // calculate target element width and modify views 19 | var sidebarEle = document.getElementById('property-sidebar'); 20 | if (targetResizeEle === sidebarEle) { 21 | var widthStr = (startResizeWidth - event.clientX + startResizeX) + 'px'; 22 | var fileExpEle = document.getElementById('file-explorer'); 23 | 24 | targetResizeEle.style.width = widthStr; 25 | fileExpEle.style.right = widthStr; 26 | } else { 27 | targetResizeEle.style.width = (startResizeWidth + event.clientX - startResizeX) + 'px'; 28 | } 29 | 30 | if (event.preventDefault) { 31 | event.preventDefault(); 32 | } 33 | } 34 | 35 | function stopResizeDrag(event) { 36 | document.documentElement.removeEventListener('mousemove', doResizeDrag, false); 37 | document.documentElement.removeEventListener('mouseup', stopResizeDrag, false); 38 | } 39 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/pages/send_info/send_info.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 12px; 3 | } 4 | textarea { 5 | width: 500px; 6 | height: 200px; 7 | } 8 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/pages/send_info/send_info.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %%LocalizedSendInfoTitle%% 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 |
15 |
16 |

Response Data:

17 |

18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/pages/send_info/send_info.js: -------------------------------------------------------------------------------- 1 | function submitForm() { 2 | const infoStr = document.getElementById('info_textarea').value.trim(); 3 | 4 | const infoXHR = new XMLHttpRequest(); 5 | const requestURL = `${document.location.protocol}//${document.location.host}/api/send_info`; 6 | infoXHR.open('POST', requestURL); 7 | infoXHR.onload = function () { 8 | if (infoXHR.status === 200) { 9 | const responseText = infoXHR.responseText; 10 | const responseEle = document.getElementById('response_data'); 11 | responseEle.innerText = responseText; 12 | } 13 | }; 14 | infoXHR.send(infoStr); 15 | } 16 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/pages/view_debug/view_debug.css: -------------------------------------------------------------------------------- 1 | /* global */ 2 | body { 3 | font-size: 12px; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | input { 8 | margin: 0; 9 | padding: 0; 10 | } 11 | 12 | /* canvas area */ 13 | div.view-hierarchy-area { 14 | position: absolute; 15 | top: 28px; 16 | bottom: 28px; 17 | left: 0; 18 | right: 0; 19 | background-color: #e2e3e7; 20 | } 21 | #canvas-frame { 22 | width: 100%; 23 | height: 100%; 24 | } 25 | 26 | /* sidebar */ 27 | .sidebar { 28 | position: absolute; 29 | top: 0; 30 | bottom: 0; 31 | width: 0; 32 | display: none; 33 | color: #292929; 34 | } 35 | .sidebar.active { 36 | display: block; 37 | width: 300px; 38 | } 39 | .sidebar.navigation-sidebar { 40 | left: 0; 41 | background-color: #b9b9b9 42 | } 43 | .sidebar.property-sidebar { 44 | right: 0; 45 | background-color: #ececec; 46 | } 47 | .sidebar .container { 48 | position: absolute; 49 | top: 0; 50 | bottom: 0; 51 | overflow: scroll; 52 | height: 100%; 53 | } 54 | .navigation-sidebar .container { 55 | left: 0; 56 | right: 3px; 57 | } 58 | .property-sidebar .container { 59 | left: 3px; 60 | right: 0; 61 | } 62 | .sidebar ul { 63 | list-style-type: none; 64 | margin: 0; 65 | } 66 | .sidebar .split-handler { 67 | position: absolute; 68 | top: 0; 69 | bottom: 0; 70 | width: 3px; 71 | cursor: col-resize; 72 | } 73 | .navigation-sidebar .split-handler { 74 | right: 0; 75 | border-right: 1px solid #7a7a7a; 76 | } 77 | .property-sidebar .split-handler { 78 | left: 0; 79 | border-left: 1px solid #b1b1b1; 80 | } 81 | #view-hierarchy-list ul { 82 | padding: 0 0 0 10px; 83 | } 84 | #view-hierarchy-list li { 85 | white-space: nowrap; 86 | line-height: 35px; 87 | cursor: pointer; 88 | } 89 | #view-hierarchy-list li.active { 90 | background-color: #dad5d5; 91 | } 92 | #view-hierarchy-list li span { 93 | border-left: 1px solid; 94 | padding-left: 4px; 95 | display: inline-block; 96 | } 97 | .view-property-list ul { 98 | padding: 0; 99 | } 100 | .view-property-list img { 101 | width: 75%; 102 | margin-left: auto; 103 | margin-right: auto; 104 | display: block; 105 | } 106 | .property-sidebar .container .not-applicable { 107 | display: flex; 108 | justify-content: center; 109 | align-items: center; 110 | font-size: 18px; 111 | color: #737373; 112 | text-align: center; 113 | height: 100%; 114 | } 115 | 116 | /* navigation toolbar */ 117 | .navigation-toolbar { 118 | position: absolute; 119 | top: 0; 120 | height: 28px; 121 | left: 300px; 122 | right: 0; 123 | background-color: #fff; 124 | display: flex; 125 | align-items: center; 126 | overflow-x: scroll; 127 | } 128 | .navigation-toolbar .item { 129 | min-width: 23px; 130 | margin-left: 5px; 131 | text-align: center; 132 | background-color: lightgrey; 133 | text-overflow: ellipsis; 134 | overflow-x: hidden; 135 | } 136 | 137 | /* canvas toolbar */ 138 | #canvas-toolbar { 139 | position: absolute; 140 | height: 28px; 141 | bottom: 0; 142 | left: 300px; 143 | right: 0; 144 | background-color: #e7e8ea; 145 | } 146 | #canvas-toolbar .control-tool { 147 | display: none; 148 | } 149 | #canvas-toolbar .control-tool.available { 150 | display: inline-block; 151 | } 152 | #canvas-toolbar .control-tool.depth-unit { 153 | position: absolute; 154 | left: 5px; 155 | width: 130px; 156 | top: 0; 157 | bottom: 0; 158 | } 159 | #canvas-toolbar input[type="range"] { 160 | display: block; 161 | width: 100%; 162 | height: 100%; 163 | } 164 | #canvas-toolbar .tools-collection { 165 | display: block; 166 | width: 200px; 167 | height: 100%; 168 | margin-left: auto; 169 | margin-right: auto; 170 | } 171 | #canvas-toolbar .tools-collection:before { 172 | content: ""; 173 | display: inline-block; 174 | vertical-align: middle; 175 | height: 100%; 176 | } 177 | #canvas-toolbar button.control-tool { 178 | width: 24px; 179 | height: 24px; 180 | padding: 0; 181 | border: 1px solid transparent; 182 | /* border: 1px solid black; */ 183 | background-color: transparent; 184 | text-align: center; 185 | vertical-align: middle; 186 | font-size: 0; 187 | } 188 | #canvas-toolbar button.control-tool::before { 189 | content: ""; 190 | display: inline-block; 191 | vertical-align: middle; 192 | height: 100%; 193 | } 194 | #canvas-toolbar button div { 195 | width: 13px; 196 | height: 13px; 197 | vertical-align: middle; 198 | } 199 | #canvas-toolbar button div img { 200 | width: 13px; 201 | height: 13px; 202 | } 203 | #canvas-toolbar button div { 204 | display: inline-block; 205 | overflow: hidden; 206 | } 207 | #canvas-toolbar button div img { 208 | display: inline-block; 209 | position: relative; 210 | } 211 | #canvas-toolbar button.control-tool.text-content span { 212 | display: inline-block; 213 | font-size: 16px; 214 | vertical-align: middle; 215 | } 216 | /* area control */ 217 | #canvas-toolbar .area-control-collection { 218 | display: block; 219 | position: absolute; 220 | top: 0; 221 | bottom: 0; 222 | right: 10px; 223 | width: 100px; 224 | } 225 | #canvas-toolbar .area-control-collection button { 226 | width: 27px; 227 | height: 24px; 228 | margin-top: 3px; 229 | padding: 1px 0 0 0; 230 | text-align: center; 231 | } 232 | #canvas-toolbar .area-control-collection button div, 233 | #canvas-toolbar .area-control-collection button div img { 234 | width: 25px; 235 | height: 20px; 236 | } 237 | #canvas-toolbar button.navigator-control, 238 | #canvas-toolbar button.utilities-control { 239 | position: absolute; 240 | top: 0; 241 | bottom: 0; 242 | } 243 | #canvas-toolbar button.navigator-control { 244 | right: 32px; 245 | } 246 | #canvas-toolbar button.utilities-control { 247 | right: 0; 248 | } 249 | #canvas-toolbar .tools-collection button.selected img { 250 | left: -13px; 251 | border-right: 13px solid transparent; 252 | filter: drop-shadow(13px 0 0 rgb(53, 130, 242)); 253 | } 254 | #canvas-toolbar .area-control-collection button.selected img { 255 | left: -25px; 256 | border-right: 25px solid transparent; 257 | filter: drop-shadow(25px 0 0 rgb(53, 130, 242)); 258 | } 259 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/pages/view_debug/view_debug.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 视图调试 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 | 21 | 22 | 28 | 29 | 30 | 32 | 33 | 34 |
35 |
36 | 37 |
38 |
39 | 42 | 45 | 48 | 49 | 50 | 51 |
52 |
53 | 56 | 59 |
60 |
61 | 62 | 63 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/pages/view_debug/view_debug_sidebar.js: -------------------------------------------------------------------------------- 1 | function initSideBarAdjust() { 2 | // sidebar 3 | var navSideBarEle = document.querySelector('.navigation-sidebar'); 4 | var navSplitHandlerEle = navSideBarEle.getElementsByClassName('split-handler')[0]; 5 | var propertySideBarEle = document.getElementsByClassName('property-sidebar')[0]; 6 | var propertySplitHandlerEle = propertySideBarEle.getElementsByClassName('split-handler')[0]; 7 | // toolbar 8 | const navToolbarEle = document.querySelector('.navigation-toolbar'); 9 | const canvasToolbarEle = document.querySelector('#canvas-toolbar'); 10 | 11 | // addEventListener 12 | navSplitHandlerEle.addEventListener('mousedown', initDrag, false); 13 | propertySplitHandlerEle.addEventListener('mousedown', initDrag, false); 14 | 15 | var startX; 16 | var startWidth; 17 | var sideBarEle; 18 | var splitHandlerEle; 19 | function initDrag(e) { 20 | // initialize data 21 | splitHandlerEle = e.currentTarget; 22 | if (splitHandlerEle === navSplitHandlerEle) { 23 | sideBarEle = navSideBarEle; 24 | } else { 25 | sideBarEle = propertySideBarEle; 26 | } 27 | startX = e.clientX; 28 | startWidth = parseInt(document.defaultView.getComputedStyle(sideBarEle).width, 10); 29 | 30 | // addEventListener 31 | document.documentElement.addEventListener('mousemove', doDrag, false); 32 | document.documentElement.addEventListener('mouseup', stopDrag, false); 33 | } 34 | 35 | function doDrag(e) { 36 | var offset = e.clientX - startX; 37 | var length; 38 | if (sideBarEle === navSideBarEle) { 39 | // navigation siderbar 40 | length = startWidth + offset 41 | sideBarEle.style.width = length + 'px'; 42 | navToolbarEle.style.left = length + 'px'; 43 | canvasToolbarEle.style.left = length + 'px'; 44 | } else { 45 | // property siderbar 46 | length = startWidth - offset; 47 | sideBarEle.style.width = length + 'px'; 48 | navToolbarEle.style.right = length + 'px'; 49 | canvasToolbarEle.style.right = length + 'px'; 50 | } 51 | 52 | if (e.preventDefault) { 53 | e.preventDefault(); 54 | } 55 | } 56 | 57 | function stopDrag(e) { 58 | document.documentElement.removeEventListener('mousemove', doDrag, false); 59 | document.documentElement.removeEventListener('mouseup', stopDrag, false); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/pages/web_upload/web_upload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 文件导入 7 | 9 | 11 | 12 | 13 |
14 | 15 |
16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/resources/XC_O_area_button_navigator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rob2468/HttpServerDebug/951ce642aecd7ef56f10e455028ffc56b20b67a3/Resources/HttpServerDebug.bundle/web/resources/XC_O_area_button_navigator.png -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/resources/XC_O_utilities_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rob2468/HttpServerDebug/951ce642aecd7ef56f10e455028ffc56b20b67a3/Resources/HttpServerDebug.bundle/web/resources/XC_O_utilities_button.png -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/resources/db_icon_view_orient_2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rob2468/HttpServerDebug/951ce642aecd7ef56f10e455028ffc56b20b67a3/Resources/HttpServerDebug.bundle/web/resources/db_icon_view_orient_2d.png -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/resources/db_icon_view_orient_3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rob2468/HttpServerDebug/951ce642aecd7ef56f10e455028ffc56b20b67a3/Resources/HttpServerDebug.bundle/web/resources/db_icon_view_orient_3d.png -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/resources/db_icon_view_show_clipped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rob2468/HttpServerDebug/951ce642aecd7ef56f10e455028ffc56b20b67a3/Resources/HttpServerDebug.bundle/web/resources/db_icon_view_show_clipped.png -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/resources/directory-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rob2468/HttpServerDebug/951ce642aecd7ef56f10e455028ffc56b20b67a3/Resources/HttpServerDebug.bundle/web/resources/directory-icon.png -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/resources/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rob2468/HttpServerDebug/951ce642aecd7ef56f10e455028ffc56b20b67a3/Resources/HttpServerDebug.bundle/web/resources/favicon.ico -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/resources/file-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rob2468/HttpServerDebug/951ce642aecd7ef56f10e455028ffc56b20b67a3/Resources/HttpServerDebug.bundle/web/resources/file-icon.png -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/resources/loading-bubbles.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 10 | 11 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Resources/HttpServerDebug.bundle/web/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * request and init the localization json data 3 | * @param {function} cb callback 4 | */ 5 | function requestLocalizationInfo(cb) { 6 | const xhr = new XMLHttpRequest(); 7 | const requestURL = `${document.location.protocol}//${document.location.host}/api/localization`; 8 | xhr.open('GET', requestURL); 9 | xhr.onload = function () { 10 | if (xhr.status === 200) { 11 | const responseText = xhr.responseText; 12 | const responseJSON = JSON.parse(responseText); 13 | const localStrings = responseJSON.result; 14 | cb && cb(localStrings); 15 | } 16 | }; 17 | xhr.send(null); 18 | } 19 | 20 | /** 21 | * parse language type 22 | */ 23 | function getLanguageType() { 24 | let languageType = getCookie('languageType'); 25 | languageType = languageType || 'zhcn'; 26 | return languageType; 27 | } 28 | 29 | /** 30 | * set cookie 31 | * @param {string} cname key 32 | * @param {string} cvalue value 33 | * @param {number} exdays expire days 34 | */ 35 | function setCookie(cname, cvalue, exdays = 365) { 36 | var d = new Date(); 37 | d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); 38 | var expires = 'expires='+d.toUTCString(); 39 | document.cookie = cname + '=' + cvalue + ';' + expires + ';path=/'; 40 | } 41 | 42 | /** 43 | * get cookie 44 | * @param {string} cname key 45 | */ 46 | function getCookie(cname) { 47 | var name = cname + '='; 48 | var ca = document.cookie.split(';'); 49 | for(var i = 0; i < ca.length; i++) { 50 | var c = ca[i]; 51 | while (c.charAt(0) == ' ') { 52 | c = c.substring(1); 53 | } 54 | if (c.indexOf(name) == 0) { 55 | return c.substring(name.length, c.length); 56 | } 57 | } 58 | return ''; 59 | } 60 | -------------------------------------------------------------------------------- /Sample/Sample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Sample/Sample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sample/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /Sample/Sample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Sample/Sample/Database/HSDSampleCategoryDataModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDSampleCategoryDataModel.h 3 | // Sample 4 | // 5 | // Created by chenjun on 2018/5/17. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | // “分类”数据模型 9 | 10 | #import 11 | 12 | @interface HSDSampleCategoryDataModel : NSObject 13 | 14 | @property (assign, nonatomic) NSInteger ID; // 唯一标识符 15 | @property (copy, nonatomic) NSString *name; // 分类名 16 | 17 | - (instancetype)init; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Sample/Sample/Database/HSDSampleCategoryDataModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // HSDSampleCategoryDataModel.m 3 | // Sample 4 | // 5 | // Created by chenjun on 2018/5/17. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import "HSDSampleCategoryDataModel.h" 10 | 11 | @implementation HSDSampleCategoryDataModel 12 | 13 | - (instancetype)init { 14 | self = [super init]; 15 | if (self) { 16 | self.ID = NSNotFound; 17 | self.name = @""; 18 | } 19 | return self; 20 | }; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Sample/Sample/Database/HSDSampleDBCategoryManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDSampleDBCategoryManager.h 3 | // Sample 4 | // 5 | // Created by chenjun on 2018/5/17. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import 10 | @class HSDSampleCategoryDataModel; 11 | 12 | @interface HSDSampleDBCategoryManager : NSObject 13 | 14 | // 获取所有“分类” 15 | + (NSArray *)fetchAllCategories; 16 | 17 | // 添加“分类” 18 | + (void)addCategory:(HSDSampleCategoryDataModel *)category; 19 | 20 | // 更新“分类” 21 | + (void)updateCategory:(HSDSampleCategoryDataModel *)category; 22 | 23 | // 删除指定“分类” 24 | + (void)deleteCategoryWithID:(NSInteger)ID; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Sample/Sample/Database/HSDSampleDBCategoryManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // HSDSampleDBCategoryManager.m 3 | // Sample 4 | // 5 | // Created by chenjun on 2018/5/17. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import "HSDSampleDBCategoryManager.h" 10 | #import "HSDSampleCategoryDataModel.h" 11 | #import "FMDB.h" 12 | #import "HSDSampleDBManager.h" 13 | 14 | @implementation HSDSampleDBCategoryManager 15 | 16 | // 获取所有“分类” 17 | + (NSArray *)fetchAllCategories { 18 | NSMutableArray *dataList = [[NSMutableArray alloc] init]; 19 | FMDatabaseQueue *databaseQueue = [HSDSampleDBManager sharedInstance].databaseQueue; 20 | [databaseQueue inDatabase:^(FMDatabase * _Nonnull db) { 21 | NSString *tableCategory = kTABLECATEGORY; 22 | NSString *stat = [NSString stringWithFormat:@"SELECT * FROM %@;", tableCategory]; 23 | FMResultSet *resultSet = [db executeQuery:stat]; 24 | if (resultSet) { 25 | FMResultSet *rs = resultSet; 26 | NSString *categoryFieldID = kCATEGORYFIELDID; 27 | NSString *categoryFieldName = kCATEGORYFIELDNAME; 28 | while ([rs next]) { 29 | NSInteger ID = [rs longForColumn:categoryFieldID]; 30 | NSString *name = [rs stringForColumn:categoryFieldName]; 31 | HSDSampleCategoryDataModel *category = [[HSDSampleCategoryDataModel alloc] init]; 32 | category.ID = ID; 33 | category.name = name; 34 | [dataList addObject:category]; 35 | } 36 | [rs close]; 37 | } 38 | }]; 39 | return dataList; 40 | } 41 | 42 | // 添加“分类” 43 | + (void)addCategory:(HSDSampleCategoryDataModel *)category { 44 | FMDatabaseQueue *databaseQueue = [HSDSampleDBManager sharedInstance].databaseQueue; 45 | [databaseQueue inDatabase:^(FMDatabase * _Nonnull db) { 46 | NSString *tableName = kTABLECATEGORY; 47 | NSString *nameField = kCATEGORYFIELDNAME; 48 | NSString *name = category.name; 49 | NSString *stat = [NSString stringWithFormat:@"INSERT INTO %@ (%@) VALUES ('%@');", tableName, nameField, name]; 50 | [db executeUpdate:stat]; 51 | }]; 52 | } 53 | 54 | // 更新“分类” 55 | + (void)updateCategory:(HSDSampleCategoryDataModel *)category { 56 | FMDatabaseQueue *databaseQueue = [HSDSampleDBManager sharedInstance].databaseQueue; 57 | [databaseQueue inDatabase:^(FMDatabase * _Nonnull db) { 58 | NSString *tableName = kTABLECATEGORY; 59 | NSString *idField = kCATEGORYFIELDID; 60 | NSString *nameField = kCATEGORYFIELDNAME; 61 | NSInteger ID = category.ID; 62 | NSString *name = category.name; 63 | NSString *stat = [NSString stringWithFormat:@"UPDATE %@ SET %@ = '%@' WHERE %@ = %ld;", tableName, nameField, name, idField, (long)ID]; 64 | [db executeUpdate:stat]; 65 | }]; 66 | } 67 | 68 | // 删除指定“分类” 69 | + (void)deleteCategoryWithID:(NSInteger)ID { 70 | FMDatabaseQueue *databaseQueue = [HSDSampleDBManager sharedInstance].databaseQueue; 71 | [databaseQueue inDatabase:^(FMDatabase * _Nonnull db) { 72 | NSString *tableCategory = kTABLECATEGORY; 73 | NSString *categoryFieldID = kCATEGORYFIELDID; 74 | NSString *stat = [NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ = %ld;", tableCategory, categoryFieldID, (long)ID]; 75 | [db executeUpdate:stat]; 76 | }]; 77 | } 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /Sample/Sample/Database/HSDSampleDBManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDSampleDBManager.h 3 | // Sample 4 | // 5 | // Created by chenjun on 2018/5/17. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import 10 | @class FMDatabaseQueue; 11 | 12 | // 表名 13 | extern NSString * const kTABLECATEGORY; 14 | // 字段名 15 | extern NSString * const kCATEGORYFIELDID; 16 | extern NSString * const kCATEGORYFIELDNAME; 17 | 18 | @interface HSDSampleDBManager : NSObject 19 | 20 | @property (strong, nonatomic, readonly) FMDatabaseQueue *databaseQueue; 21 | 22 | + (instancetype)sharedInstance; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Sample/Sample/Database/HSDSampleDBManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // HSDSampleDBManager.m 3 | // Sample 4 | // 5 | // Created by chenjun on 2018/5/17. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import "HSDSampleDBManager.h" 10 | #import "FMDB.h" 11 | 12 | static const NSInteger kCurrentVersion = 1; 13 | 14 | // 表名 15 | NSString * const kTABLECATEGORY = @"category"; 16 | // 字段名 17 | NSString * const kCATEGORYFIELDID = @"id"; 18 | NSString * const kCATEGORYFIELDNAME = @"name"; 19 | 20 | @interface HSDSampleDBManager () 21 | 22 | @property (strong, nonatomic) NSURL *dbFilePath; 23 | @property (strong, nonatomic, readwrite) FMDatabaseQueue *databaseQueue; 24 | 25 | @end 26 | 27 | @implementation HSDSampleDBManager 28 | 29 | + (instancetype)sharedInstance { 30 | static HSDSampleDBManager *instance; 31 | static dispatch_once_t onceToken; 32 | dispatch_once(&onceToken, ^{ 33 | instance = [[HSDSampleDBManager alloc] init]; 34 | }); 35 | return instance; 36 | } 37 | 38 | - (instancetype)init { 39 | self = [super init]; 40 | if (self) { 41 | NSURL *path = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject; 42 | self.dbFilePath = [path URLByAppendingPathComponent:@"Closet.sqlite"]; 43 | self.databaseQueue = [[FMDatabaseQueue alloc] initWithURL:self.dbFilePath]; 44 | // 读取数据库版本号 45 | __block NSInteger version = 0; 46 | [self.databaseQueue inDatabase:^(FMDatabase * _Nonnull db) { 47 | FMResultSet *rs = [db executeQuery:@"PRAGMA user_version"]; 48 | if (rs) { 49 | FMResultSet *resultSet = rs; 50 | if ([resultSet next]) { 51 | version = [resultSet intForColumnIndex:0]; 52 | } 53 | [resultSet close]; 54 | } 55 | 56 | }]; 57 | NSAssert(version <= kCurrentVersion, @""); 58 | // 数据库升级 59 | [self upgradeDatabaseFromVersion:version]; 60 | } 61 | return self; 62 | } 63 | 64 | - (void)upgradeDatabaseFromVersion:(NSInteger)version { 65 | // 数据库逐版本升级 66 | for (NSInteger i = version; i < kCurrentVersion; i++) { 67 | if (i == 0) { 68 | [self.databaseQueue inTransaction:^(FMDatabase * _Nonnull db, BOOL * _Nonnull rollback) { 69 | // 创建“分类”表 70 | NSString *stat = [NSString stringWithFormat:@"CREATE TABLE %@ (%@ INTEGER PRIMARY KEY AUTOINCREMENT, %@ TEXT);", kTABLECATEGORY, kCATEGORYFIELDID, kCATEGORYFIELDNAME]; 71 | [db executeUpdate:stat]; 72 | }]; 73 | } 74 | } 75 | 76 | // 更新数据库版本号 77 | [self.databaseQueue inDatabase:^(FMDatabase * _Nonnull db) { 78 | NSString *stat = [NSString stringWithFormat:@"PRAGMA user_version = %ld;", (long)kCurrentVersion]; 79 | [db executeUpdate:stat]; 80 | }]; 81 | } 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /Sample/Sample/HSDSampleAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDSampleAppDelegate.h 3 | // Sample 4 | // 5 | // Created by chenjun on 2018/4/23. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "HSDDelegate.h" 11 | 12 | @interface HSDSampleAppDelegate : UIResponder 13 | 15 | 16 | @property (strong, nonatomic) UIWindow *window; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Sample/Sample/HSDSampleAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // HSDSampleAppDelegate.m 3 | // Sample 4 | // 5 | // Created by chenjun on 2018/4/23. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import "HSDSampleAppDelegate.h" 10 | #import "HSDManager.h" 11 | #import "HSDSampleRootController.h" 12 | 13 | @implementation HSDSampleAppDelegate 14 | 15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 16 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 17 | HSDSampleRootController *rootVC = [[HSDSampleRootController alloc] init]; 18 | rootVC.view.backgroundColor = [UIColor yellowColor]; 19 | self.window.rootViewController = rootVC; 20 | [self.window makeKeyAndVisible]; 21 | 22 | [HSDManager updateHSDDelegate:self]; 23 | [HSDManager updateHttpServerPort:5555]; 24 | 25 | // demo for console log component 26 | static NSInteger tmp = 0; 27 | NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) { 28 | tmp++; 29 | NSLog(@"test for console log display %ld", (long)tmp); 30 | }]; 31 | [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 32 | 33 | return YES; 34 | } 35 | 36 | -(NSDictionary *)onHSDReceiveInfo:(NSString *)info { 37 | info = info.length > 0 ? info : @""; 38 | return @{@"sent_info": info}; 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /Sample/Sample/HSDSampleRootController.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDSampleRootController.h 3 | // Sample 4 | // 5 | // Created by chenjun on 2018/4/23. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HSDSampleRootController : UIViewController 12 | 13 | + (HSDSampleRootController *)fetchRootVC; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Sample/Sample/HSDSampleRootController.m: -------------------------------------------------------------------------------- 1 | // 2 | // HSDSampleRootController.m 3 | // Sample 4 | // 5 | // Created by chenjun on 2018/4/23. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import "HSDSampleRootController.h" 10 | #import "HSDSampleHomeViewController.h" 11 | 12 | @interface HSDSampleRootController () 13 | 14 | @property (strong, nonatomic) UINavigationController *rootNav; 15 | 16 | @end 17 | 18 | @implementation HSDSampleRootController 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | 23 | self.view.backgroundColor = [UIColor whiteColor]; 24 | HSDSampleHomeViewController *homeController = [[HSDSampleHomeViewController alloc] init]; 25 | self.rootNav = [[UINavigationController alloc] initWithRootViewController:homeController]; 26 | self.rootNav.view.frame = self.view.bounds; 27 | self.rootNav.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 28 | [self.view addSubview:self.rootNav.view]; 29 | } 30 | 31 | + (HSDSampleRootController *)fetchRootVC { 32 | HSDSampleRootController *rootController = (HSDSampleRootController *)UIApplication.sharedApplication.keyWindow.rootViewController; 33 | return rootController; 34 | } 35 | 36 | - (void)didReceiveMemoryWarning { 37 | [super didReceiveMemoryWarning]; 38 | // Dispose of any resources that can be recreated. 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /Sample/Sample/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sample/Sample/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Sample/Sample/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Sample/Sample/ViewController/HSDSampleCategoryEditController.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDSampleCategoryEditController.h 3 | // Sample 4 | // 5 | // Created by chenjun on 2018/5/17. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class HSDSampleCategoryDataModel; 12 | @protocol HSDSampleCategoryEditControllerDelegate; 13 | 14 | @interface HSDSampleCategoryEditController : UIViewController 15 | 16 | @property (weak, nonatomic) id delegate; 17 | 18 | - (instancetype)initWithCategory:(HSDSampleCategoryDataModel *)category; 19 | 20 | @end 21 | 22 | @protocol HSDSampleCategoryEditControllerDelegate 23 | 24 | - (void)onCategoryEditControllerDismiss; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Sample/Sample/ViewController/HSDSampleDBInspectViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDSampleDBInspectViewController.h 3 | // Sample 4 | // 5 | // Created by chenjun on 2018/5/17. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HSDSampleDBInspectViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Sample/Sample/ViewController/HSDSampleHomeViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDSampleHomeViewController.h 3 | // Sample 4 | // 5 | // Created by chenjun on 2018/4/26. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HSDSampleHomeViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Sample/Sample/ViewController/HSDSampleHomeViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // HSDSampleHomeViewController.m 3 | // Sample 4 | // 5 | // Created by chenjun on 2018/4/26. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import "HSDSampleHomeViewController.h" 10 | #import "HSDHttpServerControlPannelController.h" 11 | #import "HSDSampleDBInspectViewController.h" 12 | #import "HSDSampleViewDebugViewController.h" 13 | 14 | static NSString * const kHSDCtrlPannel = @"HSD Control Pannel"; 15 | static NSString * const kHSDDatabaseInspect = @"Database Inspect"; 16 | static NSString * const kHSDViewDebug = @"View Debug"; 17 | 18 | @interface HSDSampleHomeViewController () 19 | 20 | 21 | @property (strong, nonatomic) UITableView *tableView; 22 | @property (copy, nonatomic) NSArray *dataList; 23 | 24 | @end 25 | 26 | @implementation HSDSampleHomeViewController 27 | 28 | - (instancetype)init { 29 | self = [super init]; 30 | if (self) { 31 | self.dataList = @[kHSDCtrlPannel, kHSDDatabaseInspect, kHSDViewDebug]; 32 | } 33 | return self; 34 | } 35 | 36 | - (void)viewDidLoad { 37 | [super viewDidLoad]; 38 | self.view.backgroundColor = [UIColor whiteColor]; 39 | self.title = @"HSD"; 40 | 41 | // tableView 42 | self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:(UITableViewStylePlain)]; 43 | self.tableView.dataSource = self; 44 | self.tableView.delegate = self; 45 | [self.view addSubview:self.tableView]; 46 | 47 | [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"]; 48 | } 49 | 50 | #pragma mark - UITableViewDataSource 51 | 52 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 53 | return [self.dataList count]; 54 | } 55 | 56 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 57 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath]; 58 | NSInteger row = [indexPath row]; 59 | NSString *title = [self.dataList objectAtIndex:row]; 60 | cell.textLabel.text = title; 61 | return cell; 62 | } 63 | 64 | #pragma mark - UITableViewDelegate 65 | 66 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 67 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 68 | NSInteger row = [indexPath row]; 69 | NSString *title = [self.dataList objectAtIndex:row]; 70 | if ([title isEqualToString:kHSDCtrlPannel]) { 71 | HSDHttpServerControlPannelController *vc = [[HSDHttpServerControlPannelController alloc] init]; 72 | vc.backBlock = ^{ 73 | [self.navigationController popViewControllerAnimated:YES]; 74 | }; 75 | [self.navigationController pushViewController:vc animated:YES]; 76 | } else if ([title isEqualToString:kHSDDatabaseInspect]) { 77 | HSDSampleDBInspectViewController *vc = [[HSDSampleDBInspectViewController alloc] init]; 78 | [self.navigationController pushViewController:vc animated:YES]; 79 | } else if ([title isEqualToString:kHSDViewDebug]) { 80 | HSDSampleViewDebugViewController *vc = [[HSDSampleViewDebugViewController alloc] init]; 81 | [self.navigationController pushViewController:vc animated:YES]; 82 | } 83 | } 84 | 85 | - (void)didReceiveMemoryWarning { 86 | [super didReceiveMemoryWarning]; 87 | // Dispose of any resources that can be recreated. 88 | } 89 | 90 | @end 91 | -------------------------------------------------------------------------------- /Sample/Sample/ViewController/HSDSampleViewDebugViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSDSampleViewDebugViewController.h 3 | // Closet 4 | // 5 | // Created by chenjun on 2018/5/27. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HSDSampleViewDebugViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Sample/Sample/ViewController/HSDSampleViewDebugViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // HSDSampleViewDebugViewController.m 3 | // Closet 4 | // 5 | // Created by chenjun on 2018/5/27. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import "HSDSampleViewDebugViewController.h" 10 | 11 | #pragma mark - interface 12 | 13 | @interface HSDSampleVDCase1Controller : UIViewController 14 | 15 | @end 16 | 17 | @interface HSDSampleVDCase2Controller : UIViewController 18 | 19 | @end 20 | 21 | #pragma mark - extension & implementation 22 | 23 | @interface HSDSampleViewDebugViewController () 24 | 25 | 26 | @property (nonatomic, strong) NSArray *dataList; 27 | @property (nonatomic, strong) UITableView *tableView; 28 | 29 | @end 30 | 31 | @implementation HSDSampleViewDebugViewController 32 | 33 | - (instancetype)init { 34 | self = [super init]; 35 | if (self) { 36 | self.dataList = @[@"Case 1", @"Case 2"]; 37 | } 38 | return self; 39 | } 40 | 41 | - (void)viewDidLoad { 42 | [super viewDidLoad]; 43 | self.view.backgroundColor = [UIColor whiteColor]; 44 | self.title = @"View Debug"; 45 | 46 | self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain]; 47 | self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 48 | self.tableView.dataSource = self; 49 | self.tableView.delegate = self; 50 | [self.view addSubview:self.tableView]; 51 | [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"]; 52 | } 53 | 54 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 55 | return [self.dataList count]; 56 | } 57 | 58 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 59 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; 60 | NSInteger row = [indexPath row]; 61 | NSString *title = [self.dataList objectAtIndex:row]; 62 | cell.textLabel.text = title; 63 | return cell; 64 | } 65 | 66 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 67 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 68 | NSInteger row = [indexPath row]; 69 | NSString *title = [self.dataList objectAtIndex:row]; 70 | if ([title isEqualToString:@"Case 1"]) { 71 | HSDSampleVDCase1Controller *vc = [[HSDSampleVDCase1Controller alloc] init]; 72 | [self.navigationController pushViewController:vc animated:YES]; 73 | } else if ([title isEqualToString:@"Case 2"]) { 74 | HSDSampleVDCase2Controller *vc = [[HSDSampleVDCase2Controller alloc] init]; 75 | [self.navigationController pushViewController:vc animated:YES]; 76 | } 77 | } 78 | 79 | - (void)didReceiveMemoryWarning { 80 | [super didReceiveMemoryWarning]; 81 | // Dispose of any resources that can be recreated. 82 | } 83 | 84 | @end 85 | 86 | 87 | @interface HSDSampleVDCase1Controller () 88 | 89 | @end 90 | 91 | @implementation HSDSampleVDCase1Controller 92 | 93 | - (void)viewDidLoad { 94 | [super viewDidLoad]; 95 | self.view.backgroundColor = [UIColor whiteColor]; 96 | self.title = @"View Debug Case 1"; 97 | 98 | // redView 99 | CGRect viewFrame = CGRectMake(30, 135, 60, 50); 100 | UIView *redView = [[UIView alloc] initWithFrame:viewFrame]; 101 | redView.backgroundColor = [UIColor redColor]; 102 | [self.view addSubview:redView]; 103 | 104 | // greenView 105 | viewFrame = CGRectMake(30, 140, 70, 20); 106 | UIView *greenView = [[UIView alloc] initWithFrame:viewFrame]; 107 | greenView.backgroundColor = [UIColor greenColor]; 108 | [self.view addSubview:greenView]; 109 | } 110 | 111 | @end 112 | 113 | @interface HSDSampleVDCase2Controller () 114 | 115 | @end 116 | 117 | @implementation HSDSampleVDCase2Controller 118 | 119 | - (void)viewDidLoad { 120 | [super viewDidLoad]; 121 | self.view.backgroundColor = [UIColor whiteColor]; 122 | self.title = @"View Debug Case 2"; 123 | 124 | // scrollView 125 | CGRect viewFrame = CGRectMake(10, 80, 200, 200); 126 | UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:viewFrame]; 127 | if (@available(iOS 11.0, *)) { 128 | scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; 129 | } else { 130 | // Fallback on earlier versions 131 | } 132 | scrollView.backgroundColor = [UIColor lightGrayColor]; 133 | [self.view addSubview:scrollView]; 134 | [scrollView setContentSize:CGSizeMake(CGRectGetWidth(self.view.bounds), 1000)]; 135 | 136 | // redView 137 | viewFrame = CGRectMake(-40, -30, 60, 50); 138 | UIView *redView = [[UIView alloc] initWithFrame:viewFrame]; 139 | redView.backgroundColor = [UIColor redColor]; 140 | [scrollView addSubview:redView]; 141 | } 142 | 143 | @end 144 | -------------------------------------------------------------------------------- /Sample/Sample/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Sample 4 | // 5 | // Created by chenjun on 2018/4/23. 6 | // Copyright © 2018年 chenjun. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "HSDSampleAppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([HSDSampleAppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ThirdParties/FMDB/extra/InMemoryOnDiskIO/FMDatabase+InMemoryOnDiskIO.h: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabase+InMemoryOnDiskIO.h 3 | // FMDB 4 | // 5 | // Created by Peter Carr on 6/12/12. 6 | // 7 | // I find there is a massive performance hit using an "on-disk" representation when 8 | // constantly reading from or writing to the DB. If your machine has sufficient memory, you 9 | // should get a significant performance boost using an "in-memory" representation. The FMDB 10 | // warpper does not contain methods to load an "on-disk" representation into memory and 11 | // similarly save an "in-memory" representation to disk. However, SQLite3 has built-in 12 | // support for this functionality via its "Backup" API. Here, we extend the FMBD wrapper 13 | // to include this functionality. 14 | // 15 | // http://www.sqlite.org/backup.html 16 | 17 | #import "FMDatabase.h" 18 | 19 | @interface FMDatabase (InMemoryOnDiskIO) 20 | 21 | // Loads an on-disk representation into memory. 22 | - (BOOL)readFromFile:(NSString*)filePath; 23 | 24 | // Saves an in-memory representation to disk 25 | - (BOOL)writeToFile:(NSString *)filePath; 26 | @end 27 | -------------------------------------------------------------------------------- /ThirdParties/FMDB/extra/InMemoryOnDiskIO/FMDatabase+InMemoryOnDiskIO.m: -------------------------------------------------------------------------------- 1 | #import "FMDatabase+InMemoryOnDiskIO.h" 2 | #import 3 | 4 | 5 | // http://www.sqlite.org/backup.html 6 | static 7 | int loadOrSaveDb(sqlite3 *pInMemory, const char *zFilename, int isSave) 8 | { 9 | int rc; /* Function return code */ 10 | sqlite3 *pFile; /* Database connection opened on zFilename */ 11 | sqlite3_backup *pBackup; /* Backup object used to copy data */ 12 | sqlite3 *pTo; /* Database to copy to (pFile or pInMemory) */ 13 | sqlite3 *pFrom; /* Database to copy from (pFile or pInMemory) */ 14 | 15 | /* Open the database file identified by zFilename. Exit early if this fails 16 | ** for any reason. */ 17 | rc = sqlite3_open(zFilename, &pFile); 18 | if( rc==SQLITE_OK ){ 19 | 20 | /* If this is a 'load' operation (isSave==0), then data is copied 21 | ** from the database file just opened to database pInMemory. 22 | ** Otherwise, if this is a 'save' operation (isSave==1), then data 23 | ** is copied from pInMemory to pFile. Set the variables pFrom and 24 | ** pTo accordingly. */ 25 | pFrom = (isSave ? pInMemory : pFile); 26 | pTo = (isSave ? pFile : pInMemory); 27 | 28 | /* Set up the backup procedure to copy from the "main" database of 29 | ** connection pFile to the main database of connection pInMemory. 30 | ** If something goes wrong, pBackup will be set to NULL and an error 31 | ** code and message left in connection pTo. 32 | ** 33 | ** If the backup object is successfully created, call backup_step() 34 | ** to copy data from pFile to pInMemory. Then call backup_finish() 35 | ** to release resources associated with the pBackup object. If an 36 | ** error occurred, then an error code and message will be left in 37 | ** connection pTo. If no error occurred, then the error code belonging 38 | ** to pTo is set to SQLITE_OK. 39 | */ 40 | pBackup = sqlite3_backup_init(pTo, "main", pFrom, "main"); 41 | if( pBackup ){ 42 | (void)sqlite3_backup_step(pBackup, -1); 43 | (void)sqlite3_backup_finish(pBackup); 44 | } 45 | rc = sqlite3_errcode(pTo); 46 | } 47 | 48 | /* Close the database connection opened on database file zFilename 49 | ** and return the result of this function. */ 50 | (void)sqlite3_close(pFile); 51 | return rc; 52 | } 53 | 54 | 55 | 56 | @implementation FMDatabase (InMemoryOnDiskIO) 57 | 58 | - (BOOL)readFromFile:(NSString*)filePath 59 | { 60 | // only attempt to load an on-disk representation for an in-memory database 61 | if ( [self databasePath] != nil ) 62 | { 63 | NSLog(@"Database is not an in-memory representation." ); 64 | return NO; 65 | } 66 | 67 | // and only if the database is open 68 | if ( [self sqliteHandle] == nil ) 69 | { 70 | NSLog(@"Invalid database connection." ); 71 | return NO; 72 | } 73 | 74 | return ( SQLITE_OK == loadOrSaveDb( [self sqliteHandle], [filePath fileSystemRepresentation], false ) ); 75 | 76 | } 77 | 78 | - (BOOL)writeToFile:(NSString *)filePath 79 | { 80 | // only attempt to save an on-disk representation for an in-memory database 81 | if ( [self databasePath] != nil ) 82 | { 83 | NSLog(@"Database is not an in-memory representation." ); 84 | return NO; 85 | } 86 | 87 | // and only if the database is open 88 | if ( [self sqliteHandle] == nil ) 89 | { 90 | NSLog(@"Invalid database connection." ); 91 | return NO; 92 | } 93 | 94 | // save the in-memory representation 95 | return ( SQLITE_OK == loadOrSaveDb( [self sqliteHandle], [filePath fileSystemRepresentation], true ) ); 96 | } 97 | 98 | @end 99 | -------------------------------------------------------------------------------- /ThirdParties/FMDB/extra/fts3/FMDatabase+FTS3.h: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabase+FTS3.h 3 | // fmdb 4 | // 5 | // Created by Andrew on 3/27/14. 6 | // Copyright (c) 2014 Andrew Goodale. All rights reserved. 7 | // 8 | 9 | #import "FMDatabase.h" 10 | 11 | /** 12 | Names of commands that can be issued against an FTS table. 13 | */ 14 | extern NSString *const kFTSCommandOptimize; // "optimize" 15 | extern NSString *const kFTSCommandRebuild; // "rebuild" 16 | extern NSString *const kFTSCommandIntegrityCheck; // "integrity-check" 17 | extern NSString *const kFTSCommandMerge; // "merge=%u,%u" 18 | extern NSString *const kFTSCommandAutoMerge; // "automerge=%u" 19 | 20 | @protocol FMTokenizerDelegate; 21 | 22 | /** 23 | This category provides methods to access the FTS3 extensions in SQLite. 24 | */ 25 | @interface FMDatabase (FTS3) 26 | 27 | /** 28 | Register a delegate implementation in the global table. This should be used when using a single tokenizer. 29 | */ 30 | + (void)registerTokenizer:(id)tokenizer; 31 | 32 | /** 33 | Register a delegate implementation in the global table. The key should be used 34 | as a parameter when creating the table. 35 | */ 36 | + (void)registerTokenizer:(id)tokenizer withKey:(NSString *)key; 37 | 38 | /** 39 | Calls the `fts3_tokenizer()` function on this database, installing tokenizer module with the 'fmdb' name. 40 | */ 41 | - (BOOL)installTokenizerModule; 42 | 43 | /** 44 | Calls the `fts3_tokenizer()` function on this database, installing the tokenizer module with specified name. 45 | */ 46 | - (BOOL)installTokenizerModuleWithName:(NSString *)name; 47 | 48 | /** 49 | Runs a "special command" for FTS3/FTS4 tables. 50 | */ 51 | - (BOOL)issueCommand:(NSString *)command forTable:(NSString *)tableName; 52 | 53 | @end 54 | 55 | #pragma mark 56 | 57 | /* Extend this structure with your own custom cursor data */ 58 | typedef struct FMTokenizerCursor 59 | { 60 | void *tokenizer; /* Internal SQLite reference */ 61 | CFStringRef inputString; /* The input text being tokenized */ 62 | CFRange currentRange; /* The current range within `inputString` */ 63 | CFStringRef tokenString; /* The contents of the current token */ 64 | CFTypeRef userObject; /* Additional state for the cursor */ 65 | int tokenIndex; /* Index of next token to be returned */ 66 | UInt8 outputBuf[128]; /* Result for SQLite */ 67 | CFRange previousRange; /* Cached range of previous token within `inputString` */ 68 | CFRange previousOffsetRange; /* Cached range of previous token as UTF-8 offset */ 69 | } FMTokenizerCursor; 70 | 71 | @protocol FMTokenizerDelegate 72 | 73 | - (void)openTokenizerCursor:(FMTokenizerCursor *)cursor; 74 | 75 | - (BOOL)nextTokenForCursor:(FMTokenizerCursor *)cursor; 76 | 77 | - (void)closeTokenizerCursor:(FMTokenizerCursor *)cursor; 78 | 79 | @end 80 | 81 | #pragma mark 82 | 83 | /** 84 | The container of offset information. 85 | */ 86 | @interface FMTextOffsets : NSObject 87 | 88 | - (instancetype)initWithDBOffsets:(const char *)offsets; 89 | 90 | /** 91 | Enumerate each set of offsets in the result. The column number can be turned into a column name 92 | using `[FMResultSet columnNameForIndex:]`. The `matchRange` is in UTF-8 byte positions, so it must be 93 | modified to use with `NSString` data. 94 | */ 95 | - (void)enumerateWithBlock:(void (^)(NSInteger columnNumber, NSInteger termNumber, NSRange matchRange))block; 96 | 97 | @end 98 | 99 | /** 100 | A category that adds support for the encoded data returned by FTS3 functions. 101 | */ 102 | @interface FMResultSet (FTS3) 103 | 104 | /** 105 | Returns a structure containing values from the `offsets()` function. Make sure the column index corresponds 106 | to the column index in the SQL query. 107 | 108 | @param columnIdx Zero-based index for column. 109 | 110 | @return `FMTextOffsets` structure. 111 | */ 112 | - (FMTextOffsets *)offsetsForColumnIndex:(int)columnIdx; 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /ThirdParties/FMDB/extra/fts3/FMTokenizers.h: -------------------------------------------------------------------------------- 1 | // 2 | // FMTokenizers.h 3 | // fmdb 4 | // 5 | // Created by Andrew on 4/9/14. 6 | // Copyright (c) 2014 Andrew Goodale. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FMDatabase+FTS3.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | /** 15 | This is the base tokenizer implementation, using a CFStringTokenizer to find words. 16 | */ 17 | @interface FMSimpleTokenizer : NSObject 18 | 19 | /** 20 | Create the tokenizer with a given locale. The locale will be used to initialize the string tokenizer and to lowercase the parsed word. 21 | The locale can be `NULL`, in which case the current locale will be used. 22 | */ 23 | - (instancetype)initWithLocale:(CFLocaleRef _Nullable)locale; 24 | 25 | @end 26 | 27 | #pragma mark 28 | 29 | /** 30 | This tokenizer extends the simple tokenizer with support for a stop word list. 31 | */ 32 | @interface FMStopWordTokenizer : NSObject 33 | 34 | @property (atomic, copy) NSSet *words; 35 | 36 | /** 37 | Load a stop-word tokenizer using a file containing words delimited by newlines. The file should be encoded in UTF-8. 38 | */ 39 | + (instancetype)tokenizerWithFileURL:(NSURL *)wordFileURL baseTokenizer:(id)tokenizer error:(NSError * _Nullable *)error; 40 | 41 | /** 42 | Initialize an instance of the tokenizer using the set of words. The words should be lowercase if you're using the 43 | `FMSimpleTokenizer` as the base. 44 | */ 45 | - (instancetype)initWithWords:(NSSet *)words baseTokenizer:(id)tokenizer; 46 | 47 | @end 48 | 49 | NS_ASSUME_NONNULL_END 50 | -------------------------------------------------------------------------------- /ThirdParties/FMDB/extra/fts3/FMTokenizers.m: -------------------------------------------------------------------------------- 1 | // 2 | // FMTokenizers.m 3 | // fmdb 4 | // 5 | // Created by Andrew on 4/9/14. 6 | // Copyright (c) 2014 Andrew Goodale. All rights reserved. 7 | // 8 | 9 | #import "FMTokenizers.h" 10 | 11 | @implementation FMSimpleTokenizer 12 | { 13 | CFLocaleRef m_locale; 14 | } 15 | 16 | - (id)initWithLocale:(CFLocaleRef)locale 17 | { 18 | if ((self = [super init])) { 19 | m_locale = (locale != NULL) ? CFRetain(locale) : CFLocaleCopyCurrent(); 20 | } 21 | return self; 22 | } 23 | 24 | - (void)dealloc 25 | { 26 | CFRelease(m_locale); 27 | } 28 | 29 | - (void)openTokenizerCursor:(FMTokenizerCursor *)cursor 30 | { 31 | cursor->tokenString = CFStringCreateMutable(NULL, 0); 32 | cursor->userObject = CFStringTokenizerCreate(NULL, cursor->inputString, 33 | CFRangeMake(0, CFStringGetLength(cursor->inputString)), 34 | kCFStringTokenizerUnitWord, m_locale); 35 | } 36 | 37 | - (BOOL)nextTokenForCursor:(FMTokenizerCursor *)cursor 38 | { 39 | CFStringTokenizerRef tokenizer = (CFStringTokenizerRef) cursor->userObject; 40 | CFMutableStringRef tokenString = (CFMutableStringRef) cursor->tokenString; 41 | 42 | CFStringTokenizerTokenType tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer); 43 | 44 | if (tokenType == kCFStringTokenizerTokenNone) { 45 | // No more tokens, we are finished. 46 | return YES; 47 | } 48 | 49 | // Found a regular word. The token is the lowercase version of the word. 50 | cursor->currentRange = CFStringTokenizerGetCurrentTokenRange(tokenizer); 51 | 52 | // The inline buffer approach is faster and uses less memory than CFStringCreateWithSubstring() 53 | CFStringInlineBuffer inlineBuf; 54 | CFStringInitInlineBuffer(cursor->inputString, &inlineBuf, cursor->currentRange); 55 | CFStringDelete(tokenString, CFRangeMake(0, CFStringGetLength(tokenString))); 56 | 57 | for (int i = 0; i < cursor->currentRange.length; ++i) { 58 | UniChar nextChar = CFStringGetCharacterFromInlineBuffer(&inlineBuf, i); 59 | CFStringAppendCharacters(tokenString, &nextChar, 1); 60 | } 61 | 62 | CFStringLowercase(tokenString, m_locale); 63 | 64 | return NO; 65 | } 66 | 67 | - (void)closeTokenizerCursor:(FMTokenizerCursor *)cursor 68 | { 69 | // FMDatabase will CFRelease the tokenString and the userObject. 70 | } 71 | 72 | @end 73 | 74 | #pragma mark 75 | 76 | @implementation FMStopWordTokenizer 77 | { 78 | id m_baseTokenizer; 79 | } 80 | 81 | @synthesize words = m_words; 82 | 83 | + (instancetype)tokenizerWithFileURL:(NSURL *)wordFileURL 84 | baseTokenizer:(id)tokenizer 85 | error:(NSError *__autoreleasing *)error 86 | { 87 | NSParameterAssert(wordFileURL); 88 | 89 | NSString *contents = [NSString stringWithContentsOfURL:wordFileURL encoding:NSUTF8StringEncoding error:error]; 90 | NSArray *stopWords = [contents componentsSeparatedByString:@"\n"]; 91 | 92 | if (contents == nil) { 93 | return nil; 94 | } 95 | return [[self alloc] initWithWords:[NSSet setWithArray:stopWords] baseTokenizer:tokenizer]; 96 | } 97 | 98 | - (instancetype)initWithWords:(NSSet *)words baseTokenizer:(id)tokenizer 99 | { 100 | NSParameterAssert(tokenizer); 101 | 102 | if ((self = [super init])) { 103 | m_words = [words copy]; 104 | m_baseTokenizer = tokenizer; 105 | } 106 | return self; 107 | } 108 | 109 | - (void)openTokenizerCursor:(FMTokenizerCursor *)cursor 110 | { 111 | [m_baseTokenizer openTokenizerCursor:cursor]; 112 | } 113 | 114 | - (BOOL)nextTokenForCursor:(FMTokenizerCursor *)cursor 115 | { 116 | BOOL done = [m_baseTokenizer nextTokenForCursor:cursor]; 117 | 118 | // Don't use stop words for prefix queries since it's fine for the prefix to be in the stop list 119 | if (CFStringHasSuffix(cursor->inputString, CFSTR("*"))) { 120 | return done; 121 | } 122 | 123 | while (!done && [self.words containsObject:(__bridge id)(cursor->tokenString)]) { 124 | done = [m_baseTokenizer nextTokenForCursor:cursor]; 125 | } 126 | 127 | return done; 128 | } 129 | 130 | - (void)closeTokenizerCursor:(FMTokenizerCursor *)cursor 131 | { 132 | [m_baseTokenizer closeTokenizerCursor:cursor]; 133 | } 134 | 135 | @end -------------------------------------------------------------------------------- /ThirdParties/FMDB/fmdb/FMDB.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | FOUNDATION_EXPORT double FMDBVersionNumber; 4 | FOUNDATION_EXPORT const unsigned char FMDBVersionString[]; 5 | 6 | #import "FMDatabase.h" 7 | #import "FMResultSet.h" 8 | #import "FMDatabaseAdditions.h" 9 | #import "FMDatabaseQueue.h" 10 | #import "FMDatabasePool.h" 11 | -------------------------------------------------------------------------------- /archive.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # CONFIGURATION_SETTING="Debug" 4 | CONFIGURATION_SETTING="Release" 5 | 6 | # Dependencies onfiguration 7 | FMDB_INCLUDE=1 8 | 9 | # Constant variables 10 | PROJECT_NAME="HttpServerDebug" 11 | OUTPUT_FOLDER_NAME="output" 12 | BUILD_FOLDER_NAME="build" 13 | IPHONEOS_SDK="iphoneos" 14 | IPHONESIMULATOR_SDK="iphonesimulator" 15 | 16 | rm -rf ${OUTPUT_FOLDER_NAME} 17 | mkdir ${OUTPUT_FOLDER_NAME} 18 | 19 | # Build Device and Simulator versions 20 | build_combine() { 21 | SDK=$1 22 | build_cmd='xcodebuild -project "${PROJECT_NAME}.xcodeproj" -configuration ${CONFIGURATION_SETTING} -sdk ${SDK} ONLY_ACTIVE_ARCH=NO' 23 | combine_cmd='libtool -static -o "${BUILD_FOLDER_NAME}/${CONFIGURATION_SETTING}-${SDK}/aggregation.a" "${BUILD_FOLDER_NAME}/${CONFIGURATION_SETTING}-${SDK}/libHttpServerDebug.a"' 24 | 25 | eval ${build_cmd}' -target "HttpServerDebug"' 26 | if [[ FMDB_INCLUDE -eq 1 ]]; then 27 | eval ${build_cmd}' -target "FMDB"' 28 | combine_cmd=${combine_cmd}' "${BUILD_FOLDER_NAME}/${CONFIGURATION_SETTING}-${SDK}/libFMDB.a"' 29 | fi 30 | eval ${combine_cmd} 31 | } 32 | 33 | build_combine ${IPHONEOS_SDK} 34 | build_combine ${IPHONESIMULATOR_SDK} 35 | 36 | # Create universal binary file 37 | lipo -create -output "${OUTPUT_FOLDER_NAME}/libHttpServerDebug.a" "${BUILD_FOLDER_NAME}/${CONFIGURATION_SETTING}-${IPHONEOS_SDK}/aggregation.a" "${BUILD_FOLDER_NAME}/${CONFIGURATION_SETTING}-${IPHONESIMULATOR_SDK}/aggregation.a" 38 | 39 | # Copy header files 40 | cp -R "${BUILD_FOLDER_NAME}/${CONFIGURATION_SETTING}-${IPHONEOS_SDK}/include/" "${OUTPUT_FOLDER_NAME}/Headers/" 41 | 42 | # Copy bundle 43 | cp -R "Resources/${PROJECT_NAME}.bundle" "${OUTPUT_FOLDER_NAME}/" 44 | 45 | # Copy documents 46 | cp -R "./Documents" "${OUTPUT_FOLDER_NAME}/" 47 | --------------------------------------------------------------------------------