├── .gitignore ├── Classes ├── CocoaAsyncSocket │ ├── GCDAsyncSocket.h │ ├── GCDAsyncSocket.m │ ├── GCDAsyncUdpSocket.h │ └── GCDAsyncUdpSocket.m ├── CocoaHttpServer │ ├── CHSHTTPAuthenticationRequest.h │ ├── CHSHTTPAuthenticationRequest.m │ ├── CHSHTTPConnection.h │ ├── CHSHTTPConnection.m │ ├── CHSHTTPLogging.h │ ├── CHSHTTPMessage.h │ ├── CHSHTTPMessage.m │ ├── CHSHTTPResponse.h │ ├── CHSHTTPServer.h │ ├── CHSHTTPServer.m │ ├── CHSWebSocket.h │ ├── CHSWebSocket.m │ ├── Categories │ │ ├── DDData.h │ │ ├── DDData.m │ │ ├── DDNumber.h │ │ ├── DDNumber.m │ │ ├── DDRange.h │ │ └── DDRange.m │ ├── Mime │ │ ├── CHSMultipartFormDataParser.h │ │ ├── CHSMultipartFormDataParser.m │ │ ├── CHSMultipartMessageHeader.h │ │ ├── CHSMultipartMessageHeader.m │ │ ├── CHSMultipartMessageHeaderField.h │ │ └── CHSMultipartMessageHeaderField.m │ └── Responses │ │ ├── CHSHTTPAsyncFileResponse.h │ │ ├── CHSHTTPAsyncFileResponse.m │ │ ├── CHSHTTPDataResponse.h │ │ ├── CHSHTTPDataResponse.m │ │ ├── CHSHTTPDynamicFileResponse.h │ │ ├── CHSHTTPDynamicFileResponse.m │ │ ├── CHSHTTPFileResponse.h │ │ ├── CHSHTTPFileResponse.m │ │ ├── CHSHTTPRedirectResponse.h │ │ └── CHSHTTPRedirectResponse.m ├── PBMHTMLBuilder.h ├── PBMHTMLBuilder.m ├── PBMHTTPConnection.h ├── PBMHTTPConnection.m ├── PBMIPTool.h ├── PBMIPTool.m ├── PBMTool.h ├── PBMTool.m └── Web.bundle │ ├── bower.json │ ├── bower_components │ ├── foundation.min.css │ ├── foundation.min.js │ ├── jquery-ui.min.css │ ├── jquery-ui.min.js │ └── jquery.min.js │ └── index.html ├── LICENSE ├── PleaseBaoMe.podspec ├── PleaseBaoMe.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── PleaseBaoMe.xcworkspace └── contents.xcworkspacedata ├── PleaseBaoMe ├── AppDelegate.h ├── AppDelegate.m ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── Demo │ ├── PBMDemoTool.h │ └── PBMDemoTool.m ├── Images.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── appicon-60@2x-1.png │ │ ├── appicon-60@2x.png │ │ ├── appicon-60@3x.png │ │ ├── appicon-76.png │ │ ├── appicon-76@2x.png │ │ ├── appicon-Small-40.png │ │ ├── appicon-Small-40@2x-1.png │ │ ├── appicon-Small-40@2x.png │ │ ├── appicon-Small.png │ │ ├── appicon-Small@2x-1.png │ │ ├── appicon-Small@2x.png │ │ └── appicon-Small@3x.png │ ├── LaunchImage.launchimage │ │ ├── Contents.json │ │ ├── Default-1.png │ │ ├── Default-568h@2x-1.png │ │ ├── Default-568h@2x.png │ │ ├── Default-667h@2x.png │ │ ├── Default-Landscape-736h@3x.png │ │ ├── Default-Portrait-736h@3x.png │ │ ├── Default@2x-1.png │ │ └── Default@2x.png │ └── Loading.imageset │ │ ├── Contents.json │ │ └── Logo.pdf ├── Info.plist ├── ViewController.h ├── ViewController.m └── main.m ├── Podfile ├── Podfile.lock ├── Pods ├── FMDB │ ├── LICENSE.txt │ ├── README.markdown │ └── src │ │ └── fmdb │ │ ├── FMDB.h │ │ ├── FMDatabase.h │ │ ├── FMDatabase.m │ │ ├── FMDatabaseAdditions.h │ │ ├── FMDatabaseAdditions.m │ │ ├── FMDatabasePool.h │ │ ├── FMDatabasePool.m │ │ ├── FMDatabaseQueue.h │ │ ├── FMDatabaseQueue.m │ │ ├── FMResultSet.h │ │ └── FMResultSet.m ├── Headers │ ├── Private │ │ └── FMDB │ │ │ ├── FMDB.h │ │ │ ├── FMDatabase.h │ │ │ ├── FMDatabaseAdditions.h │ │ │ ├── FMDatabasePool.h │ │ │ ├── FMDatabaseQueue.h │ │ │ └── FMResultSet.h │ └── Public │ │ └── FMDB │ │ ├── FMDB.h │ │ ├── FMDatabase.h │ │ ├── FMDatabaseAdditions.h │ │ ├── FMDatabasePool.h │ │ ├── FMDatabaseQueue.h │ │ └── FMResultSet.h ├── Manifest.lock ├── Pods.xcodeproj │ └── project.pbxproj └── Target Support Files │ ├── FMDB │ ├── FMDB-Private.xcconfig │ ├── FMDB-dummy.m │ ├── FMDB-prefix.pch │ └── FMDB.xcconfig │ └── Pods-PleaseBaoMe │ ├── Pods-PleaseBaoMe-acknowledgements.markdown │ ├── Pods-PleaseBaoMe-acknowledgements.plist │ ├── Pods-PleaseBaoMe-dummy.m │ ├── Pods-PleaseBaoMe-resources.sh │ ├── Pods-PleaseBaoMe.debug.xcconfig │ └── Pods-PleaseBaoMe.release.xcconfig └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### Xcode ### 4 | build/ 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | *.moved-aside 16 | DerivedData 17 | *.xcuserstate 18 | 19 | 20 | ### JetBrains ### 21 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 22 | 23 | *.iml 24 | 25 | ## Directory-based project format: 26 | .idea/ 27 | # if you remove the above rule, at least ignore the following: 28 | 29 | # User-specific stuff: 30 | # .idea/workspace.xml 31 | # .idea/tasks.xml 32 | # .idea/dictionaries 33 | 34 | # Sensitive or high-churn files: 35 | # .idea/dataSources.ids 36 | # .idea/dataSources.xml 37 | # .idea/sqlDataSources.xml 38 | # .idea/dynamic.xml 39 | # .idea/uiDesigner.xml 40 | 41 | # Gradle: 42 | # .idea/gradle.xml 43 | # .idea/libraries 44 | 45 | # Mongo Explorer plugin: 46 | # .idea/mongoSettings.xml 47 | 48 | ## File-based project format: 49 | *.ipr 50 | *.iws 51 | 52 | ## Plugin-specific files: 53 | 54 | # IntelliJ 55 | /out/ 56 | 57 | # mpeltonen/sbt-idea plugin 58 | .idea_modules/ 59 | 60 | # JIRA plugin 61 | atlassian-ide-plugin.xml 62 | 63 | # Crashlytics plugin (for Android Studio and IntelliJ) 64 | com_crashlytics_export_strings.xml 65 | crashlytics.properties 66 | crashlytics-build.properties 67 | 68 | 69 | ### Objective-C ### 70 | # Xcode 71 | # 72 | build/ 73 | *.pbxuser 74 | !default.pbxuser 75 | *.mode1v3 76 | !default.mode1v3 77 | *.mode2v3 78 | !default.mode2v3 79 | *.perspectivev3 80 | !default.perspectivev3 81 | xcuserdata 82 | *.xccheckout 83 | *.moved-aside 84 | DerivedData 85 | *.hmap 86 | *.ipa 87 | *.xcuserstate 88 | 89 | # CocoaPods 90 | # 91 | # We recommend against adding the Pods directory to your .gitignore. However 92 | # you should judge for yourself, the pros and cons are mentioned at: 93 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 94 | # 95 | #Pods/ 96 | 97 | 98 | ### Bower ### 99 | # bower_components 100 | .bower-cache 101 | .bower-registry 102 | .bower-tmp -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/CHSHTTPAuthenticationRequest.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #if TARGET_OS_IPHONE 4 | // Note: You may need to add the CFNetwork Framework to your project 5 | #import 6 | #endif 7 | 8 | @class CHSHTTPMessage; 9 | 10 | 11 | @interface CHSHTTPAuthenticationRequest : NSObject 12 | { 13 | BOOL isBasic; 14 | BOOL isDigest; 15 | 16 | NSString *base64Credentials; 17 | 18 | NSString *username; 19 | NSString *realm; 20 | NSString *nonce; 21 | NSString *uri; 22 | NSString *qop; 23 | NSString *nc; 24 | NSString *cnonce; 25 | NSString *response; 26 | } 27 | - (id)initWithRequest:(CHSHTTPMessage *)request; 28 | 29 | - (BOOL)isBasic; 30 | - (BOOL)isDigest; 31 | 32 | // Basic 33 | - (NSString *)base64Credentials; 34 | 35 | // Digest 36 | - (NSString *)username; 37 | - (NSString *)realm; 38 | - (NSString *)nonce; 39 | - (NSString *)uri; 40 | - (NSString *)qop; 41 | - (NSString *)nc; 42 | - (NSString *)cnonce; 43 | - (NSString *)response; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/CHSHTTPAuthenticationRequest.m: -------------------------------------------------------------------------------- 1 | #import "CHSHTTPAuthenticationRequest.h" 2 | #import "CHSHTTPMessage.h" 3 | 4 | #if ! __has_feature(objc_arc) 5 | #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). 6 | #endif 7 | 8 | @interface CHSHTTPAuthenticationRequest (PrivateAPI) 9 | - (NSString *)quotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header; 10 | - (NSString *)nonquotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header; 11 | @end 12 | 13 | 14 | @implementation CHSHTTPAuthenticationRequest 15 | 16 | - (id)initWithRequest:(CHSHTTPMessage *)request 17 | { 18 | if ((self = [super init])) 19 | { 20 | NSString *authInfo = [request headerField:@"Authorization"]; 21 | 22 | isBasic = NO; 23 | if ([authInfo length] >= 6) 24 | { 25 | isBasic = [[authInfo substringToIndex:6] caseInsensitiveCompare:@"Basic "] == NSOrderedSame; 26 | } 27 | 28 | isDigest = NO; 29 | if ([authInfo length] >= 7) 30 | { 31 | isDigest = [[authInfo substringToIndex:7] caseInsensitiveCompare:@"Digest "] == NSOrderedSame; 32 | } 33 | 34 | if (isBasic) 35 | { 36 | NSMutableString *temp = [[authInfo substringFromIndex:6] mutableCopy]; 37 | CFStringTrimWhitespace((__bridge CFMutableStringRef)temp); 38 | 39 | base64Credentials = [temp copy]; 40 | } 41 | 42 | if (isDigest) 43 | { 44 | username = [self quotedSubHeaderFieldValue:@"username" fromHeaderFieldValue:authInfo]; 45 | realm = [self quotedSubHeaderFieldValue:@"realm" fromHeaderFieldValue:authInfo]; 46 | nonce = [self quotedSubHeaderFieldValue:@"nonce" fromHeaderFieldValue:authInfo]; 47 | uri = [self quotedSubHeaderFieldValue:@"uri" fromHeaderFieldValue:authInfo]; 48 | 49 | // It appears from RFC 2617 that the qop is to be given unquoted 50 | // Tests show that Firefox performs this way, but Safari does not 51 | // Thus we'll attempt to retrieve the value as nonquoted, but we'll verify it doesn't start with a quote 52 | qop = [self nonquotedSubHeaderFieldValue:@"qop" fromHeaderFieldValue:authInfo]; 53 | if(qop && ([qop characterAtIndex:0] == '"')) 54 | { 55 | qop = [self quotedSubHeaderFieldValue:@"qop" fromHeaderFieldValue:authInfo]; 56 | } 57 | 58 | nc = [self nonquotedSubHeaderFieldValue:@"nc" fromHeaderFieldValue:authInfo]; 59 | cnonce = [self quotedSubHeaderFieldValue:@"cnonce" fromHeaderFieldValue:authInfo]; 60 | response = [self quotedSubHeaderFieldValue:@"response" fromHeaderFieldValue:authInfo]; 61 | } 62 | } 63 | return self; 64 | } 65 | 66 | 67 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 68 | #pragma mark Accessors: 69 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 70 | 71 | - (BOOL)isBasic { 72 | return isBasic; 73 | } 74 | 75 | - (BOOL)isDigest { 76 | return isDigest; 77 | } 78 | 79 | - (NSString *)base64Credentials { 80 | return base64Credentials; 81 | } 82 | 83 | - (NSString *)username { 84 | return username; 85 | } 86 | 87 | - (NSString *)realm { 88 | return realm; 89 | } 90 | 91 | - (NSString *)nonce { 92 | return nonce; 93 | } 94 | 95 | - (NSString *)uri { 96 | return uri; 97 | } 98 | 99 | - (NSString *)qop { 100 | return qop; 101 | } 102 | 103 | - (NSString *)nc { 104 | return nc; 105 | } 106 | 107 | - (NSString *)cnonce { 108 | return cnonce; 109 | } 110 | 111 | - (NSString *)response { 112 | return response; 113 | } 114 | 115 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 116 | #pragma mark Private API: 117 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 118 | 119 | /** 120 | * Retrieves a "Sub Header Field Value" from a given header field value. 121 | * The sub header field is expected to be quoted. 122 | * 123 | * In the following header field: 124 | * Authorization: Digest username="Mufasa", qop=auth, response="6629fae4939" 125 | * The sub header field titled 'username' is quoted, and this method would return the value @"Mufasa". 126 | **/ 127 | - (NSString *)quotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header 128 | { 129 | NSRange startRange = [header rangeOfString:[NSString stringWithFormat:@"%@=\"", param]]; 130 | if(startRange.location == NSNotFound) 131 | { 132 | // The param was not found anywhere in the header 133 | return nil; 134 | } 135 | 136 | NSUInteger postStartRangeLocation = startRange.location + startRange.length; 137 | NSUInteger postStartRangeLength = [header length] - postStartRangeLocation; 138 | NSRange postStartRange = NSMakeRange(postStartRangeLocation, postStartRangeLength); 139 | 140 | NSRange endRange = [header rangeOfString:@"\"" options:0 range:postStartRange]; 141 | if(endRange.location == NSNotFound) 142 | { 143 | // The ending double-quote was not found anywhere in the header 144 | return nil; 145 | } 146 | 147 | NSRange subHeaderRange = NSMakeRange(postStartRangeLocation, endRange.location - postStartRangeLocation); 148 | return [header substringWithRange:subHeaderRange]; 149 | } 150 | 151 | /** 152 | * Retrieves a "Sub Header Field Value" from a given header field value. 153 | * The sub header field is expected to not be quoted. 154 | * 155 | * In the following header field: 156 | * Authorization: Digest username="Mufasa", qop=auth, response="6629fae4939" 157 | * The sub header field titled 'qop' is nonquoted, and this method would return the value @"auth". 158 | **/ 159 | - (NSString *)nonquotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header 160 | { 161 | NSRange startRange = [header rangeOfString:[NSString stringWithFormat:@"%@=", param]]; 162 | if(startRange.location == NSNotFound) 163 | { 164 | // The param was not found anywhere in the header 165 | return nil; 166 | } 167 | 168 | NSUInteger postStartRangeLocation = startRange.location + startRange.length; 169 | NSUInteger postStartRangeLength = [header length] - postStartRangeLocation; 170 | NSRange postStartRange = NSMakeRange(postStartRangeLocation, postStartRangeLength); 171 | 172 | NSRange endRange = [header rangeOfString:@"," options:0 range:postStartRange]; 173 | if(endRange.location == NSNotFound) 174 | { 175 | // The ending comma was not found anywhere in the header 176 | // However, if the nonquoted param is at the end of the string, there would be no comma 177 | // This is only possible if there are no spaces anywhere 178 | NSRange endRange2 = [header rangeOfString:@" " options:0 range:postStartRange]; 179 | if(endRange2.location != NSNotFound) 180 | { 181 | return nil; 182 | } 183 | else 184 | { 185 | return [header substringWithRange:postStartRange]; 186 | } 187 | } 188 | else 189 | { 190 | NSRange subHeaderRange = NSMakeRange(postStartRangeLocation, endRange.location - postStartRangeLocation); 191 | return [header substringWithRange:subHeaderRange]; 192 | } 193 | } 194 | 195 | @end 196 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/CHSHTTPConnection.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class GCDAsyncSocket; 4 | @class CHSHTTPMessage; 5 | @class CHSHTTPServer; 6 | @class CHSWebSocket; 7 | @protocol CHSHTTPResponse; 8 | 9 | 10 | #define HTTPConnectionDidDieNotification @"HTTPConnectionDidDie" 11 | 12 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 13 | #pragma mark - 14 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 15 | 16 | @interface CHSHTTPConfig : NSObject 17 | { 18 | CHSHTTPServer __unsafe_unretained *server; 19 | NSString __strong *documentRoot; 20 | dispatch_queue_t queue; 21 | } 22 | 23 | - (id)initWithServer:(CHSHTTPServer *)server documentRoot:(NSString *)documentRoot; 24 | - (id)initWithServer:(CHSHTTPServer *)server documentRoot:(NSString *)documentRoot queue:(dispatch_queue_t)q; 25 | 26 | @property (nonatomic, unsafe_unretained, readonly) CHSHTTPServer *server; 27 | @property (nonatomic, strong, readonly) NSString *documentRoot; 28 | @property (nonatomic, readonly) dispatch_queue_t queue; 29 | 30 | @end 31 | 32 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 33 | #pragma mark - 34 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 35 | 36 | @interface CHSHTTPConnection : NSObject 37 | { 38 | dispatch_queue_t connectionQueue; 39 | GCDAsyncSocket *asyncSocket; 40 | CHSHTTPConfig *config; 41 | 42 | BOOL started; 43 | 44 | CHSHTTPMessage *request; 45 | unsigned int numHeaderLines; 46 | 47 | BOOL sentResponseHeaders; 48 | 49 | NSString *nonce; 50 | long lastNC; 51 | 52 | NSObject *httpResponse; 53 | 54 | NSMutableArray *ranges; 55 | NSMutableArray *ranges_headers; 56 | NSString *ranges_boundry; 57 | int rangeIndex; 58 | 59 | UInt64 requestContentLength; 60 | UInt64 requestContentLengthReceived; 61 | UInt64 requestChunkSize; 62 | UInt64 requestChunkSizeReceived; 63 | 64 | NSMutableArray *responseDataSizes; 65 | } 66 | 67 | - (id)initWithAsyncSocket:(GCDAsyncSocket *)newSocket configuration:(CHSHTTPConfig *)aConfig; 68 | 69 | - (void)start; 70 | - (void)stop; 71 | 72 | - (void)startConnection; 73 | 74 | - (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path; 75 | - (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path; 76 | 77 | - (BOOL)isSecureServer; 78 | - (NSArray *)sslIdentityAndCertificates; 79 | 80 | - (BOOL)isPasswordProtected:(NSString *)path; 81 | - (BOOL)useDigestAccessAuthentication; 82 | - (NSString *)realm; 83 | - (NSString *)passwordForUser:(NSString *)username; 84 | 85 | - (NSDictionary *)parseParams:(NSString *)query; 86 | - (NSDictionary *)parseGetParams; 87 | 88 | - (NSString *)requestURI; 89 | 90 | - (NSArray *)directoryIndexFileNames; 91 | - (NSString *)filePathForURI:(NSString *)path; 92 | - (NSString *)filePathForURI:(NSString *)path allowDirectory:(BOOL)allowDirectory; 93 | - (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path; 94 | - (CHSWebSocket *)webSocketForURI:(NSString *)path; 95 | 96 | - (void)prepareForBodyWithSize:(UInt64)contentLength; 97 | - (void)processBodyData:(NSData *)postDataChunk; 98 | - (void)finishBody; 99 | 100 | - (void)handleVersionNotSupported:(NSString *)version; 101 | - (void)handleAuthenticationFailed; 102 | - (void)handleResourceNotFound; 103 | - (void)handleInvalidRequest:(NSData *)data; 104 | - (void)handleUnknownMethod:(NSString *)method; 105 | 106 | - (NSData *)preprocessResponse:(CHSHTTPMessage *)response; 107 | - (NSData *)preprocessErrorResponse:(CHSHTTPMessage *)response; 108 | 109 | - (void)finishResponse; 110 | 111 | - (BOOL)shouldDie; 112 | - (void)die; 113 | 114 | @end 115 | 116 | @interface CHSHTTPConnection (AsynchronousHTTPResponse) 117 | - (void)responseHasAvailableData:(NSObject *)sender; 118 | - (void)responseDidAbort:(NSObject *)sender; 119 | @end 120 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/CHSHTTPLogging.h: -------------------------------------------------------------------------------- 1 | /** 2 | * In order to provide fast and flexible logging, this project uses Cocoa Lumberjack. 3 | * 4 | * The Google Code page has a wealth of documentation if you have any questions. 5 | * https://github.com/robbiehanson/CocoaLumberjack 6 | * 7 | * Here's what you need to know concerning how logging is setup for CocoaHTTPServer: 8 | * 9 | * There are 4 log levels: 10 | * - Error 11 | * - Warning 12 | * - Info 13 | * - Verbose 14 | * 15 | * In addition to this, there is a Trace flag that can be enabled. 16 | * When tracing is enabled, it spits out the methods that are being called. 17 | * 18 | * Please note that tracing is separate from the log levels. 19 | * For example, one could set the log level to warning, and enable tracing. 20 | * 21 | * All logging is asynchronous, except errors. 22 | * To use logging within your own custom files, follow the steps below. 23 | * 24 | * Step 1: 25 | * Import this header in your implementation file: 26 | * 27 | * #import "HTTPLogging.h" 28 | * 29 | * Step 2: 30 | * Define your logging level in your implementation file: 31 | * 32 | * // Log levels: off, error, warn, info, verbose 33 | * static const int httpLogLevel = HTTP_LOG_LEVEL_VERBOSE; 34 | * 35 | * If you wish to enable tracing, you could do something like this: 36 | * 37 | * // Debug levels: off, error, warn, info, verbose 38 | * static const int httpLogLevel = HTTP_LOG_LEVEL_INFO | HTTP_LOG_FLAG_TRACE; 39 | * 40 | * Step 3: 41 | * Replace your NSLog statements with HTTPLog statements according to the severity of the message. 42 | * 43 | * NSLog(@"Fatal error, no dohickey found!"); -> HTTPLogError(@"Fatal error, no dohickey found!"); 44 | * 45 | * HTTPLog works exactly the same as NSLog. 46 | * This means you can pass it multiple variables just like NSLog. 47 | **/ 48 | 49 | // Configure log levels. 50 | #define HTTP_LOG_LEVEL_OFF 0 // 0...00000 51 | #define HTTP_LOG_LEVEL_ERROR (HTTP_LOG_LEVEL_OFF | HTTP_LOG_FLAG_ERROR) // 0...00001 52 | #define HTTP_LOG_LEVEL_WARN (HTTP_LOG_LEVEL_ERROR | HTTP_LOG_FLAG_WARN) // 0...00011 53 | #define HTTP_LOG_LEVEL_INFO (HTTP_LOG_LEVEL_WARN | HTTP_LOG_FLAG_INFO) // 0...00111 54 | #define HTTP_LOG_LEVEL_VERBOSE (HTTP_LOG_LEVEL_INFO | HTTP_LOG_FLAG_VERBOSE) // 0...01111 55 | 56 | // Setup fine grained logging. 57 | // The first 4 bits are being used by the standard log levels (0 - 3) 58 | // 59 | // We're going to add tracing, but NOT as a log level. 60 | // Tracing can be turned on and off independently of log level. 61 | 62 | #define HTTP_LOG_FLAG_TRACE (1 << 4) // 0...10000 63 | 64 | #define THIS_FILE [NSString stringWithFormat:@"%s", __PRETTY_FUNCTION__] 65 | #define THIS_METHOD [NSString stringWithFormat:@"%s", __PRETTY_FUNCTION__] 66 | 67 | // Define logging primitives. 68 | #define HTTPLogError(frmt, ...) if (NO) { NSLog(frmt, ##__VA_ARGS__);} 69 | 70 | #define HTTPLogWarn(frmt, ...) if (NO) { NSLog(frmt, ##__VA_ARGS__);} 71 | 72 | #define HTTPLogInfo(frmt, ...) if (NO) { NSLog(frmt, ##__VA_ARGS__);} 73 | 74 | #define HTTPLogVerbose(frmt, ...) if (NO) { NSLog(frmt, ##__VA_ARGS__);} 75 | 76 | #define HTTPLogTrace() if (NO) { } 77 | 78 | #define HTTPLogTrace2(frmt, ...) if (NO) { NSLog(frmt, ##__VA_ARGS__);} -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/CHSHTTPMessage.h: -------------------------------------------------------------------------------- 1 | /** 2 | * The HTTPMessage class is a simple Objective-C wrapper around Apple's CFHTTPMessage class. 3 | **/ 4 | 5 | #import 6 | 7 | #if TARGET_OS_IPHONE 8 | // Note: You may need to add the CFNetwork Framework to your project 9 | #import 10 | #endif 11 | 12 | #define HTTPVersion1_0 ((NSString *)kCFHTTPVersion1_0) 13 | #define HTTPVersion1_1 ((NSString *)kCFHTTPVersion1_1) 14 | 15 | 16 | @interface CHSHTTPMessage : NSObject 17 | { 18 | CFHTTPMessageRef message; 19 | } 20 | 21 | - (id)initEmptyRequest; 22 | 23 | - (id)initRequestWithMethod:(NSString *)method URL:(NSURL *)url version:(NSString *)version; 24 | 25 | - (id)initResponseWithStatusCode:(NSInteger)code description:(NSString *)description version:(NSString *)version; 26 | 27 | - (BOOL)appendData:(NSData *)data; 28 | 29 | - (BOOL)isHeaderComplete; 30 | 31 | - (NSString *)version; 32 | 33 | - (NSString *)method; 34 | - (NSURL *)url; 35 | 36 | - (NSInteger)statusCode; 37 | 38 | - (NSDictionary *)allHeaderFields; 39 | - (NSString *)headerField:(NSString *)headerField; 40 | 41 | - (void)setHeaderField:(NSString *)headerField value:(NSString *)headerFieldValue; 42 | 43 | - (NSData *)messageData; 44 | 45 | - (NSData *)body; 46 | - (void)setBody:(NSData *)body; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/CHSHTTPMessage.m: -------------------------------------------------------------------------------- 1 | #import "CHSHTTPMessage.h" 2 | 3 | #if ! __has_feature(objc_arc) 4 | #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). 5 | #endif 6 | 7 | 8 | @implementation CHSHTTPMessage 9 | 10 | - (id)initEmptyRequest 11 | { 12 | if ((self = [super init])) 13 | { 14 | message = CFHTTPMessageCreateEmpty(NULL, YES); 15 | } 16 | return self; 17 | } 18 | 19 | - (id)initRequestWithMethod:(NSString *)method URL:(NSURL *)url version:(NSString *)version 20 | { 21 | if ((self = [super init])) 22 | { 23 | message = CFHTTPMessageCreateRequest(NULL, 24 | (__bridge CFStringRef)method, 25 | (__bridge CFURLRef)url, 26 | (__bridge CFStringRef)version); 27 | } 28 | return self; 29 | } 30 | 31 | - (id)initResponseWithStatusCode:(NSInteger)code description:(NSString *)description version:(NSString *)version 32 | { 33 | if ((self = [super init])) 34 | { 35 | message = CFHTTPMessageCreateResponse(NULL, 36 | (CFIndex)code, 37 | (__bridge CFStringRef)description, 38 | (__bridge CFStringRef)version); 39 | } 40 | return self; 41 | } 42 | 43 | - (void)dealloc 44 | { 45 | if (message) 46 | { 47 | CFRelease(message); 48 | } 49 | } 50 | 51 | - (BOOL)appendData:(NSData *)data 52 | { 53 | return CFHTTPMessageAppendBytes(message, [data bytes], [data length]); 54 | } 55 | 56 | - (BOOL)isHeaderComplete 57 | { 58 | return CFHTTPMessageIsHeaderComplete(message); 59 | } 60 | 61 | - (NSString *)version 62 | { 63 | return (__bridge_transfer NSString *)CFHTTPMessageCopyVersion(message); 64 | } 65 | 66 | - (NSString *)method 67 | { 68 | return (__bridge_transfer NSString *)CFHTTPMessageCopyRequestMethod(message); 69 | } 70 | 71 | - (NSURL *)url 72 | { 73 | return (__bridge_transfer NSURL *)CFHTTPMessageCopyRequestURL(message); 74 | } 75 | 76 | - (NSInteger)statusCode 77 | { 78 | return (NSInteger)CFHTTPMessageGetResponseStatusCode(message); 79 | } 80 | 81 | - (NSDictionary *)allHeaderFields 82 | { 83 | return (__bridge_transfer NSDictionary *)CFHTTPMessageCopyAllHeaderFields(message); 84 | } 85 | 86 | - (NSString *)headerField:(NSString *)headerField 87 | { 88 | return (__bridge_transfer NSString *)CFHTTPMessageCopyHeaderFieldValue(message, (__bridge CFStringRef)headerField); 89 | } 90 | 91 | - (void)setHeaderField:(NSString *)headerField value:(NSString *)headerFieldValue 92 | { 93 | CFHTTPMessageSetHeaderFieldValue(message, 94 | (__bridge CFStringRef)headerField, 95 | (__bridge CFStringRef)headerFieldValue); 96 | } 97 | 98 | - (NSData *)messageData 99 | { 100 | return (__bridge_transfer NSData *)CFHTTPMessageCopySerializedMessage(message); 101 | } 102 | 103 | - (NSData *)body 104 | { 105 | return (__bridge_transfer NSData *)CFHTTPMessageCopyBody(message); 106 | } 107 | 108 | - (void)setBody:(NSData *)body 109 | { 110 | CFHTTPMessageSetBody(message, (__bridge CFDataRef)body); 111 | } 112 | 113 | @end 114 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/CHSHTTPResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @protocol CHSHTTPResponse 5 | 6 | /** 7 | * Returns the length of the data in bytes. 8 | * If you don't know the length in advance, implement the isChunked method and have it return YES. 9 | **/ 10 | - (UInt64)contentLength; 11 | 12 | /** 13 | * The HTTP server supports range requests in order to allow things like 14 | * file download resumption and optimized streaming on mobile devices. 15 | **/ 16 | - (UInt64)offset; 17 | - (void)setOffset:(UInt64)offset; 18 | 19 | /** 20 | * Returns the data for the response. 21 | * You do not have to return data of the exact length that is given. 22 | * You may optionally return data of a lesser length. 23 | * However, you must never return data of a greater length than requested. 24 | * Doing so could disrupt proper support for range requests. 25 | * 26 | * To support asynchronous responses, read the discussion at the bottom of this header. 27 | **/ 28 | - (NSData *)readDataOfLength:(NSUInteger)length; 29 | 30 | /** 31 | * Should only return YES after the CHSHTTPConnection has read all available data. 32 | * That is, all data for the response has been returned to the CHSHTTPConnection via the readDataOfLength method. 33 | **/ 34 | - (BOOL)isDone; 35 | 36 | @optional 37 | 38 | /** 39 | * If you need time to calculate any part of the HTTP response headers (status code or header fields), 40 | * this method allows you to delay sending the headers so that you may asynchronously execute the calculations. 41 | * Simply implement this method and return YES until you have everything you need concerning the headers. 42 | * 43 | * This method ties into the asynchronous response architecture of the CHSHTTPConnection. 44 | * You should read the full discussion at the bottom of this header. 45 | * 46 | * If you return YES from this method, 47 | * the CHSHTTPConnection will wait for you to invoke the responseHasAvailableData method. 48 | * After you do, the CHSHTTPConnection will again invoke this method to see if the response is ready to send the headers. 49 | * 50 | * You should only delay sending the headers until you have everything you need concerning just the headers. 51 | * Asynchronously generating the body of the response is not an excuse to delay sending the headers. 52 | * Instead you should tie into the asynchronous response architecture, and use techniques such as the isChunked method. 53 | * 54 | * Important: You should read the discussion at the bottom of this header. 55 | **/ 56 | - (BOOL)delayResponseHeaders; 57 | 58 | /** 59 | * Status code for response. 60 | * Allows for responses such as redirect (301), etc. 61 | **/ 62 | - (NSInteger)status; 63 | 64 | /** 65 | * If you want to add any extra HTTP headers to the response, 66 | * simply return them in a dictionary in this method. 67 | **/ 68 | - (NSDictionary *)httpHeaders; 69 | 70 | /** 71 | * If you don't know the content-length in advance, 72 | * implement this method in your custom response class and return YES. 73 | * 74 | * Important: You should read the discussion at the bottom of this header. 75 | **/ 76 | - (BOOL)isChunked; 77 | 78 | /** 79 | * This method is called from the CHSHTTPConnection class when the connection is closed, 80 | * or when the connection is finished with the response. 81 | * If your response is asynchronous, you should implement this method so you know not to 82 | * invoke any methods on the CHSHTTPConnection after this method is called (as the connection may be deallocated). 83 | **/ 84 | - (void)connectionDidClose; 85 | 86 | @end 87 | 88 | 89 | /** 90 | * Important notice to those implementing custom asynchronous and/or chunked responses: 91 | * 92 | * CHSHTTPConnection supports asynchronous responses. All you have to do in your custom response class is 93 | * asynchronously generate the response, and invoke CHSHTTPConnection's responseHasAvailableData method. 94 | * You don't have to wait until you have all of the response ready to invoke this method. For example, if you 95 | * generate the response in incremental chunks, you could call responseHasAvailableData after generating 96 | * each chunk. Please see the CHSHTTPAsyncFileResponse class for an example of how to do this. 97 | * 98 | * The normal flow of events for an CHSHTTPConnection while responding to a request is like this: 99 | * - Send http resopnse headers 100 | * - Get data from response via readDataOfLength method. 101 | * - Add data to asyncSocket's write queue. 102 | * - Wait for asyncSocket to notify it that the data has been sent. 103 | * - Get more data from response via readDataOfLength method. 104 | * - ... continue this cycle until the entire response has been sent. 105 | * 106 | * With an asynchronous response, the flow is a little different. 107 | * 108 | * First the CHSHTTPResponse is given the opportunity to postpone sending the HTTP response headers. 109 | * This allows the response to asynchronously execute any code needed to calculate a part of the header. 110 | * An example might be the response needs to generate some custom header fields, 111 | * or perhaps the response needs to look for a resource on network-attached storage. 112 | * Since the network-attached storage may be slow, the response doesn't know whether to send a 200 or 404 yet. 113 | * In situations such as this, the CHSHTTPResponse simply implements the delayResponseHeaders method and returns YES. 114 | * After returning YES from this method, the CHSHTTPConnection will wait until the response invokes its 115 | * responseHasAvailableData method. After this occurs, the CHSHTTPConnection will again query the delayResponseHeaders 116 | * method to see if the response is ready to send the headers. 117 | * This cycle will continue until the delayResponseHeaders method returns NO. 118 | * 119 | * You should only delay sending the response headers until you have everything you need concerning just the headers. 120 | * Asynchronously generating the body of the response is not an excuse to delay sending the headers. 121 | * 122 | * After the response headers have been sent, the CHSHTTPConnection calls your readDataOfLength method. 123 | * You may or may not have any available data at this point. If you don't, then simply return nil. 124 | * You should later invoke CHSHTTPConnection's responseHasAvailableData when you have data to send. 125 | * 126 | * You don't have to keep track of when you return nil in the readDataOfLength method, or how many times you've invoked 127 | * responseHasAvailableData. Just simply call responseHasAvailableData whenever you've generated new data, and 128 | * return nil in your readDataOfLength whenever you don't have any available data in the requested range. 129 | * CHSHTTPConnection will automatically detect when it should be requesting new data and will act appropriately. 130 | * 131 | * It's important that you also keep in mind that the HTTP server supports range requests. 132 | * The setOffset method is mandatory, and should not be ignored. 133 | * Make sure you take into account the offset within the readDataOfLength method. 134 | * You should also be aware that the CHSHTTPConnection automatically sorts any range requests. 135 | * So if your setOffset method is called with a value of 100, then you can safely release bytes 0-99. 136 | * 137 | * CHSHTTPConnection can also help you keep your memory footprint small. 138 | * Imagine you're dynamically generating a 10 MB response. You probably don't want to load all this data into 139 | * RAM, and sit around waiting for CHSHTTPConnection to slowly send it out over the network. All you need to do 140 | * is pay attention to when CHSHTTPConnection requests more data via readDataOfLength. This is because CHSHTTPConnection 141 | * will never allow asyncSocket's write queue to get much bigger than READ_CHUNKSIZE bytes. You should 142 | * consider how you might be able to take advantage of this fact to generate your asynchronous response on demand, 143 | * while at the same time keeping your memory footprint small, and your application lightning fast. 144 | * 145 | * If you don't know the content-length in advanced, you should also implement the isChunked method. 146 | * This means the response will not include a Content-Length header, and will instead use "Transfer-Encoding: chunked". 147 | * There's a good chance that if your response is asynchronous and dynamic, it's also chunked. 148 | * If your response is chunked, you don't need to worry about range requests. 149 | **/ 150 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/CHSHTTPServer.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class GCDAsyncSocket; 4 | @class CHSWebSocket; 5 | 6 | #if TARGET_OS_IPHONE 7 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000 // iPhone 4.0 8 | #define IMPLEMENTED_PROTOCOLS 9 | #else 10 | #define IMPLEMENTED_PROTOCOLS 11 | #endif 12 | #else 13 | #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 // Mac OS X 10.6 14 | #define IMPLEMENTED_PROTOCOLS 15 | #else 16 | #define IMPLEMENTED_PROTOCOLS 17 | #endif 18 | #endif 19 | 20 | 21 | @interface CHSHTTPServer : NSObject IMPLEMENTED_PROTOCOLS 22 | { 23 | // Underlying asynchronous TCP/IP socket 24 | dispatch_queue_t serverQueue; 25 | dispatch_queue_t connectionQueue; 26 | GCDAsyncSocket *asyncSocket; 27 | 28 | // HTTP server configuration 29 | NSString *documentRoot; 30 | Class connectionClass; 31 | NSString *interface; 32 | UInt16 port; 33 | 34 | // NSNetService and related variables 35 | NSNetService *netService; 36 | NSString *domain; 37 | NSString *type; 38 | NSString *name; 39 | NSString *publishedName; 40 | NSDictionary *txtRecordDictionary; 41 | 42 | // Connection management 43 | NSMutableArray *connections; 44 | NSMutableArray *webSockets; 45 | NSLock *connectionsLock; 46 | NSLock *webSocketsLock; 47 | 48 | BOOL isRunning; 49 | } 50 | 51 | /** 52 | * Specifies the document root to serve files from. 53 | * For example, if you set this to "/Users//Sites", 54 | * then it will serve files out of the local Sites directory (including subdirectories). 55 | * 56 | * The default value is nil. 57 | * The default server configuration will not serve any files until this is set. 58 | * 59 | * If you change the documentRoot while the server is running, 60 | * the change will affect future incoming http connections. 61 | **/ 62 | - (NSString *)documentRoot; 63 | - (void)setDocumentRoot:(NSString *)value; 64 | 65 | /** 66 | * The connection class is the class used to handle incoming HTTP connections. 67 | * 68 | * The default value is [CHSHTTPConnection class]. 69 | * You can override CHSHTTPConnection, and then set this to [MyHTTPConnection class]. 70 | * 71 | * If you change the connectionClass while the server is running, 72 | * the change will affect future incoming http connections. 73 | **/ 74 | - (Class)connectionClass; 75 | - (void)setConnectionClass:(Class)value; 76 | 77 | /** 78 | * Set what interface you'd like the server to listen on. 79 | * By default this is nil, which causes the server to listen on all available interfaces like en1, wifi etc. 80 | * 81 | * The interface may be specified by name (e.g. "en1" or "lo0") or by IP address (e.g. "192.168.4.34"). 82 | * You may also use the special strings "localhost" or "loopback" to specify that 83 | * the socket only accept connections from the local machine. 84 | **/ 85 | - (NSString *)interface; 86 | - (void)setInterface:(NSString *)value; 87 | 88 | /** 89 | * The port number to run the HTTP server on. 90 | * 91 | * The default port number is zero, meaning the server will automatically use any available port. 92 | * This is the recommended port value, as it avoids possible port conflicts with other applications. 93 | * Technologies such as Bonjour can be used to allow other applications to automatically discover the port number. 94 | * 95 | * Note: As is common on most OS's, you need root privledges to bind to port numbers below 1024. 96 | * 97 | * You can change the port property while the server is running, but it won't affect the running server. 98 | * To actually change the port the server is listening for connections on you'll need to restart the server. 99 | * 100 | * The listeningPort method will always return the port number the running server is listening for connections on. 101 | * If the server is not running this method returns 0. 102 | **/ 103 | - (UInt16)port; 104 | - (UInt16)listeningPort; 105 | - (void)setPort:(UInt16)value; 106 | 107 | /** 108 | * Bonjour domain for publishing the service. 109 | * The default value is "local.". 110 | * 111 | * Note: Bonjour publishing requires you set a type. 112 | * 113 | * If you change the domain property after the bonjour service has already been published (server already started), 114 | * you'll need to invoke the republishBonjour method to update the broadcasted bonjour service. 115 | **/ 116 | - (NSString *)domain; 117 | - (void)setDomain:(NSString *)value; 118 | 119 | /** 120 | * Bonjour name for publishing the service. 121 | * The default value is "". 122 | * 123 | * If using an empty string ("") for the service name when registering, 124 | * the system will automatically use the "Computer Name". 125 | * Using an empty string will also handle name conflicts 126 | * by automatically appending a digit to the end of the name. 127 | * 128 | * Note: Bonjour publishing requires you set a type. 129 | * 130 | * If you change the name after the bonjour service has already been published (server already started), 131 | * you'll need to invoke the republishBonjour method to update the broadcasted bonjour service. 132 | * 133 | * The publishedName method will always return the actual name that was published via the bonjour service. 134 | * If the service is not running this method returns nil. 135 | **/ 136 | - (NSString *)name; 137 | - (NSString *)publishedName; 138 | - (void)setName:(NSString *)value; 139 | 140 | /** 141 | * Bonjour type for publishing the service. 142 | * The default value is nil. 143 | * The service will not be published via bonjour unless the type is set. 144 | * 145 | * If you wish to publish the service as a traditional HTTP server, you should set the type to be "_http._tcp.". 146 | * 147 | * If you change the type after the bonjour service has already been published (server already started), 148 | * you'll need to invoke the republishBonjour method to update the broadcasted bonjour service. 149 | **/ 150 | - (NSString *)type; 151 | - (void)setType:(NSString *)value; 152 | 153 | /** 154 | * Republishes the service via bonjour if the server is running. 155 | * If the service was not previously published, this method will publish it (if the server is running). 156 | **/ 157 | - (void)republishBonjour; 158 | 159 | /** 160 | * 161 | **/ 162 | - (NSDictionary *)TXTRecordDictionary; 163 | - (void)setTXTRecordDictionary:(NSDictionary *)dict; 164 | 165 | /** 166 | * Attempts to starts the server on the configured port, interface, etc. 167 | * 168 | * If an error occurs, this method returns NO and sets the errPtr (if given). 169 | * Otherwise returns YES on success. 170 | * 171 | * Some examples of errors that might occur: 172 | * - You specified the server listen on a port which is already in use by another application. 173 | * - You specified the server listen on a port number below 1024, which requires root priviledges. 174 | * 175 | * Code Example: 176 | * 177 | * NSError *err = nil; 178 | * if (![httpServer start:&err]) 179 | * { 180 | * NSLog(@"Error starting http server: %@", err); 181 | * } 182 | **/ 183 | - (BOOL)start:(NSError **)errPtr; 184 | 185 | /** 186 | * Stops the server, preventing it from accepting any new connections. 187 | * You may specify whether or not you want to close the existing client connections. 188 | * 189 | * The default stop method (with no arguments) will close any existing connections. (It invokes [self stop:NO]) 190 | **/ 191 | - (void)stop; 192 | - (void)stop:(BOOL)keepExistingConnections; 193 | 194 | - (BOOL)isRunning; 195 | 196 | - (void)addWebSocket:(CHSWebSocket *)ws; 197 | 198 | - (NSUInteger)numberOfHTTPConnections; 199 | - (NSUInteger)numberOfWebSocketConnections; 200 | 201 | @end 202 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/CHSWebSocket.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class CHSHTTPMessage; 4 | @class GCDAsyncSocket; 5 | 6 | 7 | #define WebSocketDidDieNotification @"WebSocketDidDie" 8 | 9 | @interface CHSWebSocket : NSObject 10 | { 11 | dispatch_queue_t websocketQueue; 12 | 13 | CHSHTTPMessage *request; 14 | GCDAsyncSocket *asyncSocket; 15 | 16 | NSData *term; 17 | 18 | BOOL isStarted; 19 | BOOL isOpen; 20 | BOOL isVersion76; 21 | 22 | id __unsafe_unretained delegate; 23 | } 24 | 25 | + (BOOL)isWebSocketRequest:(CHSHTTPMessage *)request; 26 | 27 | - (id)initWithRequest:(CHSHTTPMessage *)request socket:(GCDAsyncSocket *)socket; 28 | 29 | /** 30 | * Delegate option. 31 | * 32 | * In most cases it will be easier to subclass CHSWebSocket, 33 | * but some circumstances may lead one to prefer standard delegate callbacks instead. 34 | **/ 35 | @property (/* atomic */ unsafe_unretained) id delegate; 36 | 37 | /** 38 | * The CHSWebSocket class is thread-safe, generally via it's GCD queue. 39 | * All public API methods are thread-safe, 40 | * and the subclass API methods are thread-safe as they are all invoked on the same GCD queue. 41 | **/ 42 | @property (nonatomic, readonly) dispatch_queue_t websocketQueue; 43 | 44 | /** 45 | * Public API 46 | * 47 | * These methods are automatically called by the CHSHTTPServer. 48 | * You may invoke the stop method yourself to close the CHSWebSocket manually. 49 | **/ 50 | - (void)start; 51 | - (void)stop; 52 | 53 | /** 54 | * Public API 55 | * 56 | * Sends a message over the CHSWebSocket. 57 | * This method is thread-safe. 58 | **/ 59 | - (void)sendMessage:(NSString *)msg; 60 | 61 | /** 62 | * Subclass API 63 | * 64 | * These methods are designed to be overriden by subclasses. 65 | **/ 66 | - (void)didOpen; 67 | - (void)didReceiveMessage:(NSString *)msg; 68 | - (void)didClose; 69 | 70 | @end 71 | 72 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 73 | #pragma mark - 74 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 75 | 76 | /** 77 | * There are two ways to create your own custom CHSWebSocket: 78 | * 79 | * - Subclass it and override the methods you're interested in. 80 | * - Use traditional delegate paradigm along with your own custom class. 81 | * 82 | * They both exist to allow for maximum flexibility. 83 | * In most cases it will be easier to subclass CHSWebSocket. 84 | * However some circumstances may lead one to prefer standard delegate callbacks instead. 85 | * One such example, you're already subclassing another class, so subclassing CHSWebSocket isn't an option. 86 | **/ 87 | 88 | @protocol WebSocketDelegate 89 | @optional 90 | 91 | - (void)webSocketDidOpen:(CHSWebSocket *)ws; 92 | 93 | - (void)webSocket:(CHSWebSocket *)ws didReceiveMessage:(NSString *)msg; 94 | 95 | - (void)webSocketDidClose:(CHSWebSocket *)ws; 96 | 97 | @end -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Categories/DDData.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSData (DDData) 4 | 5 | - (NSData *)md5Digest; 6 | 7 | - (NSData *)sha1Digest; 8 | 9 | - (NSString *)hexStringValue; 10 | 11 | - (NSString *)base64Encoded; 12 | - (NSData *)base64Decoded; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Categories/DDData.m: -------------------------------------------------------------------------------- 1 | #import "DDData.h" 2 | #import 3 | 4 | 5 | @implementation NSData (DDData) 6 | 7 | static char encodingTable[64] = { 8 | 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', 9 | 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', 10 | 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', 11 | 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' }; 12 | 13 | - (NSData *)md5Digest 14 | { 15 | unsigned char result[CC_MD5_DIGEST_LENGTH]; 16 | 17 | CC_MD5([self bytes], (CC_LONG)[self length], result); 18 | return [NSData dataWithBytes:result length:CC_MD5_DIGEST_LENGTH]; 19 | } 20 | 21 | - (NSData *)sha1Digest 22 | { 23 | unsigned char result[CC_SHA1_DIGEST_LENGTH]; 24 | 25 | CC_SHA1([self bytes], (CC_LONG)[self length], result); 26 | return [NSData dataWithBytes:result length:CC_SHA1_DIGEST_LENGTH]; 27 | } 28 | 29 | - (NSString *)hexStringValue 30 | { 31 | NSMutableString *stringBuffer = [NSMutableString stringWithCapacity:([self length] * 2)]; 32 | 33 | const unsigned char *dataBuffer = [self bytes]; 34 | int i; 35 | 36 | for (i = 0; i < [self length]; ++i) 37 | { 38 | [stringBuffer appendFormat:@"%02x", (unsigned int)dataBuffer[i]]; 39 | } 40 | 41 | return [stringBuffer copy]; 42 | } 43 | 44 | - (NSString *)base64Encoded 45 | { 46 | const unsigned char *bytes = [self bytes]; 47 | NSMutableString *result = [NSMutableString stringWithCapacity:[self length]]; 48 | unsigned long ixtext = 0; 49 | unsigned long lentext = [self length]; 50 | long ctremaining = 0; 51 | unsigned char inbuf[3], outbuf[4]; 52 | unsigned short i = 0; 53 | unsigned short charsonline = 0, ctcopy = 0; 54 | unsigned long ix = 0; 55 | 56 | while( YES ) 57 | { 58 | ctremaining = lentext - ixtext; 59 | if( ctremaining <= 0 ) break; 60 | 61 | for( i = 0; i < 3; i++ ) { 62 | ix = ixtext + i; 63 | if( ix < lentext ) inbuf[i] = bytes[ix]; 64 | else inbuf [i] = 0; 65 | } 66 | 67 | outbuf [0] = (inbuf [0] & 0xFC) >> 2; 68 | outbuf [1] = ((inbuf [0] & 0x03) << 4) | ((inbuf [1] & 0xF0) >> 4); 69 | outbuf [2] = ((inbuf [1] & 0x0F) << 2) | ((inbuf [2] & 0xC0) >> 6); 70 | outbuf [3] = inbuf [2] & 0x3F; 71 | ctcopy = 4; 72 | 73 | switch( ctremaining ) 74 | { 75 | case 1: 76 | ctcopy = 2; 77 | break; 78 | case 2: 79 | ctcopy = 3; 80 | break; 81 | } 82 | 83 | for( i = 0; i < ctcopy; i++ ) 84 | [result appendFormat:@"%c", encodingTable[outbuf[i]]]; 85 | 86 | for( i = ctcopy; i < 4; i++ ) 87 | [result appendString:@"="]; 88 | 89 | ixtext += 3; 90 | charsonline += 4; 91 | } 92 | 93 | return [NSString stringWithString:result]; 94 | } 95 | 96 | - (NSData *)base64Decoded 97 | { 98 | const unsigned char *bytes = [self bytes]; 99 | NSMutableData *result = [NSMutableData dataWithCapacity:[self length]]; 100 | 101 | unsigned long ixtext = 0; 102 | unsigned long lentext = [self length]; 103 | unsigned char ch = 0; 104 | unsigned char inbuf[4] = {0, 0, 0, 0}; 105 | unsigned char outbuf[3] = {0, 0, 0}; 106 | short i = 0, ixinbuf = 0; 107 | BOOL flignore = NO; 108 | BOOL flendtext = NO; 109 | 110 | while( YES ) 111 | { 112 | if( ixtext >= lentext ) break; 113 | ch = bytes[ixtext++]; 114 | flignore = NO; 115 | 116 | if( ( ch >= 'A' ) && ( ch <= 'Z' ) ) ch = ch - 'A'; 117 | else if( ( ch >= 'a' ) && ( ch <= 'z' ) ) ch = ch - 'a' + 26; 118 | else if( ( ch >= '0' ) && ( ch <= '9' ) ) ch = ch - '0' + 52; 119 | else if( ch == '+' ) ch = 62; 120 | else if( ch == '=' ) flendtext = YES; 121 | else if( ch == '/' ) ch = 63; 122 | else flignore = YES; 123 | 124 | if( ! flignore ) 125 | { 126 | short ctcharsinbuf = 3; 127 | BOOL flbreak = NO; 128 | 129 | if( flendtext ) 130 | { 131 | if( ! ixinbuf ) break; 132 | if( ( ixinbuf == 1 ) || ( ixinbuf == 2 ) ) ctcharsinbuf = 1; 133 | else ctcharsinbuf = 2; 134 | ixinbuf = 3; 135 | flbreak = YES; 136 | } 137 | 138 | inbuf [ixinbuf++] = ch; 139 | 140 | if( ixinbuf == 4 ) 141 | { 142 | ixinbuf = 0; 143 | outbuf [0] = ( inbuf[0] << 2 ) | ( ( inbuf[1] & 0x30) >> 4 ); 144 | outbuf [1] = ( ( inbuf[1] & 0x0F ) << 4 ) | ( ( inbuf[2] & 0x3C ) >> 2 ); 145 | outbuf [2] = ( ( inbuf[2] & 0x03 ) << 6 ) | ( inbuf[3] & 0x3F ); 146 | 147 | for( i = 0; i < ctcharsinbuf; i++ ) 148 | [result appendBytes:&outbuf[i] length:1]; 149 | } 150 | 151 | if( flbreak ) break; 152 | } 153 | } 154 | 155 | return [NSData dataWithData:result]; 156 | } 157 | 158 | @end 159 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Categories/DDNumber.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSNumber (DDNumber) 5 | 6 | + (BOOL)parseString:(NSString *)str intoSInt64:(SInt64 *)pNum; 7 | + (BOOL)parseString:(NSString *)str intoUInt64:(UInt64 *)pNum; 8 | 9 | + (BOOL)parseString:(NSString *)str intoNSInteger:(NSInteger *)pNum; 10 | + (BOOL)parseString:(NSString *)str intoNSUInteger:(NSUInteger *)pNum; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Categories/DDNumber.m: -------------------------------------------------------------------------------- 1 | #import "DDNumber.h" 2 | 3 | 4 | @implementation NSNumber (DDNumber) 5 | 6 | + (BOOL)parseString:(NSString *)str intoSInt64:(SInt64 *)pNum 7 | { 8 | if(str == nil) 9 | { 10 | *pNum = 0; 11 | return NO; 12 | } 13 | 14 | errno = 0; 15 | 16 | // On both 32-bit and 64-bit machines, long long = 64 bit 17 | 18 | *pNum = strtoll([str UTF8String], NULL, 10); 19 | 20 | if(errno != 0) 21 | return NO; 22 | else 23 | return YES; 24 | } 25 | 26 | + (BOOL)parseString:(NSString *)str intoUInt64:(UInt64 *)pNum 27 | { 28 | if(str == nil) 29 | { 30 | *pNum = 0; 31 | return NO; 32 | } 33 | 34 | errno = 0; 35 | 36 | // On both 32-bit and 64-bit machines, unsigned long long = 64 bit 37 | 38 | *pNum = strtoull([str UTF8String], NULL, 10); 39 | 40 | if(errno != 0) 41 | return NO; 42 | else 43 | return YES; 44 | } 45 | 46 | + (BOOL)parseString:(NSString *)str intoNSInteger:(NSInteger *)pNum 47 | { 48 | if(str == nil) 49 | { 50 | *pNum = 0; 51 | return NO; 52 | } 53 | 54 | errno = 0; 55 | 56 | // On LP64, NSInteger = long = 64 bit 57 | // Otherwise, NSInteger = int = long = 32 bit 58 | 59 | *pNum = strtol([str UTF8String], NULL, 10); 60 | 61 | if(errno != 0) 62 | return NO; 63 | else 64 | return YES; 65 | } 66 | 67 | + (BOOL)parseString:(NSString *)str intoNSUInteger:(NSUInteger *)pNum 68 | { 69 | if(str == nil) 70 | { 71 | *pNum = 0; 72 | return NO; 73 | } 74 | 75 | errno = 0; 76 | 77 | // On LP64, NSUInteger = unsigned long = 64 bit 78 | // Otherwise, NSUInteger = unsigned int = unsigned long = 32 bit 79 | 80 | *pNum = strtoul([str UTF8String], NULL, 10); 81 | 82 | if(errno != 0) 83 | return NO; 84 | else 85 | return YES; 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Categories/DDRange.h: -------------------------------------------------------------------------------- 1 | /** 2 | * DDRange is the functional equivalent of a 64 bit NSRange. 3 | * The HTTP Server is designed to support very large files. 4 | * On 32 bit architectures (ppc, i386) NSRange uses unsigned 32 bit integers. 5 | * This only supports a range of up to 4 gigabytes. 6 | * By defining our own variant, we can support a range up to 16 exabytes. 7 | * 8 | * All effort is given such that DDRange functions EXACTLY the same as NSRange. 9 | **/ 10 | 11 | #import 12 | #import 13 | 14 | @class NSString; 15 | 16 | typedef struct _DDRange { 17 | UInt64 location; 18 | UInt64 length; 19 | } DDRange; 20 | 21 | typedef DDRange *DDRangePointer; 22 | 23 | NS_INLINE DDRange DDMakeRange(UInt64 loc, UInt64 len) { 24 | DDRange r; 25 | r.location = loc; 26 | r.length = len; 27 | return r; 28 | } 29 | 30 | NS_INLINE UInt64 DDMaxRange(DDRange range) { 31 | return (range.location + range.length); 32 | } 33 | 34 | NS_INLINE BOOL DDLocationInRange(UInt64 loc, DDRange range) { 35 | return (loc - range.location < range.length); 36 | } 37 | 38 | NS_INLINE BOOL DDEqualRanges(DDRange range1, DDRange range2) { 39 | return ((range1.location == range2.location) && (range1.length == range2.length)); 40 | } 41 | 42 | FOUNDATION_EXPORT DDRange DDUnionRange(DDRange range1, DDRange range2); 43 | FOUNDATION_EXPORT DDRange DDIntersectionRange(DDRange range1, DDRange range2); 44 | FOUNDATION_EXPORT NSString *DDStringFromRange(DDRange range); 45 | FOUNDATION_EXPORT DDRange DDRangeFromString(NSString *aString); 46 | 47 | NSInteger DDRangeCompare(DDRangePointer pDDRange1, DDRangePointer pDDRange2); 48 | 49 | @interface NSValue (NSValueDDRangeExtensions) 50 | 51 | + (NSValue *)valueWithDDRange:(DDRange)range; 52 | - (DDRange)ddrangeValue; 53 | 54 | - (NSInteger)ddrangeCompare:(NSValue *)ddrangeValue; 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Categories/DDRange.m: -------------------------------------------------------------------------------- 1 | #import "DDRange.h" 2 | #import "DDNumber.h" 3 | 4 | DDRange DDUnionRange(DDRange range1, DDRange range2) 5 | { 6 | DDRange result; 7 | 8 | result.location = MIN(range1.location, range2.location); 9 | result.length = MAX(DDMaxRange(range1), DDMaxRange(range2)) - result.location; 10 | 11 | return result; 12 | } 13 | 14 | DDRange DDIntersectionRange(DDRange range1, DDRange range2) 15 | { 16 | DDRange result; 17 | 18 | if((DDMaxRange(range1) < range2.location) || (DDMaxRange(range2) < range1.location)) 19 | { 20 | return DDMakeRange(0, 0); 21 | } 22 | 23 | result.location = MAX(range1.location, range2.location); 24 | result.length = MIN(DDMaxRange(range1), DDMaxRange(range2)) - result.location; 25 | 26 | return result; 27 | } 28 | 29 | NSString *DDStringFromRange(DDRange range) 30 | { 31 | return [NSString stringWithFormat:@"{%qu, %qu}", range.location, range.length]; 32 | } 33 | 34 | DDRange DDRangeFromString(NSString *aString) 35 | { 36 | DDRange result = DDMakeRange(0, 0); 37 | 38 | // NSRange will ignore '-' characters, but not '+' characters 39 | NSCharacterSet *cset = [NSCharacterSet characterSetWithCharactersInString:@"+0123456789"]; 40 | 41 | NSScanner *scanner = [NSScanner scannerWithString:aString]; 42 | [scanner setCharactersToBeSkipped:[cset invertedSet]]; 43 | 44 | NSString *str1 = nil; 45 | NSString *str2 = nil; 46 | 47 | BOOL found1 = [scanner scanCharactersFromSet:cset intoString:&str1]; 48 | BOOL found2 = [scanner scanCharactersFromSet:cset intoString:&str2]; 49 | 50 | if(found1) [NSNumber parseString:str1 intoUInt64:&result.location]; 51 | if(found2) [NSNumber parseString:str2 intoUInt64:&result.length]; 52 | 53 | return result; 54 | } 55 | 56 | NSInteger DDRangeCompare(DDRangePointer pDDRange1, DDRangePointer pDDRange2) 57 | { 58 | // Comparison basis: 59 | // Which range would you encouter first if you started at zero, and began walking towards infinity. 60 | // If you encouter both ranges at the same time, which range would end first. 61 | 62 | if(pDDRange1->location < pDDRange2->location) 63 | { 64 | return NSOrderedAscending; 65 | } 66 | if(pDDRange1->location > pDDRange2->location) 67 | { 68 | return NSOrderedDescending; 69 | } 70 | if(pDDRange1->length < pDDRange2->length) 71 | { 72 | return NSOrderedAscending; 73 | } 74 | if(pDDRange1->length > pDDRange2->length) 75 | { 76 | return NSOrderedDescending; 77 | } 78 | 79 | return NSOrderedSame; 80 | } 81 | 82 | @implementation NSValue (NSValueDDRangeExtensions) 83 | 84 | + (NSValue *)valueWithDDRange:(DDRange)range 85 | { 86 | return [NSValue valueWithBytes:&range objCType:@encode(DDRange)]; 87 | } 88 | 89 | - (DDRange)ddrangeValue 90 | { 91 | DDRange result; 92 | [self getValue:&result]; 93 | return result; 94 | } 95 | 96 | - (NSInteger)ddrangeCompare:(NSValue *)other 97 | { 98 | DDRange r1 = [self ddrangeValue]; 99 | DDRange r2 = [other ddrangeValue]; 100 | 101 | return DDRangeCompare(&r1, &r2); 102 | } 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Mime/CHSMultipartFormDataParser.h: -------------------------------------------------------------------------------- 1 | 2 | #import "CHSMultipartMessageHeader.h" 3 | 4 | /* 5 | Part one: http://tools.ietf.org/html/rfc2045 (Format of Internet Message Bodies) 6 | Part two: http://tools.ietf.org/html/rfc2046 (Media Types) 7 | Part three: http://tools.ietf.org/html/rfc2047 (Message Header Extensions for Non-ASCII Text) 8 | Part four: http://tools.ietf.org/html/rfc4289 (Registration Procedures) 9 | Part five: http://tools.ietf.org/html/rfc2049 (Conformance Criteria and Examples) 10 | 11 | Internet message format: http://tools.ietf.org/html/rfc2822 12 | 13 | Multipart/form-data http://tools.ietf.org/html/rfc2388 14 | */ 15 | 16 | @class CHSMultipartFormDataParser; 17 | 18 | //----------------------------------------------------------------- 19 | // protocol CHSMultipartFormDataParser 20 | //----------------------------------------------------------------- 21 | 22 | @protocol CHSMultipartFormDataParserDelegate 23 | @optional 24 | - (void) processContent:(NSData*) data WithHeader:(CHSMultipartMessageHeader *) header; 25 | - (void) processEndOfPartWithHeader:(CHSMultipartMessageHeader *) header; 26 | - (void) processPreambleData:(NSData*) data; 27 | - (void) processEpilogueData:(NSData*) data; 28 | - (void) processStartOfPartWithHeader:(CHSMultipartMessageHeader *) header; 29 | @end 30 | 31 | //----------------------------------------------------------------- 32 | // interface CHSMultipartFormDataParser 33 | //----------------------------------------------------------------- 34 | 35 | @interface CHSMultipartFormDataParser : NSObject { 36 | NSMutableData* pendingData; 37 | NSData* boundaryData; 38 | CHSMultipartMessageHeader * currentHeader; 39 | 40 | BOOL waitingForCRLF; 41 | BOOL reachedEpilogue; 42 | BOOL processedPreamble; 43 | BOOL checkForContentEnd; 44 | 45 | #if __has_feature(objc_arc_weak) 46 | __weak id delegate; 47 | #else 48 | __unsafe_unretained id delegate; 49 | #endif 50 | int currentEncoding; 51 | NSStringEncoding formEncoding; 52 | } 53 | 54 | - (BOOL) appendData:(NSData*) data; 55 | 56 | - (id) initWithBoundary:(NSString*) boundary formEncoding:(NSStringEncoding) formEncoding; 57 | 58 | #if __has_feature(objc_arc_weak) 59 | @property(weak, readwrite) id delegate; 60 | #else 61 | @property(unsafe_unretained, readwrite) id delegate; 62 | #endif 63 | @property(readwrite) NSStringEncoding formEncoding; 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Mime/CHSMultipartMessageHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // MultipartMessagePart.h 3 | // HttpServer 4 | // 5 | // Created by Валерий Гаврилов on 29.03.12. 6 | // Copyright (c) 2012 LLC "Online Publishing Partners" (onlinepp.ru). All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | //----------------------------------------------------------------- 13 | // interface CHSMultipartMessageHeader 14 | //----------------------------------------------------------------- 15 | enum { 16 | contentTransferEncoding_unknown, 17 | contentTransferEncoding_7bit, 18 | contentTransferEncoding_8bit, 19 | contentTransferEncoding_binary, 20 | contentTransferEncoding_base64, 21 | contentTransferEncoding_quotedPrintable, 22 | }; 23 | 24 | @interface CHSMultipartMessageHeader : NSObject { 25 | NSMutableDictionary* fields; 26 | int encoding; 27 | NSString* contentDispositionName; 28 | } 29 | @property (strong,readonly) NSDictionary* fields; 30 | @property (readonly) int encoding; 31 | 32 | - (id) initWithData:(NSData*) data formEncoding:(NSStringEncoding) encoding; 33 | @end 34 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Mime/CHSMultipartMessageHeader.m: -------------------------------------------------------------------------------- 1 | // 2 | // MultipartMessagePart.m 3 | // HttpServer 4 | // 5 | // Created by Валерий Гаврилов on 29.03.12. 6 | // Copyright (c) 2012 LLC "Online Publishing Partners" (onlinepp.ru). All rights reserved. 7 | 8 | #import "CHSMultipartMessageHeader.h" 9 | #import "CHSMultipartMessageHeaderField.h" 10 | 11 | #import "CHSHTTPLogging.h" 12 | 13 | //----------------------------------------------------------------- 14 | // implementation CHSMultipartMessageHeader 15 | //----------------------------------------------------------------- 16 | 17 | 18 | @implementation CHSMultipartMessageHeader 19 | @synthesize fields,encoding; 20 | 21 | 22 | - (id) initWithData:(NSData *)data formEncoding:(NSStringEncoding) formEncoding { 23 | if( nil == (self = [super init]) ) { 24 | return self; 25 | } 26 | 27 | fields = [[NSMutableDictionary alloc] initWithCapacity:1]; 28 | 29 | // In case encoding is not mentioned, 30 | encoding = contentTransferEncoding_unknown; 31 | 32 | char* bytes = (char*)data.bytes; 33 | NSInteger length = data.length; 34 | NSInteger offset = 0; 35 | 36 | // split header into header fields, separated by \r\n 37 | uint16_t fields_separator = 0x0A0D; // \r\n 38 | while( offset < length - 2 ) { 39 | 40 | // the !isspace condition is to support header unfolding 41 | if( (*(uint16_t*) (bytes+offset) == fields_separator) && ((offset == length - 2) || !(isspace(bytes[offset+2])) )) { 42 | NSData* fieldData = [NSData dataWithBytesNoCopy:bytes length:offset freeWhenDone:NO]; 43 | CHSMultipartMessageHeaderField * field = [[CHSMultipartMessageHeaderField alloc] initWithData: fieldData contentEncoding:formEncoding]; 44 | if( field ) { 45 | [fields setObject:field forKey:field.name]; 46 | HTTPLogVerbose(@"CHSMultipartFormDataParser: Processed Header field '%@'",field.name); 47 | } 48 | else { 49 | NSString* fieldStr = [[NSString alloc] initWithData:fieldData encoding:NSASCIIStringEncoding]; 50 | HTTPLogWarn(@"CHSMultipartFormDataParser: Failed to parse MIME header field. Input ASCII string:%@",fieldStr); 51 | } 52 | 53 | // move to the next header field 54 | bytes += offset + 2; 55 | length -= offset + 2; 56 | offset = 0; 57 | continue; 58 | } 59 | ++ offset; 60 | } 61 | 62 | if( !fields.count ) { 63 | // it was an empty header. 64 | // we have to set default values. 65 | // default header. 66 | [fields setObject:@"text/plain" forKey:@"Content-Type"]; 67 | } 68 | 69 | return self; 70 | } 71 | 72 | - (NSString *)description { 73 | return [NSString stringWithFormat:@"%@",fields]; 74 | } 75 | 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Mime/CHSMultipartMessageHeaderField.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | 4 | //----------------------------------------------------------------- 5 | // interface CHSMultipartMessageHeaderField 6 | //----------------------------------------------------------------- 7 | 8 | @interface CHSMultipartMessageHeaderField : NSObject { 9 | NSString* name; 10 | NSString* value; 11 | NSMutableDictionary* params; 12 | } 13 | 14 | @property (strong, readonly) NSString* value; 15 | @property (strong, readonly) NSDictionary* params; 16 | @property (strong, readonly) NSString* name; 17 | 18 | //- (id) initWithLine:(NSString*) line; 19 | //- (id) initWithName:(NSString*) paramName value:(NSString*) paramValue; 20 | 21 | - (id) initWithData:(NSData*) data contentEncoding:(NSStringEncoding) encoding; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Mime/CHSMultipartMessageHeaderField.m: -------------------------------------------------------------------------------- 1 | 2 | #import "CHSMultipartMessageHeaderField.h" 3 | #import "CHSHTTPLogging.h" 4 | 5 | // helpers 6 | NSUInteger findChar(const char* str,NSUInteger length, char c); 7 | NSString* extractParamValue(const char* bytes, NSUInteger length, NSStringEncoding encoding); 8 | 9 | //----------------------------------------------------------------- 10 | // interface CHSMultipartMessageHeaderField (private) 11 | //----------------------------------------------------------------- 12 | 13 | 14 | @interface CHSMultipartMessageHeaderField (private) 15 | -(BOOL) parseHeaderValueBytes:(char*) bytes length:(NSUInteger) length encoding:(NSStringEncoding) encoding; 16 | @end 17 | 18 | 19 | //----------------------------------------------------------------- 20 | // implementation CHSMultipartMessageHeaderField 21 | //----------------------------------------------------------------- 22 | 23 | @implementation CHSMultipartMessageHeaderField 24 | @synthesize name,value,params; 25 | 26 | - (id) initWithData:(NSData *)data contentEncoding:(NSStringEncoding)encoding { 27 | 28 | params = [[NSMutableDictionary alloc] initWithCapacity:1]; 29 | 30 | char* bytes = (char*)data.bytes; 31 | NSUInteger length = data.length; 32 | 33 | NSUInteger separatorOffset = findChar(bytes, length, ':'); 34 | if( (-1 == separatorOffset) || (separatorOffset >= length-2) ) { 35 | HTTPLogError(@"CHSMultipartFormDataParser: Bad format.No colon in field header."); 36 | // tear down 37 | return nil; 38 | } 39 | 40 | // header name is always ascii encoded; 41 | name = [[NSString alloc] initWithBytes: bytes length: separatorOffset encoding: NSASCIIStringEncoding]; 42 | if( nil == name ) { 43 | HTTPLogError(@"CHSMultipartFormDataParser: Bad MIME header name."); 44 | // tear down 45 | return nil; 46 | } 47 | 48 | // skip the separator and the next ' ' symbol 49 | bytes += separatorOffset + 2; 50 | length -= separatorOffset + 2; 51 | 52 | separatorOffset = findChar(bytes, length, ';'); 53 | if( separatorOffset == -1 ) { 54 | // couldn't find ';', means we don't have extra params here. 55 | value = [[NSString alloc] initWithBytes:bytes length: length encoding:encoding]; 56 | 57 | if( nil == value ) { 58 | HTTPLogError(@"CHSMultipartFormDataParser: Bad MIME header value for header name: '%@'",name); 59 | // tear down 60 | return nil; 61 | } 62 | return self; 63 | } 64 | 65 | value = [[NSString alloc] initWithBytes:bytes length: separatorOffset encoding:encoding]; 66 | HTTPLogVerbose(@"CHSMultipartFormDataParser: Processing header field '%@' : '%@'",name,value); 67 | // skipe the separator and the next ' ' symbol 68 | bytes += separatorOffset + 2; 69 | length -= separatorOffset + 2; 70 | 71 | // parse the "params" part of the header 72 | if( ![self parseHeaderValueBytes:bytes length:length encoding:encoding] ) { 73 | NSString* paramsStr = [[NSString alloc] initWithBytes:bytes length:length encoding:NSASCIIStringEncoding]; 74 | HTTPLogError(@"CHSMultipartFormDataParser: Bad params for header with name '%@' and value '%@'",name,value); 75 | HTTPLogError(@"CHSMultipartFormDataParser: Params str: %@",paramsStr); 76 | 77 | return nil; 78 | } 79 | return self; 80 | } 81 | 82 | 83 | -(BOOL) parseHeaderValueBytes:(char*) bytes length:(NSUInteger) length encoding:(NSStringEncoding) encoding { 84 | int offset = 0; 85 | NSString* currentParam = nil; 86 | BOOL insideQuote = NO; 87 | while( offset < length ) { 88 | if( bytes[offset] == '\"' ) { 89 | if( !offset || bytes[offset-1] != '\\' ) { 90 | insideQuote = !insideQuote; 91 | } 92 | } 93 | 94 | // skip quoted symbols 95 | if( insideQuote ) { 96 | ++ offset; 97 | continue; 98 | } 99 | if( bytes[offset] == '=' ) { 100 | if( currentParam ) { 101 | // found '=' before terminating previous param. 102 | return NO; 103 | } 104 | currentParam = [[NSString alloc] initWithBytes:bytes length:offset encoding:NSASCIIStringEncoding]; 105 | 106 | bytes+=offset + 1; 107 | length -= offset + 1; 108 | offset = 0; 109 | continue; 110 | } 111 | if( bytes[offset] == ';' ) { 112 | if( !currentParam ) { 113 | // found ; before stating '='. 114 | HTTPLogError(@"CHSMultipartFormDataParser: Unexpected ';' when parsing header"); 115 | return NO; 116 | } 117 | NSString* paramValue = extractParamValue(bytes, offset,encoding); 118 | if( nil == paramValue ) { 119 | HTTPLogWarn(@"CHSMultipartFormDataParser: Failed to exctract paramValue for key %@ in header %@",currentParam,name); 120 | } 121 | else { 122 | #ifdef DEBUG 123 | if( [params objectForKey:currentParam] ) { 124 | HTTPLogWarn(@"CHSMultipartFormDataParser: param %@ mentioned more then once in header %@",currentParam,name); 125 | } 126 | #endif 127 | [params setObject:paramValue forKey:currentParam]; 128 | HTTPLogVerbose(@"CHSMultipartFormDataParser: header param: %@ = %@",currentParam,paramValue); 129 | } 130 | 131 | currentParam = nil; 132 | 133 | // ';' separator has ' ' following, skip them. 134 | bytes+=offset + 2; 135 | length -= offset + 2; 136 | offset = 0; 137 | } 138 | ++ offset; 139 | } 140 | 141 | // add last param 142 | if( insideQuote ) { 143 | HTTPLogWarn(@"CHSMultipartFormDataParser: unterminated quote in header %@",name); 144 | // return YES; 145 | } 146 | if( currentParam ) { 147 | NSString* paramValue = extractParamValue(bytes, length,encoding); 148 | 149 | if( nil == paramValue ) { 150 | HTTPLogError(@"CHSMultipartFormDataParser: Failed to exctract paramValue for key %@ in header %@",currentParam,name); 151 | } 152 | 153 | #ifdef DEBUG 154 | if( [params objectForKey:currentParam] ) { 155 | HTTPLogWarn(@"CHSMultipartFormDataParser: param %@ mentioned more then once in one header",currentParam); 156 | } 157 | #endif 158 | [params setObject:paramValue forKey:currentParam]; 159 | HTTPLogVerbose(@"CHSMultipartFormDataParser: header param: %@ = %@",currentParam,paramValue); 160 | currentParam = nil; 161 | } 162 | 163 | return YES; 164 | } 165 | 166 | 167 | - (NSString *)description { 168 | return [NSString stringWithFormat:@"%@:%@\n params: %@",name,value,params]; 169 | } 170 | 171 | 172 | @end 173 | 174 | 175 | NSUInteger findChar(const char* str,NSUInteger length, char c) { 176 | int offset = 0; 177 | while( offset < length ) { 178 | if( str[offset] == c ) 179 | return offset; 180 | ++ offset; 181 | } 182 | return -1; 183 | } 184 | 185 | 186 | NSString* extractParamValue(const char* bytes, NSUInteger length, NSStringEncoding encoding) { 187 | if( !length ) 188 | return nil; 189 | NSMutableString* value = nil; 190 | 191 | if( bytes[0] == '"' ) { 192 | // values may be quoted. Strip the quotes to get what we need. 193 | value = [[NSMutableString alloc] initWithBytes:bytes + 1 length: length - 2 encoding:encoding]; 194 | } 195 | else { 196 | value = [[NSMutableString alloc] initWithBytes:bytes length: length encoding:encoding]; 197 | } 198 | // restore escaped symbols 199 | NSRange range= [value rangeOfString:@"\\"]; 200 | while ( range.length ) { 201 | [value deleteCharactersInRange:range]; 202 | range.location ++; 203 | range = [value rangeOfString:@"\\" options:NSLiteralSearch range: range]; 204 | } 205 | return value; 206 | } 207 | 208 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Responses/CHSHTTPAsyncFileResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "CHSHTTPResponse.h" 3 | 4 | @class CHSHTTPConnection; 5 | 6 | /** 7 | * This is an asynchronous version of CHSHTTPFileResponse. 8 | * It reads data from the given file asynchronously via GCD. 9 | * 10 | * It may be overriden to allow custom post-processing of the data that has been read from the file. 11 | * An example of this is the CHSHTTPDynamicFileResponse class. 12 | **/ 13 | 14 | @interface CHSHTTPAsyncFileResponse : NSObject 15 | { 16 | CHSHTTPConnection *connection; 17 | 18 | NSString *filePath; 19 | UInt64 fileLength; 20 | UInt64 fileOffset; // File offset as pertains to data given to connection 21 | UInt64 readOffset; // File offset as pertains to data read from file (but maybe not returned to connection) 22 | 23 | BOOL aborted; 24 | 25 | NSData *data; 26 | 27 | int fileFD; 28 | void *readBuffer; 29 | NSUInteger readBufferSize; // Malloced size of readBuffer 30 | NSUInteger readBufferOffset; // Offset within readBuffer where the end of existing data is 31 | NSUInteger readRequestLength; 32 | dispatch_queue_t readQueue; 33 | dispatch_source_t readSource; 34 | BOOL readSourceSuspended; 35 | } 36 | 37 | - (id)initWithFilePath:(NSString *)filePath forConnection:(CHSHTTPConnection *)connection; 38 | - (NSString *)filePath; 39 | 40 | @end 41 | 42 | /** 43 | * Explanation of Variables (excluding those that are obvious) 44 | * 45 | * fileOffset 46 | * This is the number of bytes that have been returned to the connection via the readDataOfLength method. 47 | * If 1KB of data has been read from the file, but none of that data has yet been returned to the connection, 48 | * then the fileOffset variable remains at zero. 49 | * This variable is used in the calculation of the isDone method. 50 | * Only after all data has been returned to the connection are we actually done. 51 | * 52 | * readOffset 53 | * Represents the offset of the file descriptor. 54 | * In other words, the file position indidcator for our read stream. 55 | * It might be easy to think of it as the total number of bytes that have been read from the file. 56 | * However, this isn't entirely accurate, as the setOffset: method may have caused us to 57 | * jump ahead in the file (lseek). 58 | * 59 | * readBuffer 60 | * Malloc'd buffer to hold data read from the file. 61 | * 62 | * readBufferSize 63 | * Total allocation size of malloc'd buffer. 64 | * 65 | * readBufferOffset 66 | * Represents the position in the readBuffer where we should store new bytes. 67 | * 68 | * readRequestLength 69 | * The total number of bytes that were requested from the connection. 70 | * It's OK if we return a lesser number of bytes to the connection. 71 | * It's NOT OK if we return a greater number of bytes to the connection. 72 | * Doing so would disrupt proper support for range requests. 73 | * If, however, the response is chunked then we don't need to worry about this. 74 | * Chunked responses inheritly don't support range requests. 75 | **/ -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Responses/CHSHTTPAsyncFileResponse.m: -------------------------------------------------------------------------------- 1 | #import "CHSHTTPAsyncFileResponse.h" 2 | #import "CHSHTTPConnection.h" 3 | #import "CHSHTTPLogging.h" 4 | 5 | #import 6 | #import 7 | 8 | #if ! __has_feature(objc_arc) 9 | #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). 10 | #endif 11 | 12 | /** 13 | * Does ARC support support GCD objects? 14 | * It does if the minimum deployment target is iOS 6+ or Mac OS X 8+ 15 | **/ 16 | #if TARGET_OS_IPHONE 17 | 18 | // Compiling for iOS 19 | 20 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later 21 | #define NEEDS_DISPATCH_RETAIN_RELEASE 0 22 | #else // iOS 5.X or earlier 23 | #define NEEDS_DISPATCH_RETAIN_RELEASE 1 24 | #endif 25 | 26 | #else 27 | 28 | // Compiling for Mac OS X 29 | 30 | #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later 31 | #define NEEDS_DISPATCH_RETAIN_RELEASE 0 32 | #else 33 | #define NEEDS_DISPATCH_RETAIN_RELEASE 1 // Mac OS X 10.7 or earlier 34 | #endif 35 | 36 | #endif 37 | 38 | #define NULL_FD -1 39 | 40 | /** 41 | * Architecure overview: 42 | * 43 | * CHSHTTPConnection will invoke our readDataOfLength: method to fetch data. 44 | * We will return nil, and then proceed to read the data via our readSource on our readQueue. 45 | * Once the requested amount of data has been read, we then pause our readSource, 46 | * and inform the connection of the available data. 47 | * 48 | * While our read is in progress, we don't have to worry about the connection calling any other methods, 49 | * except the connectionDidClose method, which would be invoked if the remote end closed the socket connection. 50 | * To safely handle this, we do a synchronous dispatch on the readQueue, 51 | * and nilify the connection as well as cancel our readSource. 52 | * 53 | * In order to minimize resource consumption during a HEAD request, 54 | * we don't open the file until we have to (until the connection starts requesting data). 55 | **/ 56 | 57 | @implementation CHSHTTPAsyncFileResponse 58 | 59 | - (id)initWithFilePath:(NSString *)fpath forConnection:(CHSHTTPConnection *)parent 60 | { 61 | if ((self = [super init])) 62 | { 63 | HTTPLogTrace(); 64 | 65 | connection = parent; // Parents retain children, children do NOT retain parents 66 | 67 | fileFD = NULL_FD; 68 | filePath = [fpath copy]; 69 | if (filePath == nil) 70 | { 71 | HTTPLogWarn(@"%@: Init failed - Nil filePath", THIS_FILE); 72 | 73 | return nil; 74 | } 75 | 76 | NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL]; 77 | if (fileAttributes == nil) 78 | { 79 | HTTPLogWarn(@"%@: Init failed - Unable to get file attributes. filePath: %@", THIS_FILE, filePath); 80 | 81 | return nil; 82 | } 83 | 84 | fileLength = (UInt64)[[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue]; 85 | fileOffset = 0; 86 | 87 | aborted = NO; 88 | 89 | // We don't bother opening the file here. 90 | // If this is a HEAD request we only need to know the fileLength. 91 | } 92 | return self; 93 | } 94 | 95 | - (void)abort 96 | { 97 | HTTPLogTrace(); 98 | 99 | [connection responseDidAbort:self]; 100 | aborted = YES; 101 | } 102 | 103 | - (void)processReadBuffer 104 | { 105 | // This method is here to allow superclasses to perform post-processing of the data. 106 | // For an example, see the CHSHTTPDynamicFileResponse class. 107 | // 108 | // At this point, the readBuffer has readBufferOffset bytes available. 109 | // This method is in charge of updating the readBufferOffset. 110 | // Failure to do so will cause the readBuffer to grow to fileLength. (Imagine a 1 GB file...) 111 | 112 | // Copy the data out of the temporary readBuffer. 113 | data = [[NSData alloc] initWithBytes:readBuffer length:readBufferOffset]; 114 | 115 | // Reset the read buffer. 116 | readBufferOffset = 0; 117 | 118 | // Notify the connection that we have data available for it. 119 | [connection responseHasAvailableData:self]; 120 | } 121 | 122 | - (void)pauseReadSource 123 | { 124 | if (!readSourceSuspended) 125 | { 126 | HTTPLogVerbose(@"%@[%p]: Suspending readSource", THIS_FILE, self); 127 | 128 | readSourceSuspended = YES; 129 | dispatch_suspend(readSource); 130 | } 131 | } 132 | 133 | - (void)resumeReadSource 134 | { 135 | if (readSourceSuspended) 136 | { 137 | HTTPLogVerbose(@"%@[%p]: Resuming readSource", THIS_FILE, self); 138 | 139 | readSourceSuspended = NO; 140 | dispatch_resume(readSource); 141 | } 142 | } 143 | 144 | - (void)cancelReadSource 145 | { 146 | HTTPLogVerbose(@"%@[%p]: Canceling readSource", THIS_FILE, self); 147 | 148 | dispatch_source_cancel(readSource); 149 | 150 | // Cancelling a dispatch source doesn't 151 | // invoke the cancel handler if the dispatch source is paused. 152 | 153 | if (readSourceSuspended) 154 | { 155 | readSourceSuspended = NO; 156 | dispatch_resume(readSource); 157 | } 158 | } 159 | 160 | - (BOOL)openFileAndSetupReadSource 161 | { 162 | HTTPLogTrace(); 163 | 164 | fileFD = open([filePath UTF8String], (O_RDONLY | O_NONBLOCK)); 165 | if (fileFD == NULL_FD) 166 | { 167 | HTTPLogError(@"%@: Unable to open file. filePath: %@", THIS_FILE, filePath); 168 | 169 | return NO; 170 | } 171 | 172 | HTTPLogVerbose(@"%@[%p]: Open fd[%i] -> %@", THIS_FILE, self, fileFD, filePath); 173 | 174 | readQueue = dispatch_queue_create("CHSHTTPAsyncFileResponse", NULL); 175 | readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fileFD, 0, readQueue); 176 | 177 | 178 | dispatch_source_set_event_handler(readSource, ^{ 179 | 180 | HTTPLogTrace2(@"%@: eventBlock - fd[%i]", THIS_FILE, fileFD); 181 | 182 | // Determine how much data we should read. 183 | // 184 | // It is OK if we ask to read more bytes than exist in the file. 185 | // It is NOT OK to over-allocate the buffer. 186 | 187 | unsigned long long _bytesAvailableOnFD = dispatch_source_get_data(readSource); 188 | 189 | UInt64 _bytesLeftInFile = fileLength - readOffset; 190 | 191 | NSUInteger bytesAvailableOnFD; 192 | NSUInteger bytesLeftInFile; 193 | 194 | bytesAvailableOnFD = (_bytesAvailableOnFD > NSUIntegerMax) ? NSUIntegerMax : (NSUInteger)_bytesAvailableOnFD; 195 | bytesLeftInFile = (_bytesLeftInFile > NSUIntegerMax) ? NSUIntegerMax : (NSUInteger)_bytesLeftInFile; 196 | 197 | NSUInteger bytesLeftInRequest = readRequestLength - readBufferOffset; 198 | 199 | NSUInteger bytesLeft = MIN(bytesLeftInRequest, bytesLeftInFile); 200 | 201 | NSUInteger bytesToRead = MIN(bytesAvailableOnFD, bytesLeft); 202 | 203 | // Make sure buffer is big enough for read request. 204 | // Do not over-allocate. 205 | 206 | if (readBuffer == NULL || bytesToRead > (readBufferSize - readBufferOffset)) 207 | { 208 | readBufferSize = bytesToRead; 209 | readBuffer = reallocf(readBuffer, (size_t)bytesToRead); 210 | 211 | if (readBuffer == NULL) 212 | { 213 | HTTPLogError(@"%@[%p]: Unable to allocate buffer", THIS_FILE, self); 214 | 215 | [self pauseReadSource]; 216 | [self abort]; 217 | 218 | return; 219 | } 220 | } 221 | 222 | // Perform the read 223 | 224 | HTTPLogVerbose(@"%@[%p]: Attempting to read %lu bytes from file", THIS_FILE, self, (unsigned long)bytesToRead); 225 | 226 | ssize_t result = read(fileFD, readBuffer + readBufferOffset, (size_t)bytesToRead); 227 | 228 | // Check the results 229 | if (result < 0) 230 | { 231 | HTTPLogError(@"%@: Error(%i) reading file(%@)", THIS_FILE, errno, filePath); 232 | 233 | [self pauseReadSource]; 234 | [self abort]; 235 | } 236 | else if (result == 0) 237 | { 238 | HTTPLogError(@"%@: Read EOF on file(%@)", THIS_FILE, filePath); 239 | 240 | [self pauseReadSource]; 241 | [self abort]; 242 | } 243 | else // (result > 0) 244 | { 245 | HTTPLogVerbose(@"%@[%p]: Read %lu bytes from file", THIS_FILE, self, (unsigned long)result); 246 | 247 | readOffset += result; 248 | readBufferOffset += result; 249 | 250 | [self pauseReadSource]; 251 | [self processReadBuffer]; 252 | } 253 | 254 | }); 255 | 256 | int theFileFD = fileFD; 257 | #if NEEDS_DISPATCH_RETAIN_RELEASE 258 | dispatch_source_t theReadSource = readSource; 259 | #endif 260 | 261 | dispatch_source_set_cancel_handler(readSource, ^{ 262 | 263 | // Do not access self from within this block in any way, shape or form. 264 | // 265 | // Note: You access self if you reference an iVar. 266 | 267 | HTTPLogTrace2(@"%@: cancelBlock - Close fd[%i]", THIS_FILE, theFileFD); 268 | 269 | #if NEEDS_DISPATCH_RETAIN_RELEASE 270 | dispatch_release(theReadSource); 271 | #endif 272 | close(theFileFD); 273 | }); 274 | 275 | readSourceSuspended = YES; 276 | 277 | return YES; 278 | } 279 | 280 | - (BOOL)openFileIfNeeded 281 | { 282 | if (aborted) 283 | { 284 | // The file operation has been aborted. 285 | // This could be because we failed to open the file, 286 | // or the reading process failed. 287 | return NO; 288 | } 289 | 290 | if (fileFD != NULL_FD) 291 | { 292 | // File has already been opened. 293 | return YES; 294 | } 295 | 296 | return [self openFileAndSetupReadSource]; 297 | } 298 | 299 | - (UInt64)contentLength 300 | { 301 | HTTPLogTrace2(@"%@[%p]: contentLength - %llu", THIS_FILE, self, fileLength); 302 | 303 | return fileLength; 304 | } 305 | 306 | - (UInt64)offset 307 | { 308 | HTTPLogTrace(); 309 | 310 | return fileOffset; 311 | } 312 | 313 | - (void)setOffset:(UInt64)offset 314 | { 315 | HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset); 316 | 317 | if (![self openFileIfNeeded]) 318 | { 319 | // File opening failed, 320 | // or response has been aborted due to another error. 321 | return; 322 | } 323 | 324 | fileOffset = offset; 325 | readOffset = offset; 326 | 327 | off_t result = lseek(fileFD, (off_t)offset, SEEK_SET); 328 | if (result == -1) 329 | { 330 | HTTPLogError(@"%@[%p]: lseek failed - errno(%i) filePath(%@)", THIS_FILE, self, errno, filePath); 331 | 332 | [self abort]; 333 | } 334 | } 335 | 336 | - (NSData *)readDataOfLength:(NSUInteger)length 337 | { 338 | HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)length); 339 | 340 | if (data) 341 | { 342 | NSUInteger dataLength = [data length]; 343 | 344 | HTTPLogVerbose(@"%@[%p]: Returning data of length %lu", THIS_FILE, self, (unsigned long)dataLength); 345 | 346 | fileOffset += dataLength; 347 | 348 | NSData *result = data; 349 | data = nil; 350 | 351 | return result; 352 | } 353 | else 354 | { 355 | if (![self openFileIfNeeded]) 356 | { 357 | // File opening failed, 358 | // or response has been aborted due to another error. 359 | return nil; 360 | } 361 | 362 | dispatch_sync(readQueue, ^{ 363 | 364 | NSAssert(readSourceSuspended, @"Invalid logic - perhaps CHSHTTPConnection has changed."); 365 | 366 | readRequestLength = length; 367 | [self resumeReadSource]; 368 | }); 369 | 370 | return nil; 371 | } 372 | } 373 | 374 | - (BOOL)isDone 375 | { 376 | BOOL result = (fileOffset == fileLength); 377 | 378 | HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO")); 379 | 380 | return result; 381 | } 382 | 383 | - (NSString *)filePath 384 | { 385 | return filePath; 386 | } 387 | 388 | - (BOOL)isAsynchronous 389 | { 390 | HTTPLogTrace(); 391 | 392 | return YES; 393 | } 394 | 395 | - (void)connectionDidClose 396 | { 397 | HTTPLogTrace(); 398 | 399 | if (fileFD != NULL_FD) 400 | { 401 | dispatch_sync(readQueue, ^{ 402 | 403 | // Prevent any further calls to the connection 404 | connection = nil; 405 | 406 | // Cancel the readSource. 407 | // We do this here because the readSource's eventBlock has retained self. 408 | // In other words, if we don't cancel the readSource, we will never get deallocated. 409 | 410 | [self cancelReadSource]; 411 | }); 412 | } 413 | } 414 | 415 | - (void)dealloc 416 | { 417 | HTTPLogTrace(); 418 | 419 | #if NEEDS_DISPATCH_RETAIN_RELEASE 420 | if (readQueue) dispatch_release(readQueue); 421 | #endif 422 | 423 | if (readBuffer) 424 | free(readBuffer); 425 | } 426 | 427 | @end 428 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Responses/CHSHTTPDataResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "CHSHTTPResponse.h" 3 | 4 | 5 | @interface CHSHTTPDataResponse : NSObject 6 | { 7 | NSUInteger offset; 8 | NSData *data; 9 | } 10 | 11 | - (id)initWithData:(NSData *)data; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Responses/CHSHTTPDataResponse.m: -------------------------------------------------------------------------------- 1 | #import "CHSHTTPDataResponse.h" 2 | #import "CHSHTTPLogging.h" 3 | 4 | #if ! __has_feature(objc_arc) 5 | #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). 6 | #endif 7 | 8 | @implementation CHSHTTPDataResponse 9 | 10 | - (id)initWithData:(NSData *)dataParam 11 | { 12 | if((self = [super init])) 13 | { 14 | HTTPLogTrace(); 15 | 16 | offset = 0; 17 | data = dataParam; 18 | } 19 | return self; 20 | } 21 | 22 | - (void)dealloc 23 | { 24 | HTTPLogTrace(); 25 | 26 | } 27 | 28 | - (UInt64)contentLength 29 | { 30 | UInt64 result = (UInt64)[data length]; 31 | 32 | HTTPLogTrace2(@"%@[%p]: contentLength - %llu", THIS_FILE, self, result); 33 | 34 | return result; 35 | } 36 | 37 | - (UInt64)offset 38 | { 39 | HTTPLogTrace(); 40 | 41 | return offset; 42 | } 43 | 44 | - (void)setOffset:(UInt64)offsetParam 45 | { 46 | HTTPLogTrace2(@"%@[%p]: setOffset:%lu", THIS_FILE, self, (unsigned long)offset); 47 | 48 | offset = (NSUInteger)offsetParam; 49 | } 50 | 51 | - (NSData *)readDataOfLength:(NSUInteger)lengthParameter 52 | { 53 | HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)lengthParameter); 54 | 55 | NSUInteger remaining = [data length] - offset; 56 | NSUInteger length = lengthParameter < remaining ? lengthParameter : remaining; 57 | 58 | void *bytes = (void *)([data bytes] + offset); 59 | 60 | offset += length; 61 | 62 | return [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:NO]; 63 | } 64 | 65 | - (BOOL)isDone 66 | { 67 | BOOL result = (offset == [data length]); 68 | 69 | HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO")); 70 | 71 | return result; 72 | } 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Responses/CHSHTTPDynamicFileResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "CHSHTTPResponse.h" 3 | #import "CHSHTTPAsyncFileResponse.h" 4 | 5 | /** 6 | * This class is designed to assist with dynamic content. 7 | * Imagine you have a file that you want to make dynamic: 8 | * 9 | * 10 | * 11 | *

ComputerName Control Panel

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

    %%ComputerName%% Control Panel

    25 | * ... 26 | *
  • System Time: %%SysTime%%
  • 27 | * 28 | * And then you create an instance of this class with: 29 | * 30 | * - separator = @"%%" 31 | * - replacementDictionary = { "ComputerName"="Black MacBook", "SysTime"="2010-04-30 03:18:24" } 32 | * 33 | * This class will then perform the replacements for you, on the fly, as it reads the file data. 34 | * This class is also asynchronous, so it will perform the file IO using its own GCD queue. 35 | * 36 | * All keys for the replacementDictionary must be NSString's. 37 | * Values for the replacementDictionary may be NSString's, or any object that 38 | * returns what you want when its description method is invoked. 39 | **/ 40 | 41 | @interface CHSHTTPDynamicFileResponse : CHSHTTPAsyncFileResponse 42 | { 43 | NSData *separator; 44 | NSDictionary *replacementDict; 45 | } 46 | 47 | - (id)initWithFilePath:(NSString *)filePath 48 | forConnection:(CHSHTTPConnection *)connection 49 | separator:(NSString *)separatorStr 50 | replacementDictionary:(NSDictionary *)dictionary; 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Responses/CHSHTTPDynamicFileResponse.m: -------------------------------------------------------------------------------- 1 | #import "CHSHTTPDynamicFileResponse.h" 2 | #import "CHSHTTPConnection.h" 3 | #import "CHSHTTPLogging.h" 4 | 5 | #if ! __has_feature(objc_arc) 6 | #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). 7 | #endif 8 | 9 | #define NULL_FD -1 10 | 11 | 12 | @implementation CHSHTTPDynamicFileResponse 13 | 14 | - (id)initWithFilePath:(NSString *)fpath 15 | forConnection:(CHSHTTPConnection *)parent 16 | separator:(NSString *)separatorStr 17 | replacementDictionary:(NSDictionary *)dict 18 | { 19 | if ((self = [super initWithFilePath:fpath forConnection:parent])) 20 | { 21 | HTTPLogTrace(); 22 | 23 | separator = [separatorStr dataUsingEncoding:NSUTF8StringEncoding]; 24 | replacementDict = dict; 25 | } 26 | return self; 27 | } 28 | 29 | - (BOOL)isChunked 30 | { 31 | HTTPLogTrace(); 32 | 33 | return YES; 34 | } 35 | 36 | - (UInt64)contentLength 37 | { 38 | // This method shouldn't be called since we're using a chunked response. 39 | // We override it just to be safe. 40 | 41 | HTTPLogTrace(); 42 | 43 | return 0; 44 | } 45 | 46 | - (void)setOffset:(UInt64)offset 47 | { 48 | // This method shouldn't be called since we're using a chunked response. 49 | // We override it just to be safe. 50 | 51 | HTTPLogTrace(); 52 | } 53 | 54 | - (BOOL)isDone 55 | { 56 | BOOL result = (readOffset == fileLength) && (readBufferOffset == 0); 57 | 58 | HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO")); 59 | 60 | return result; 61 | } 62 | 63 | - (void)processReadBuffer 64 | { 65 | HTTPLogTrace(); 66 | 67 | // At this point, the readBuffer has readBufferOffset bytes available. 68 | // This method is in charge of updating the readBufferOffset. 69 | 70 | NSUInteger bufLen = readBufferOffset; 71 | NSUInteger sepLen = [separator length]; 72 | 73 | // We're going to start looking for the separator at the beginning of the buffer, 74 | // and stop when we get to the point where the separator would no longer fit in the buffer. 75 | 76 | NSUInteger offset = 0; 77 | NSUInteger stopOffset = (bufLen > sepLen) ? bufLen - sepLen + 1 : 0; 78 | 79 | // In order to do the replacement, we need to find the starting and ending separator. 80 | // For example: 81 | // 82 | // %%USER_NAME%% 83 | // 84 | // Where "%%" is the separator. 85 | 86 | BOOL found1 = NO; 87 | BOOL found2 = NO; 88 | 89 | NSUInteger s1 = 0; 90 | NSUInteger s2 = 0; 91 | 92 | const void *sep = [separator bytes]; 93 | 94 | while (offset < stopOffset) 95 | { 96 | const void *subBuffer = readBuffer + offset; 97 | 98 | if (memcmp(subBuffer, sep, sepLen) == 0) 99 | { 100 | if (!found1) 101 | { 102 | // Found the first separator 103 | 104 | found1 = YES; 105 | s1 = offset; 106 | offset += sepLen; 107 | 108 | HTTPLogVerbose(@"%@[%p]: Found s1 at %lu", THIS_FILE, self, (unsigned long)s1); 109 | } 110 | else 111 | { 112 | // Found the second separator 113 | 114 | found2 = YES; 115 | s2 = offset; 116 | offset += sepLen; 117 | 118 | HTTPLogVerbose(@"%@[%p]: Found s2 at %lu", THIS_FILE, self, (unsigned long)s2); 119 | } 120 | 121 | if (found1 && found2) 122 | { 123 | // We found our separators. 124 | // Now extract the string between the two separators. 125 | 126 | NSRange fullRange = NSMakeRange(s1, (s2 - s1 + sepLen)); 127 | NSRange strRange = NSMakeRange(s1 + sepLen, (s2 - s1 - sepLen)); 128 | 129 | // Wish we could use the simple subdataWithRange method. 130 | // But that method copies the bytes... 131 | // So for performance reasons, we need to use the methods that don't copy the bytes. 132 | 133 | void *strBuf = readBuffer + strRange.location; 134 | NSUInteger strLen = strRange.length; 135 | 136 | NSString *key = [[NSString alloc] initWithBytes:strBuf length:strLen encoding:NSUTF8StringEncoding]; 137 | if (key) 138 | { 139 | // Is there a given replacement for this key? 140 | 141 | id value = [replacementDict objectForKey:key]; 142 | if (value) 143 | { 144 | // Found the replacement value. 145 | // Now perform the replacement in the buffer. 146 | 147 | HTTPLogVerbose(@"%@[%p]: key(%@) -> value(%@)", THIS_FILE, self, key, value); 148 | 149 | NSData *v = [[value description] dataUsingEncoding:NSUTF8StringEncoding]; 150 | NSUInteger vLength = [v length]; 151 | 152 | if (fullRange.length == vLength) 153 | { 154 | // Replacement is exactly the same size as what it is replacing 155 | 156 | // memcpy(void *restrict dst, const void *restrict src, size_t n); 157 | 158 | memcpy(readBuffer + fullRange.location, [v bytes], vLength); 159 | } 160 | else // (fullRange.length != vLength) 161 | { 162 | NSInteger diff = (NSInteger)vLength - (NSInteger)fullRange.length; 163 | 164 | if (diff > 0) 165 | { 166 | // Replacement is bigger than what it is replacing. 167 | // Make sure there is room in the buffer for the replacement. 168 | 169 | if (diff > (readBufferSize - bufLen)) 170 | { 171 | NSUInteger inc = MAX(diff, 256); 172 | 173 | readBufferSize += inc; 174 | readBuffer = reallocf(readBuffer, readBufferSize); 175 | } 176 | } 177 | 178 | // Move the data that comes after the replacement. 179 | // 180 | // If replacement is smaller than what it is replacing, 181 | // then we are shifting the data toward the beginning of the buffer. 182 | // 183 | // If replacement is bigger than what it is replacing, 184 | // then we are shifting the data toward the end of the buffer. 185 | // 186 | // memmove(void *dst, const void *src, size_t n); 187 | // 188 | // The memmove() function copies n bytes from src to dst. 189 | // The two areas may overlap; the copy is always done in a non-destructive manner. 190 | 191 | void *src = readBuffer + fullRange.location + fullRange.length; 192 | void *dst = readBuffer + fullRange.location + vLength; 193 | 194 | NSUInteger remaining = bufLen - (fullRange.location + fullRange.length); 195 | 196 | memmove(dst, src, remaining); 197 | 198 | // Now copy the replacement into its location. 199 | // 200 | // memcpy(void *restrict dst, const void *restrict src, size_t n) 201 | // 202 | // The memcpy() function copies n bytes from src to dst. 203 | // If the two areas overlap, behavior is undefined. 204 | 205 | memcpy(readBuffer + fullRange.location, [v bytes], vLength); 206 | 207 | // And don't forget to update our indices. 208 | 209 | bufLen += diff; 210 | offset += diff; 211 | stopOffset += diff; 212 | } 213 | } 214 | 215 | } 216 | 217 | found1 = found2 = NO; 218 | } 219 | } 220 | else 221 | { 222 | offset++; 223 | } 224 | } 225 | 226 | // We've gone through our buffer now, and performed all the replacements that we could. 227 | // It's now time to update the amount of available data we have. 228 | 229 | if (readOffset == fileLength) 230 | { 231 | // We've read in the entire file. 232 | // So there can be no more replacements. 233 | 234 | data = [[NSData alloc] initWithBytes:readBuffer length:bufLen]; 235 | readBufferOffset = 0; 236 | } 237 | else 238 | { 239 | // There are a couple different situations that we need to take into account here. 240 | // 241 | // Imagine the following file: 242 | // My name is %%USER_NAME%% 243 | // 244 | // Situation 1: 245 | // The first chunk of data we read was "My name is %%". 246 | // So we found the first separator, but not the second. 247 | // In this case we can only return the data that precedes the first separator. 248 | // 249 | // Situation 2: 250 | // The first chunk of data we read was "My name is %". 251 | // So we didn't find any separators, but part of a separator may be included in our buffer. 252 | 253 | NSUInteger available; 254 | if (found1) 255 | { 256 | // Situation 1 257 | available = s1; 258 | } 259 | else 260 | { 261 | // Situation 2 262 | available = stopOffset; 263 | } 264 | 265 | // Copy available data 266 | 267 | data = [[NSData alloc] initWithBytes:readBuffer length:available]; 268 | 269 | // Remove the copied data from the buffer. 270 | // We do this by shifting the remaining data toward the beginning of the buffer. 271 | 272 | NSUInteger remaining = bufLen - available; 273 | 274 | memmove(readBuffer, readBuffer + available, remaining); 275 | readBufferOffset = remaining; 276 | } 277 | 278 | [connection responseHasAvailableData:self]; 279 | } 280 | 281 | - (void)dealloc 282 | { 283 | HTTPLogTrace(); 284 | 285 | 286 | } 287 | 288 | @end 289 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Responses/CHSHTTPFileResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "CHSHTTPResponse.h" 3 | 4 | @class CHSHTTPConnection; 5 | 6 | 7 | @interface CHSHTTPFileResponse : NSObject 8 | { 9 | CHSHTTPConnection *connection; 10 | 11 | NSString *filePath; 12 | UInt64 fileLength; 13 | UInt64 fileOffset; 14 | 15 | BOOL aborted; 16 | 17 | int fileFD; 18 | void *buffer; 19 | NSUInteger bufferSize; 20 | } 21 | 22 | - (id)initWithFilePath:(NSString *)filePath forConnection:(CHSHTTPConnection *)connection; 23 | - (NSString *)filePath; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Responses/CHSHTTPFileResponse.m: -------------------------------------------------------------------------------- 1 | #import "CHSHTTPFileResponse.h" 2 | #import "CHSHTTPConnection.h" 3 | #import "CHSHTTPLogging.h" 4 | 5 | #import 6 | #import 7 | 8 | #if ! __has_feature(objc_arc) 9 | #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). 10 | #endif 11 | 12 | #define NULL_FD -1 13 | 14 | 15 | @implementation CHSHTTPFileResponse 16 | 17 | - (id)initWithFilePath:(NSString *)fpath forConnection:(CHSHTTPConnection *)parent 18 | { 19 | if((self = [super init])) 20 | { 21 | HTTPLogTrace(); 22 | 23 | connection = parent; // Parents retain children, children do NOT retain parents 24 | 25 | fileFD = NULL_FD; 26 | filePath = [[fpath copy] stringByResolvingSymlinksInPath]; 27 | if (filePath == nil) 28 | { 29 | HTTPLogWarn(@"%@: Init failed - Nil filePath", THIS_FILE); 30 | 31 | return nil; 32 | } 33 | 34 | NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; 35 | if (fileAttributes == nil) 36 | { 37 | HTTPLogWarn(@"%@: Init failed - Unable to get file attributes. filePath: %@", THIS_FILE, filePath); 38 | 39 | return nil; 40 | } 41 | 42 | fileLength = (UInt64)[[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue]; 43 | fileOffset = 0; 44 | 45 | aborted = NO; 46 | 47 | // We don't bother opening the file here. 48 | // If this is a HEAD request we only need to know the fileLength. 49 | } 50 | return self; 51 | } 52 | 53 | - (void)abort 54 | { 55 | HTTPLogTrace(); 56 | 57 | [connection responseDidAbort:self]; 58 | aborted = YES; 59 | } 60 | 61 | - (BOOL)openFile 62 | { 63 | HTTPLogTrace(); 64 | 65 | fileFD = open([filePath UTF8String], O_RDONLY); 66 | if (fileFD == NULL_FD) 67 | { 68 | HTTPLogError(@"%@[%p]: Unable to open file. filePath: %@", THIS_FILE, self, filePath); 69 | 70 | [self abort]; 71 | return NO; 72 | } 73 | 74 | HTTPLogVerbose(@"%@[%p]: Open fd[%i] -> %@", THIS_FILE, self, fileFD, filePath); 75 | 76 | return YES; 77 | } 78 | 79 | - (BOOL)openFileIfNeeded 80 | { 81 | if (aborted) 82 | { 83 | // The file operation has been aborted. 84 | // This could be because we failed to open the file, 85 | // or the reading process failed. 86 | return NO; 87 | } 88 | 89 | if (fileFD != NULL_FD) 90 | { 91 | // File has already been opened. 92 | return YES; 93 | } 94 | 95 | return [self openFile]; 96 | } 97 | 98 | - (UInt64)contentLength 99 | { 100 | HTTPLogTrace(); 101 | 102 | return fileLength; 103 | } 104 | 105 | - (UInt64)offset 106 | { 107 | HTTPLogTrace(); 108 | 109 | return fileOffset; 110 | } 111 | 112 | - (void)setOffset:(UInt64)offset 113 | { 114 | HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset); 115 | 116 | if (![self openFileIfNeeded]) 117 | { 118 | // File opening failed, 119 | // or response has been aborted due to another error. 120 | return; 121 | } 122 | 123 | fileOffset = offset; 124 | 125 | off_t result = lseek(fileFD, (off_t)offset, SEEK_SET); 126 | if (result == -1) 127 | { 128 | HTTPLogError(@"%@[%p]: lseek failed - errno(%i) filePath(%@)", THIS_FILE, self, errno, filePath); 129 | 130 | [self abort]; 131 | } 132 | } 133 | 134 | - (NSData *)readDataOfLength:(NSUInteger)length 135 | { 136 | HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)length); 137 | 138 | if (![self openFileIfNeeded]) 139 | { 140 | // File opening failed, 141 | // or response has been aborted due to another error. 142 | return nil; 143 | } 144 | 145 | // Determine how much data we should read. 146 | // 147 | // It is OK if we ask to read more bytes than exist in the file. 148 | // It is NOT OK to over-allocate the buffer. 149 | 150 | UInt64 bytesLeftInFile = fileLength - fileOffset; 151 | 152 | NSUInteger bytesToRead = (NSUInteger)MIN(length, bytesLeftInFile); 153 | 154 | // Make sure buffer is big enough for read request. 155 | // Do not over-allocate. 156 | 157 | if (buffer == NULL || bufferSize < bytesToRead) 158 | { 159 | bufferSize = bytesToRead; 160 | buffer = reallocf(buffer, (size_t)bufferSize); 161 | 162 | if (buffer == NULL) 163 | { 164 | HTTPLogError(@"%@[%p]: Unable to allocate buffer", THIS_FILE, self); 165 | 166 | [self abort]; 167 | return nil; 168 | } 169 | } 170 | 171 | // Perform the read 172 | 173 | HTTPLogVerbose(@"%@[%p]: Attempting to read %lu bytes from file", THIS_FILE, self, (unsigned long)bytesToRead); 174 | 175 | ssize_t result = read(fileFD, buffer, bytesToRead); 176 | 177 | // Check the results 178 | 179 | if (result < 0) 180 | { 181 | HTTPLogError(@"%@: Error(%i) reading file(%@)", THIS_FILE, errno, filePath); 182 | 183 | [self abort]; 184 | return nil; 185 | } 186 | else if (result == 0) 187 | { 188 | HTTPLogError(@"%@: Read EOF on file(%@)", THIS_FILE, filePath); 189 | 190 | [self abort]; 191 | return nil; 192 | } 193 | else // (result > 0) 194 | { 195 | HTTPLogVerbose(@"%@[%p]: Read %ld bytes from file", THIS_FILE, self, (long)result); 196 | 197 | fileOffset += result; 198 | 199 | return [NSData dataWithBytes:buffer length:result]; 200 | } 201 | } 202 | 203 | - (BOOL)isDone 204 | { 205 | BOOL result = (fileOffset == fileLength); 206 | 207 | HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO")); 208 | 209 | return result; 210 | } 211 | 212 | - (NSString *)filePath 213 | { 214 | return filePath; 215 | } 216 | 217 | - (void)dealloc 218 | { 219 | HTTPLogTrace(); 220 | 221 | if (fileFD != NULL_FD) 222 | { 223 | HTTPLogVerbose(@"%@[%p]: Close fd[%i]", THIS_FILE, self, fileFD); 224 | 225 | close(fileFD); 226 | } 227 | 228 | if (buffer) 229 | free(buffer); 230 | 231 | } 232 | 233 | @end 234 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Responses/CHSHTTPRedirectResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "CHSHTTPResponse.h" 3 | 4 | 5 | @interface CHSHTTPRedirectResponse : NSObject 6 | { 7 | NSString *redirectPath; 8 | } 9 | 10 | - (id)initWithPath:(NSString *)redirectPath; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /Classes/CocoaHttpServer/Responses/CHSHTTPRedirectResponse.m: -------------------------------------------------------------------------------- 1 | #import "CHSHTTPRedirectResponse.h" 2 | #import "CHSHTTPLogging.h" 3 | 4 | #if ! __has_feature(objc_arc) 5 | #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). 6 | #endif 7 | 8 | @implementation CHSHTTPRedirectResponse 9 | 10 | - (id)initWithPath:(NSString *)path 11 | { 12 | if ((self = [super init])) 13 | { 14 | HTTPLogTrace(); 15 | 16 | redirectPath = [path copy]; 17 | } 18 | return self; 19 | } 20 | 21 | - (UInt64)contentLength 22 | { 23 | return 0; 24 | } 25 | 26 | - (UInt64)offset 27 | { 28 | return 0; 29 | } 30 | 31 | - (void)setOffset:(UInt64)offset 32 | { 33 | // Nothing to do 34 | } 35 | 36 | - (NSData *)readDataOfLength:(NSUInteger)length 37 | { 38 | HTTPLogTrace(); 39 | 40 | return nil; 41 | } 42 | 43 | - (BOOL)isDone 44 | { 45 | return YES; 46 | } 47 | 48 | - (NSDictionary *)httpHeaders 49 | { 50 | HTTPLogTrace(); 51 | 52 | return [NSDictionary dictionaryWithObject:redirectPath forKey:@"Location"]; 53 | } 54 | 55 | - (NSInteger)status 56 | { 57 | HTTPLogTrace(); 58 | 59 | return 302; 60 | } 61 | 62 | - (void)dealloc 63 | { 64 | HTTPLogTrace(); 65 | 66 | } 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /Classes/PBMHTMLBuilder.h: -------------------------------------------------------------------------------- 1 | // 2 | // PBMHTMLBuilder.h 3 | // PleaseBaoMe 4 | // 5 | // Created by why on 7/7/15. 6 | // Copyright (c) 2015 why. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PBMHTMLBuilder : NSObject 12 | 13 | /** 14 | * Build Table Name List HTML 15 | * 16 | * @param tables @[@"table1", @"table2"] 17 | * 18 | * @return \
  • %@
  • 19 | */ 20 | +(NSString *)getTableListWithTables:(NSArray*)tables; 21 | 22 | 23 | /** 24 | * Build table content 25 | * 26 | * @param headers @[@"table1", @"table2"] 27 | * @param dictionary @{@"table1":@[@"1", @"2"], @"table2":@[@"3", @"4"]} 28 | * 29 | * @return 30 | */ 31 | +(NSString *)getTableContentWithHeaders:(NSArray*)headers andContent:(NSDictionary*)dictionary; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /Classes/PBMHTMLBuilder.m: -------------------------------------------------------------------------------- 1 | // 2 | // PBMHTMLBuilder.m 3 | // PleaseBaoMe 4 | // 5 | // Created by why on 7/7/15. 6 | // Copyright (c) 2015 why. All rights reserved. 7 | // 8 | 9 | #import "PBMHTMLBuilder.h" 10 | 11 | @implementation PBMHTMLBuilder 12 | 13 | +(NSString*)getTableListWithTables:(NSArray*)tables { 14 | NSMutableString *result = [NSMutableString string]; 15 | for (NSString *tableName in tables) { 16 | [result appendFormat:@"
  • %@
  • ", tableName, tableName]; 17 | } 18 | return result; 19 | } 20 | 21 | +(NSString *)getTableContentWithHeaders:(NSArray *)headers andContent:(NSDictionary *)dictionary { 22 | if (dictionary.allValues.count < 1) { 23 | return @""; 24 | } 25 | NSArray *fisrtArray = dictionary.allValues[0]; 26 | NSInteger lines = fisrtArray.count; 27 | NSMutableString *pageSource = [NSMutableString string]; 28 | 29 | // build table header 30 | [pageSource appendFormat:@""]; 31 | for (NSString *key in headers) { 32 | [pageSource appendFormat:@"%@", key]; 33 | } 34 | [pageSource appendFormat:@""]; 35 | 36 | // build table 37 | [pageSource appendFormat:@""]; 38 | for (int i = 0; i < lines; i++) { 39 | [pageSource appendFormat:@""]; 40 | for (int j = 0; j < headers.count; j++) { 41 | NSString *colName = headers[j]; 42 | NSArray *tempArray = dictionary[colName]; 43 | [pageSource appendFormat:@"%@", tempArray[i]]; 44 | } 45 | [pageSource appendFormat:@""]; 46 | } 47 | [pageSource appendFormat:@""]; 48 | return pageSource; 49 | } 50 | 51 | 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /Classes/PBMHTTPConnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // PBMHTTPConnection.h 3 | // PleaseBaoMe 4 | // 5 | // Created by why on 7/7/15. 6 | // Copyright (c) 2015 why. All rights reserved. 7 | // 8 | 9 | #import "CHSHTTPConnection.h" 10 | 11 | @interface PBMHTTPConnection : CHSHTTPConnection 12 | 13 | /** 14 | * Set the Database file path. 15 | * 16 | * @param path db file path. 17 | */ 18 | +(void)setDBFilePath:(NSString*)path; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Classes/PBMHTTPConnection.m: -------------------------------------------------------------------------------- 1 | // 2 | // PBMHTTPConnection.m 3 | // PleaseBaoMe 4 | // 5 | // Created by why on 7/7/15. 6 | // Copyright (c) 2015 why. All rights reserved. 7 | // 8 | 9 | 10 | 11 | #import 12 | #import "CHSHTTPDynamicFileResponse.h" 13 | #import "PBMHTTPConnection.h" 14 | #import "PBMHTMLBuilder.h" 15 | 16 | 17 | static NSString *kDBPath; 18 | 19 | @implementation PBMHTTPConnection 20 | #pragma mark - HTTPConnection Override 21 | - (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path { 22 | NSString *sqlStr = [[path lastPathComponent] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 23 | 24 | // handle normal request without spaces 25 | if ([sqlStr componentsSeparatedByString:@" "].count == 1 && sqlStr.length > 1) { 26 | return [super httpResponseForMethod:method URI:path]; 27 | } 28 | 29 | // if database file not exist 30 | if (![[NSFileManager defaultManager] fileExistsAtPath:kDBPath]) { 31 | NSLog(@"DB Error: DB file not exist!"); 32 | return nil; 33 | } 34 | 35 | // open database 36 | FMDatabase *db = [FMDatabase databaseWithPath:kDBPath]; 37 | if (![db open]) { 38 | return nil; 39 | } 40 | 41 | // table name list 42 | NSArray *tableNames = [self htmlForTableNameListWithDB:db]; 43 | NSString *tableListHTML = [PBMHTMLBuilder getTableListWithTables:tableNames]; 44 | 45 | // current table name 46 | NSString *tableName = @"Select Table"; 47 | NSArray *sqlWords = [sqlStr componentsSeparatedByString:@" "]; 48 | for (int i = 0; i < sqlWords.count; i++) { 49 | NSString *word = sqlWords[i]; 50 | if ([word.lowercaseString isEqualToString:@"from"]) { 51 | tableName = sqlWords[++i]; 52 | break; 53 | } 54 | } 55 | 56 | // table content 57 | NSString *tableContentHTML = [self getTableContentWithDB:db andSQL:sqlStr]; 58 | 59 | // close database 60 | [db close]; 61 | 62 | return [[CHSHTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:@"/index.html"] 63 | forConnection:self 64 | separator:@"%%" 65 | replacementDictionary:@{ @"SQL_QUERY": sqlStr, 66 | @"TABLE_NAME": tableName, 67 | @"TABLE_NAME_LIST": tableListHTML, 68 | @"PLEASE_BAO_ME": tableContentHTML 69 | }]; 70 | 71 | return nil; 72 | } 73 | 74 | 75 | #pragma mark - Private 76 | -(NSArray*)htmlForTableNameListWithDB:(FMDatabase*)db { 77 | FMResultSet *s = [db executeQuery:@"SELECT name FROM sqlite_master WHERE type = 'table'"]; 78 | NSMutableArray *tableNameArray = [NSMutableArray array]; 79 | while ([s next]) { 80 | [tableNameArray addObject:[s stringForColumnIndex:0]]; 81 | } 82 | return tableNameArray; 83 | } 84 | 85 | 86 | -(NSString*)getTableContentWithDB:(FMDatabase*)db andSQL:(NSString*)sql { 87 | FMResultSet *s = [db executeQuery:sql]; 88 | NSMutableDictionary *resultDic = [NSMutableDictionary dictionary]; 89 | NSMutableOrderedSet *cols = [NSMutableOrderedSet orderedSet]; 90 | while ([s next]) { 91 | for (int i = 0; i < s.columnCount; i++) { 92 | NSString *colName = [s columnNameForIndex:i]; 93 | NSString *value = [NSString stringWithFormat:@"%@", [s objectForColumnIndex:i]]; 94 | NSMutableArray *array = [NSMutableArray arrayWithArray:resultDic[colName]]; 95 | [array addObject:value]; 96 | resultDic[colName] = array; 97 | [cols addObject:colName]; 98 | } 99 | } 100 | return [PBMHTMLBuilder getTableContentWithHeaders:[cols array] andContent:resultDic]; 101 | } 102 | 103 | 104 | #pragma mark - Public 105 | +(void)setDBFilePath:(NSString*)path { 106 | kDBPath = path; 107 | } 108 | @end 109 | -------------------------------------------------------------------------------- /Classes/PBMIPTool.h: -------------------------------------------------------------------------------- 1 | // 2 | // PBMIPTool.h 3 | // PleaseBaoMe 4 | // 5 | // Created by why on 7/7/15. 6 | // Copyright (c) 2015 why. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PBMIPTool : NSObject 12 | 13 | +(NSString *)IPAddress; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Classes/PBMIPTool.m: -------------------------------------------------------------------------------- 1 | // 2 | // PBMIPTool.m 3 | // PleaseBaoMe 4 | // 5 | // Created by why on 7/7/15. 6 | // Copyright (c) 2015 why. All rights reserved. 7 | // 8 | 9 | #import "PBMIPTool.h" 10 | #import 11 | #import 12 | 13 | 14 | @implementation PBMIPTool 15 | // Get IP Address 16 | + (NSString *)IPAddress { 17 | NSString *address = @"error"; 18 | struct ifaddrs *interfaces = NULL; 19 | struct ifaddrs *temp_addr = NULL; 20 | int success = 0; 21 | // retrieve the current interfaces - returns 0 on success 22 | success = getifaddrs(&interfaces); 23 | if (success == 0) { 24 | // Loop through linked list of interfaces 25 | temp_addr = interfaces; 26 | while(temp_addr != NULL) { 27 | if(temp_addr->ifa_addr->sa_family == AF_INET) { 28 | // Check if interface is en0 which is the wifi connection on the iPhone 29 | if([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) { 30 | // Get NSString from C String 31 | address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)]; 32 | } 33 | } 34 | temp_addr = temp_addr->ifa_next; 35 | } 36 | } 37 | // Free memory 38 | freeifaddrs(interfaces); 39 | return address; 40 | 41 | } 42 | @end 43 | -------------------------------------------------------------------------------- /Classes/PBMTool.h: -------------------------------------------------------------------------------- 1 | // 2 | // PBMTool.h 3 | // PleaseBaoMe 4 | // 5 | // Created by why on 7/8/15. 6 | // Copyright (c) 2015 why. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface PBMTool : NSObject 13 | 14 | +(void)start; 15 | 16 | +(void)setDBFilePath:(NSString*)path; 17 | 18 | +(NSString*)URL; 19 | @end 20 | -------------------------------------------------------------------------------- /Classes/PBMTool.m: -------------------------------------------------------------------------------- 1 | // 2 | // PBMTool.m 3 | // PleaseBaoMe 4 | // 5 | // Created by why on 7/8/15. 6 | // Copyright (c) 2015 why. All rights reserved. 7 | // 8 | 9 | #import "CHSHTTPServer.h" 10 | 11 | #import "PBMTool.h" 12 | #import "PBMHTTPConnection.h" 13 | #import "PBMIPTool.h" 14 | 15 | @interface PBMTool() 16 | @property (nonatomic, strong) CHSHTTPServer *httpServer; 17 | @end 18 | 19 | @implementation PBMTool 20 | 21 | + (PBMTool *)sharedInstance { 22 | static PBMTool *sharedPBMTool = nil; 23 | static dispatch_once_t onceToken; 24 | dispatch_once(&onceToken, ^{ 25 | @synchronized(self){ 26 | sharedPBMTool = [[self alloc] init]; 27 | } 28 | }); 29 | return sharedPBMTool; 30 | } 31 | 32 | +(void)start { 33 | [[self sharedInstance] start]; 34 | } 35 | 36 | +(void)setDBFilePath:(NSString*)path { 37 | [PBMHTTPConnection setDBFilePath:path]; 38 | } 39 | 40 | +(NSString*)URL { 41 | return [NSString stringWithFormat:@"%@:%hu", [PBMIPTool IPAddress], [[PBMTool sharedInstance].httpServer listeningPort]]; 42 | } 43 | 44 | -(void)start { 45 | if (_httpServer) { 46 | return; 47 | } 48 | _httpServer = [[CHSHTTPServer alloc] init]; 49 | [_httpServer setConnectionClass:[PBMHTTPConnection class]]; 50 | [_httpServer setType:@"_http._tcp."]; 51 | [_httpServer setPort:12345]; 52 | NSString *webPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Web.bundle"]; 53 | [_httpServer setDocumentRoot:webPath]; 54 | 55 | NSError *error; 56 | if([_httpServer start:&error]) { 57 | NSLog(@"You can see you SQLite in %@", [PBMTool URL]); 58 | } else { 59 | NSLog(@"Error starting HTTP Server: %@", error); 60 | } 61 | 62 | } 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /Classes/Web.bundle/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app-name", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "foundation": "*", 6 | "jquery-ui": "*" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Classes/Web.bundle/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | PleaseBaoMe 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 32 | 33 | 52 | 53 | 54 | 55 | 56 | 81 | 82 |

    83 | %%SQL_QUERY%% 84 |

    85 | 86 |
    87 | 88 | %%PLEASE_BAO_ME%% 89 |
    90 |
    91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 callmewhy 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. 22 | 23 | -------------------------------------------------------------------------------- /PleaseBaoMe.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "PleaseBaoMe" 3 | s.version = "1.1" 4 | s.summary = "A useful tool to view SQLite file in Web browser during app runningprocedure." 5 | s.description = <<-DESC 6 | You can input your SQL query in the URI. And you can set TableName、 LIMIT、 OFFSET in navigation bar easily, too. 7 | DESC 8 | s.homepage = "https://github.com/callmewhy/PleaseBaoMe" 9 | 10 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 11 | s.license = { :type => "MIT", :file => "LICENSE" } 12 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 13 | s.author = { "callmewhy" => "https://github.com/callmewhy" } 14 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 15 | s.platform = :ios, "7.0" 16 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 17 | s.source = { :git => "https://github.com/callmewhy/PleaseBaoMe.git", :tag => '1.1'} 18 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 19 | s.source_files = "Classes/**/*.{h,m}" 20 | # ――― Dependency ―――――――――――――――――――――――――――-――――――――――――――――――――――――――――――――――― # 21 | s.dependencies = { 22 | 'FMDB' => '~> 2' 23 | } 24 | # ――― Dependency ―――――――――――――――――――――――――――-――――――――――――――――――――――――――――――――――― # 25 | s.resource = "Classes/Web.bundle" 26 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 27 | s.requires_arc = true 28 | end -------------------------------------------------------------------------------- /PleaseBaoMe.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PleaseBaoMe.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /PleaseBaoMe/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // PleaseBaoMe 4 | // 5 | // Created by why on 7/7/15. 6 | // Copyright (c) 2015 why. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /PleaseBaoMe/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // PleaseBaoMe 4 | // 5 | // Created by why on 7/7/15. 6 | // Copyright (c) 2015 why. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "PBMTool.h" 11 | 12 | @interface AppDelegate () 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | [PBMTool start]; 21 | 22 | return YES; 23 | } 24 | 25 | - (void)applicationWillResignActive:(UIApplication *)application { 26 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 27 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 28 | } 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | - (void)applicationWillEnterForeground:(UIApplication *)application { 36 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 37 | } 38 | 39 | - (void)applicationDidBecomeActive:(UIApplication *)application { 40 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 41 | } 42 | 43 | - (void)applicationWillTerminate:(UIApplication *)application { 44 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /PleaseBaoMe/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /PleaseBaoMe/Base.lproj/Main.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 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /PleaseBaoMe/Demo/PBMDemoTool.h: -------------------------------------------------------------------------------- 1 | // 2 | // PBMDemoTool.h 3 | // PleaseBaoMe 4 | // 5 | // Created by why on 7/7/15. 6 | // Copyright (c) 2015 why. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "PBMTool.h" 11 | 12 | @interface PBMDemoTool : NSObject 13 | 14 | // make fake data 15 | +(void)setupDemoData; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /PleaseBaoMe/Demo/PBMDemoTool.m: -------------------------------------------------------------------------------- 1 | // 2 | // PBMDemoTool.m 3 | // PleaseBaoMe 4 | // 5 | // Created by why on 7/7/15. 6 | // Copyright (c) 2015 why. All rights reserved. 7 | // 8 | 9 | #import "PBMDemoTool.h" 10 | #import "FMDB.h" 11 | 12 | @implementation PBMDemoTool 13 | 14 | +(void)setupDemoData { 15 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 16 | NSString *documentsDirectory = [paths objectAtIndex:0]; 17 | NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:@"demo.db"]; 18 | [PBMTool setDBFilePath:writableDBPath]; 19 | 20 | FMDatabase* db = [FMDatabase databaseWithPath:writableDBPath]; 21 | if (![db open]) { 22 | return; 23 | } 24 | 25 | NSMutableString *sql = [[NSMutableString alloc] initWithString:@"CREATE TABLE nothing (id integer primary key autoincrement, a, b, c, d, e);" 26 | "CREATE TABLE bao (id integer primary key autoincrement, name);" 27 | "INSERT INTO bao (name) VALUES ('bao');" 28 | "INSERT INTO bao (name) VALUES ('dmj');" 29 | "INSERT INTO bao (name) VALUES ('why');" 30 | "CREATE TABLE test_table (id integer primary key autoincrement, a TEXT, b INTEGER, c INTEGER, d NULL, e TEXT);" ]; 31 | 32 | NSString *randomString = @"INSERT INTO test_table (a, b, c, d, e) VALUES ('TEST', 2333, '%@', 'test_table', 'TEST');"; 33 | for (int i = 0; i < 100; i ++) { 34 | [sql appendFormat:randomString, [PBMDemoTool randomStringWithLength:arc4random() % 100]]; 35 | } 36 | [db executeStatements:sql]; 37 | [db close]; 38 | } 39 | 40 | NSString *letters = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 41 | 42 | +(NSString *)randomStringWithLength:(int)len { 43 | NSMutableString *randomString = [NSMutableString stringWithCapacity: len]; 44 | for (int i=0; i 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.callmewhy.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /PleaseBaoMe/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // PleaseBaoMe 4 | // 5 | // Created by why on 7/7/15. 6 | // Copyright (c) 2015 why. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /PleaseBaoMe/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // PleaseBaoMe 4 | // 5 | // Created by why on 7/7/15. 6 | // Copyright (c) 2015 why. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "PBMDemoTool.h" 11 | 12 | @interface ViewController () 13 | @property (weak, nonatomic) IBOutlet UILabel *tipLabel; 14 | 15 | @end 16 | 17 | @implementation ViewController 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | [PBMDemoTool setupDemoData]; 22 | _tipLabel.text = [PBMTool URL]; 23 | } 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /PleaseBaoMe/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // PleaseBaoMe 4 | // 5 | // Created by why on 7/7/15. 6 | // Copyright (c) 2015 why. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, ‘7.0’ 3 | 4 | target 'PleaseBaoMe' do 5 | pod 'FMDB' 6 | end 7 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - FMDB (2.5): 3 | - FMDB/standard (= 2.5) 4 | - FMDB/common (2.5) 5 | - FMDB/standard (2.5): 6 | - FMDB/common 7 | 8 | DEPENDENCIES: 9 | - FMDB 10 | 11 | SPEC CHECKSUMS: 12 | FMDB: 96e8f1bcc1329e269330f99770ad4285d9003e52 13 | 14 | COCOAPODS: 0.38.2 15 | -------------------------------------------------------------------------------- /Pods/FMDB/LICENSE.txt: -------------------------------------------------------------------------------- 1 | If you are using FMDB in your project, I'd love to hear about it. Let Gus know 2 | by sending an email to gus@flyingmeat.com. 3 | 4 | And if you happen to come across either Gus Mueller or Rob Ryan in a bar, you 5 | might consider purchasing a drink of their choosing if FMDB has been useful to 6 | you. 7 | 8 | Finally, and shortly, this is the MIT License. 9 | 10 | Copyright (c) 2008-2014 Flying Meat Inc. 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a copy 13 | of this software and associated documentation files (the "Software"), to deal 14 | in the Software without restriction, including without limitation the rights 15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | copies of the Software, and to permit persons to whom the Software is 17 | furnished to do so, subject to the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be included in 20 | all copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | THE SOFTWARE. -------------------------------------------------------------------------------- /Pods/FMDB/src/fmdb/FMDB.h: -------------------------------------------------------------------------------- 1 | #import "FMDatabase.h" 2 | #import "FMResultSet.h" 3 | #import "FMDatabaseAdditions.h" 4 | #import "FMDatabaseQueue.h" 5 | #import "FMDatabasePool.h" 6 | -------------------------------------------------------------------------------- /Pods/FMDB/src/fmdb/FMDatabaseAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabaseAdditions.h 3 | // fmdb 4 | // 5 | // Created by August Mueller on 10/30/05. 6 | // Copyright 2005 Flying Meat Inc.. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FMDatabase.h" 11 | 12 | 13 | /** Category of additions for `` class. 14 | 15 | ### See also 16 | 17 | - `` 18 | */ 19 | 20 | @interface FMDatabase (FMDatabaseAdditions) 21 | 22 | ///---------------------------------------- 23 | /// @name Return results of SQL to variable 24 | ///---------------------------------------- 25 | 26 | /** Return `int` value for query 27 | 28 | @param query The SQL query to be performed. 29 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 30 | 31 | @return `int` value. 32 | 33 | @note To use this method from Swift, you must include `FMDatabaseAdditionsVariadic.swift` in your project. 34 | */ 35 | 36 | - (int)intForQuery:(NSString*)query, ...; 37 | 38 | /** Return `long` value for query 39 | 40 | @param query The SQL query to be performed. 41 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 42 | 43 | @return `long` value. 44 | 45 | @note To use this method from Swift, you must include `FMDatabaseAdditionsVariadic.swift` in your project. 46 | */ 47 | 48 | - (long)longForQuery:(NSString*)query, ...; 49 | 50 | /** Return `BOOL` value for query 51 | 52 | @param query The SQL query to be performed. 53 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 54 | 55 | @return `BOOL` value. 56 | 57 | @note To use this method from Swift, you must include `FMDatabaseAdditionsVariadic.swift` in your project. 58 | */ 59 | 60 | - (BOOL)boolForQuery:(NSString*)query, ...; 61 | 62 | /** Return `double` value for query 63 | 64 | @param query The SQL query to be performed. 65 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 66 | 67 | @return `double` value. 68 | 69 | @note To use this method from Swift, you must include `FMDatabaseAdditionsVariadic.swift` in your project. 70 | */ 71 | 72 | - (double)doubleForQuery:(NSString*)query, ...; 73 | 74 | /** Return `NSString` value for query 75 | 76 | @param query The SQL query to be performed. 77 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 78 | 79 | @return `NSString` value. 80 | 81 | @note To use this method from Swift, you must include `FMDatabaseAdditionsVariadic.swift` in your project. 82 | */ 83 | 84 | - (NSString*)stringForQuery:(NSString*)query, ...; 85 | 86 | /** Return `NSData` value for query 87 | 88 | @param query The SQL query to be performed. 89 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 90 | 91 | @return `NSData` value. 92 | 93 | @note To use this method from Swift, you must include `FMDatabaseAdditionsVariadic.swift` in your project. 94 | */ 95 | 96 | - (NSData*)dataForQuery:(NSString*)query, ...; 97 | 98 | /** Return `NSDate` value for query 99 | 100 | @param query The SQL query to be performed. 101 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 102 | 103 | @return `NSDate` value. 104 | 105 | @note To use this method from Swift, you must include `FMDatabaseAdditionsVariadic.swift` in your project. 106 | */ 107 | 108 | - (NSDate*)dateForQuery:(NSString*)query, ...; 109 | 110 | 111 | // Notice that there's no dataNoCopyForQuery:. 112 | // That would be a bad idea, because we close out the result set, and then what 113 | // happens to the data that we just didn't copy? Who knows, not I. 114 | 115 | 116 | ///-------------------------------- 117 | /// @name Schema related operations 118 | ///-------------------------------- 119 | 120 | /** Does table exist in database? 121 | 122 | @param tableName The name of the table being looked for. 123 | 124 | @return `YES` if table found; `NO` if not found. 125 | */ 126 | 127 | - (BOOL)tableExists:(NSString*)tableName; 128 | 129 | /** The schema of the database. 130 | 131 | This will be the schema for the entire database. For each entity, each row of the result set will include the following fields: 132 | 133 | - `type` - The type of entity (e.g. table, index, view, or trigger) 134 | - `name` - The name of the object 135 | - `tbl_name` - The name of the table to which the object references 136 | - `rootpage` - The page number of the root b-tree page for tables and indices 137 | - `sql` - The SQL that created the entity 138 | 139 | @return `FMResultSet` of schema; `nil` on error. 140 | 141 | @see [SQLite File Format](http://www.sqlite.org/fileformat.html) 142 | */ 143 | 144 | - (FMResultSet*)getSchema; 145 | 146 | /** The schema of the database. 147 | 148 | This will be the schema for a particular table as report by SQLite `PRAGMA`, for example: 149 | 150 | PRAGMA table_info('employees') 151 | 152 | This will report: 153 | 154 | - `cid` - The column ID number 155 | - `name` - The name of the column 156 | - `type` - The data type specified for the column 157 | - `notnull` - whether the field is defined as NOT NULL (i.e. values required) 158 | - `dflt_value` - The default value for the column 159 | - `pk` - Whether the field is part of the primary key of the table 160 | 161 | @param tableName The name of the table for whom the schema will be returned. 162 | 163 | @return `FMResultSet` of schema; `nil` on error. 164 | 165 | @see [table_info](http://www.sqlite.org/pragma.html#pragma_table_info) 166 | */ 167 | 168 | - (FMResultSet*)getTableSchema:(NSString*)tableName; 169 | 170 | /** Test to see if particular column exists for particular table in database 171 | 172 | @param columnName The name of the column. 173 | 174 | @param tableName The name of the table. 175 | 176 | @return `YES` if column exists in table in question; `NO` otherwise. 177 | */ 178 | 179 | - (BOOL)columnExists:(NSString*)columnName inTableWithName:(NSString*)tableName; 180 | 181 | /** Test to see if particular column exists for particular table in database 182 | 183 | @param columnName The name of the column. 184 | 185 | @param tableName The name of the table. 186 | 187 | @return `YES` if column exists in table in question; `NO` otherwise. 188 | 189 | @see columnExists:inTableWithName: 190 | 191 | @warning Deprecated - use `` instead. 192 | */ 193 | 194 | - (BOOL)columnExists:(NSString*)tableName columnName:(NSString*)columnName __attribute__ ((deprecated)); 195 | 196 | 197 | /** Validate SQL statement 198 | 199 | This validates SQL statement by performing `sqlite3_prepare_v2`, but not returning the results, but instead immediately calling `sqlite3_finalize`. 200 | 201 | @param sql The SQL statement being validated. 202 | 203 | @param error This is a pointer to a `NSError` object that will receive the autoreleased `NSError` object if there was any error. If this is `nil`, no `NSError` result will be returned. 204 | 205 | @return `YES` if validation succeeded without incident; `NO` otherwise. 206 | 207 | */ 208 | 209 | - (BOOL)validateSQL:(NSString*)sql error:(NSError**)error; 210 | 211 | 212 | #if SQLITE_VERSION_NUMBER >= 3007017 213 | 214 | ///----------------------------------- 215 | /// @name Application identifier tasks 216 | ///----------------------------------- 217 | 218 | /** Retrieve application ID 219 | 220 | @return The `uint32_t` numeric value of the application ID. 221 | 222 | @see setApplicationID: 223 | */ 224 | 225 | - (uint32_t)applicationID; 226 | 227 | /** Set the application ID 228 | 229 | @param appID The `uint32_t` numeric value of the application ID. 230 | 231 | @see applicationID 232 | */ 233 | 234 | - (void)setApplicationID:(uint32_t)appID; 235 | 236 | #if TARGET_OS_MAC && !TARGET_OS_IPHONE 237 | /** Retrieve application ID string 238 | 239 | @return The `NSString` value of the application ID. 240 | 241 | @see setApplicationIDString: 242 | */ 243 | 244 | 245 | - (NSString*)applicationIDString; 246 | 247 | /** Set the application ID string 248 | 249 | @param string The `NSString` value of the application ID. 250 | 251 | @see applicationIDString 252 | */ 253 | 254 | - (void)setApplicationIDString:(NSString*)string; 255 | #endif 256 | 257 | #endif 258 | 259 | ///----------------------------------- 260 | /// @name user version identifier tasks 261 | ///----------------------------------- 262 | 263 | /** Retrieve user version 264 | 265 | @return The `uint32_t` numeric value of the user version. 266 | 267 | @see setUserVersion: 268 | */ 269 | 270 | - (uint32_t)userVersion; 271 | 272 | /** Set the user-version 273 | 274 | @param version The `uint32_t` numeric value of the user version. 275 | 276 | @see userVersion 277 | */ 278 | 279 | - (void)setUserVersion:(uint32_t)version; 280 | 281 | @end 282 | -------------------------------------------------------------------------------- /Pods/FMDB/src/fmdb/FMDatabaseAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabaseAdditions.m 3 | // fmdb 4 | // 5 | // Created by August Mueller on 10/30/05. 6 | // Copyright 2005 Flying Meat Inc.. All rights reserved. 7 | // 8 | 9 | #import "FMDatabase.h" 10 | #import "FMDatabaseAdditions.h" 11 | #import "TargetConditionals.h" 12 | 13 | @interface FMDatabase (PrivateStuff) 14 | - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args; 15 | @end 16 | 17 | @implementation FMDatabase (FMDatabaseAdditions) 18 | 19 | #define RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(type, sel) \ 20 | va_list args; \ 21 | va_start(args, query); \ 22 | FMResultSet *resultSet = [self executeQuery:query withArgumentsInArray:0x00 orDictionary:0x00 orVAList:args]; \ 23 | va_end(args); \ 24 | if (![resultSet next]) { return (type)0; } \ 25 | type ret = [resultSet sel:0]; \ 26 | [resultSet close]; \ 27 | [resultSet setParentDB:nil]; \ 28 | return ret; 29 | 30 | 31 | - (NSString*)stringForQuery:(NSString*)query, ... { 32 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSString *, stringForColumnIndex); 33 | } 34 | 35 | - (int)intForQuery:(NSString*)query, ... { 36 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(int, intForColumnIndex); 37 | } 38 | 39 | - (long)longForQuery:(NSString*)query, ... { 40 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(long, longForColumnIndex); 41 | } 42 | 43 | - (BOOL)boolForQuery:(NSString*)query, ... { 44 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(BOOL, boolForColumnIndex); 45 | } 46 | 47 | - (double)doubleForQuery:(NSString*)query, ... { 48 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(double, doubleForColumnIndex); 49 | } 50 | 51 | - (NSData*)dataForQuery:(NSString*)query, ... { 52 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSData *, dataForColumnIndex); 53 | } 54 | 55 | - (NSDate*)dateForQuery:(NSString*)query, ... { 56 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSDate *, dateForColumnIndex); 57 | } 58 | 59 | 60 | - (BOOL)tableExists:(NSString*)tableName { 61 | 62 | tableName = [tableName lowercaseString]; 63 | 64 | FMResultSet *rs = [self executeQuery:@"select [sql] from sqlite_master where [type] = 'table' and lower(name) = ?", tableName]; 65 | 66 | //if at least one next exists, table exists 67 | BOOL returnBool = [rs next]; 68 | 69 | //close and free object 70 | [rs close]; 71 | 72 | return returnBool; 73 | } 74 | 75 | /* 76 | get table with list of tables: result colums: type[STRING], name[STRING],tbl_name[STRING],rootpage[INTEGER],sql[STRING] 77 | check if table exist in database (patch from OZLB) 78 | */ 79 | - (FMResultSet*)getSchema { 80 | 81 | //result colums: type[STRING], name[STRING],tbl_name[STRING],rootpage[INTEGER],sql[STRING] 82 | FMResultSet *rs = [self executeQuery:@"SELECT type, name, tbl_name, rootpage, sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type != 'meta' AND name NOT LIKE 'sqlite_%' ORDER BY tbl_name, type DESC, name"]; 83 | 84 | return rs; 85 | } 86 | 87 | /* 88 | get table schema: result colums: cid[INTEGER], name,type [STRING], notnull[INTEGER], dflt_value[],pk[INTEGER] 89 | */ 90 | - (FMResultSet*)getTableSchema:(NSString*)tableName { 91 | 92 | //result colums: cid[INTEGER], name,type [STRING], notnull[INTEGER], dflt_value[],pk[INTEGER] 93 | FMResultSet *rs = [self executeQuery:[NSString stringWithFormat: @"pragma table_info('%@')", tableName]]; 94 | 95 | return rs; 96 | } 97 | 98 | - (BOOL)columnExists:(NSString*)columnName inTableWithName:(NSString*)tableName { 99 | 100 | BOOL returnBool = NO; 101 | 102 | tableName = [tableName lowercaseString]; 103 | columnName = [columnName lowercaseString]; 104 | 105 | FMResultSet *rs = [self getTableSchema:tableName]; 106 | 107 | //check if column is present in table schema 108 | while ([rs next]) { 109 | if ([[[rs stringForColumn:@"name"] lowercaseString] isEqualToString:columnName]) { 110 | returnBool = YES; 111 | break; 112 | } 113 | } 114 | 115 | //If this is not done FMDatabase instance stays out of pool 116 | [rs close]; 117 | 118 | return returnBool; 119 | } 120 | 121 | 122 | #if SQLITE_VERSION_NUMBER >= 3007017 123 | 124 | - (uint32_t)applicationID { 125 | 126 | uint32_t r = 0; 127 | 128 | FMResultSet *rs = [self executeQuery:@"pragma application_id"]; 129 | 130 | if ([rs next]) { 131 | r = (uint32_t)[rs longLongIntForColumnIndex:0]; 132 | } 133 | 134 | [rs close]; 135 | 136 | return r; 137 | } 138 | 139 | - (void)setApplicationID:(uint32_t)appID { 140 | NSString *query = [NSString stringWithFormat:@"pragma application_id=%d", appID]; 141 | FMResultSet *rs = [self executeQuery:query]; 142 | [rs next]; 143 | [rs close]; 144 | } 145 | 146 | 147 | #if TARGET_OS_MAC && !TARGET_OS_IPHONE 148 | - (NSString*)applicationIDString { 149 | NSString *s = NSFileTypeForHFSTypeCode([self applicationID]); 150 | 151 | assert([s length] == 6); 152 | 153 | s = [s substringWithRange:NSMakeRange(1, 4)]; 154 | 155 | 156 | return s; 157 | 158 | } 159 | 160 | - (void)setApplicationIDString:(NSString*)s { 161 | 162 | if ([s length] != 4) { 163 | NSLog(@"setApplicationIDString: string passed is not exactly 4 chars long. (was %ld)", [s length]); 164 | } 165 | 166 | [self setApplicationID:NSHFSTypeCodeFromFileType([NSString stringWithFormat:@"'%@'", s])]; 167 | } 168 | 169 | 170 | #endif 171 | 172 | #endif 173 | 174 | - (uint32_t)userVersion { 175 | uint32_t r = 0; 176 | 177 | FMResultSet *rs = [self executeQuery:@"pragma user_version"]; 178 | 179 | if ([rs next]) { 180 | r = (uint32_t)[rs longLongIntForColumnIndex:0]; 181 | } 182 | 183 | [rs close]; 184 | return r; 185 | } 186 | 187 | - (void)setUserVersion:(uint32_t)version { 188 | NSString *query = [NSString stringWithFormat:@"pragma user_version = %d", version]; 189 | FMResultSet *rs = [self executeQuery:query]; 190 | [rs next]; 191 | [rs close]; 192 | } 193 | 194 | #pragma clang diagnostic push 195 | #pragma clang diagnostic ignored "-Wdeprecated-implementations" 196 | 197 | - (BOOL)columnExists:(NSString*)tableName columnName:(NSString*)columnName __attribute__ ((deprecated)) { 198 | return [self columnExists:columnName inTableWithName:tableName]; 199 | } 200 | 201 | #pragma clang diagnostic pop 202 | 203 | 204 | - (BOOL)validateSQL:(NSString*)sql error:(NSError**)error { 205 | sqlite3_stmt *pStmt = NULL; 206 | BOOL validationSucceeded = YES; 207 | 208 | int rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0); 209 | if (rc != SQLITE_OK) { 210 | validationSucceeded = NO; 211 | if (error) { 212 | *error = [NSError errorWithDomain:NSCocoaErrorDomain 213 | code:[self lastErrorCode] 214 | userInfo:[NSDictionary dictionaryWithObject:[self lastErrorMessage] 215 | forKey:NSLocalizedDescriptionKey]]; 216 | } 217 | } 218 | 219 | sqlite3_finalize(pStmt); 220 | 221 | return validationSucceeded; 222 | } 223 | 224 | @end 225 | -------------------------------------------------------------------------------- /Pods/FMDB/src/fmdb/FMDatabasePool.h: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabasePool.h 3 | // fmdb 4 | // 5 | // Created by August Mueller on 6/22/11. 6 | // Copyright 2011 Flying Meat Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "sqlite3.h" 11 | 12 | @class FMDatabase; 13 | 14 | /** Pool of `` objects. 15 | 16 | ### See also 17 | 18 | - `` 19 | - `` 20 | 21 | @warning Before using `FMDatabasePool`, please consider using `` instead. 22 | 23 | If you really really really know what you're doing and `FMDatabasePool` is what 24 | you really really need (ie, you're using a read only database), OK you can use 25 | it. But just be careful not to deadlock! 26 | 27 | For an example on deadlocking, search for: 28 | `ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS_OTHERWISE_YOULL_DEADLOCK_USE_FMDATABASEQUEUE_INSTEAD` 29 | in the main.m file. 30 | */ 31 | 32 | @interface FMDatabasePool : NSObject { 33 | NSString *_path; 34 | 35 | dispatch_queue_t _lockQueue; 36 | 37 | NSMutableArray *_databaseInPool; 38 | NSMutableArray *_databaseOutPool; 39 | 40 | __unsafe_unretained id _delegate; 41 | 42 | NSUInteger _maximumNumberOfDatabasesToCreate; 43 | int _openFlags; 44 | } 45 | 46 | /** Database path */ 47 | 48 | @property (atomic, retain) NSString *path; 49 | 50 | /** Delegate object */ 51 | 52 | @property (atomic, assign) id delegate; 53 | 54 | /** Maximum number of databases to create */ 55 | 56 | @property (atomic, assign) NSUInteger maximumNumberOfDatabasesToCreate; 57 | 58 | /** Open flags */ 59 | 60 | @property (atomic, readonly) int openFlags; 61 | 62 | 63 | ///--------------------- 64 | /// @name Initialization 65 | ///--------------------- 66 | 67 | /** Create pool using path. 68 | 69 | @param aPath The file path of the database. 70 | 71 | @return The `FMDatabasePool` object. `nil` on error. 72 | */ 73 | 74 | + (instancetype)databasePoolWithPath:(NSString*)aPath; 75 | 76 | /** Create pool using path and specified flags 77 | 78 | @param aPath The file path of the database. 79 | @param openFlags Flags passed to the openWithFlags method of the database 80 | 81 | @return The `FMDatabasePool` object. `nil` on error. 82 | */ 83 | 84 | + (instancetype)databasePoolWithPath:(NSString*)aPath flags:(int)openFlags; 85 | 86 | /** Create pool using path. 87 | 88 | @param aPath The file path of the database. 89 | 90 | @return The `FMDatabasePool` object. `nil` on error. 91 | */ 92 | 93 | - (instancetype)initWithPath:(NSString*)aPath; 94 | 95 | /** Create pool using path and specified flags. 96 | 97 | @param aPath The file path of the database. 98 | @param openFlags Flags passed to the openWithFlags method of the database 99 | 100 | @return The `FMDatabasePool` object. `nil` on error. 101 | */ 102 | 103 | - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags; 104 | 105 | ///------------------------------------------------ 106 | /// @name Keeping track of checked in/out databases 107 | ///------------------------------------------------ 108 | 109 | /** Number of checked-in databases in pool 110 | 111 | @returns Number of databases 112 | */ 113 | 114 | - (NSUInteger)countOfCheckedInDatabases; 115 | 116 | /** Number of checked-out databases in pool 117 | 118 | @returns Number of databases 119 | */ 120 | 121 | - (NSUInteger)countOfCheckedOutDatabases; 122 | 123 | /** Total number of databases in pool 124 | 125 | @returns Number of databases 126 | */ 127 | 128 | - (NSUInteger)countOfOpenDatabases; 129 | 130 | /** Release all databases in pool */ 131 | 132 | - (void)releaseAllDatabases; 133 | 134 | ///------------------------------------------ 135 | /// @name Perform database operations in pool 136 | ///------------------------------------------ 137 | 138 | /** Synchronously perform database operations in pool. 139 | 140 | @param block The code to be run on the `FMDatabasePool` pool. 141 | */ 142 | 143 | - (void)inDatabase:(void (^)(FMDatabase *db))block; 144 | 145 | /** Synchronously perform database operations in pool using transaction. 146 | 147 | @param block The code to be run on the `FMDatabasePool` pool. 148 | */ 149 | 150 | - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; 151 | 152 | /** Synchronously perform database operations in pool using deferred transaction. 153 | 154 | @param block The code to be run on the `FMDatabasePool` pool. 155 | */ 156 | 157 | - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; 158 | 159 | #if SQLITE_VERSION_NUMBER >= 3007000 160 | 161 | /** Synchronously perform database operations in pool using save point. 162 | 163 | @param block The code to be run on the `FMDatabasePool` pool. 164 | 165 | @return `NSError` object if error; `nil` if successful. 166 | 167 | @warning You can not nest these, since calling it will pull another database out of the pool and you'll get a deadlock. If you need to nest, use `<[FMDatabase startSavePointWithName:error:]>` instead. 168 | */ 169 | 170 | - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block; 171 | #endif 172 | 173 | @end 174 | 175 | 176 | /** FMDatabasePool delegate category 177 | 178 | This is a category that defines the protocol for the FMDatabasePool delegate 179 | */ 180 | 181 | @interface NSObject (FMDatabasePoolDelegate) 182 | 183 | /** Asks the delegate whether database should be added to the pool. 184 | 185 | @param pool The `FMDatabasePool` object. 186 | @param database The `FMDatabase` object. 187 | 188 | @return `YES` if it should add database to pool; `NO` if not. 189 | 190 | */ 191 | 192 | - (BOOL)databasePool:(FMDatabasePool*)pool shouldAddDatabaseToPool:(FMDatabase*)database; 193 | 194 | /** Tells the delegate that database was added to the pool. 195 | 196 | @param pool The `FMDatabasePool` object. 197 | @param database The `FMDatabase` object. 198 | 199 | */ 200 | 201 | - (void)databasePool:(FMDatabasePool*)pool didAddDatabase:(FMDatabase*)database; 202 | 203 | @end 204 | 205 | -------------------------------------------------------------------------------- /Pods/FMDB/src/fmdb/FMDatabasePool.m: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabasePool.m 3 | // fmdb 4 | // 5 | // Created by August Mueller on 6/22/11. 6 | // Copyright 2011 Flying Meat Inc. All rights reserved. 7 | // 8 | 9 | #import "FMDatabasePool.h" 10 | #import "FMDatabase.h" 11 | 12 | @interface FMDatabasePool() 13 | 14 | - (void)pushDatabaseBackInPool:(FMDatabase*)db; 15 | - (FMDatabase*)db; 16 | 17 | @end 18 | 19 | 20 | @implementation FMDatabasePool 21 | @synthesize path=_path; 22 | @synthesize delegate=_delegate; 23 | @synthesize maximumNumberOfDatabasesToCreate=_maximumNumberOfDatabasesToCreate; 24 | @synthesize openFlags=_openFlags; 25 | 26 | 27 | + (instancetype)databasePoolWithPath:(NSString*)aPath { 28 | return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath]); 29 | } 30 | 31 | + (instancetype)databasePoolWithPath:(NSString*)aPath flags:(int)openFlags { 32 | return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath flags:openFlags]); 33 | } 34 | 35 | - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags { 36 | 37 | self = [super init]; 38 | 39 | if (self != nil) { 40 | _path = [aPath copy]; 41 | _lockQueue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL); 42 | _databaseInPool = FMDBReturnRetained([NSMutableArray array]); 43 | _databaseOutPool = FMDBReturnRetained([NSMutableArray array]); 44 | _openFlags = openFlags; 45 | } 46 | 47 | return self; 48 | } 49 | 50 | - (instancetype)initWithPath:(NSString*)aPath 51 | { 52 | // default flags for sqlite3_open 53 | return [self initWithPath:aPath flags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE]; 54 | } 55 | 56 | - (instancetype)init { 57 | return [self initWithPath:nil]; 58 | } 59 | 60 | 61 | - (void)dealloc { 62 | 63 | _delegate = 0x00; 64 | FMDBRelease(_path); 65 | FMDBRelease(_databaseInPool); 66 | FMDBRelease(_databaseOutPool); 67 | 68 | if (_lockQueue) { 69 | FMDBDispatchQueueRelease(_lockQueue); 70 | _lockQueue = 0x00; 71 | } 72 | #if ! __has_feature(objc_arc) 73 | [super dealloc]; 74 | #endif 75 | } 76 | 77 | 78 | - (void)executeLocked:(void (^)(void))aBlock { 79 | dispatch_sync(_lockQueue, aBlock); 80 | } 81 | 82 | - (void)pushDatabaseBackInPool:(FMDatabase*)db { 83 | 84 | if (!db) { // db can be null if we set an upper bound on the # of databases to create. 85 | return; 86 | } 87 | 88 | [self executeLocked:^() { 89 | 90 | if ([self->_databaseInPool containsObject:db]) { 91 | [[NSException exceptionWithName:@"Database already in pool" reason:@"The FMDatabase being put back into the pool is already present in the pool" userInfo:nil] raise]; 92 | } 93 | 94 | [self->_databaseInPool addObject:db]; 95 | [self->_databaseOutPool removeObject:db]; 96 | 97 | }]; 98 | } 99 | 100 | - (FMDatabase*)db { 101 | 102 | __block FMDatabase *db; 103 | 104 | 105 | [self executeLocked:^() { 106 | db = [self->_databaseInPool lastObject]; 107 | 108 | BOOL shouldNotifyDelegate = NO; 109 | 110 | if (db) { 111 | [self->_databaseOutPool addObject:db]; 112 | [self->_databaseInPool removeLastObject]; 113 | } 114 | else { 115 | 116 | if (self->_maximumNumberOfDatabasesToCreate) { 117 | NSUInteger currentCount = [self->_databaseOutPool count] + [self->_databaseInPool count]; 118 | 119 | if (currentCount >= self->_maximumNumberOfDatabasesToCreate) { 120 | NSLog(@"Maximum number of databases (%ld) has already been reached!", (long)currentCount); 121 | return; 122 | } 123 | } 124 | 125 | db = [FMDatabase databaseWithPath:self->_path]; 126 | shouldNotifyDelegate = YES; 127 | } 128 | 129 | //This ensures that the db is opened before returning 130 | #if SQLITE_VERSION_NUMBER >= 3005000 131 | BOOL success = [db openWithFlags:self->_openFlags]; 132 | #else 133 | BOOL success = [db open]; 134 | #endif 135 | if (success) { 136 | if ([self->_delegate respondsToSelector:@selector(databasePool:shouldAddDatabaseToPool:)] && ![self->_delegate databasePool:self shouldAddDatabaseToPool:db]) { 137 | [db close]; 138 | db = 0x00; 139 | } 140 | else { 141 | //It should not get added in the pool twice if lastObject was found 142 | if (![self->_databaseOutPool containsObject:db]) { 143 | [self->_databaseOutPool addObject:db]; 144 | 145 | if (shouldNotifyDelegate && [self->_delegate respondsToSelector:@selector(databasePool:didAddDatabase:)]) { 146 | [self->_delegate databasePool:self didAddDatabase:db]; 147 | } 148 | } 149 | } 150 | } 151 | else { 152 | NSLog(@"Could not open up the database at path %@", self->_path); 153 | db = 0x00; 154 | } 155 | }]; 156 | 157 | return db; 158 | } 159 | 160 | - (NSUInteger)countOfCheckedInDatabases { 161 | 162 | __block NSUInteger count; 163 | 164 | [self executeLocked:^() { 165 | count = [self->_databaseInPool count]; 166 | }]; 167 | 168 | return count; 169 | } 170 | 171 | - (NSUInteger)countOfCheckedOutDatabases { 172 | 173 | __block NSUInteger count; 174 | 175 | [self executeLocked:^() { 176 | count = [self->_databaseOutPool count]; 177 | }]; 178 | 179 | return count; 180 | } 181 | 182 | - (NSUInteger)countOfOpenDatabases { 183 | __block NSUInteger count; 184 | 185 | [self executeLocked:^() { 186 | count = [self->_databaseOutPool count] + [self->_databaseInPool count]; 187 | }]; 188 | 189 | return count; 190 | } 191 | 192 | - (void)releaseAllDatabases { 193 | [self executeLocked:^() { 194 | [self->_databaseOutPool removeAllObjects]; 195 | [self->_databaseInPool removeAllObjects]; 196 | }]; 197 | } 198 | 199 | - (void)inDatabase:(void (^)(FMDatabase *db))block { 200 | 201 | FMDatabase *db = [self db]; 202 | 203 | block(db); 204 | 205 | [self pushDatabaseBackInPool:db]; 206 | } 207 | 208 | - (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block { 209 | 210 | BOOL shouldRollback = NO; 211 | 212 | FMDatabase *db = [self db]; 213 | 214 | if (useDeferred) { 215 | [db beginDeferredTransaction]; 216 | } 217 | else { 218 | [db beginTransaction]; 219 | } 220 | 221 | 222 | block(db, &shouldRollback); 223 | 224 | if (shouldRollback) { 225 | [db rollback]; 226 | } 227 | else { 228 | [db commit]; 229 | } 230 | 231 | [self pushDatabaseBackInPool:db]; 232 | } 233 | 234 | - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { 235 | [self beginTransaction:YES withBlock:block]; 236 | } 237 | 238 | - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { 239 | [self beginTransaction:NO withBlock:block]; 240 | } 241 | #if SQLITE_VERSION_NUMBER >= 3007000 242 | - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block { 243 | 244 | static unsigned long savePointIdx = 0; 245 | 246 | NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++]; 247 | 248 | BOOL shouldRollback = NO; 249 | 250 | FMDatabase *db = [self db]; 251 | 252 | NSError *err = 0x00; 253 | 254 | if (![db startSavePointWithName:name error:&err]) { 255 | [self pushDatabaseBackInPool:db]; 256 | return err; 257 | } 258 | 259 | block(db, &shouldRollback); 260 | 261 | if (shouldRollback) { 262 | // We need to rollback and release this savepoint to remove it 263 | [db rollbackToSavePointWithName:name error:&err]; 264 | } 265 | [db releaseSavePointWithName:name error:&err]; 266 | 267 | [self pushDatabaseBackInPool:db]; 268 | 269 | return err; 270 | } 271 | #endif 272 | 273 | @end 274 | -------------------------------------------------------------------------------- /Pods/FMDB/src/fmdb/FMDatabaseQueue.h: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabaseQueue.h 3 | // fmdb 4 | // 5 | // Created by August Mueller on 6/22/11. 6 | // Copyright 2011 Flying Meat Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "sqlite3.h" 11 | 12 | @class FMDatabase; 13 | 14 | /** To perform queries and updates on multiple threads, you'll want to use `FMDatabaseQueue`. 15 | 16 | Using a single instance of `` from multiple threads at once is a bad idea. It has always been OK to make a `` object *per thread*. Just don't share a single instance across threads, and definitely not across multiple threads at the same time. 17 | 18 | Instead, use `FMDatabaseQueue`. Here's how to use it: 19 | 20 | First, make your queue. 21 | 22 | FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath]; 23 | 24 | Then use it like so: 25 | 26 | [queue inDatabase:^(FMDatabase *db) { 27 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]]; 28 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]]; 29 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]]; 30 | 31 | FMResultSet *rs = [db executeQuery:@"select * from foo"]; 32 | while ([rs next]) { 33 | //… 34 | } 35 | }]; 36 | 37 | An easy way to wrap things up in a transaction can be done like this: 38 | 39 | [queue inTransaction:^(FMDatabase *db, BOOL *rollback) { 40 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]]; 41 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]]; 42 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]]; 43 | 44 | if (whoopsSomethingWrongHappened) { 45 | *rollback = YES; 46 | return; 47 | } 48 | // etc… 49 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:4]]; 50 | }]; 51 | 52 | `FMDatabaseQueue` will run the blocks on a serialized queue (hence the name of the class). So if you call `FMDatabaseQueue`'s methods from multiple threads at the same time, they will be executed in the order they are received. This way queries and updates won't step on each other's toes, and every one is happy. 53 | 54 | ### See also 55 | 56 | - `` 57 | 58 | @warning Do not instantiate a single `` object and use it across multiple threads. Use `FMDatabaseQueue` instead. 59 | 60 | @warning The calls to `FMDatabaseQueue`'s methods are blocking. So even though you are passing along blocks, they will **not** be run on another thread. 61 | 62 | */ 63 | 64 | @interface FMDatabaseQueue : NSObject { 65 | NSString *_path; 66 | dispatch_queue_t _queue; 67 | FMDatabase *_db; 68 | int _openFlags; 69 | } 70 | 71 | /** Path of database */ 72 | 73 | @property (atomic, retain) NSString *path; 74 | 75 | /** Open flags */ 76 | 77 | @property (atomic, readonly) int openFlags; 78 | 79 | ///---------------------------------------------------- 80 | /// @name Initialization, opening, and closing of queue 81 | ///---------------------------------------------------- 82 | 83 | /** Create queue using path. 84 | 85 | @param aPath The file path of the database. 86 | 87 | @return The `FMDatabaseQueue` object. `nil` on error. 88 | */ 89 | 90 | + (instancetype)databaseQueueWithPath:(NSString*)aPath; 91 | 92 | /** Create queue using path and specified flags. 93 | 94 | @param aPath The file path of the database. 95 | @param openFlags Flags passed to the openWithFlags method of the database 96 | 97 | @return The `FMDatabaseQueue` object. `nil` on error. 98 | */ 99 | + (instancetype)databaseQueueWithPath:(NSString*)aPath flags:(int)openFlags; 100 | 101 | /** Create queue using path. 102 | 103 | @param aPath The file path of the database. 104 | 105 | @return The `FMDatabaseQueue` object. `nil` on error. 106 | */ 107 | 108 | - (instancetype)initWithPath:(NSString*)aPath; 109 | 110 | /** Create queue using path and specified flags. 111 | 112 | @param aPath The file path of the database. 113 | @param openFlags Flags passed to the openWithFlags method of the database 114 | 115 | @return The `FMDatabaseQueue` object. `nil` on error. 116 | */ 117 | 118 | - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags; 119 | 120 | /** Returns the Class of 'FMDatabase' subclass, that will be used to instantiate database object. 121 | 122 | Subclasses can override this method to return specified Class of 'FMDatabase' subclass. 123 | 124 | @return The Class of 'FMDatabase' subclass, that will be used to instantiate database object. 125 | */ 126 | 127 | + (Class)databaseClass; 128 | 129 | /** Close database used by queue. */ 130 | 131 | - (void)close; 132 | 133 | ///----------------------------------------------- 134 | /// @name Dispatching database operations to queue 135 | ///----------------------------------------------- 136 | 137 | /** Synchronously perform database operations on queue. 138 | 139 | @param block The code to be run on the queue of `FMDatabaseQueue` 140 | */ 141 | 142 | - (void)inDatabase:(void (^)(FMDatabase *db))block; 143 | 144 | /** Synchronously perform database operations on queue, using transactions. 145 | 146 | @param block The code to be run on the queue of `FMDatabaseQueue` 147 | */ 148 | 149 | - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; 150 | 151 | /** Synchronously perform database operations on queue, using deferred transactions. 152 | 153 | @param block The code to be run on the queue of `FMDatabaseQueue` 154 | */ 155 | 156 | - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; 157 | 158 | ///----------------------------------------------- 159 | /// @name Dispatching database operations to queue 160 | ///----------------------------------------------- 161 | 162 | /** Synchronously perform database operations using save point. 163 | 164 | @param block The code to be run on the queue of `FMDatabaseQueue` 165 | */ 166 | 167 | #if SQLITE_VERSION_NUMBER >= 3007000 168 | // NOTE: you can not nest these, since calling it will pull another database out of the pool and you'll get a deadlock. 169 | // If you need to nest, use FMDatabase's startSavePointWithName:error: instead. 170 | - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block; 171 | #endif 172 | 173 | @end 174 | 175 | -------------------------------------------------------------------------------- /Pods/FMDB/src/fmdb/FMDatabaseQueue.m: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabaseQueue.m 3 | // fmdb 4 | // 5 | // Created by August Mueller on 6/22/11. 6 | // Copyright 2011 Flying Meat Inc. All rights reserved. 7 | // 8 | 9 | #import "FMDatabaseQueue.h" 10 | #import "FMDatabase.h" 11 | 12 | /* 13 | 14 | Note: we call [self retain]; before using dispatch_sync, just incase 15 | FMDatabaseQueue is released on another thread and we're in the middle of doing 16 | something in dispatch_sync 17 | 18 | */ 19 | 20 | /* 21 | * A key used to associate the FMDatabaseQueue object with the dispatch_queue_t it uses. 22 | * This in turn is used for deadlock detection by seeing if inDatabase: is called on 23 | * the queue's dispatch queue, which should not happen and causes a deadlock. 24 | */ 25 | static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey; 26 | 27 | @implementation FMDatabaseQueue 28 | 29 | @synthesize path = _path; 30 | @synthesize openFlags = _openFlags; 31 | 32 | + (instancetype)databaseQueueWithPath:(NSString*)aPath { 33 | 34 | FMDatabaseQueue *q = [[self alloc] initWithPath:aPath]; 35 | 36 | FMDBAutorelease(q); 37 | 38 | return q; 39 | } 40 | 41 | + (instancetype)databaseQueueWithPath:(NSString*)aPath flags:(int)openFlags { 42 | 43 | FMDatabaseQueue *q = [[self alloc] initWithPath:aPath flags:openFlags]; 44 | 45 | FMDBAutorelease(q); 46 | 47 | return q; 48 | } 49 | 50 | + (Class)databaseClass { 51 | return [FMDatabase class]; 52 | } 53 | 54 | - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags { 55 | 56 | self = [super init]; 57 | 58 | if (self != nil) { 59 | 60 | _db = [[[self class] databaseClass] databaseWithPath:aPath]; 61 | FMDBRetain(_db); 62 | 63 | #if SQLITE_VERSION_NUMBER >= 3005000 64 | BOOL success = [_db openWithFlags:openFlags]; 65 | #else 66 | BOOL success = [_db open]; 67 | #endif 68 | if (!success) { 69 | NSLog(@"Could not create database queue for path %@", aPath); 70 | FMDBRelease(self); 71 | return 0x00; 72 | } 73 | 74 | _path = FMDBReturnRetained(aPath); 75 | 76 | _queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL); 77 | dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL); 78 | _openFlags = openFlags; 79 | } 80 | 81 | return self; 82 | } 83 | 84 | - (instancetype)initWithPath:(NSString*)aPath { 85 | 86 | // default flags for sqlite3_open 87 | return [self initWithPath:aPath flags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE]; 88 | } 89 | 90 | - (instancetype)init { 91 | return [self initWithPath:nil]; 92 | } 93 | 94 | 95 | - (void)dealloc { 96 | 97 | FMDBRelease(_db); 98 | FMDBRelease(_path); 99 | 100 | if (_queue) { 101 | FMDBDispatchQueueRelease(_queue); 102 | _queue = 0x00; 103 | } 104 | #if ! __has_feature(objc_arc) 105 | [super dealloc]; 106 | #endif 107 | } 108 | 109 | - (void)close { 110 | FMDBRetain(self); 111 | dispatch_sync(_queue, ^() { 112 | [self->_db close]; 113 | FMDBRelease(_db); 114 | self->_db = 0x00; 115 | }); 116 | FMDBRelease(self); 117 | } 118 | 119 | - (FMDatabase*)database { 120 | if (!_db) { 121 | _db = FMDBReturnRetained([FMDatabase databaseWithPath:_path]); 122 | 123 | #if SQLITE_VERSION_NUMBER >= 3005000 124 | BOOL success = [_db openWithFlags:_openFlags]; 125 | #else 126 | BOOL success = [_db open]; 127 | #endif 128 | if (!success) { 129 | NSLog(@"FMDatabaseQueue could not reopen database for path %@", _path); 130 | FMDBRelease(_db); 131 | _db = 0x00; 132 | return 0x00; 133 | } 134 | } 135 | 136 | return _db; 137 | } 138 | 139 | - (void)inDatabase:(void (^)(FMDatabase *db))block { 140 | /* Get the currently executing queue (which should probably be nil, but in theory could be another DB queue 141 | * and then check it against self to make sure we're not about to deadlock. */ 142 | FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey); 143 | assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock"); 144 | 145 | FMDBRetain(self); 146 | 147 | dispatch_sync(_queue, ^() { 148 | 149 | FMDatabase *db = [self database]; 150 | block(db); 151 | 152 | if ([db hasOpenResultSets]) { 153 | NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]"); 154 | 155 | #if defined(DEBUG) && DEBUG 156 | NSSet *openSetCopy = FMDBReturnAutoreleased([[db valueForKey:@"_openResultSets"] copy]); 157 | for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) { 158 | FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue]; 159 | NSLog(@"query: '%@'", [rs query]); 160 | } 161 | #endif 162 | } 163 | }); 164 | 165 | FMDBRelease(self); 166 | } 167 | 168 | 169 | - (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block { 170 | FMDBRetain(self); 171 | dispatch_sync(_queue, ^() { 172 | 173 | BOOL shouldRollback = NO; 174 | 175 | if (useDeferred) { 176 | [[self database] beginDeferredTransaction]; 177 | } 178 | else { 179 | [[self database] beginTransaction]; 180 | } 181 | 182 | block([self database], &shouldRollback); 183 | 184 | if (shouldRollback) { 185 | [[self database] rollback]; 186 | } 187 | else { 188 | [[self database] commit]; 189 | } 190 | }); 191 | 192 | FMDBRelease(self); 193 | } 194 | 195 | - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { 196 | [self beginTransaction:YES withBlock:block]; 197 | } 198 | 199 | - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { 200 | [self beginTransaction:NO withBlock:block]; 201 | } 202 | 203 | #if SQLITE_VERSION_NUMBER >= 3007000 204 | - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block { 205 | 206 | static unsigned long savePointIdx = 0; 207 | __block NSError *err = 0x00; 208 | FMDBRetain(self); 209 | dispatch_sync(_queue, ^() { 210 | 211 | NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++]; 212 | 213 | BOOL shouldRollback = NO; 214 | 215 | if ([[self database] startSavePointWithName:name error:&err]) { 216 | 217 | block([self database], &shouldRollback); 218 | 219 | if (shouldRollback) { 220 | // We need to rollback and release this savepoint to remove it 221 | [[self database] rollbackToSavePointWithName:name error:&err]; 222 | } 223 | [[self database] releaseSavePointWithName:name error:&err]; 224 | 225 | } 226 | }); 227 | FMDBRelease(self); 228 | return err; 229 | } 230 | #endif 231 | 232 | @end 233 | -------------------------------------------------------------------------------- /Pods/Headers/Private/FMDB/FMDB.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMDB.h -------------------------------------------------------------------------------- /Pods/Headers/Private/FMDB/FMDatabase.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMDatabase.h -------------------------------------------------------------------------------- /Pods/Headers/Private/FMDB/FMDatabaseAdditions.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMDatabaseAdditions.h -------------------------------------------------------------------------------- /Pods/Headers/Private/FMDB/FMDatabasePool.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMDatabasePool.h -------------------------------------------------------------------------------- /Pods/Headers/Private/FMDB/FMDatabaseQueue.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMDatabaseQueue.h -------------------------------------------------------------------------------- /Pods/Headers/Private/FMDB/FMResultSet.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMResultSet.h -------------------------------------------------------------------------------- /Pods/Headers/Public/FMDB/FMDB.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMDB.h -------------------------------------------------------------------------------- /Pods/Headers/Public/FMDB/FMDatabase.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMDatabase.h -------------------------------------------------------------------------------- /Pods/Headers/Public/FMDB/FMDatabaseAdditions.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMDatabaseAdditions.h -------------------------------------------------------------------------------- /Pods/Headers/Public/FMDB/FMDatabasePool.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMDatabasePool.h -------------------------------------------------------------------------------- /Pods/Headers/Public/FMDB/FMDatabaseQueue.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMDatabaseQueue.h -------------------------------------------------------------------------------- /Pods/Headers/Public/FMDB/FMResultSet.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMResultSet.h -------------------------------------------------------------------------------- /Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - FMDB (2.5): 3 | - FMDB/standard (= 2.5) 4 | - FMDB/common (2.5) 5 | - FMDB/standard (2.5): 6 | - FMDB/common 7 | 8 | DEPENDENCIES: 9 | - FMDB 10 | 11 | SPEC CHECKSUMS: 12 | FMDB: 96e8f1bcc1329e269330f99770ad4285d9003e52 13 | 14 | COCOAPODS: 0.38.2 15 | -------------------------------------------------------------------------------- /Pods/Target Support Files/FMDB/FMDB-Private.xcconfig: -------------------------------------------------------------------------------- 1 | #include "FMDB.xcconfig" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/FMDB" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/FMDB" 4 | OTHER_LDFLAGS = ${FMDB_OTHER_LDFLAGS} 5 | PODS_ROOT = ${SRCROOT} 6 | SKIP_INSTALL = YES -------------------------------------------------------------------------------- /Pods/Target Support Files/FMDB/FMDB-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_FMDB : NSObject 3 | @end 4 | @implementation PodsDummy_FMDB 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/FMDB/FMDB-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #endif 4 | 5 | -------------------------------------------------------------------------------- /Pods/Target Support Files/FMDB/FMDB.xcconfig: -------------------------------------------------------------------------------- 1 | FMDB_OTHER_LDFLAGS = -l"sqlite3" -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-PleaseBaoMe/Pods-PleaseBaoMe-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## FMDB 5 | 6 | If you are using FMDB in your project, I'd love to hear about it. Let Gus know 7 | by sending an email to gus@flyingmeat.com. 8 | 9 | And if you happen to come across either Gus Mueller or Rob Ryan in a bar, you 10 | might consider purchasing a drink of their choosing if FMDB has been useful to 11 | you. 12 | 13 | Finally, and shortly, this is the MIT License. 14 | 15 | Copyright (c) 2008-2014 Flying Meat Inc. 16 | 17 | Permission is hereby granted, free of charge, to any person obtaining a copy 18 | of this software and associated documentation files (the "Software"), to deal 19 | in the Software without restriction, including without limitation the rights 20 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | copies of the Software, and to permit persons to whom the Software is 22 | furnished to do so, subject to the following conditions: 23 | 24 | The above copyright notice and this permission notice shall be included in 25 | all copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 33 | THE SOFTWARE. 34 | Generated by CocoaPods - http://cocoapods.org 35 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-PleaseBaoMe/Pods-PleaseBaoMe-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | If you are using FMDB in your project, I'd love to hear about it. Let Gus know 18 | by sending an email to gus@flyingmeat.com. 19 | 20 | And if you happen to come across either Gus Mueller or Rob Ryan in a bar, you 21 | might consider purchasing a drink of their choosing if FMDB has been useful to 22 | you. 23 | 24 | Finally, and shortly, this is the MIT License. 25 | 26 | Copyright (c) 2008-2014 Flying Meat Inc. 27 | 28 | Permission is hereby granted, free of charge, to any person obtaining a copy 29 | of this software and associated documentation files (the "Software"), to deal 30 | in the Software without restriction, including without limitation the rights 31 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 32 | copies of the Software, and to permit persons to whom the Software is 33 | furnished to do so, subject to the following conditions: 34 | 35 | The above copyright notice and this permission notice shall be included in 36 | all copies or substantial portions of the Software. 37 | 38 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 39 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 40 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 41 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 42 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 43 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 44 | THE SOFTWARE. 45 | Title 46 | FMDB 47 | Type 48 | PSGroupSpecifier 49 | 50 | 51 | FooterText 52 | Generated by CocoaPods - http://cocoapods.org 53 | Title 54 | 55 | Type 56 | PSGroupSpecifier 57 | 58 | 59 | StringsTable 60 | Acknowledgements 61 | Title 62 | Acknowledgements 63 | 64 | 65 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-PleaseBaoMe/Pods-PleaseBaoMe-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_PleaseBaoMe : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_PleaseBaoMe 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-PleaseBaoMe/Pods-PleaseBaoMe-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | realpath() { 12 | DIRECTORY="$(cd "${1%/*}" && pwd)" 13 | FILENAME="${1##*/}" 14 | echo "$DIRECTORY/$FILENAME" 15 | } 16 | 17 | install_resource() 18 | { 19 | case $1 in 20 | *.storyboard) 21 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .storyboard`.storyboardc ${PODS_ROOT}/$1 --sdk ${SDKROOT}" 22 | ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .storyboard`.storyboardc" "${PODS_ROOT}/$1" --sdk "${SDKROOT}" 23 | ;; 24 | *.xib) 25 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .xib`.nib ${PODS_ROOT}/$1 --sdk ${SDKROOT}" 26 | ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .xib`.nib" "${PODS_ROOT}/$1" --sdk "${SDKROOT}" 27 | ;; 28 | *.framework) 29 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 30 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 31 | echo "rsync -av ${PODS_ROOT}/$1 ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 32 | rsync -av "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 33 | ;; 34 | *.xcdatamodel) 35 | echo "xcrun momc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1"`.mom\"" 36 | xcrun momc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodel`.mom" 37 | ;; 38 | *.xcdatamodeld) 39 | echo "xcrun momc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodeld`.momd\"" 40 | xcrun momc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodeld`.momd" 41 | ;; 42 | *.xcmappingmodel) 43 | echo "xcrun mapc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcmappingmodel`.cdm\"" 44 | xcrun mapc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcmappingmodel`.cdm" 45 | ;; 46 | *.xcassets) 47 | ABSOLUTE_XCASSET_FILE=$(realpath "${PODS_ROOT}/$1") 48 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 49 | ;; 50 | /*) 51 | echo "$1" 52 | echo "$1" >> "$RESOURCES_TO_COPY" 53 | ;; 54 | *) 55 | echo "${PODS_ROOT}/$1" 56 | echo "${PODS_ROOT}/$1" >> "$RESOURCES_TO_COPY" 57 | ;; 58 | esac 59 | } 60 | 61 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 62 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 63 | if [[ "${ACTION}" == "install" ]]; then 64 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 65 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 66 | fi 67 | rm -f "$RESOURCES_TO_COPY" 68 | 69 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 70 | then 71 | case "${TARGETED_DEVICE_FAMILY}" in 72 | 1,2) 73 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 74 | ;; 75 | 1) 76 | TARGET_DEVICE_ARGS="--target-device iphone" 77 | ;; 78 | 2) 79 | TARGET_DEVICE_ARGS="--target-device ipad" 80 | ;; 81 | *) 82 | TARGET_DEVICE_ARGS="--target-device mac" 83 | ;; 84 | esac 85 | 86 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 87 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 88 | while read line; do 89 | if [[ $line != "`realpath $PODS_ROOT`*" ]]; then 90 | XCASSET_FILES+=("$line") 91 | fi 92 | done <<<"$OTHER_XCASSETS" 93 | 94 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${IPHONEOS_DEPLOYMENT_TARGET}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 95 | fi 96 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-PleaseBaoMe/Pods-PleaseBaoMe.debug.xcconfig: -------------------------------------------------------------------------------- 1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 2 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/FMDB" 3 | OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/FMDB" 4 | OTHER_LDFLAGS = $(inherited) -ObjC -l"FMDB" -l"sqlite3" 5 | PODS_ROOT = ${SRCROOT}/Pods -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-PleaseBaoMe/Pods-PleaseBaoMe.release.xcconfig: -------------------------------------------------------------------------------- 1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 2 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/FMDB" 3 | OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/FMDB" 4 | OTHER_LDFLAGS = $(inherited) -ObjC -l"FMDB" -l"sqlite3" 5 | PODS_ROOT = ${SRCROOT}/Pods -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PleaseBaoMe 2 | 3 | A useful tool to view SQLite file in Web browser during app running procedure. 4 | 5 | ![](http://ww1.sinaimg.cn/large/61d238c7gw1etx1ugxqphj20sd0fdtd4.jpg) 6 | 7 | ## Why it's called PleaseBaoMe? 8 | 9 | Viewing SQLite file in iOS developing brings a variety of inconvenience. Therefore, a visible and effective tool to view SQLite file is terriblydemanded. 10 | 11 | Thanks [CocoaHTTPServer](https://github.com/robbiehanson/CocoaHTTPServer) for making this idea come true. Thanks Tonny for fixing grammar mistakes in README.md. Thanks my friend [Mr.Bao](https://github.com/baoyongzhang), who has enlightened me to use DynamicServer to handle request. 12 | 13 | So this project is named after him for showing gratitude. 14 | 15 | In Chinese, 'Bao' means 'Hug', and represents purity and happiness. 16 | 17 | ## Installation 18 | 19 | There are three ways to use PleaseBaoMe in your project: 20 | 21 | - using Cocoapods 22 | - copying all the files into your project 23 | 24 | ### With Cocoapods 25 | 26 | Podfile: 27 | 28 | pod "PleaseBaoMe" 29 | 30 | 31 | ### Copying all the files 32 | 33 | All needed files are in "Classes" folder. And all dependencies are in "FMDB" folder. You can copy them into your project to import PleaseBaoMe. 34 | 35 | ## How To Use 36 | 37 | You can see the sample code in Demo project. 38 | 39 | ### Step1: start 40 | 41 | You can start the serve by using: 42 | 43 | [PBMTool start]; 44 | 45 | ### Step2: setup 46 | 47 | Then please set file path for your database file: 48 | 49 | [PBMTool setDBFilePath:writableDBPath]; 50 | 51 | ### Step3: finish 52 | 53 | Run your app, you will see the following log in console: 54 | 55 | You can see you SQLite in 192.168.xxx.xx:12345 56 | 57 | Copy and paste the address to your browser's address bar and press enter key, you will see the entire database in your browser. 58 | 59 | 60 | ## Function 61 | 62 | ### Input SQL in URL 63 | 64 | You can input your SQL query in the URI just like this: 65 | 66 | http://192.168.xxx.xx:12345/SELECT * from test_table 67 | 68 | ### Convenient Filter 69 | 70 | You can set TableName、 LIMIT、 OFFSET in navigation bar easily. 71 | 72 | 73 | # License 74 | MIT --------------------------------------------------------------------------------