├── .gitignore ├── CHANGELOG.md ├── Classes ├── OCFWebServer.h ├── OCFWebServer.m ├── OCFWebServerConnection.h ├── OCFWebServerConnection.m ├── OCFWebServerPrivate.h ├── OCFWebServerRequest.h ├── OCFWebServerRequest.m ├── OCFWebServerRequest_Types.h ├── OCFWebServerResponse.h └── OCFWebServerResponse.m ├── LICENSE ├── OCFWebServer ├── OCFWebServer.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── OCFWebServer │ ├── OCFWebServer-Info.plist │ ├── OCFWebServer-Prefix.pch │ └── en.lproj │ │ └── InfoPlist.strings └── OCFWebServerTests │ ├── OCFWebServerTests-Info.plist │ ├── OCFWebServerTests.m │ └── en.lproj │ └── InfoPlist.strings └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | 19 | #CocoaPods 20 | Pods 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # OCFWebServer CHANGELOG 2 | 3 | ## 0.1.0 4 | 5 | Initial release. 6 | -------------------------------------------------------------------------------- /Classes/OCFWebServer.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file belongs to the OCFWebServer project. OCFWebServer is a fork of GCDWebServer (originally developed by 3 | Pierre-Olivier Latour). We have forked GCDWebServer because we made extensive and incompatible changes to it. 4 | To find out more have a look at README.md. 5 | 6 | Copyright (c) 2013, Christian Kienle / chris@objective-cloud.com 7 | All rights reserved. 8 | 9 | Original Copyright Statement: 10 | Copyright (c) 2012-2013, Pierre-Olivier Latour 11 | All rights reserved. 12 | 13 | Redistribution and use in source and binary forms, with or without 14 | modification, are permitted provided that the following conditions are met: 15 | * Redistributions of source code must retain the above copyright 16 | notice, this list of conditions and the following disclaimer. 17 | * Redistributions in binary form must reproduce the above copyright 18 | notice, this list of conditions and the following disclaimer in the 19 | documentation and/or other materials provided with the distribution. 20 | * Neither the name of the nor the 21 | names of its contributors may be used to endorse or promote products 22 | derived from this software without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 25 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 26 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 28 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | 36 | #import "OCFWebServerRequest_Types.h" 37 | 38 | @class OCFWebServerRequest; 39 | 40 | typedef OCFWebServerRequest*(^OCFWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery); 41 | typedef void(^OCFWebServerProcessBlock)(OCFWebServerRequest* request); 42 | 43 | @interface OCFWebServer : NSObject 44 | 45 | #pragma mark - Properties 46 | @property (nonatomic, assign, readonly, getter=isRunning) BOOL running; 47 | @property (nonatomic, assign, readonly) NSUInteger port; 48 | @property (nonatomic, assign, readonly) NSUInteger maxPendingConnections; // default: 16 49 | 50 | #pragma mark - OCFWebServer 51 | - (void)addHandlerWithMatchBlock:(OCFWebServerMatchBlock)matchBlock processBlock:(OCFWebServerProcessBlock)processBlock; 52 | - (void)removeAllHandlers; 53 | 54 | - (BOOL)start; // Default is 8080 port and computer name 55 | - (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name; // Pass nil name to disable Bonjour or empty string to use computer name 56 | - (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name maxPendingConnections:(NSUInteger)maxPendingConnections; 57 | - (void)stop; 58 | 59 | @end 60 | 61 | @interface OCFWebServer (Subclassing) 62 | + (Class)connectionClass; 63 | + (NSString*)serverName; // Default is class name 64 | @end 65 | 66 | @interface OCFWebServer (Extensions) 67 | - (BOOL)runWithPort:(NSUInteger)port; // Starts then automatically stops on SIGINT i.e. Ctrl-C (use on main thread only) 68 | @end 69 | 70 | @interface OCFWebServer (Handlers) 71 | - (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)class processBlock:(OCFWebServerProcessBlock)block; 72 | - (void)addHandlerForBasePath:(NSString*)basePath localPath:(NSString*)localPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge; // Base path is recursive and case-sensitive 73 | - (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)class processBlock:(OCFWebServerProcessBlock)block; // Path is case-insensitive 74 | - (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)class processBlock:(OCFWebServerProcessBlock)block; // Regular expression is case-insensitive 75 | @end 76 | -------------------------------------------------------------------------------- /Classes/OCFWebServer.m: -------------------------------------------------------------------------------- 1 | /* 2 | This file belongs to the OCFWebServer project. OCFWebServer is a fork of GCDWebServer (originally developed by 3 | Pierre-Olivier Latour). We have forked GCDWebServer because we made extensive and incompatible changes to it. 4 | To find out more have a look at README.md. 5 | 6 | Copyright (c) 2013, Christian Kienle / chris@objective-cloud.com 7 | All rights reserved. 8 | 9 | Original Copyright Statement: 10 | Copyright (c) 2012-2013, Pierre-Olivier Latour 11 | All rights reserved. 12 | 13 | Redistribution and use in source and binary forms, with or without 14 | modification, are permitted provided that the following conditions are met: 15 | * Redistributions of source code must retain the above copyright 16 | notice, this list of conditions and the following disclaimer. 17 | * Redistributions in binary form must reproduce the above copyright 18 | notice, this list of conditions and the following disclaimer in the 19 | documentation and/or other materials provided with the distribution. 20 | * Neither the name of the nor the 21 | names of its contributors may be used to endorse or promote products 22 | derived from this software without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 25 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 26 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 28 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | 36 | #import 37 | #if TARGET_OS_IPHONE 38 | #import 39 | #endif 40 | 41 | #import 42 | 43 | #import "OCFWebServerPrivate.h" 44 | #import "OCFWebServerRequest.h" 45 | #import "OCFWebServerResponse.h" 46 | 47 | static BOOL _run; 48 | 49 | NSString* OCFWebServerGetMimeTypeForExtension(NSString* extension) { 50 | static NSDictionary* _overrides = nil; 51 | if (_overrides == nil) { 52 | _overrides = @{@"css": @"text/css"}; 53 | } 54 | NSString* mimeType = nil; 55 | extension = [extension lowercaseString]; 56 | if (extension.length) { 57 | mimeType = _overrides[extension]; 58 | if (mimeType == nil) { 59 | CFStringRef cfExtension = CFBridgingRetain(extension); 60 | CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, cfExtension, NULL); 61 | CFRelease(cfExtension); 62 | if (uti) { 63 | mimeType = (id)CFBridgingRelease(UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)); 64 | CFRelease(uti); 65 | } 66 | } 67 | } 68 | return mimeType; 69 | } 70 | 71 | NSString* OCFWebServerUnescapeURLString(NSString* string) { 72 | return (id)CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""), 73 | kCFStringEncodingUTF8)); 74 | } 75 | 76 | NSDictionary* OCFWebServerParseURLEncodedForm(NSString* form) { 77 | NSMutableDictionary* parameters = [NSMutableDictionary dictionary]; 78 | NSScanner* scanner = [[NSScanner alloc] initWithString:form]; 79 | [scanner setCharactersToBeSkipped:nil]; 80 | while (1) { 81 | NSString* key = nil; 82 | if (![scanner scanUpToString:@"=" intoString:&key] || [scanner isAtEnd]) { 83 | break; 84 | } 85 | [scanner setScanLocation:([scanner scanLocation] + 1)]; 86 | 87 | NSString* value = nil; 88 | if (![scanner scanUpToString:@"&" intoString:&value]) { 89 | break; 90 | } 91 | 92 | key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "]; 93 | value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "]; 94 | parameters[OCFWebServerUnescapeURLString(key)] = OCFWebServerUnescapeURLString(value); 95 | 96 | if ([scanner isAtEnd]) { 97 | break; 98 | } 99 | [scanner setScanLocation:([scanner scanLocation] + 1)]; 100 | } 101 | return parameters; 102 | } 103 | 104 | static void _SignalHandler(int signal) { 105 | _run = NO; 106 | printf("\n"); 107 | } 108 | 109 | 110 | @interface OCFWebServerHandler () 111 | 112 | #pragma mark - Properties 113 | @property(nonatomic, copy, readwrite) OCFWebServerMatchBlock matchBlock; 114 | @property(nonatomic, copy, readwrite) OCFWebServerProcessBlock processBlock; 115 | 116 | @end 117 | 118 | @implementation OCFWebServerHandler 119 | 120 | #pragma mark - Creating 121 | - (instancetype)initWithMatchBlock:(OCFWebServerMatchBlock)matchBlock processBlock:(OCFWebServerProcessBlock)processBlock { 122 | self = [super init]; 123 | if(self) { 124 | self.matchBlock = matchBlock; 125 | self.processBlock = processBlock; 126 | } 127 | return self; 128 | } 129 | 130 | 131 | @end 132 | 133 | @interface OCFWebServer () 134 | 135 | #pragma mark - Properties 136 | @property (nonatomic, readwrite) NSUInteger port; 137 | @property (nonatomic, strong) dispatch_source_t source; 138 | @property (nonatomic, assign) CFNetServiceRef service; 139 | @property (nonatomic, strong) NSMutableArray *connections; 140 | @end 141 | 142 | @implementation OCFWebServer { 143 | NSMutableArray *_handlers; 144 | } 145 | 146 | #pragma mark - Properties 147 | - (void)setHandlers:(NSArray *)handlers { 148 | _handlers = [handlers mutableCopy]; 149 | } 150 | 151 | - (NSArray *)handlers { 152 | return [_handlers copy]; 153 | } 154 | 155 | + (void)initialize { 156 | [OCFWebServerConnection class]; // Initialize class immediately to make sure it happens on the main thread 157 | } 158 | 159 | #pragma mark - Creating 160 | - (instancetype)init { 161 | self = [super init]; 162 | if(self) { 163 | self.handlers = @[]; 164 | self.connections = [NSMutableArray new]; 165 | [self setupHeaderLogging]; 166 | } 167 | return self; 168 | } 169 | 170 | - (void)setupHeaderLogging { 171 | NSProcessInfo *processInfo = [NSProcessInfo processInfo]; 172 | NSDictionary *environment = processInfo.environment; 173 | NSString *headerLoggingEnabledString = environment[@"OCFWS_HEADER_LOGGING_ENABLED"]; 174 | if(headerLoggingEnabledString == nil) { 175 | self.headerLoggingEnabled = NO; 176 | return; 177 | } 178 | if([headerLoggingEnabledString.uppercaseString isEqualToString:@"YES"]) { 179 | self.headerLoggingEnabled = YES; 180 | return; 181 | } 182 | self.headerLoggingEnabled = NO; 183 | } 184 | 185 | #pragma mark - NSObject 186 | - (void)dealloc { 187 | if (self.source) { 188 | [self stop]; 189 | } 190 | } 191 | 192 | #pragma mark - OCFWebServer 193 | - (void)addHandlerWithMatchBlock:(OCFWebServerMatchBlock)matchBlock processBlock:(OCFWebServerProcessBlock)handlerBlock { 194 | DCHECK(self.source == NULL); 195 | OCFWebServerHandler *handler = [[OCFWebServerHandler alloc] initWithMatchBlock:matchBlock processBlock:handlerBlock]; 196 | [_handlers insertObject:handler atIndex:0]; 197 | } 198 | 199 | - (void)removeAllHandlers { 200 | DCHECK(self.source == NULL); 201 | [_handlers removeAllObjects]; 202 | } 203 | 204 | - (BOOL)start { 205 | return [self startWithPort:8080 bonjourName:@""]; 206 | } 207 | 208 | static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* error, void* info) { 209 | @autoreleasepool { 210 | if (error->error) { 211 | LOG_ERROR(@"Bonjour error %i (domain %i)", error->error, (int)error->domain); 212 | } else { 213 | LOG_VERBOSE(@"Registered Bonjour service \"%@\" with type '%@' on port %i", CFNetServiceGetName(service), CFNetServiceGetType(service), CFNetServiceGetPortNumber(service)); 214 | } 215 | } 216 | } 217 | 218 | - (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString *)name { 219 | return [self startWithPort:port bonjourName:name maxPendingConnections:16]; 220 | } 221 | 222 | - (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name maxPendingConnections:(NSUInteger)maxPendingConnections { 223 | DCHECK(self.source == NULL); 224 | if (maxPendingConnections > SOMAXCONN) { 225 | // We could truncate maxPendingConnections to SOMAXCONN here but let's not do this. listen(int, int) does that internally already. 226 | // This should be more future proof but we want to let the developer know about that. 227 | LOG_WARNING(@"Max. number of pending connections was set to %i. The kernel truncates this value to %i to be aware of that (see ‘$ man listen' for details)."); 228 | } 229 | self.maxPendingConnections = maxPendingConnections; 230 | 231 | int listeningSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); 232 | if (listeningSocket > 0) { 233 | int yes = 1; 234 | setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); 235 | setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes)); 236 | 237 | struct sockaddr_in addr4; 238 | bzero(&addr4, sizeof(addr4)); 239 | addr4.sin_len = sizeof(addr4); 240 | addr4.sin_family = AF_INET; 241 | addr4.sin_port = htons(port); 242 | addr4.sin_addr.s_addr = htonl(INADDR_ANY); 243 | if (bind(listeningSocket, (void*)&addr4, sizeof(addr4)) == 0) { 244 | if (listen(listeningSocket, (int)self.maxPendingConnections) == 0) { 245 | self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, kOCFWebServerGCDQueue); 246 | dispatch_source_set_cancel_handler(self.source, ^{ 247 | @autoreleasepool { 248 | int result = close(listeningSocket); 249 | if (result != 0) { 250 | LOG_ERROR(@"Failed closing socket (%i): %s", errno, strerror(errno)); 251 | } else { 252 | LOG_DEBUG(@"Closed listening socket"); 253 | } 254 | } 255 | }); 256 | dispatch_source_set_event_handler(self.source, ^{ 257 | 258 | @autoreleasepool { 259 | struct sockaddr addr; 260 | socklen_t addrlen = sizeof(addr); 261 | int socket = accept(listeningSocket, &addr, &addrlen); 262 | if (socket > 0) { 263 | int yes = 1; 264 | setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &yes, sizeof(yes)); // Make sure this socket cannot generate SIG_PIPE 265 | 266 | NSData* data = [NSData dataWithBytes:&addr length:addrlen]; 267 | Class connectionClass = [[self class] connectionClass]; 268 | OCFWebServerConnection *connection = [[connectionClass alloc] initWithServer:self address:data socket:socket]; 269 | @synchronized(_connections) { 270 | [self.connections addObject:connection]; 271 | LOG_DEBUG(@"%lu number of connections", self.connections.count); 272 | } 273 | __typeof__(connection) __weak weakConnection = connection; 274 | [connection openWithCompletionHandler:^{ 275 | @synchronized(_connections) { 276 | if(weakConnection != nil) { 277 | [self.connections removeObject:weakConnection]; 278 | LOG_DEBUG(@"%lu number of connections", self.connections.count); 279 | } 280 | } 281 | }]; 282 | } else { 283 | LOG_ERROR(@"Failed accepting socket (%i): %s", errno, strerror(errno)); 284 | } 285 | } 286 | 287 | }); 288 | 289 | if (port == 0) { // Determine the actual port we are listening on 290 | struct sockaddr addr; 291 | socklen_t addrlen = sizeof(addr); 292 | if (getsockname(listeningSocket, &addr, &addrlen) == 0) { 293 | struct sockaddr_in* sockaddr = (struct sockaddr_in*)&addr; 294 | _port = ntohs(sockaddr->sin_port); 295 | } else { 296 | LOG_ERROR(@"Failed retrieving socket address (%i): %s", errno, strerror(errno)); 297 | } 298 | } else { 299 | _port = port; 300 | } 301 | 302 | if (name) { 303 | CFStringRef cfName = CFBridgingRetain(name); 304 | _service = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), CFSTR("_http._tcp"), cfName, (SInt32)_port); 305 | CFRelease(cfName); 306 | if (_service) { 307 | CFNetServiceClientContext context = {0, (__bridge void *)(self), NULL, NULL, NULL}; 308 | CFNetServiceSetClient(_service, _NetServiceClientCallBack, &context); 309 | CFNetServiceScheduleWithRunLoop(_service, CFRunLoopGetMain(), kCFRunLoopCommonModes); 310 | CFStreamError error = {0}; 311 | CFNetServiceRegisterWithOptions(_service, 0, &error); 312 | } else { 313 | LOG_ERROR(@"Failed creating CFNetService"); 314 | } 315 | } 316 | 317 | dispatch_resume(self.source); 318 | LOG_VERBOSE(@"%@ started on port %i", [self class], (int)_port); 319 | } else { 320 | LOG_ERROR(@"Failed listening on socket (%i): %s", errno, strerror(errno)); 321 | close(listeningSocket); 322 | } 323 | } else { 324 | LOG_ERROR(@"Failed binding socket (%i): %s", errno, strerror(errno)); 325 | close(listeningSocket); 326 | } 327 | } else { 328 | LOG_ERROR(@"Failed creating socket (%i): %s", errno, strerror(errno)); 329 | } 330 | return (self.source ? YES : NO); 331 | } 332 | 333 | - (BOOL)isRunning { 334 | return (self.source ? YES : NO); 335 | } 336 | 337 | - (void)stop { 338 | DCHECK(self.source != nil); 339 | if (self.source) { 340 | if (self.service) { 341 | CFNetServiceUnscheduleFromRunLoop(self.service, CFRunLoopGetMain(), kCFRunLoopCommonModes); 342 | CFNetServiceSetClient(self.service, NULL, NULL); 343 | CFRelease(self.service); 344 | self.service = NULL; 345 | } 346 | 347 | dispatch_source_cancel(self.source); // This will close the socket 348 | self.source = nil; 349 | LOG_VERBOSE(@"%@ stopped", [self class]); 350 | } 351 | self.port = 0; 352 | } 353 | 354 | @end 355 | 356 | @implementation OCFWebServer (Subclassing) 357 | 358 | + (Class)connectionClass { 359 | return [OCFWebServerConnection class]; 360 | } 361 | 362 | + (NSString *)serverName { 363 | return NSStringFromClass(self); 364 | } 365 | 366 | @end 367 | 368 | @implementation OCFWebServer (Extensions) 369 | 370 | - (BOOL)runWithPort:(NSUInteger)port { 371 | BOOL success = NO; 372 | _run = YES; 373 | void* handler = signal(SIGINT, _SignalHandler); 374 | if (handler != SIG_ERR) { 375 | if ([self startWithPort:port bonjourName:@""]) { 376 | while (_run) { 377 | CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, true); 378 | } 379 | [self stop]; 380 | success = YES; 381 | } 382 | signal(SIGINT, handler); 383 | } 384 | return success; 385 | } 386 | 387 | @end 388 | 389 | @implementation OCFWebServer (Handlers) 390 | 391 | - (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)class processBlock:(OCFWebServerProcessBlock)block { 392 | [self addHandlerWithMatchBlock:^OCFWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) { 393 | return [[class alloc] initWithMethod:requestMethod URL:requestURL headers:requestHeaders path:urlPath query:urlQuery]; 394 | } processBlock:block]; 395 | } 396 | 397 | - (OCFWebServerResponse*)_responseWithContentsOfFile:(NSString*)path { 398 | return [OCFWebServerFileResponse responseWithFile:path]; 399 | } 400 | 401 | - (OCFWebServerResponse*)_responseWithContentsOfDirectory:(NSString*)path { 402 | NSDirectoryEnumerator* enumerator = [[NSFileManager defaultManager] enumeratorAtPath:path]; 403 | if (enumerator == nil) { 404 | return nil; 405 | } 406 | NSMutableString* html = [NSMutableString string]; 407 | [html appendString:@"\n"]; 408 | [html appendString:@"
    \n"]; 409 | for (NSString* file in enumerator) { 410 | if (![file hasPrefix:@"."]) { 411 | NSString* type = [enumerator fileAttributes][NSFileType]; 412 | NSString* escapedFile = [file stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 413 | DCHECK(escapedFile); 414 | if ([type isEqualToString:NSFileTypeRegular]) { 415 | [html appendFormat:@"
  • %@
  • \n", escapedFile, file]; 416 | } else if ([type isEqualToString:NSFileTypeDirectory]) { 417 | [html appendFormat:@"
  • %@/
  • \n", escapedFile, file]; 418 | } 419 | } 420 | [enumerator skipDescendents]; 421 | } 422 | [html appendString:@"
\n"]; 423 | [html appendString:@"\n"]; 424 | return [OCFWebServerDataResponse responseWithHTML:html]; 425 | } 426 | 427 | - (void)addHandlerForBasePath:(NSString*)basePath localPath:(NSString*)localPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge { 428 | __typeof__(self) __weak weakSelf = self; 429 | if ([basePath hasPrefix:@"/"] && [basePath hasSuffix:@"/"]) { 430 | [self addHandlerWithMatchBlock:^OCFWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) { 431 | 432 | if (![requestMethod isEqualToString:@"GET"]) { 433 | return nil; 434 | } 435 | if (![urlPath hasPrefix:basePath]) { 436 | return nil; 437 | } 438 | return [[OCFWebServerRequest alloc] initWithMethod:requestMethod URL:requestURL headers:requestHeaders path:urlPath query:urlQuery]; 439 | 440 | } processBlock:^(OCFWebServerRequest* request) { 441 | OCFWebServerResponse* response = nil; 442 | NSString* filePath = [localPath stringByAppendingPathComponent:[request.path substringFromIndex:basePath.length]]; 443 | BOOL isDirectory; 444 | if ([[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory]) { 445 | if (isDirectory) { 446 | if (indexFilename) { 447 | NSString* indexPath = [filePath stringByAppendingPathComponent:indexFilename]; 448 | if ([[NSFileManager defaultManager] fileExistsAtPath:indexPath isDirectory:&isDirectory] && !isDirectory) { 449 | response = [weakSelf _responseWithContentsOfFile:indexPath]; 450 | } 451 | } 452 | response = [weakSelf _responseWithContentsOfDirectory:filePath]; 453 | } else { 454 | response = [weakSelf _responseWithContentsOfFile:filePath]; 455 | } 456 | } 457 | if (response) { 458 | response.cacheControlMaxAge = cacheAge; 459 | } else { 460 | response = [OCFWebServerResponse responseWithStatusCode:404]; 461 | } 462 | [request respondWith:response]; 463 | }]; 464 | } else { 465 | DNOT_REACHED(); 466 | } 467 | } 468 | 469 | - (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)class processBlock:(OCFWebServerProcessBlock)block { 470 | if ([path hasPrefix:@"/"] && [class isSubclassOfClass:[OCFWebServerRequest class]]) { 471 | [self addHandlerWithMatchBlock:^OCFWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) { 472 | 473 | if (![requestMethod isEqualToString:method]) { 474 | return nil; 475 | } 476 | if ([urlPath caseInsensitiveCompare:path] != NSOrderedSame) { 477 | return nil; 478 | } 479 | return [[class alloc] initWithMethod:requestMethod URL:requestURL headers:requestHeaders path:urlPath query:urlQuery]; 480 | 481 | } processBlock:block]; 482 | } else { 483 | DNOT_REACHED(); 484 | } 485 | } 486 | 487 | - (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)class processBlock:(OCFWebServerProcessBlock)block { 488 | NSRegularExpression* expression = [NSRegularExpression regularExpressionWithPattern:regex options:NSRegularExpressionCaseInsensitive error:NULL]; 489 | if (expression && [class isSubclassOfClass:[OCFWebServerRequest class]]) { 490 | [self addHandlerWithMatchBlock:^OCFWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) { 491 | 492 | if (![requestMethod isEqualToString:method]) { 493 | return nil; 494 | } 495 | if ([expression firstMatchInString:urlPath options:0 range:NSMakeRange(0, urlPath.length)] == nil) { 496 | return nil; 497 | } 498 | return [[class alloc] initWithMethod:requestMethod URL:requestURL headers:requestHeaders path:urlPath query:urlQuery]; 499 | 500 | } processBlock:block]; 501 | } else { 502 | DNOT_REACHED(); 503 | } 504 | } 505 | 506 | @end 507 | -------------------------------------------------------------------------------- /Classes/OCFWebServerConnection.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file belongs to the OCFWebServer project. OCFWebServer is a fork of GCDWebServer (originally developed by 3 | Pierre-Olivier Latour). We have forked GCDWebServer because we made extensive and incompatible changes to it. 4 | To find out more have a look at README.md. 5 | 6 | Copyright (c) 2013, Christian Kienle / chris@objective-cloud.com 7 | All rights reserved. 8 | 9 | Original Copyright Statement: 10 | Copyright (c) 2012-2013, Pierre-Olivier Latour 11 | All rights reserved. 12 | 13 | Redistribution and use in source and binary forms, with or without 14 | modification, are permitted provided that the following conditions are met: 15 | * Redistributions of source code must retain the above copyright 16 | notice, this list of conditions and the following disclaimer. 17 | * Redistributions in binary form must reproduce the above copyright 18 | notice, this list of conditions and the following disclaimer in the 19 | documentation and/or other materials provided with the distribution. 20 | * Neither the name of the nor the 21 | names of its contributors may be used to endorse or promote products 22 | derived from this software without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 25 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 26 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 28 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | #import "OCFWebServer.h" 36 | 37 | @class OCFWebServerHandler; 38 | 39 | typedef void(^OCFWebServerConnectionCompletionHandler)(void); 40 | 41 | @interface OCFWebServerConnection : NSObject 42 | 43 | #pragma mark - Properties 44 | @property(nonatomic, weak, readonly) OCFWebServer *server; 45 | @property(nonatomic, copy, readonly) NSData *address; // struct sockaddr 46 | @property(nonatomic, readonly) NSUInteger totalBytesRead; 47 | @property(nonatomic, readonly) NSUInteger totalBytesWritten; 48 | 49 | @end 50 | 51 | @interface OCFWebServerConnection (Subclassing) 52 | - (void)openWithCompletionHandler:(OCFWebServerConnectionCompletionHandler)completionHandler; 53 | - (void)close; 54 | @end 55 | -------------------------------------------------------------------------------- /Classes/OCFWebServerConnection.m: -------------------------------------------------------------------------------- 1 | /* 2 | This file belongs to the OCFWebServer project. OCFWebServer is a fork of GCDWebServer (originally developed by 3 | Pierre-Olivier Latour). We have forked GCDWebServer because we made extensive and incompatible changes to it. 4 | To find out more have a look at README.md. 5 | 6 | Copyright (c) 2013, Christian Kienle / chris@objective-cloud.com 7 | All rights reserved. 8 | 9 | Original Copyright Statement: 10 | Copyright (c) 2012-2013, Pierre-Olivier Latour 11 | All rights reserved. 12 | 13 | Redistribution and use in source and binary forms, with or without 14 | modification, are permitted provided that the following conditions are met: 15 | * Redistributions of source code must retain the above copyright 16 | notice, this list of conditions and the following disclaimer. 17 | * Redistributions in binary form must reproduce the above copyright 18 | notice, this list of conditions and the following disclaimer in the 19 | documentation and/or other materials provided with the distribution. 20 | * Neither the name of the nor the 21 | names of its contributors may be used to endorse or promote products 22 | derived from this software without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 25 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 26 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 28 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | 36 | #import "OCFWebServerPrivate.h" 37 | #import "OCFWebServerRequest.h" 38 | #import "OCFWebServerResponse.h" 39 | 40 | #define kHeadersReadBuffer 1024 41 | #define kBodyWriteBufferSize (32 * 1024) 42 | 43 | typedef void (^ReadBufferCompletionBlock)(dispatch_data_t buffer); 44 | typedef void (^ReadDataCompletionBlock)(NSData* data); 45 | typedef void (^ReadHeadersCompletionBlock)(NSData* extraData); 46 | typedef void (^ReadBodyCompletionBlock)(BOOL success); 47 | 48 | typedef void (^WriteBufferCompletionBlock)(BOOL success); 49 | typedef void (^WriteDataCompletionBlock)(BOOL success); 50 | typedef void (^WriteHeadersCompletionBlock)(BOOL success); 51 | typedef void (^WriteBodyCompletionBlock)(BOOL success); 52 | 53 | static NSData* _separatorData = nil; 54 | static NSData* _continueData = nil; 55 | static NSDateFormatter* _dateFormatter = nil; 56 | static dispatch_queue_t _formatterQueue = NULL; 57 | 58 | @interface OCFWebServerConnection () 59 | 60 | #pragma mark - Properties 61 | @property (nonatomic, weak, readwrite) OCFWebServer* server; 62 | @property (nonatomic, copy, readwrite) NSData *address; // struct sockaddr 63 | @property (nonatomic, readwrite) NSUInteger totalBytesRead; 64 | @property (nonatomic, readwrite) NSUInteger totalBytesWritten; 65 | @property (nonatomic, assign) CFSocketNativeHandle socket; 66 | @property (nonatomic, assign) CFHTTPMessageRef requestMessage; 67 | @property (nonatomic, strong) OCFWebServerRequest *request; 68 | @property (nonatomic, strong) OCFWebServerHandler *handler; 69 | @property (nonatomic, assign) CFHTTPMessageRef responseMessage; 70 | @property (nonatomic, strong) OCFWebServerResponse *response; 71 | @property (nonatomic, copy) OCFWebServerConnectionCompletionHandler completionHandler; 72 | 73 | @end 74 | 75 | @implementation OCFWebServerConnection (Read) 76 | 77 | - (void)_readBufferWithLength:(NSUInteger)length completionBlock:(ReadBufferCompletionBlock)block { 78 | dispatch_read(self.socket, length, kOCFWebServerGCDQueue, ^(dispatch_data_t buffer, int error) { 79 | @autoreleasepool 80 | { 81 | if (error == 0) { 82 | size_t size = dispatch_data_get_size(buffer); 83 | if (size > 0) { 84 | LOG_DEBUG(@"Connection received %i bytes on socket %i", size, self.socket); 85 | self.totalBytesRead = self.totalBytesRead + size; 86 | block(buffer); 87 | } else { 88 | if (self.totalBytesRead > 0) { 89 | LOG_ERROR(@"No more data available on socket %i", self.socket); 90 | } else { 91 | LOG_WARNING(@"No data received from socket %i", self.socket); 92 | } 93 | block(NULL); 94 | } 95 | } else { 96 | LOG_ERROR(@"Error while reading from socket %i: %s (%i)", self.socket, strerror(error), error); 97 | block(NULL); 98 | } 99 | } 100 | }); 101 | } 102 | 103 | - (void)_readDataWithCompletionBlock:(ReadDataCompletionBlock)block { 104 | [self _readBufferWithLength:SIZE_T_MAX completionBlock:^(dispatch_data_t buffer) { 105 | if(buffer) { 106 | NSMutableData* data = [[NSMutableData alloc] initWithCapacity:dispatch_data_get_size(buffer)]; 107 | dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t offset, const void* buffer, size_t size) { 108 | [data appendBytes:buffer length:size]; 109 | return true; 110 | }); 111 | block(data); 112 | } else { 113 | block(nil); 114 | } 115 | }]; 116 | } 117 | 118 | - (void)_readHeadersWithCompletionBlock:(ReadHeadersCompletionBlock)block { 119 | DCHECK(self.requestMessage); 120 | [self _readBufferWithLength:SIZE_T_MAX completionBlock:^(dispatch_data_t buffer) { 121 | if(buffer) { 122 | NSMutableData* data = [NSMutableData dataWithCapacity:kHeadersReadBuffer]; 123 | dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t offset, const void* buffer, size_t size) { 124 | [data appendBytes:buffer length:size]; 125 | return true; 126 | }); 127 | NSRange range = [data rangeOfData:_separatorData options:0 range:NSMakeRange(0, data.length)]; 128 | if (range.location == NSNotFound) { 129 | if (CFHTTPMessageAppendBytes(self.requestMessage, data.bytes, data.length)) { 130 | [self _readHeadersWithCompletionBlock:block]; 131 | } else { 132 | LOG_ERROR(@"Failed appending request headers data from socket %i", self.socket); 133 | block(nil); 134 | } 135 | } else { 136 | NSUInteger length = range.location + range.length; 137 | if (CFHTTPMessageAppendBytes(self.requestMessage, data.bytes, length)) { 138 | if (CFHTTPMessageIsHeaderComplete(self.requestMessage)) { 139 | block([data subdataWithRange:NSMakeRange(length, data.length - length)]); 140 | } else { 141 | LOG_ERROR(@"Failed parsing request headers from socket %i", self.socket); 142 | block(nil); 143 | } 144 | } else { 145 | LOG_ERROR(@"Failed appending request headers data from socket %i", self.socket); 146 | block(nil); 147 | } 148 | } 149 | } else { 150 | block(nil); 151 | } 152 | }]; 153 | } 154 | 155 | - (void)_readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block { 156 | DCHECK([self.request hasBody]); 157 | [self _readBufferWithLength:length completionBlock:^(dispatch_data_t buffer) { 158 | 159 | if (buffer) { 160 | NSInteger remainingLength = length - dispatch_data_get_size(buffer); 161 | if (remainingLength >= 0) { 162 | bool success = dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t offset, const void* buffer, size_t size) { 163 | NSInteger result = [self.request write:buffer maxLength:size]; 164 | if (result != size) { 165 | LOG_ERROR(@"Failed writing request body on socket %i (error %i)", self.socket, (int)result); 166 | return false; 167 | } 168 | return true; 169 | }); 170 | if (success) { 171 | if (remainingLength > 0) { 172 | [self _readBodyWithRemainingLength:remainingLength completionBlock:block]; 173 | } else { 174 | block(YES); 175 | } 176 | } else { 177 | block(NO); 178 | } 179 | } else { 180 | DNOT_REACHED(); 181 | block(NO); 182 | } 183 | } else { 184 | block(NO); 185 | } 186 | 187 | }]; 188 | } 189 | 190 | @end 191 | 192 | @implementation OCFWebServerConnection (Write) 193 | 194 | - (void)_writeBuffer:(dispatch_data_t)buffer withCompletionBlock:(WriteBufferCompletionBlock)block { 195 | size_t size = dispatch_data_get_size(buffer); 196 | dispatch_write(self.socket, buffer, kOCFWebServerGCDQueue, ^(dispatch_data_t data, int error) { 197 | @autoreleasepool { 198 | if (error == 0) { 199 | DCHECK(data == NULL); 200 | LOG_DEBUG(@"Connection sent %i bytes on socket %i", size, self.socket); 201 | self.totalBytesWritten = self.totalBytesWritten + size; 202 | block(YES); 203 | } else { 204 | LOG_ERROR(@"Error while writing to socket %i: %s (%i)", self.socket, strerror(error), error); 205 | block(NO); 206 | } 207 | } 208 | }); 209 | } 210 | 211 | - (void)_writeData:(NSData *)data withCompletionBlock:(WriteDataCompletionBlock)block { 212 | // Remarks by Christian: 213 | // data is either the serialized HTTP header or the serialized "continue" delimiter (\n\n). 214 | // If data is the serialized HTTP header then ARC wants to release this value at some point. 215 | // If we are not using data before the end of this scope ARC will release data. 216 | // Then data.bytes will become invalid and buffer will work with garbage. 217 | // We could work around this problem by having the serialized header as a ivar. 218 | // I have decided to not do that. Instead I am simply passing DISPATCH_DATA_DESTRUCTOR_DEFAULT as 219 | // the destructor block which causes dispatch_data_create to copy data.bytes immediately. 220 | // This is not so bad because data is usually very small (a header or HTTP continue). 221 | 222 | dispatch_data_t buffer = dispatch_data_create(data.bytes, data.length, kOCFWebServerGCDQueue, DISPATCH_DATA_DESTRUCTOR_DEFAULT); 223 | [self _writeBuffer:buffer withCompletionBlock:block]; 224 | } 225 | 226 | - (void)_writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block { 227 | DCHECK(self.responseMessage); 228 | CFDataRef message = CFHTTPMessageCopySerializedMessage(self.responseMessage); 229 | NSData *data = (__bridge_transfer NSData *)message; 230 | [self _writeData:data withCompletionBlock:block]; 231 | } 232 | 233 | - (void)_writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block { 234 | DCHECK([self.response hasBody]); 235 | void *buffer = malloc(kBodyWriteBufferSize); 236 | NSInteger result = [self.response read:buffer maxLength:kBodyWriteBufferSize]; 237 | if (result > 0) { 238 | dispatch_data_t wrapper = dispatch_data_create(buffer, result, kOCFWebServerGCDQueue, ^(){ 239 | free(buffer); 240 | }); 241 | [self _writeBuffer:wrapper withCompletionBlock:^(BOOL success) { 242 | if (success) { 243 | [self _writeBodyWithCompletionBlock:block]; 244 | } else { 245 | block(NO); 246 | } 247 | }]; 248 | } else if (result < 0) { 249 | LOG_ERROR(@"Failed reading response body on socket %i (error %i)", self.socket, (int)result); 250 | block(NO); 251 | free(buffer); 252 | } else { 253 | block(YES); 254 | free(buffer); 255 | } 256 | } 257 | 258 | @end 259 | 260 | @implementation OCFWebServerConnection 261 | 262 | + (void)initialize { 263 | DCHECK([NSThread isMainThread]); // NSDateFormatter should be initialized on main thread 264 | if (_separatorData == nil) { 265 | _separatorData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4]; 266 | DCHECK(_separatorData); 267 | } 268 | if (_continueData == nil) { 269 | CFHTTPMessageRef message = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 100, NULL, kCFHTTPVersion1_1); 270 | _continueData = (NSData*)CFBridgingRelease(CFHTTPMessageCopySerializedMessage(message)); 271 | CFRelease(message); 272 | DCHECK(_continueData); 273 | } 274 | if (_dateFormatter == nil) { 275 | _dateFormatter = [[NSDateFormatter alloc] init]; 276 | _dateFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; 277 | _dateFormatter.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'"; 278 | _dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; 279 | DCHECK(_dateFormatter); 280 | } 281 | if (_formatterQueue == NULL) { 282 | _formatterQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); 283 | DCHECK(_formatterQueue); 284 | } 285 | } 286 | 287 | - (void)_initializeResponseHeadersWithStatusCode:(NSInteger)statusCode { 288 | self.responseMessage = CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_1); 289 | CFHTTPMessageSetHeaderFieldValue(self.responseMessage, CFSTR("Connection"), CFSTR("Close")); 290 | CFHTTPMessageSetHeaderFieldValue(self.responseMessage, CFSTR("Server"), (__bridge CFStringRef)[[self.server class] serverName]); 291 | dispatch_sync(_formatterQueue, ^{ 292 | NSString* date = [_dateFormatter stringFromDate:[NSDate date]]; 293 | CFStringRef cfDate = (CFStringRef)CFBridgingRetain(date); 294 | CFHTTPMessageSetHeaderFieldValue(self.responseMessage, CFSTR("Date"), cfDate); 295 | CFRelease(cfDate); 296 | }); 297 | } 298 | 299 | - (void)_abortWithStatusCode:(NSUInteger)statusCode { 300 | DCHECK(self.responseMessage == NULL); 301 | DCHECK((statusCode >= 400) && (statusCode < 600)); 302 | [self _initializeResponseHeadersWithStatusCode:statusCode]; 303 | [self _writeHeadersWithCompletionBlock:^(BOOL success) { 304 | [self close]; 305 | }]; 306 | LOG_DEBUG(@"Connection aborted with status code %i on socket %i", statusCode, self.socket); 307 | } 308 | 309 | // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 310 | // This method is already called on the dispatch queue of the web server so there is no need to dispatch again. 311 | - (void)_processRequest { 312 | DCHECK(self.responseMessage == NULL); 313 | @try { 314 | __typeof__(self) __weak weakSelf = self; 315 | self.request.responseBlock = ^(OCFWebServerResponse *response) { 316 | if (![response hasBody] || [response open]) { 317 | weakSelf.response = response; 318 | } 319 | if (weakSelf.response) { 320 | [weakSelf _initializeResponseHeadersWithStatusCode:weakSelf.response.statusCode]; 321 | NSUInteger maxAge = weakSelf.response.cacheControlMaxAge; 322 | if (maxAge > 0) { 323 | CFHTTPMessageSetHeaderFieldValue(weakSelf.responseMessage, CFSTR("Cache-Control"), (__bridge CFStringRef)[NSString stringWithFormat:@"max-age=%i, public", (int)maxAge]); 324 | } else { 325 | CFHTTPMessageSetHeaderFieldValue(weakSelf.responseMessage, CFSTR("Cache-Control"), CFSTR("no-cache")); 326 | } 327 | [weakSelf.response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL* stop) { 328 | CFHTTPMessageSetHeaderFieldValue(weakSelf.responseMessage, (__bridge CFStringRef)(key), (__bridge CFStringRef)(obj)); 329 | }]; 330 | 331 | if ([weakSelf.response hasBody]) { 332 | CFHTTPMessageSetHeaderFieldValue(weakSelf.responseMessage, CFSTR("Content-Type"), (__bridge CFStringRef)weakSelf.response.contentType); 333 | CFHTTPMessageSetHeaderFieldValue(weakSelf.responseMessage, CFSTR("Content-Length"), (__bridge CFStringRef)[NSString stringWithFormat:@"%i", (int)weakSelf.response.contentLength]); 334 | } 335 | [weakSelf _writeHeadersWithCompletionBlock:^(BOOL success) { 336 | if (success) { 337 | if ([weakSelf.response hasBody]) { 338 | [weakSelf _writeBodyWithCompletionBlock:^(BOOL success) { 339 | [weakSelf.response close]; // Can't do anything with result anyway 340 | [weakSelf close]; 341 | }]; 342 | } 343 | } else if ([weakSelf.response hasBody]) { 344 | [weakSelf.response close]; // Can't do anything with result anyway 345 | [weakSelf close]; 346 | } 347 | }]; 348 | } else { 349 | [weakSelf _abortWithStatusCode:500]; 350 | } 351 | }; 352 | self.handler.processBlock(self.request); 353 | } 354 | @catch (NSException* exception) { 355 | LOG_EXCEPTION(exception); 356 | [self _abortWithStatusCode:500]; 357 | } 358 | @finally { 359 | 360 | } 361 | } 362 | 363 | - (void)_readRequestBody:(NSData*)initialData { 364 | if ([self.request open]) { 365 | NSInteger length = self.request.contentLength; 366 | if (initialData.length) { 367 | NSInteger result = [self.request write:initialData.bytes maxLength:initialData.length]; 368 | if (result == initialData.length) { 369 | length -= initialData.length; 370 | DCHECK(length >= 0); 371 | } else { 372 | LOG_ERROR(@"Failed writing request body on socket %i (error %i)", self.socket, (int)result); 373 | length = -1; 374 | } 375 | } 376 | if (length > 0) { 377 | [self _readBodyWithRemainingLength:length completionBlock:^(BOOL success) { 378 | 379 | if (![self.request close]) { 380 | success = NO; 381 | } 382 | if (success) { 383 | [self _processRequest]; 384 | } else { 385 | [self _abortWithStatusCode:500]; 386 | } 387 | 388 | }]; 389 | } else if (length == 0) { 390 | if ([self.request close]) { 391 | [self _processRequest]; 392 | } else { 393 | [self _abortWithStatusCode:500]; 394 | } 395 | } else { 396 | [self.request close]; // Can't do anything with result anyway 397 | [self _abortWithStatusCode:500]; 398 | } 399 | } else { 400 | [self _abortWithStatusCode:500]; 401 | } 402 | } 403 | 404 | - (void)_readRequestHeaders { 405 | self.requestMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true); 406 | [self _readHeadersWithCompletionBlock:^(NSData* extraData) { 407 | if (extraData) { 408 | NSString* requestMethod = [(id)CFBridgingRelease(CFHTTPMessageCopyRequestMethod(self.requestMessage)) uppercaseString]; 409 | DCHECK(requestMethod); 410 | NSURL* requestURL = (id)CFBridgingRelease(CFHTTPMessageCopyRequestURL(self.requestMessage)); 411 | DCHECK(requestURL); 412 | NSString* requestPath = OCFWebServerUnescapeURLString((id)CFBridgingRelease(CFURLCopyPath((CFURLRef)requestURL))); // Don't use -[NSURL path] which strips the ending slash 413 | if(requestPath == nil) { 414 | requestPath = @"/"; 415 | } 416 | DCHECK(requestPath); 417 | NSDictionary* requestQuery = nil; 418 | NSString* queryString = (id)CFBridgingRelease(CFURLCopyQueryString((CFURLRef)requestURL, NULL)); // Don't use -[NSURL query] to make sure query is not unescaped; 419 | if (queryString.length) { 420 | requestQuery = OCFWebServerParseURLEncodedForm(queryString); 421 | DCHECK(requestQuery); 422 | } 423 | NSDictionary* requestHeaders = (id)CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(self.requestMessage)); 424 | DCHECK(requestHeaders); 425 | for (OCFWebServerHandler *handler in self.server.handlers) { 426 | self.request = handler.matchBlock(requestMethod, requestURL, requestHeaders, requestPath, requestQuery); 427 | if (self.request) { 428 | self.handler = handler; 429 | break; 430 | } 431 | } 432 | if (self.request) { 433 | if (self.request.hasBody) { 434 | if (extraData.length <= self.request.contentLength) { 435 | NSString* expectHeader = (id)CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(self.requestMessage, CFSTR("Expect"))); 436 | if (expectHeader) { 437 | if ([expectHeader caseInsensitiveCompare:@"100-continue"] == NSOrderedSame) { 438 | [self _writeData:_continueData withCompletionBlock:^(BOOL success) { 439 | if (success) { 440 | [self _readRequestBody:extraData]; 441 | } 442 | }]; 443 | } else { 444 | LOG_ERROR(@"Unsupported 'Expect' / 'Content-Length' header combination on socket %i", self.socket); 445 | [self _abortWithStatusCode:417]; 446 | } 447 | } else { 448 | [self _readRequestBody:extraData]; 449 | } 450 | } else { 451 | LOG_ERROR(@"Unexpected 'Content-Length' header value on socket %i", self.socket); 452 | [self _abortWithStatusCode:400]; 453 | } 454 | } else { 455 | [self _processRequest]; 456 | } 457 | } else { 458 | [self _abortWithStatusCode:405]; 459 | } 460 | } else { 461 | [self _abortWithStatusCode:500]; 462 | } 463 | }]; 464 | } 465 | 466 | - (instancetype)initWithServer:(OCFWebServer *)server address:(NSData *)address socket:(CFSocketNativeHandle)socket { 467 | if((self = [super init])) { 468 | self.totalBytesRead = 0; 469 | self.totalBytesWritten = 0; 470 | self.server = server; 471 | self.address = address; 472 | self.socket = socket; 473 | } 474 | return self; 475 | } 476 | 477 | - (void)dealloc { 478 | if(self.requestMessage) { 479 | CFRelease(self.requestMessage); 480 | } 481 | if(self.responseMessage) { 482 | CFRelease(self.responseMessage); 483 | } 484 | } 485 | 486 | @end 487 | 488 | @implementation OCFWebServerConnection (Subclassing) 489 | 490 | - (void)openWithCompletionHandler:(OCFWebServerConnectionCompletionHandler)completionHandler { 491 | LOG_DEBUG(@"Did open connection on socket %i", self.socket); 492 | self.completionHandler = completionHandler; 493 | [self _readRequestHeaders]; 494 | } 495 | 496 | - (void)close { 497 | int result = close(self.socket); 498 | if (result != 0) { 499 | LOG_ERROR(@"Failed closing socket %i for connection (%i): %s", self.socket, errno, strerror(errno)); 500 | } 501 | LOG_DEBUG(@"Did close connection on socket %i", self.socket); 502 | self.completionHandler ? self.completionHandler() : nil; 503 | } 504 | 505 | @end 506 | -------------------------------------------------------------------------------- /Classes/OCFWebServerPrivate.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file belongs to the OCFWebServer project. OCFWebServer is a fork of GCDWebServer (originally developed by 3 | Pierre-Olivier Latour). We have forked GCDWebServer because we made extensive and incompatible changes to it. 4 | To find out more have a look at README.md. 5 | 6 | Copyright (c) 2013, Christian Kienle / chris@objective-cloud.com 7 | All rights reserved. 8 | 9 | Original Copyright Statement: 10 | Copyright (c) 2012-2013, Pierre-Olivier Latour 11 | All rights reserved. 12 | 13 | Redistribution and use in source and binary forms, with or without 14 | modification, are permitted provided that the following conditions are met: 15 | * Redistributions of source code must retain the above copyright 16 | notice, this list of conditions and the following disclaimer. 17 | * Redistributions in binary form must reproduce the above copyright 18 | notice, this list of conditions and the following disclaimer in the 19 | documentation and/or other materials provided with the distribution. 20 | * Neither the name of the nor the 21 | names of its contributors may be used to endorse or promote products 22 | derived from this software without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 25 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 26 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 28 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | 36 | #import "OCFWebServerConnection.h" 37 | 38 | #ifdef __GCDWEBSERVER_LOGGING_HEADER__ 39 | 40 | // Define __GCDWEBSERVER_LOGGING_HEADER__ as a preprocessor constant to redirect GCDWebServer logging to your own system 41 | #import __GCDWEBSERVER_LOGGING_HEADER__ 42 | 43 | #else 44 | 45 | static inline void __LogMessage(long level, NSString* format, ...) { 46 | static const char* levelNames[] = {"DEBUG", "VERBOSE", "INFO", "WARNING", "ERROR", "EXCEPTION"}; 47 | static long minLevel = -1; 48 | if (minLevel < 0) { 49 | const char* logLevel = getenv("logLevel"); 50 | minLevel = logLevel ? atoi(logLevel) : 0; 51 | } 52 | if (level >= minLevel) { 53 | va_list arguments; 54 | va_start(arguments, format); 55 | NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments]; 56 | va_end(arguments); 57 | printf("[%s] %s\n", levelNames[level], [message UTF8String]); 58 | } 59 | } 60 | 61 | #define LOG_VERBOSE(...) __LogMessage(1, __VA_ARGS__) 62 | #define LOG_INFO(...) __LogMessage(2, __VA_ARGS__) 63 | #define LOG_WARNING(...) __LogMessage(3, __VA_ARGS__) 64 | #define LOG_ERROR(...) __LogMessage(4, __VA_ARGS__) 65 | #define LOG_EXCEPTION(__EXCEPTION__) __LogMessage(5, @"%@", __EXCEPTION__) 66 | 67 | #ifdef NDEBUG 68 | 69 | #define DCHECK(__CONDITION__) 70 | #define DNOT_REACHED() 71 | #define LOG_DEBUG(...) 72 | 73 | #else 74 | 75 | #define DCHECK(__CONDITION__) \ 76 | do { \ 77 | if (!(__CONDITION__)) { \ 78 | abort(); \ 79 | } \ 80 | } while (0) 81 | #define DNOT_REACHED() abort() 82 | #define LOG_DEBUG(...) __LogMessage(0, __VA_ARGS__) 83 | 84 | #endif 85 | 86 | #endif 87 | 88 | #define kOCFWebServerDefaultMimeType @"application/octet-stream" 89 | #define kOCFWebServerGCDQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 90 | 91 | #ifdef __cplusplus 92 | extern "C" { 93 | #endif 94 | 95 | NSString* OCFWebServerGetMimeTypeForExtension(NSString* extension); 96 | NSString* OCFWebServerUnescapeURLString(NSString* string); 97 | NSDictionary* OCFWebServerParseURLEncodedForm(NSString* form); 98 | 99 | #ifdef __cplusplus 100 | } 101 | #endif 102 | 103 | 104 | @interface OCFWebServerConnection () 105 | - (instancetype)initWithServer:(OCFWebServer *)server address:(NSData *)address socket:(CFSocketNativeHandle)socket; 106 | @end 107 | 108 | @interface OCFWebServer () 109 | 110 | #pragma mark - Properties 111 | @property (nonatomic, copy, readonly) NSArray* handlers; 112 | @property (nonatomic, assign, readwrite) NSUInteger maxPendingConnections; 113 | @property (assign, readwrite, setter = setHeaderLoggingEnabled:) BOOL headerLoggingEnabled; 114 | 115 | @end 116 | 117 | @interface OCFWebServerHandler : NSObject 118 | @property(nonatomic, copy, readonly) OCFWebServerMatchBlock matchBlock; 119 | @property(nonatomic, copy, readonly) OCFWebServerProcessBlock processBlock; 120 | - (id)initWithMatchBlock:(OCFWebServerMatchBlock)matchBlock processBlock:(OCFWebServerProcessBlock)processBlock; 121 | @end 122 | -------------------------------------------------------------------------------- /Classes/OCFWebServerRequest.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file belongs to the OCFWebServer project. OCFWebServer is a fork of GCDWebServer (originally developed by 3 | Pierre-Olivier Latour). We have forked GCDWebServer because we made extensive and incompatible changes to it. 4 | To find out more have a look at README.md. 5 | 6 | Copyright (c) 2013, Christian Kienle / chris@objective-cloud.com 7 | All rights reserved. 8 | 9 | Original Copyright Statement: 10 | Copyright (c) 2012-2013, Pierre-Olivier Latour 11 | All rights reserved. 12 | 13 | Redistribution and use in source and binary forms, with or without 14 | modification, are permitted provided that the following conditions are met: 15 | * Redistributions of source code must retain the above copyright 16 | notice, this list of conditions and the following disclaimer. 17 | * Redistributions in binary form must reproduce the above copyright 18 | notice, this list of conditions and the following disclaimer in the 19 | documentation and/or other materials provided with the distribution. 20 | * Neither the name of the nor the 21 | names of its contributors may be used to endorse or promote products 22 | derived from this software without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 25 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 26 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 28 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | 36 | #import 37 | #import "OCFWebServerRequest_Types.h" 38 | 39 | @class OCFWebServerResponse; 40 | 41 | @interface OCFWebServerRequest : NSObject 42 | 43 | #pragma mark - Properties 44 | @property(nonatomic, copy, readonly) NSString *method; 45 | @property(nonatomic, copy, readonly) NSURL *URL; 46 | @property(nonatomic, copy, readonly) NSDictionary *headers; 47 | @property(nonatomic, copy, readonly) NSString *path; 48 | @property(nonatomic, copy, readonly) NSDictionary *query; // May be nil 49 | @property(nonatomic, copy, readonly) NSString *contentType; // Automatically parsed from headers (nil if request has no body) 50 | @property(nonatomic, readonly) NSUInteger contentLength; // Automatically parsed from headers 51 | 52 | #pragma mark - Creating 53 | - (instancetype)initWithMethod:(NSString *)method URL:(NSURL *)url headers:(NSDictionary *)headers path:(NSString *)path query:(NSDictionary *)query; 54 | - (BOOL)hasBody; // Convenience method 55 | 56 | #pragma mark - Responding 57 | - (void)respondWith:(OCFWebServerResponse *)response; 58 | @property (nonatomic, copy) OCFWebServerResponseBlock responseBlock; 59 | 60 | @end 61 | 62 | @interface OCFWebServerRequest (Subclassing) 63 | - (BOOL)open; // Implementation required 64 | - (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length; // Implementation required 65 | - (BOOL)close; // Implementation required 66 | @end 67 | 68 | @interface OCFWebServerDataRequest : OCFWebServerRequest 69 | 70 | #pragma mark - Properties 71 | @property(nonatomic, copy, readonly) NSData *data; // Only valid after open / write / close sequence 72 | 73 | @end 74 | 75 | @interface OCFWebServerFileRequest : OCFWebServerRequest 76 | 77 | #pragma mark - Properties 78 | @property(nonatomic, copy, readonly) NSString *filePath; // Only valid after open / write / close sequence 79 | 80 | @end 81 | 82 | @interface OCFWebServerURLEncodedFormRequest : OCFWebServerDataRequest 83 | 84 | #pragma mark - Properties 85 | @property(nonatomic, copy, readonly) NSDictionary* arguments; // Only valid after open / write / close sequence 86 | 87 | #pragma mark - Global Stuff 88 | + (NSString *)mimeType; 89 | 90 | @end 91 | 92 | @interface OCFWebServerMultiPart : NSObject 93 | 94 | #pragma mark - Properties 95 | @property(nonatomic, copy, readonly) NSString *contentType; // May be nil 96 | @property(nonatomic, copy, readonly) NSString *mimeType; // Defaults to "text/plain" per specifications if undefined 97 | @end 98 | 99 | @interface OCFWebServerMultiPartArgument : OCFWebServerMultiPart 100 | 101 | #pragma mark - Properties 102 | @property(nonatomic, copy, readonly) NSData *data; 103 | @property(nonatomic, copy, readonly) NSString *string; // May be nil (only valid for text mime types 104 | 105 | @end 106 | 107 | @interface OCFWebServerMultiPartFile : OCFWebServerMultiPart 108 | 109 | #pragma mark - Properties 110 | @property(nonatomic, copy, readonly) NSString *fileName; // May be nil 111 | @property(nonatomic, copy, readonly) NSString *temporaryPath; 112 | 113 | @end 114 | 115 | @interface OCFWebServerMultiPartFormRequest : OCFWebServerRequest 116 | 117 | #pragma mark - Properties 118 | @property (nonatomic, copy, readonly) NSData *data; // Only valid after open / write / close sequence 119 | @property (nonatomic, copy, readonly) NSDictionary *arguments; // Only valid after open / write / close sequence 120 | @property (nonatomic, copy, readonly) NSDictionary *files; // Only valid after open / write / close sequence 121 | 122 | #pragma mark - Global Stuff 123 | + (NSString *)mimeType; 124 | 125 | @end 126 | -------------------------------------------------------------------------------- /Classes/OCFWebServerRequest.m: -------------------------------------------------------------------------------- 1 | /* 2 | This file belongs to the OCFWebServer project. OCFWebServer is a fork of GCDWebServer (originally developed by 3 | Pierre-Olivier Latour). We have forked GCDWebServer because we made extensive and incompatible changes to it. 4 | To find out more have a look at README.md. 5 | 6 | Copyright (c) 2013, Christian Kienle / chris@objective-cloud.com 7 | All rights reserved. 8 | 9 | Original Copyright Statement: 10 | Copyright (c) 2012-2013, Pierre-Olivier Latour 11 | All rights reserved. 12 | 13 | Redistribution and use in source and binary forms, with or without 14 | modification, are permitted provided that the following conditions are met: 15 | * Redistributions of source code must retain the above copyright 16 | notice, this list of conditions and the following disclaimer. 17 | * Redistributions in binary form must reproduce the above copyright 18 | notice, this list of conditions and the following disclaimer in the 19 | documentation and/or other materials provided with the distribution. 20 | * Neither the name of the nor the 21 | names of its contributors may be used to endorse or promote products 22 | derived from this software without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 25 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 26 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 28 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | 36 | #import "OCFWebServerPrivate.h" 37 | #import "OCFWebServerRequest.h" 38 | 39 | #define kMultiPartBufferSize (256 * 1024) 40 | 41 | typedef NS_ENUM(NSUInteger, OCFWebServerParserState) { 42 | OCFWebServerParserStateUndefined, 43 | OCFWebServerParserStateStart, 44 | OCFWebServerParserStateHeaders, 45 | OCFWebServerParserStateContent, 46 | OCFWebServerParserStateEnd 47 | }; 48 | 49 | static NSData* _newlineData = nil; 50 | static NSData* _newlinesData = nil; 51 | static NSData* _dashNewlineData = nil; 52 | 53 | static NSString* _ExtractHeaderParameter(NSString* header, NSString* attribute) { 54 | NSString* value = nil; 55 | if (header) { 56 | NSScanner* scanner = [[NSScanner alloc] initWithString:header]; 57 | NSString* string = [NSString stringWithFormat:@"%@=", attribute]; 58 | if ([scanner scanUpToString:string intoString:NULL]) { 59 | [scanner scanString:string intoString:NULL]; 60 | if ([scanner scanString:@"\"" intoString:NULL]) { 61 | [scanner scanUpToString:@"\"" intoString:&value]; 62 | } else { 63 | [scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&value]; 64 | } 65 | } 66 | } 67 | return value; 68 | } 69 | 70 | // http://www.w3schools.com/tags/ref_charactersets.asp 71 | static NSStringEncoding _StringEncodingFromCharset(NSString* charset) { 72 | NSStringEncoding encoding = kCFStringEncodingInvalidId; 73 | if (charset) { 74 | encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)charset)); 75 | } 76 | return (encoding != kCFStringEncodingInvalidId ? encoding : NSUTF8StringEncoding); 77 | } 78 | 79 | @interface OCFWebServerRequest () 80 | 81 | #pragma mark - Properties 82 | @property(nonatomic, copy, readwrite) NSString* method; 83 | @property(nonatomic, copy, readwrite) NSURL* URL; 84 | @property(nonatomic, copy, readwrite) NSDictionary* headers; 85 | @property(nonatomic, copy, readwrite) NSString* path; 86 | @property(nonatomic, copy, readwrite) NSDictionary* query; // May be nil 87 | @property(nonatomic, copy, readwrite) NSString* contentType; 88 | @property(nonatomic, readwrite) NSUInteger contentLength; // Automatically parsed from headers 89 | 90 | @end 91 | 92 | @implementation OCFWebServerRequest : NSObject 93 | 94 | #pragma mark - Creating 95 | - (instancetype)initWithMethod:(NSString*)method URL:(NSURL*)URL headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { 96 | if((self = [super init])) { 97 | self.method = method; 98 | self.URL = URL; 99 | self.headers = headers; 100 | self.path = path; 101 | self.query = query; 102 | 103 | self.contentType = self.headers[@"Content-Type"]; 104 | NSString *contentLengthString = self.headers[@"Content-Length"]; 105 | if(contentLengthString == nil) { 106 | LOG_DEBUG(@"Request has no content length."); 107 | // FIXME: Check RFC. This does not seem to be correct. 108 | // As far I know it is okay for requests to not 109 | // have a content length header value at all. 110 | self.contentLength = 0; 111 | } else { 112 | // FIXME: Validate contents of contentLengthString: A malformed content length value 113 | // may have bad side effects. 114 | NSInteger length = [contentLengthString integerValue]; 115 | if(length < 0) { 116 | DNOT_REACHED(); 117 | return nil; 118 | } 119 | self.contentLength = length; 120 | } 121 | 122 | if((self.contentLength > 0) && (self.contentType == nil)) { 123 | self.contentType = kOCFWebServerDefaultMimeType; 124 | } 125 | } 126 | return self; 127 | } 128 | 129 | - (BOOL)hasBody { 130 | return (self.contentType != nil ? YES : NO); 131 | } 132 | 133 | #pragma mark - Responding 134 | - (void)respondWith:(OCFWebServerResponse *)response { 135 | self.responseBlock ? self.responseBlock(response) : nil; 136 | } 137 | 138 | @end 139 | 140 | @implementation OCFWebServerRequest (Subclassing) 141 | 142 | - (BOOL)open { 143 | [self doesNotRecognizeSelector:_cmd]; 144 | return NO; 145 | } 146 | 147 | - (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length { 148 | [self doesNotRecognizeSelector:_cmd]; 149 | return -1; 150 | } 151 | 152 | - (BOOL)close { 153 | [self doesNotRecognizeSelector:_cmd]; 154 | return NO; 155 | } 156 | 157 | @end 158 | 159 | @interface OCFWebServerDataRequest () 160 | 161 | #pragma mark - Properties 162 | @property(nonatomic, copy, readwrite) NSData *data; // Only valid after open / write / close sequence 163 | 164 | @end 165 | 166 | @implementation OCFWebServerDataRequest { 167 | NSMutableData* _data; 168 | } 169 | 170 | #pragma mark - Properties 171 | - (NSData *)data { 172 | return [_data copy]; 173 | } 174 | 175 | - (void)setData:(NSData *)data { 176 | _data = [data mutableCopy]; 177 | } 178 | 179 | - (void)dealloc { 180 | DCHECK(self.data != nil); 181 | } 182 | 183 | - (BOOL)open { 184 | DCHECK(self.data == nil); 185 | self.data = [NSMutableData dataWithCapacity:self.contentLength]; 186 | return _data ? YES : NO; 187 | } 188 | 189 | - (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length { 190 | DCHECK(_data != nil); 191 | [_data appendBytes:buffer length:length]; 192 | return length; 193 | } 194 | 195 | - (BOOL)close { 196 | DCHECK(_data != nil); 197 | return YES; 198 | } 199 | 200 | @end 201 | 202 | 203 | @interface OCFWebServerFileRequest () 204 | 205 | #pragma mark - Properties 206 | @property (nonatomic, copy, readwrite) NSString *filePath; 207 | @property (nonatomic, assign) int file; 208 | 209 | @end 210 | 211 | @implementation OCFWebServerFileRequest 212 | 213 | #pragma mark - Creating 214 | - (instancetype)initWithMethod:(NSString*)method URL:(NSURL*)URL headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { 215 | if((self = [super initWithMethod:method URL:URL headers:headers path:path query:query])) { 216 | self.filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]]; 217 | self.file = 0; 218 | } 219 | return self; 220 | } 221 | 222 | - (void)dealloc { 223 | DCHECK(self.file < 0); 224 | unlink([self.filePath fileSystemRepresentation]); 225 | } 226 | 227 | - (BOOL)open { 228 | DCHECK(self.file == 0); 229 | self.file = open([self.filePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 230 | return (self.file > 0 ? YES : NO); 231 | } 232 | 233 | - (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length { 234 | DCHECK(self.file > 0); 235 | return write(self.file, buffer, length); 236 | } 237 | 238 | - (BOOL)close { 239 | DCHECK(self.file > 0); 240 | int result = close(self.file); 241 | self.file = -1; 242 | return (result == 0 ? YES : NO); 243 | } 244 | 245 | @end 246 | 247 | @interface OCFWebServerURLEncodedFormRequest () 248 | 249 | #pragma mark - Properties 250 | @property(nonatomic, copy, readwrite) NSDictionary *arguments; 251 | 252 | @end 253 | 254 | @implementation OCFWebServerURLEncodedFormRequest 255 | 256 | #pragma mark - Global Stuff 257 | + (NSString*)mimeType { 258 | return @"application/x-www-form-urlencoded"; 259 | } 260 | 261 | #pragma mark - OCFWebServerRequest 262 | - (BOOL)close { 263 | if (![super close]) { 264 | return NO; 265 | } 266 | 267 | NSString *charset = _ExtractHeaderParameter(self.contentType, @"charset"); 268 | NSString *string = [[NSString alloc] initWithData:self.data encoding:_StringEncodingFromCharset(charset)]; 269 | self.arguments = OCFWebServerParseURLEncodedForm(string); 270 | 271 | return (self.arguments ? YES : NO); 272 | } 273 | 274 | @end 275 | 276 | @interface OCFWebServerMultiPart () 277 | 278 | #pragma mark - Properties 279 | @property(nonatomic, copy, readwrite) NSString *contentType; 280 | @property(nonatomic, copy, readwrite) NSString *mimeType; 281 | 282 | @end 283 | 284 | @implementation OCFWebServerMultiPart 285 | 286 | #pragma mark - Creating 287 | - (instancetype)initWithContentType:(NSString*)contentType { 288 | if((self = [super init])) { 289 | self.contentType = contentType; 290 | NSArray *components = [self.contentType componentsSeparatedByString:@";"]; 291 | if(components.count > 0) { 292 | self.mimeType = [components[0] lowercaseString]; 293 | } 294 | if (self.mimeType == nil) { 295 | self.mimeType = @"text/plain"; 296 | } 297 | } 298 | return self; 299 | } 300 | 301 | @end 302 | 303 | @interface OCFWebServerMultiPartArgument () 304 | 305 | #pragma mark - Properties 306 | @property(nonatomic, copy, readwrite) NSData *data; 307 | @property(nonatomic, copy, readwrite) NSString *string; 308 | 309 | @end 310 | 311 | @implementation OCFWebServerMultiPartArgument 312 | 313 | #pragma mark - Creating 314 | - (instancetype)initWithContentType:(NSString*)contentType data:(NSData*)data { 315 | if((self = [super initWithContentType:contentType])) { 316 | self.data = data; 317 | if([self.mimeType hasPrefix:@"text/"]) { 318 | NSString* charset = _ExtractHeaderParameter(self.contentType, @"charset"); 319 | self.string = [[NSString alloc] initWithData:_data encoding:_StringEncodingFromCharset(charset)]; 320 | } 321 | } 322 | return self; 323 | } 324 | 325 | #pragma mark - NSObject 326 | - (NSString *)description { 327 | return [NSString stringWithFormat:@"<%@ | '%@' | %i bytes>", [self class], self.mimeType, (int)_data.length]; 328 | } 329 | 330 | @end 331 | 332 | @interface OCFWebServerMultiPartFile () 333 | 334 | #pragma mark - Properties 335 | @property(nonatomic, copy, readwrite) NSString *fileName; 336 | @property(nonatomic, copy, readwrite) NSString *temporaryPath; 337 | 338 | @end 339 | 340 | @implementation OCFWebServerMultiPartFile 341 | 342 | #pragma mark - Creating 343 | - (instancetype)initWithContentType:(NSString*)contentType fileName:(NSString*)fileName temporaryPath:(NSString*)temporaryPath { 344 | if((self = [super initWithContentType:contentType])) { 345 | self.fileName = fileName; 346 | self.temporaryPath = temporaryPath; 347 | } 348 | return self; 349 | } 350 | 351 | #pragma mark - NSObject 352 | - (void)dealloc { 353 | unlink([self.temporaryPath fileSystemRepresentation]); 354 | } 355 | 356 | - (NSString *)description { 357 | return [NSString stringWithFormat:@"<%@ | '%@' | '%@>'", [self class], self.mimeType, self.fileName]; 358 | } 359 | 360 | @end 361 | 362 | @interface OCFWebServerMultiPartFormRequest () 363 | 364 | #pragma mark - Properties 365 | @property (nonatomic, copy, readwrite) NSDictionary *arguments; 366 | @property (nonatomic, copy, readwrite) NSDictionary *files; 367 | @property (nonatomic, copy, readwrite) NSData *parserData; 368 | @property (nonatomic, copy) NSData *boundary; 369 | @property (nonatomic, assign) OCFWebServerParserState parserState; 370 | @property (nonatomic, copy) NSString *controlName; 371 | @property (nonatomic, copy) NSString *fileName; 372 | @property (nonatomic, copy) NSString *tmpPath; 373 | @property (nonatomic, assign) int tmpFile; 374 | @property (nonatomic, copy, readwrite) NSData *data; 375 | 376 | @end 377 | 378 | @implementation OCFWebServerMultiPartFormRequest { 379 | NSMutableDictionary *_arguments; 380 | NSMutableDictionary *_files; 381 | NSMutableData *_parserData; 382 | NSMutableData* _data; 383 | } 384 | 385 | #pragma mark - Properties 386 | - (void)setArguments:(NSDictionary *)arguments { 387 | _arguments = [arguments mutableCopy]; 388 | } 389 | 390 | - (NSDictionary *)arguments { 391 | return [_arguments copy]; 392 | } 393 | 394 | - (void)setFiles:(NSDictionary *)files { 395 | _files = [files mutableCopy]; 396 | } 397 | 398 | - (NSDictionary *)files { 399 | return [_files copy]; 400 | } 401 | 402 | - (void)setParserData:(NSData *)parserData { 403 | _parserData = [parserData mutableCopy]; 404 | } 405 | 406 | - (NSData *)parserData { 407 | return [_parserData copy]; 408 | } 409 | 410 | - (NSData *)data { 411 | return [_data copy]; 412 | } 413 | 414 | - (void)setData:(NSData *)data { 415 | _data = [data mutableCopy]; 416 | } 417 | 418 | #pragma mark - Creating 419 | - (instancetype)initWithMethod:(NSString*)method URL:(NSURL*)URL headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { 420 | if((self = [super initWithMethod:method URL:URL headers:headers path:path query:query])) { 421 | NSString *boundary = _ExtractHeaderParameter(self.contentType, @"boundary"); 422 | if(boundary) { 423 | self.boundary = [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding]; 424 | } 425 | if(self.boundary == nil) { 426 | DNOT_REACHED(); 427 | return nil; 428 | } 429 | self.arguments = @{}; 430 | self.files = @{}; 431 | self.parserState = OCFWebServerParserStateUndefined; 432 | } 433 | return self; 434 | } 435 | 436 | - (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length { 437 | DCHECK(_parserData != nil); 438 | [_parserData appendBytes:buffer length:length]; 439 | 440 | DCHECK(_data != nil); 441 | [_data appendBytes:buffer length:length]; 442 | return ([self _parseData] ? length : -1); 443 | } 444 | 445 | - (BOOL)open { 446 | DCHECK(self.parserData == nil); 447 | DCHECK(self.data == nil); 448 | self.data = [NSMutableData dataWithCapacity:self.contentLength]; 449 | 450 | self.parserData = [[NSMutableData alloc] initWithCapacity:kMultiPartBufferSize]; 451 | self.parserState = OCFWebServerParserStateStart; 452 | return YES; 453 | } 454 | 455 | // http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4 456 | - (BOOL)_parseData { 457 | BOOL success = YES; 458 | 459 | if (self.parserState == OCFWebServerParserStateHeaders) { 460 | NSRange range = [_parserData rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _parserData.length)]; 461 | if (range.location != NSNotFound) { 462 | 463 | self.controlName = nil; 464 | self.fileName = nil; 465 | self.contentType = nil; 466 | self.tmpPath = nil; 467 | CFHTTPMessageRef message = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true); 468 | const char* temp = "GET / HTTP/1.0\r\n"; 469 | CFHTTPMessageAppendBytes(message, (const UInt8*)temp, strlen(temp)); 470 | CFHTTPMessageAppendBytes(message, _parserData.bytes, range.location + range.length); 471 | if (CFHTTPMessageIsHeaderComplete(message)) { 472 | NSString* controlName = nil; 473 | NSString* fileName = nil; 474 | NSDictionary* headers = (id)CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(message)); 475 | NSString* contentDisposition = headers[@"Content-Disposition"]; 476 | if ([[contentDisposition lowercaseString] hasPrefix:@"form-data;"]) { 477 | controlName = _ExtractHeaderParameter(contentDisposition, @"name"); 478 | fileName = _ExtractHeaderParameter(contentDisposition, @"filename"); 479 | } 480 | self.controlName = controlName; 481 | self.fileName = fileName; 482 | self.contentType = headers[@"Content-Type"]; 483 | } 484 | CFRelease(message); 485 | if (self.controlName) { 486 | if (self.fileName) { 487 | NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; 488 | self.tmpFile = open([path fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 489 | if (self.tmpFile > 0) { 490 | self.tmpPath = path; 491 | } else { 492 | DNOT_REACHED(); 493 | success = NO; 494 | } 495 | } 496 | } else { 497 | DNOT_REACHED(); 498 | success = NO; 499 | } 500 | 501 | [_parserData replaceBytesInRange:NSMakeRange(0, range.location + range.length) withBytes:NULL length:0]; 502 | self.parserState = OCFWebServerParserStateContent; 503 | } 504 | } 505 | 506 | if ((self.parserState == OCFWebServerParserStateStart) || (self.parserState == OCFWebServerParserStateContent)) { 507 | NSRange range = [_parserData rangeOfData:_boundary options:0 range:NSMakeRange(0, _parserData.length)]; 508 | if (range.location != NSNotFound) { 509 | NSRange subRange = NSMakeRange(range.location + range.length, _parserData.length - range.location - range.length); 510 | NSRange subRange1 = [_parserData rangeOfData:_newlineData options:NSDataSearchAnchored range:subRange]; 511 | NSRange subRange2 = [_parserData rangeOfData:_dashNewlineData options:NSDataSearchAnchored range:subRange]; 512 | if ((subRange1.location != NSNotFound) || (subRange2.location != NSNotFound)) { 513 | 514 | if (self.parserState == OCFWebServerParserStateContent) { 515 | const void* dataBytes = _parserData.bytes; 516 | NSUInteger dataLength = range.location - 2; 517 | if (self.tmpPath) { 518 | ssize_t result = write(self.tmpFile, dataBytes, (size_t)dataLength); 519 | if (result == dataLength) { 520 | if (close(self.tmpFile) == 0) { 521 | self.tmpFile = 0; 522 | OCFWebServerMultiPartFile *file = [[OCFWebServerMultiPartFile alloc] initWithContentType:self.contentType fileName:self.fileName temporaryPath:self.tmpPath]; 523 | _files[self.controlName] = file; 524 | } else { 525 | DNOT_REACHED(); 526 | success = NO; 527 | } 528 | } else { 529 | DNOT_REACHED(); 530 | success = NO; 531 | } 532 | self.tmpPath = nil; 533 | } else { 534 | NSData *data = [[NSData alloc] initWithBytesNoCopy:(void*)dataBytes length:dataLength freeWhenDone:NO]; 535 | OCFWebServerMultiPartArgument *argument = [[OCFWebServerMultiPartArgument alloc] initWithContentType:self.contentType data:data]; 536 | _arguments[self.controlName] = argument; 537 | } 538 | } 539 | 540 | if (subRange1.location != NSNotFound) { 541 | [_parserData replaceBytesInRange:NSMakeRange(0, subRange1.location + subRange1.length) withBytes:NULL length:0]; 542 | self.parserState = OCFWebServerParserStateHeaders; 543 | success = [self _parseData]; 544 | } else { 545 | self.parserState = OCFWebServerParserStateEnd; 546 | } 547 | } 548 | } else { 549 | NSUInteger margin = 2 * self.boundary.length; 550 | if (self.tmpPath && (_parserData.length > margin)) { 551 | NSUInteger length = _parserData.length - margin; 552 | ssize_t result = write(self.tmpFile, _parserData.bytes, length); 553 | if (result == length) { 554 | [_parserData replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0]; 555 | } else { 556 | DNOT_REACHED(); 557 | success = NO; 558 | } 559 | } 560 | } 561 | } 562 | return success; 563 | } 564 | 565 | - (BOOL)close { 566 | DCHECK(_parserData != nil); 567 | self.parserData = nil; 568 | if (self.tmpFile > 0) { 569 | close(self.tmpFile); 570 | unlink([self.tmpPath fileSystemRepresentation]); 571 | } 572 | return (self.parserState == OCFWebServerParserStateEnd ? YES : NO); 573 | } 574 | 575 | #pragma mark - Global Stuff 576 | + (void)initialize { 577 | if (_newlineData == nil) { 578 | _newlineData = [[NSData alloc] initWithBytes:"\r\n" length:2]; 579 | DCHECK(_newlineData); 580 | } 581 | if (_newlinesData == nil) { 582 | _newlinesData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4]; 583 | DCHECK(_newlinesData); 584 | } 585 | if (_dashNewlineData == nil) { 586 | _dashNewlineData = [[NSData alloc] initWithBytes:"--\r\n" length:4]; 587 | DCHECK(_dashNewlineData); 588 | } 589 | } 590 | 591 | + (NSString *)mimeType { 592 | return @"multipart/form-data"; 593 | } 594 | 595 | #pragma mark - NSObject 596 | - (void)dealloc { 597 | DCHECK(_parserData == nil); 598 | } 599 | 600 | @end 601 | -------------------------------------------------------------------------------- /Classes/OCFWebServerRequest_Types.h: -------------------------------------------------------------------------------- 1 | @class OCFWebServerResponse; 2 | typedef void(^OCFWebServerResponseBlock)(OCFWebServerResponse *response); -------------------------------------------------------------------------------- /Classes/OCFWebServerResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file belongs to the OCFWebServer project. OCFWebServer is a fork of GCDWebServer (originally developed by 3 | Pierre-Olivier Latour). We have forked GCDWebServer because we made extensive and incompatible changes to it. 4 | To find out more have a look at README.md. 5 | 6 | Copyright (c) 2013, Christian Kienle / chris@objective-cloud.com 7 | All rights reserved. 8 | 9 | Original Copyright Statement: 10 | Copyright (c) 2012-2013, Pierre-Olivier Latour 11 | All rights reserved. 12 | 13 | Redistribution and use in source and binary forms, with or without 14 | modification, are permitted provided that the following conditions are met: 15 | * Redistributions of source code must retain the above copyright 16 | notice, this list of conditions and the following disclaimer. 17 | * Redistributions in binary form must reproduce the above copyright 18 | notice, this list of conditions and the following disclaimer in the 19 | documentation and/or other materials provided with the distribution. 20 | * Neither the name of the nor the 21 | names of its contributors may be used to endorse or promote products 22 | derived from this software without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 25 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 26 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 28 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | 36 | #import 37 | 38 | @interface OCFWebServerResponse : NSObject 39 | 40 | #pragma mark - Properties 41 | @property(nonatomic, copy, readonly) NSString *contentType; 42 | @property(nonatomic, readonly) NSUInteger contentLength; 43 | @property(nonatomic, assign, readwrite) NSInteger statusCode; // Default is 200 44 | @property(nonatomic) NSUInteger cacheControlMaxAge; // Default is 0 seconds i.e. "no-cache" 45 | @property(nonatomic, readonly, copy) NSDictionary *additionalHeaders; 46 | @property (nonatomic, copy) NSDictionary *userInfo; 47 | 48 | #pragma mark - Creating 49 | + (instancetype)response; 50 | - (instancetype)init; 51 | - (instancetype)initWithContentType:(NSString*)type contentLength:(NSUInteger)length; // Pass nil contentType to indicate empty body 52 | 53 | #pragma mark - Working with the Response 54 | - (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header; 55 | - (BOOL)hasBody; // Convenience method 56 | @end 57 | 58 | @interface OCFWebServerResponse (Subclassing) 59 | - (BOOL)open; // Implementation required 60 | - (NSInteger)read:(void *)buffer maxLength:(NSUInteger)length; // Implementation required 61 | - (BOOL)close; // Implementation required 62 | @end 63 | 64 | @interface OCFWebServerResponse (Extensions) 65 | 66 | #pragma mark - Creating 67 | + (instancetype)responseWithStatusCode:(NSInteger)statusCode; 68 | + (instancetype)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent; 69 | - (instancetype)initWithStatusCode:(NSInteger)statusCode; 70 | - (instancetype)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent; 71 | 72 | @end 73 | 74 | @interface OCFWebServerDataResponse : OCFWebServerResponse 75 | @property (nonatomic, copy) NSData *data; 76 | 77 | #pragma mark - Creating 78 | + (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type; 79 | - (instancetype)initWithData:(NSData*)data contentType:(NSString*)type; 80 | 81 | @end 82 | 83 | @interface OCFWebServerDataResponse (Extensions) 84 | 85 | #pragma mark - Creating 86 | + (instancetype)responseWithText:(NSString*)text; 87 | + (instancetype)responseWithHTML:(NSString*)html; 88 | + (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; 89 | - (instancetype)initWithText:(NSString*)text; // Encodes using UTF-8 90 | - (instancetype)initWithHTML:(NSString*)html; // Encodes using UTF-8 91 | - (instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; // Simple template system that replaces all occurences of "%variable%" with corresponding value (encodes using UTF-8) 92 | 93 | @end 94 | 95 | @interface OCFWebServerFileResponse : OCFWebServerResponse 96 | 97 | #pragma mark - Creating 98 | + (instancetype)responseWithFile:(NSString*)path; 99 | + (instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment; 100 | - (instancetype)initWithFile:(NSString*)path; 101 | - (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment; 102 | @end 103 | -------------------------------------------------------------------------------- /Classes/OCFWebServerResponse.m: -------------------------------------------------------------------------------- 1 | /* 2 | This file belongs to the OCFWebServer project. OCFWebServer is a fork of GCDWebServer (originally developed by 3 | Pierre-Olivier Latour). We have forked GCDWebServer because we made extensive and incompatible changes to it. 4 | To find out more have a look at README.md. 5 | 6 | Copyright (c) 2013, Christian Kienle / chris@objective-cloud.com 7 | All rights reserved. 8 | 9 | Original Copyright Statement: 10 | Copyright (c) 2012-2013, Pierre-Olivier Latour 11 | All rights reserved. 12 | 13 | Redistribution and use in source and binary forms, with or without 14 | modification, are permitted provided that the following conditions are met: 15 | * Redistributions of source code must retain the above copyright 16 | notice, this list of conditions and the following disclaimer. 17 | * Redistributions in binary form must reproduce the above copyright 18 | notice, this list of conditions and the following disclaimer in the 19 | documentation and/or other materials provided with the distribution. 20 | * Neither the name of the nor the 21 | names of its contributors may be used to endorse or promote products 22 | derived from this software without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 25 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 26 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 28 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | 36 | #import 37 | 38 | #import "OCFWebServerPrivate.h" 39 | #import "OCFWebServerResponse.h" 40 | 41 | 42 | @interface OCFWebServerResponse () 43 | 44 | #pragma mark - Properties 45 | @property(nonatomic, copy, readwrite) NSString *contentType; 46 | @property(nonatomic, readwrite) NSUInteger contentLength; 47 | //@property(nonatomic, readwrite) NSInteger statusCode; // Default is 200 48 | @property(nonatomic, readwrite, copy) NSDictionary *additionalHeaders; 49 | 50 | @end 51 | 52 | @implementation OCFWebServerResponse { 53 | NSMutableDictionary *_additionalHeaders; 54 | } 55 | 56 | #pragma mark - Properties 57 | - (void)setAdditionalHeaders:(NSDictionary *)additionalHeaders { 58 | _additionalHeaders = [additionalHeaders mutableCopy]; 59 | } 60 | 61 | - (NSDictionary *)additionalHeaders { 62 | return [_additionalHeaders copy]; 63 | } 64 | 65 | #pragma mark - Creating 66 | + (instancetype) response { 67 | return [[[self class] alloc] init]; 68 | } 69 | 70 | - (instancetype)init { 71 | return [self initWithContentType:nil contentLength:0]; 72 | } 73 | 74 | - (instancetype)initWithContentType:(NSString*)contentType contentLength:(NSUInteger)length { 75 | if((self = [super init])) { 76 | self.contentType = contentType; 77 | self.contentLength = length; 78 | self.statusCode = 200; 79 | self.cacheControlMaxAge = 0; 80 | self.additionalHeaders = @{}; 81 | 82 | if((self.contentLength > 0) && (self.contentType == nil)) { 83 | self.contentType = [kOCFWebServerDefaultMimeType copy]; 84 | } 85 | } 86 | return self; 87 | } 88 | 89 | #pragma mark - Working with the Response 90 | - (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header { 91 | _additionalHeaders[header] = value; 92 | } 93 | 94 | - (BOOL)hasBody { 95 | return self.contentType ? YES : NO; 96 | } 97 | 98 | @end 99 | 100 | @implementation OCFWebServerResponse (Subclassing) 101 | 102 | - (BOOL)open { 103 | [self doesNotRecognizeSelector:_cmd]; 104 | return NO; 105 | } 106 | 107 | - (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length { 108 | [self doesNotRecognizeSelector:_cmd]; 109 | return -1; 110 | } 111 | 112 | - (BOOL)close { 113 | [self doesNotRecognizeSelector:_cmd]; 114 | return NO; 115 | } 116 | 117 | @end 118 | 119 | @implementation OCFWebServerResponse (Extensions) 120 | 121 | + (instancetype)responseWithStatusCode:(NSInteger)statusCode { 122 | return [[self alloc] initWithStatusCode:statusCode]; 123 | } 124 | 125 | + (instancetype)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent { 126 | return [[self alloc] initWithRedirect:location permanent:permanent]; 127 | } 128 | 129 | - (instancetype)initWithStatusCode:(NSInteger)statusCode { 130 | if ((self = [self initWithContentType:nil contentLength:0])) { 131 | self.statusCode = statusCode; 132 | } 133 | return self; 134 | } 135 | 136 | - (instancetype)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent { 137 | if ((self = [self initWithContentType:nil contentLength:0])) { 138 | self.statusCode = permanent ? 301 : 307; 139 | [self setValue:[location absoluteString] forAdditionalHeader:@"Location"]; 140 | } 141 | return self; 142 | } 143 | 144 | @end 145 | 146 | @interface OCFWebServerDataResponse () 147 | 148 | #pragma mark - Properties 149 | @property (nonatomic, assign) NSInteger offset; 150 | 151 | @end 152 | 153 | @implementation OCFWebServerDataResponse 154 | 155 | + (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type { 156 | return [[[self class] alloc] initWithData:data contentType:type]; 157 | } 158 | 159 | - (instancetype)initWithData:(NSData*)data contentType:(NSString*)type { 160 | if(data == nil) { 161 | DNOT_REACHED(); 162 | return nil; 163 | } 164 | 165 | if((self = [super initWithContentType:type contentLength:data.length])) { 166 | self.data = data; 167 | self.offset = -1; 168 | } 169 | return self; 170 | } 171 | 172 | - (void)dealloc { 173 | DCHECK(self.offset < 0); 174 | } 175 | 176 | - (BOOL)open { 177 | DCHECK(self.offset < 0); 178 | self.offset = 0; 179 | return YES; 180 | } 181 | 182 | - (NSInteger)read:(void *)buffer maxLength:(NSUInteger)length { 183 | DCHECK(self.offset >= 0); 184 | NSInteger size = 0; 185 | if(self.offset < self.data.length) { 186 | size = MIN(self.data.length - self.offset, length); 187 | // the original author used the following snippet here and I do not know why 188 | // bcopy((char*)self.data.bytes + self.offset, buffer, size); 189 | NSRange range = NSMakeRange(self.offset, size); 190 | [self.data getBytes:buffer range:range]; 191 | self.offset = self.offset + size; 192 | } 193 | return size; 194 | } 195 | 196 | - (BOOL)close { 197 | DCHECK(_offset >= 0); 198 | _offset = -1; 199 | return YES; 200 | } 201 | 202 | @end 203 | 204 | @implementation OCFWebServerDataResponse (Extensions) 205 | 206 | + (instancetype)responseWithText:(NSString*)text { 207 | return [[self alloc] initWithText:text]; 208 | } 209 | 210 | + (instancetype)responseWithHTML:(NSString*)html { 211 | return [[self alloc] initWithHTML:html]; 212 | } 213 | 214 | + (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables { 215 | return [[self alloc] initWithHTMLTemplate:path variables:variables]; 216 | } 217 | 218 | - (instancetype)initWithText:(NSString*)text { 219 | NSData* data = [text dataUsingEncoding:NSUTF8StringEncoding]; 220 | if (data == nil) { 221 | DNOT_REACHED(); 222 | return nil; 223 | } 224 | return [self initWithData:data contentType:@"text/plain; charset=utf-8"]; 225 | } 226 | 227 | - (instancetype)initWithHTML:(NSString*)html { 228 | NSData* data = [html dataUsingEncoding:NSUTF8StringEncoding]; 229 | if (data == nil) { 230 | DNOT_REACHED(); 231 | return nil; 232 | } 233 | return [self initWithData:data contentType:@"text/html; charset=utf-8"]; 234 | } 235 | 236 | - (instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables { 237 | NSMutableString* html = [[NSMutableString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; 238 | [variables enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL* stop) { 239 | [html replaceOccurrencesOfString:[NSString stringWithFormat:@"%%%@%%", key] withString:value options:0 range:NSMakeRange(0, html.length)]; 240 | }]; 241 | id response = [self initWithHTML:html]; 242 | return response; 243 | } 244 | 245 | @end 246 | 247 | @interface OCFWebServerFileResponse () 248 | 249 | #pragma mark - Properties 250 | @property (nonatomic, copy) NSString *path; 251 | @property (nonatomic, assign) int file; 252 | 253 | @end 254 | 255 | @implementation OCFWebServerFileResponse 256 | 257 | #pragma mark - Creating 258 | + (instancetype)responseWithFile:(NSString*)path { 259 | return [[[self class] alloc] initWithFile:path]; 260 | } 261 | 262 | + (instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment { 263 | return [[[self class] alloc] initWithFile:path isAttachment:attachment]; 264 | } 265 | 266 | - (instancetype)initWithFile:(NSString*)path { 267 | return [self initWithFile:path isAttachment:NO]; 268 | } 269 | 270 | - (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment { 271 | struct stat info; 272 | if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) { 273 | DNOT_REACHED(); 274 | return nil; 275 | } 276 | NSString* type = OCFWebServerGetMimeTypeForExtension([path pathExtension]); 277 | if (type == nil) { 278 | type = kOCFWebServerDefaultMimeType; 279 | } 280 | 281 | if((self = [super initWithContentType:type contentLength:info.st_size])) { 282 | self.path = path; 283 | if (attachment) { // TODO: Use http://tools.ietf.org/html/rfc5987 to encode file names with special characters instead of using lossy conversion to ISO 8859-1 284 | NSData* data = [[path lastPathComponent] dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:YES]; 285 | NSString* fileName = data ? [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding] : nil; 286 | if (fileName) { 287 | [self setValue:[NSString stringWithFormat:@"attachment; filename=\"%@\"", fileName] forAdditionalHeader:@"Content-Disposition"]; 288 | } else { 289 | DNOT_REACHED(); 290 | } 291 | } 292 | self.file = 0; 293 | } 294 | return self; 295 | } 296 | 297 | - (void)dealloc { 298 | DCHECK(self.file <= 0); 299 | } 300 | 301 | - (BOOL)open { 302 | DCHECK(self.file <= 0); 303 | self.file = open([self.path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY); 304 | return (self.file > 0 ? YES : NO); 305 | } 306 | 307 | - (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length { 308 | DCHECK(self.file > 0); 309 | return read(self.file, buffer, length); 310 | } 311 | 312 | - (BOOL)close { 313 | DCHECK(self.file > 0); 314 | int result = close(self.file); 315 | self.file = 0; 316 | return (result == 0 ? YES : NO); 317 | } 318 | 319 | @end 320 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Christian Kienle (Objective-Cloud.com) 2 | All rights reserved. 3 | 4 | Original Copyright/Credits: 5 | 6 | Copyright (c) 2012-2013, Pierre-Olivier Latour 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions are met: 11 | * Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in the 15 | documentation and/or other materials provided with the distribution. 16 | * Neither the name of the nor the 17 | names of its contributors may be used to endorse or promote products 18 | derived from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 24 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /OCFWebServer/OCFWebServer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | AB7269541855DA0A0075A8CA /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB7269531855DA0A0075A8CA /* Cocoa.framework */; }; 11 | AB72695E1855DA0A0075A8CA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AB72695C1855DA0A0075A8CA /* InfoPlist.strings */; }; 12 | AB7269691855DA0A0075A8CA /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB7269681855DA0A0075A8CA /* XCTest.framework */; }; 13 | AB72696A1855DA0A0075A8CA /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB7269531855DA0A0075A8CA /* Cocoa.framework */; }; 14 | AB72696D1855DA0A0075A8CA /* OCFWebServer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB7269501855DA0A0075A8CA /* OCFWebServer.framework */; }; 15 | AB7269731855DA0A0075A8CA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AB7269711855DA0A0075A8CA /* InfoPlist.strings */; }; 16 | AB7269751855DA0A0075A8CA /* OCFWebServerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AB7269741855DA0A0075A8CA /* OCFWebServerTests.m */; }; 17 | AB7269881855DA1E0075A8CA /* OCFWebServer.h in Headers */ = {isa = PBXBuildFile; fileRef = AB72697E1855DA1E0075A8CA /* OCFWebServer.h */; }; 18 | AB7269891855DA1E0075A8CA /* OCFWebServer.m in Sources */ = {isa = PBXBuildFile; fileRef = AB72697F1855DA1E0075A8CA /* OCFWebServer.m */; }; 19 | AB72698A1855DA1E0075A8CA /* OCFWebServerConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = AB7269801855DA1E0075A8CA /* OCFWebServerConnection.h */; }; 20 | AB72698B1855DA1E0075A8CA /* OCFWebServerConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = AB7269811855DA1E0075A8CA /* OCFWebServerConnection.m */; }; 21 | AB72698C1855DA1E0075A8CA /* OCFWebServerPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = AB7269821855DA1E0075A8CA /* OCFWebServerPrivate.h */; }; 22 | AB72698D1855DA1E0075A8CA /* OCFWebServerRequest_Types.h in Headers */ = {isa = PBXBuildFile; fileRef = AB7269831855DA1E0075A8CA /* OCFWebServerRequest_Types.h */; }; 23 | AB72698E1855DA1E0075A8CA /* OCFWebServerRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = AB7269841855DA1E0075A8CA /* OCFWebServerRequest.h */; }; 24 | AB72698F1855DA1E0075A8CA /* OCFWebServerRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = AB7269851855DA1E0075A8CA /* OCFWebServerRequest.m */; }; 25 | AB7269901855DA1E0075A8CA /* OCFWebServerResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = AB7269861855DA1E0075A8CA /* OCFWebServerResponse.h */; }; 26 | AB7269911855DA1E0075A8CA /* OCFWebServerResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = AB7269871855DA1E0075A8CA /* OCFWebServerResponse.m */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXContainerItemProxy section */ 30 | AB72696B1855DA0A0075A8CA /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = AB7269471855DA0A0075A8CA /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = AB72694F1855DA0A0075A8CA; 35 | remoteInfo = OCFWebServer; 36 | }; 37 | /* End PBXContainerItemProxy section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | AB7269501855DA0A0075A8CA /* OCFWebServer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OCFWebServer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | AB7269531855DA0A0075A8CA /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 42 | AB7269561855DA0A0075A8CA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 43 | AB7269571855DA0A0075A8CA /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 44 | AB7269581855DA0A0075A8CA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 45 | AB72695B1855DA0A0075A8CA /* OCFWebServer-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "OCFWebServer-Info.plist"; sourceTree = ""; }; 46 | AB72695D1855DA0A0075A8CA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 47 | AB72695F1855DA0A0075A8CA /* OCFWebServer-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCFWebServer-Prefix.pch"; sourceTree = ""; }; 48 | AB7269671855DA0A0075A8CA /* OCFWebServerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OCFWebServerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | AB7269681855DA0A0075A8CA /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 50 | AB7269701855DA0A0075A8CA /* OCFWebServerTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "OCFWebServerTests-Info.plist"; sourceTree = ""; }; 51 | AB7269721855DA0A0075A8CA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 52 | AB7269741855DA0A0075A8CA /* OCFWebServerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCFWebServerTests.m; sourceTree = ""; }; 53 | AB72697E1855DA1E0075A8CA /* OCFWebServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OCFWebServer.h; path = ../../Classes/OCFWebServer.h; sourceTree = ""; }; 54 | AB72697F1855DA1E0075A8CA /* OCFWebServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OCFWebServer.m; path = ../../Classes/OCFWebServer.m; sourceTree = ""; }; 55 | AB7269801855DA1E0075A8CA /* OCFWebServerConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OCFWebServerConnection.h; path = ../../Classes/OCFWebServerConnection.h; sourceTree = ""; }; 56 | AB7269811855DA1E0075A8CA /* OCFWebServerConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OCFWebServerConnection.m; path = ../../Classes/OCFWebServerConnection.m; sourceTree = ""; }; 57 | AB7269821855DA1E0075A8CA /* OCFWebServerPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OCFWebServerPrivate.h; path = ../../Classes/OCFWebServerPrivate.h; sourceTree = ""; }; 58 | AB7269831855DA1E0075A8CA /* OCFWebServerRequest_Types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OCFWebServerRequest_Types.h; path = ../../Classes/OCFWebServerRequest_Types.h; sourceTree = ""; }; 59 | AB7269841855DA1E0075A8CA /* OCFWebServerRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OCFWebServerRequest.h; path = ../../Classes/OCFWebServerRequest.h; sourceTree = ""; }; 60 | AB7269851855DA1E0075A8CA /* OCFWebServerRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OCFWebServerRequest.m; path = ../../Classes/OCFWebServerRequest.m; sourceTree = ""; }; 61 | AB7269861855DA1E0075A8CA /* OCFWebServerResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OCFWebServerResponse.h; path = ../../Classes/OCFWebServerResponse.h; sourceTree = ""; }; 62 | AB7269871855DA1E0075A8CA /* OCFWebServerResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OCFWebServerResponse.m; path = ../../Classes/OCFWebServerResponse.m; sourceTree = ""; }; 63 | /* End PBXFileReference section */ 64 | 65 | /* Begin PBXFrameworksBuildPhase section */ 66 | AB72694C1855DA0A0075A8CA /* Frameworks */ = { 67 | isa = PBXFrameworksBuildPhase; 68 | buildActionMask = 2147483647; 69 | files = ( 70 | AB7269541855DA0A0075A8CA /* Cocoa.framework in Frameworks */, 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | AB7269641855DA0A0075A8CA /* Frameworks */ = { 75 | isa = PBXFrameworksBuildPhase; 76 | buildActionMask = 2147483647; 77 | files = ( 78 | AB72696D1855DA0A0075A8CA /* OCFWebServer.framework in Frameworks */, 79 | AB72696A1855DA0A0075A8CA /* Cocoa.framework in Frameworks */, 80 | AB7269691855DA0A0075A8CA /* XCTest.framework in Frameworks */, 81 | ); 82 | runOnlyForDeploymentPostprocessing = 0; 83 | }; 84 | /* End PBXFrameworksBuildPhase section */ 85 | 86 | /* Begin PBXGroup section */ 87 | AB7269461855DA0A0075A8CA = { 88 | isa = PBXGroup; 89 | children = ( 90 | AB7269591855DA0A0075A8CA /* OCFWebServer */, 91 | AB72696E1855DA0A0075A8CA /* OCFWebServerTests */, 92 | AB7269521855DA0A0075A8CA /* Frameworks */, 93 | AB7269511855DA0A0075A8CA /* Products */, 94 | ); 95 | sourceTree = ""; 96 | }; 97 | AB7269511855DA0A0075A8CA /* Products */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | AB7269501855DA0A0075A8CA /* OCFWebServer.framework */, 101 | AB7269671855DA0A0075A8CA /* OCFWebServerTests.xctest */, 102 | ); 103 | name = Products; 104 | sourceTree = ""; 105 | }; 106 | AB7269521855DA0A0075A8CA /* Frameworks */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | AB7269531855DA0A0075A8CA /* Cocoa.framework */, 110 | AB7269681855DA0A0075A8CA /* XCTest.framework */, 111 | AB7269551855DA0A0075A8CA /* Other Frameworks */, 112 | ); 113 | name = Frameworks; 114 | sourceTree = ""; 115 | }; 116 | AB7269551855DA0A0075A8CA /* Other Frameworks */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | AB7269561855DA0A0075A8CA /* Foundation.framework */, 120 | AB7269571855DA0A0075A8CA /* CoreData.framework */, 121 | AB7269581855DA0A0075A8CA /* AppKit.framework */, 122 | ); 123 | name = "Other Frameworks"; 124 | sourceTree = ""; 125 | }; 126 | AB7269591855DA0A0075A8CA /* OCFWebServer */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | AB72697E1855DA1E0075A8CA /* OCFWebServer.h */, 130 | AB72697F1855DA1E0075A8CA /* OCFWebServer.m */, 131 | AB7269801855DA1E0075A8CA /* OCFWebServerConnection.h */, 132 | AB7269811855DA1E0075A8CA /* OCFWebServerConnection.m */, 133 | AB7269821855DA1E0075A8CA /* OCFWebServerPrivate.h */, 134 | AB7269831855DA1E0075A8CA /* OCFWebServerRequest_Types.h */, 135 | AB7269841855DA1E0075A8CA /* OCFWebServerRequest.h */, 136 | AB7269851855DA1E0075A8CA /* OCFWebServerRequest.m */, 137 | AB7269861855DA1E0075A8CA /* OCFWebServerResponse.h */, 138 | AB7269871855DA1E0075A8CA /* OCFWebServerResponse.m */, 139 | AB72695A1855DA0A0075A8CA /* Supporting Files */, 140 | ); 141 | path = OCFWebServer; 142 | sourceTree = ""; 143 | }; 144 | AB72695A1855DA0A0075A8CA /* Supporting Files */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | AB72695B1855DA0A0075A8CA /* OCFWebServer-Info.plist */, 148 | AB72695C1855DA0A0075A8CA /* InfoPlist.strings */, 149 | AB72695F1855DA0A0075A8CA /* OCFWebServer-Prefix.pch */, 150 | ); 151 | name = "Supporting Files"; 152 | sourceTree = ""; 153 | }; 154 | AB72696E1855DA0A0075A8CA /* OCFWebServerTests */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | AB7269741855DA0A0075A8CA /* OCFWebServerTests.m */, 158 | AB72696F1855DA0A0075A8CA /* Supporting Files */, 159 | ); 160 | path = OCFWebServerTests; 161 | sourceTree = ""; 162 | }; 163 | AB72696F1855DA0A0075A8CA /* Supporting Files */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | AB7269701855DA0A0075A8CA /* OCFWebServerTests-Info.plist */, 167 | AB7269711855DA0A0075A8CA /* InfoPlist.strings */, 168 | ); 169 | name = "Supporting Files"; 170 | sourceTree = ""; 171 | }; 172 | /* End PBXGroup section */ 173 | 174 | /* Begin PBXHeadersBuildPhase section */ 175 | AB72694D1855DA0A0075A8CA /* Headers */ = { 176 | isa = PBXHeadersBuildPhase; 177 | buildActionMask = 2147483647; 178 | files = ( 179 | AB72698C1855DA1E0075A8CA /* OCFWebServerPrivate.h in Headers */, 180 | AB7269881855DA1E0075A8CA /* OCFWebServer.h in Headers */, 181 | AB72698A1855DA1E0075A8CA /* OCFWebServerConnection.h in Headers */, 182 | AB72698D1855DA1E0075A8CA /* OCFWebServerRequest_Types.h in Headers */, 183 | AB7269901855DA1E0075A8CA /* OCFWebServerResponse.h in Headers */, 184 | AB72698E1855DA1E0075A8CA /* OCFWebServerRequest.h in Headers */, 185 | ); 186 | runOnlyForDeploymentPostprocessing = 0; 187 | }; 188 | /* End PBXHeadersBuildPhase section */ 189 | 190 | /* Begin PBXNativeTarget section */ 191 | AB72694F1855DA0A0075A8CA /* OCFWebServer */ = { 192 | isa = PBXNativeTarget; 193 | buildConfigurationList = AB7269781855DA0A0075A8CA /* Build configuration list for PBXNativeTarget "OCFWebServer" */; 194 | buildPhases = ( 195 | AB72694B1855DA0A0075A8CA /* Sources */, 196 | AB72694C1855DA0A0075A8CA /* Frameworks */, 197 | AB72694D1855DA0A0075A8CA /* Headers */, 198 | AB72694E1855DA0A0075A8CA /* Resources */, 199 | ); 200 | buildRules = ( 201 | ); 202 | dependencies = ( 203 | ); 204 | name = OCFWebServer; 205 | productName = OCFWebServer; 206 | productReference = AB7269501855DA0A0075A8CA /* OCFWebServer.framework */; 207 | productType = "com.apple.product-type.framework"; 208 | }; 209 | AB7269661855DA0A0075A8CA /* OCFWebServerTests */ = { 210 | isa = PBXNativeTarget; 211 | buildConfigurationList = AB72697B1855DA0A0075A8CA /* Build configuration list for PBXNativeTarget "OCFWebServerTests" */; 212 | buildPhases = ( 213 | AB7269631855DA0A0075A8CA /* Sources */, 214 | AB7269641855DA0A0075A8CA /* Frameworks */, 215 | AB7269651855DA0A0075A8CA /* Resources */, 216 | ); 217 | buildRules = ( 218 | ); 219 | dependencies = ( 220 | AB72696C1855DA0A0075A8CA /* PBXTargetDependency */, 221 | ); 222 | name = OCFWebServerTests; 223 | productName = OCFWebServerTests; 224 | productReference = AB7269671855DA0A0075A8CA /* OCFWebServerTests.xctest */; 225 | productType = "com.apple.product-type.bundle.unit-test"; 226 | }; 227 | /* End PBXNativeTarget section */ 228 | 229 | /* Begin PBXProject section */ 230 | AB7269471855DA0A0075A8CA /* Project object */ = { 231 | isa = PBXProject; 232 | attributes = { 233 | LastUpgradeCheck = 0500; 234 | ORGANIZATIONNAME = "Objective-Cloud.com"; 235 | TargetAttributes = { 236 | AB7269661855DA0A0075A8CA = { 237 | TestTargetID = AB72694F1855DA0A0075A8CA; 238 | }; 239 | }; 240 | }; 241 | buildConfigurationList = AB72694A1855DA0A0075A8CA /* Build configuration list for PBXProject "OCFWebServer" */; 242 | compatibilityVersion = "Xcode 3.2"; 243 | developmentRegion = English; 244 | hasScannedForEncodings = 0; 245 | knownRegions = ( 246 | en, 247 | ); 248 | mainGroup = AB7269461855DA0A0075A8CA; 249 | productRefGroup = AB7269511855DA0A0075A8CA /* Products */; 250 | projectDirPath = ""; 251 | projectRoot = ""; 252 | targets = ( 253 | AB72694F1855DA0A0075A8CA /* OCFWebServer */, 254 | AB7269661855DA0A0075A8CA /* OCFWebServerTests */, 255 | ); 256 | }; 257 | /* End PBXProject section */ 258 | 259 | /* Begin PBXResourcesBuildPhase section */ 260 | AB72694E1855DA0A0075A8CA /* Resources */ = { 261 | isa = PBXResourcesBuildPhase; 262 | buildActionMask = 2147483647; 263 | files = ( 264 | AB72695E1855DA0A0075A8CA /* InfoPlist.strings in Resources */, 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | }; 268 | AB7269651855DA0A0075A8CA /* Resources */ = { 269 | isa = PBXResourcesBuildPhase; 270 | buildActionMask = 2147483647; 271 | files = ( 272 | AB7269731855DA0A0075A8CA /* InfoPlist.strings in Resources */, 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | /* End PBXResourcesBuildPhase section */ 277 | 278 | /* Begin PBXSourcesBuildPhase section */ 279 | AB72694B1855DA0A0075A8CA /* Sources */ = { 280 | isa = PBXSourcesBuildPhase; 281 | buildActionMask = 2147483647; 282 | files = ( 283 | AB72698B1855DA1E0075A8CA /* OCFWebServerConnection.m in Sources */, 284 | AB7269911855DA1E0075A8CA /* OCFWebServerResponse.m in Sources */, 285 | AB72698F1855DA1E0075A8CA /* OCFWebServerRequest.m in Sources */, 286 | AB7269891855DA1E0075A8CA /* OCFWebServer.m in Sources */, 287 | ); 288 | runOnlyForDeploymentPostprocessing = 0; 289 | }; 290 | AB7269631855DA0A0075A8CA /* Sources */ = { 291 | isa = PBXSourcesBuildPhase; 292 | buildActionMask = 2147483647; 293 | files = ( 294 | AB7269751855DA0A0075A8CA /* OCFWebServerTests.m in Sources */, 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | }; 298 | /* End PBXSourcesBuildPhase section */ 299 | 300 | /* Begin PBXTargetDependency section */ 301 | AB72696C1855DA0A0075A8CA /* PBXTargetDependency */ = { 302 | isa = PBXTargetDependency; 303 | target = AB72694F1855DA0A0075A8CA /* OCFWebServer */; 304 | targetProxy = AB72696B1855DA0A0075A8CA /* PBXContainerItemProxy */; 305 | }; 306 | /* End PBXTargetDependency section */ 307 | 308 | /* Begin PBXVariantGroup section */ 309 | AB72695C1855DA0A0075A8CA /* InfoPlist.strings */ = { 310 | isa = PBXVariantGroup; 311 | children = ( 312 | AB72695D1855DA0A0075A8CA /* en */, 313 | ); 314 | name = InfoPlist.strings; 315 | sourceTree = ""; 316 | }; 317 | AB7269711855DA0A0075A8CA /* InfoPlist.strings */ = { 318 | isa = PBXVariantGroup; 319 | children = ( 320 | AB7269721855DA0A0075A8CA /* en */, 321 | ); 322 | name = InfoPlist.strings; 323 | sourceTree = ""; 324 | }; 325 | /* End PBXVariantGroup section */ 326 | 327 | /* Begin XCBuildConfiguration section */ 328 | AB7269761855DA0A0075A8CA /* Debug */ = { 329 | isa = XCBuildConfiguration; 330 | buildSettings = { 331 | ALWAYS_SEARCH_USER_PATHS = NO; 332 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 333 | CLANG_CXX_LIBRARY = "libc++"; 334 | CLANG_ENABLE_OBJC_ARC = YES; 335 | CLANG_WARN_BOOL_CONVERSION = YES; 336 | CLANG_WARN_CONSTANT_CONVERSION = YES; 337 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 338 | CLANG_WARN_EMPTY_BODY = YES; 339 | CLANG_WARN_ENUM_CONVERSION = YES; 340 | CLANG_WARN_INT_CONVERSION = YES; 341 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 342 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 343 | COPY_PHASE_STRIP = NO; 344 | GCC_C_LANGUAGE_STANDARD = gnu99; 345 | GCC_DYNAMIC_NO_PIC = NO; 346 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 347 | GCC_OPTIMIZATION_LEVEL = 0; 348 | GCC_PREPROCESSOR_DEFINITIONS = ( 349 | "DEBUG=1", 350 | "$(inherited)", 351 | ); 352 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 353 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 354 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 355 | GCC_WARN_UNDECLARED_SELECTOR = YES; 356 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 357 | GCC_WARN_UNUSED_FUNCTION = YES; 358 | GCC_WARN_UNUSED_VARIABLE = YES; 359 | MACOSX_DEPLOYMENT_TARGET = 10.9; 360 | ONLY_ACTIVE_ARCH = YES; 361 | SDKROOT = macosx; 362 | }; 363 | name = Debug; 364 | }; 365 | AB7269771855DA0A0075A8CA /* Release */ = { 366 | isa = XCBuildConfiguration; 367 | buildSettings = { 368 | ALWAYS_SEARCH_USER_PATHS = NO; 369 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 370 | CLANG_CXX_LIBRARY = "libc++"; 371 | CLANG_ENABLE_OBJC_ARC = YES; 372 | CLANG_WARN_BOOL_CONVERSION = YES; 373 | CLANG_WARN_CONSTANT_CONVERSION = YES; 374 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 375 | CLANG_WARN_EMPTY_BODY = YES; 376 | CLANG_WARN_ENUM_CONVERSION = YES; 377 | CLANG_WARN_INT_CONVERSION = YES; 378 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 379 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 380 | COPY_PHASE_STRIP = YES; 381 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 382 | ENABLE_NS_ASSERTIONS = NO; 383 | GCC_C_LANGUAGE_STANDARD = gnu99; 384 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 385 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 386 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 387 | GCC_WARN_UNDECLARED_SELECTOR = YES; 388 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 389 | GCC_WARN_UNUSED_FUNCTION = YES; 390 | GCC_WARN_UNUSED_VARIABLE = YES; 391 | MACOSX_DEPLOYMENT_TARGET = 10.9; 392 | SDKROOT = macosx; 393 | }; 394 | name = Release; 395 | }; 396 | AB7269791855DA0A0075A8CA /* Debug */ = { 397 | isa = XCBuildConfiguration; 398 | buildSettings = { 399 | COMBINE_HIDPI_IMAGES = YES; 400 | DYLIB_COMPATIBILITY_VERSION = 1; 401 | DYLIB_CURRENT_VERSION = 1; 402 | FRAMEWORK_VERSION = A; 403 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 404 | GCC_PREFIX_HEADER = "OCFWebServer/OCFWebServer-Prefix.pch"; 405 | INFOPLIST_FILE = "OCFWebServer/OCFWebServer-Info.plist"; 406 | PRODUCT_NAME = "$(TARGET_NAME)"; 407 | WRAPPER_EXTENSION = framework; 408 | }; 409 | name = Debug; 410 | }; 411 | AB72697A1855DA0A0075A8CA /* Release */ = { 412 | isa = XCBuildConfiguration; 413 | buildSettings = { 414 | COMBINE_HIDPI_IMAGES = YES; 415 | DYLIB_COMPATIBILITY_VERSION = 1; 416 | DYLIB_CURRENT_VERSION = 1; 417 | FRAMEWORK_VERSION = A; 418 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 419 | GCC_PREFIX_HEADER = "OCFWebServer/OCFWebServer-Prefix.pch"; 420 | INFOPLIST_FILE = "OCFWebServer/OCFWebServer-Info.plist"; 421 | PRODUCT_NAME = "$(TARGET_NAME)"; 422 | WRAPPER_EXTENSION = framework; 423 | }; 424 | name = Release; 425 | }; 426 | AB72697C1855DA0A0075A8CA /* Debug */ = { 427 | isa = XCBuildConfiguration; 428 | buildSettings = { 429 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/OCFWebServer.framework/Versions/A/OCFWebServer"; 430 | COMBINE_HIDPI_IMAGES = YES; 431 | FRAMEWORK_SEARCH_PATHS = ( 432 | "$(DEVELOPER_FRAMEWORKS_DIR)", 433 | "$(inherited)", 434 | ); 435 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 436 | GCC_PREFIX_HEADER = "OCFWebServer/OCFWebServer-Prefix.pch"; 437 | GCC_PREPROCESSOR_DEFINITIONS = ( 438 | "DEBUG=1", 439 | "$(inherited)", 440 | ); 441 | INFOPLIST_FILE = "OCFWebServerTests/OCFWebServerTests-Info.plist"; 442 | PRODUCT_NAME = "$(TARGET_NAME)"; 443 | TEST_HOST = "$(BUNDLE_LOADER)"; 444 | WRAPPER_EXTENSION = xctest; 445 | }; 446 | name = Debug; 447 | }; 448 | AB72697D1855DA0A0075A8CA /* Release */ = { 449 | isa = XCBuildConfiguration; 450 | buildSettings = { 451 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/OCFWebServer.framework/Versions/A/OCFWebServer"; 452 | COMBINE_HIDPI_IMAGES = YES; 453 | FRAMEWORK_SEARCH_PATHS = ( 454 | "$(DEVELOPER_FRAMEWORKS_DIR)", 455 | "$(inherited)", 456 | ); 457 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 458 | GCC_PREFIX_HEADER = "OCFWebServer/OCFWebServer-Prefix.pch"; 459 | INFOPLIST_FILE = "OCFWebServerTests/OCFWebServerTests-Info.plist"; 460 | PRODUCT_NAME = "$(TARGET_NAME)"; 461 | TEST_HOST = "$(BUNDLE_LOADER)"; 462 | WRAPPER_EXTENSION = xctest; 463 | }; 464 | name = Release; 465 | }; 466 | /* End XCBuildConfiguration section */ 467 | 468 | /* Begin XCConfigurationList section */ 469 | AB72694A1855DA0A0075A8CA /* Build configuration list for PBXProject "OCFWebServer" */ = { 470 | isa = XCConfigurationList; 471 | buildConfigurations = ( 472 | AB7269761855DA0A0075A8CA /* Debug */, 473 | AB7269771855DA0A0075A8CA /* Release */, 474 | ); 475 | defaultConfigurationIsVisible = 0; 476 | defaultConfigurationName = Release; 477 | }; 478 | AB7269781855DA0A0075A8CA /* Build configuration list for PBXNativeTarget "OCFWebServer" */ = { 479 | isa = XCConfigurationList; 480 | buildConfigurations = ( 481 | AB7269791855DA0A0075A8CA /* Debug */, 482 | AB72697A1855DA0A0075A8CA /* Release */, 483 | ); 484 | defaultConfigurationIsVisible = 0; 485 | }; 486 | AB72697B1855DA0A0075A8CA /* Build configuration list for PBXNativeTarget "OCFWebServerTests" */ = { 487 | isa = XCConfigurationList; 488 | buildConfigurations = ( 489 | AB72697C1855DA0A0075A8CA /* Debug */, 490 | AB72697D1855DA0A0075A8CA /* Release */, 491 | ); 492 | defaultConfigurationIsVisible = 0; 493 | }; 494 | /* End XCConfigurationList section */ 495 | }; 496 | rootObject = AB7269471855DA0A0075A8CA /* Project object */; 497 | } 498 | -------------------------------------------------------------------------------- /OCFWebServer/OCFWebServer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /OCFWebServer/OCFWebServer/OCFWebServer-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.objective-cloud.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | NSHumanReadableCopyright 26 | Copyright © 2013 Objective-Cloud.com. All rights reserved. 27 | NSPrincipalClass 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /OCFWebServer/OCFWebServer/OCFWebServer-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /OCFWebServer/OCFWebServer/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /OCFWebServer/OCFWebServerTests/OCFWebServerTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.objective-cloud.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /OCFWebServer/OCFWebServerTests/OCFWebServerTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // OCFWebServerTests.m 3 | // OCFWebServerTests 4 | // 5 | // Created by cmk on 12/9/13. 6 | // Copyright (c) 2013 Objective-Cloud.com. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface OCFWebServerTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation OCFWebServerTests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /OCFWebServer/OCFWebServerTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | OCFWebServer is a lightweight, modern and asynchronous HTTP (version 1.1) server. It was forked from [GCDWebServer](https://raw.github.com/swisspol/GCDWebServer) and modified to fit the needs of [Objective-Cloud.com](http://objective-cloud.com) and hopefully other people's needs as well. 3 | 4 | # Who is using OCFWebServer? 5 | OCFWebServer is used by OCFWeb which is a framework for developing web applications with Objective-C. OCFWeb and OCFWebServer are both used by [Objective-Cloud.com](http://objective-cloud.com). Are you using OCFWebServer as well? [Let us know](mailto:team@objective-cloud.com) and we will link your app/project right here. 6 | 7 | # Goals 8 | OCFWebServer was developed to be used for Objective-Cloud.com. This does not mean that the goals we had while developing it are incompatible with the needs of developers of `regular` apps. These are the goals we had in mind while working on OCFWebServer: 9 | 10 | * Easy to use in your own application: Embedding OCFWebServer should be done with just a few lines of code. 11 | * Be *truly* asynchronous: Use GCD/dispatch_io everywhere and make it easy to let the user write asynchronous request handlers. 12 | * Many concurrent requests: We wanted to be able to have a minimum of 128 concurrent requests per OCFWebServer instance. OCFWebServer can do more but out of the box is supports up to 128 concurrent requests. This is enough for [Objective-Cloud.com](http://objective-cloud.com) and probably also enough for your needs as well. 13 | * Don't do everything: If you need a simple HTTP server in your app OCFWebServer is made for you. Please do not try to run an instance of OCFWebServer, publicly on the internet. Your machine will be hacked. At [Objective-Cloud.com](http://objective-cloud.com) we always have at least one proxy server in front of our instances of OCFWebServer. 14 | 15 | # Examples and getting started 16 | You can simply download the source code of OCFWebServer and add every header and implementation file to your own project. 17 | 18 | Remark: All of the following examples are adapted from the GCDWebServer README file and slightly modified to reflect the changes made by OCFWebServer. Some of the explaining texts have also been adopted. Credits: Pierre-Olivier Latour (Thank you so much Pierre!) 19 | 20 | ## Example: Hello World 21 | 22 | Setting up OCFWebServer is easy: 23 | 24 | #import "OCFWebServer.h" 25 | 26 | int main(int argc, const char* argv[]) { 27 | @autoreleasepool { 28 | OCFWebServer *server = [OCFWebServer new]; 29 | 30 | // Add a request handler for every possible GET request 31 | 32 | [server addDefaultHandlerForMethod:@"GET" 33 | requestClass:[OCFWebServerRequest class] 34 | processBlock:^void(OCFWebServerRequest *request) { 35 | 36 | // Create your response and pass it to respondWith(...) 37 | OCFWebServerResponse *response = [OCFWebServerDataResponse responseWithHTML:@"Hello World"]; 38 | [request respondWith:response]; 39 | }]; 40 | 41 | // Run the server on port 8080 42 | [server runWithPort:8080]; 43 | 44 | } 45 | return EXIT_SUCCESS; 46 | 47 | The example above assumes that you have a console based application. If you have a Cocoa or Cocoa Touch application then you might want to have a `@property (nonatomic, strong) OCFWebServer *server` in one of your controllers and use one of the `start` methods instead of `runWithPort:`. If you pass `0` as the port then OCFWebServer will automatically ask the operating system for a free port and use that. 48 | 49 | ## Example: Redirects 50 | Here's an example handler that redirects `/` to `/index.html` using the convenience method on 'OCFWebServerResponse' (it sets the HTTP status code and 'Location' header automatically): 51 | 52 | [self addHandlerForMethod:@"GET" 53 | path:@"/" 54 | requestClass:[OCFWebServerRequest class] 55 | processBlock:^void(OCFWebServerRequest* request) { 56 | NSURL *toURL = [NSURL URLWithString:@"index.html" relativeToURL:request.URL]; 57 | 58 | respondWith([OCFWebServerResponse responseWithRedirect:toURL 59 | permanent:NO]); 60 | }]; 61 | 62 | ## Example: Forms 63 | To implement an HTTP form, you need a pair of handlers: 64 | 65 | * The GET handler does not expect any body in the HTTP request and therefore uses the 'OCFWebServerRequest' class. The handler generates a response containing a simple HTML form. 66 | * The POST handler expects the form values to be in the body of the HTTP request and percent-encoded. Fortunately, OCFWebServer provides the request class 'OCFWebServerURLEncodedFormRequest' which can automatically parse such bodies. The handler simply echoes back the value from the user submitted form. 67 | 68 | Here we go: 69 | 70 | [server addHandlerForMethod:@"GET" 71 | path:@"/" 72 | requestClass:[OCFWebServerRequest class] 73 | processBlock:^void(OCFWebServerRequest* request) { 74 | 75 | NSString* html = @" \ 76 |
\ 78 | Value: \ 79 | \ 80 |
\ 81 | "; 82 | 83 | [request respondWith:[OCFWebServerDataResponse responseWithHTML:html]]; 84 | }]; 85 | 86 | [server addHandlerForMethod:@"POST" 87 | path:@"/" 88 | requestClass:[OCFWebServerURLEncodedFormRequest class] 89 | processBlock:^void(OCFWebServerRequest* request) { 90 | 91 | NSString *value = [(OCFWebServerURLEncodedFormRequest*)request arguments][@"value"]; 92 | NSString* html = [NSString stringWithFormat:@"

%@

", value]; 93 | 94 | [request respondWith:[OCFWebServerDataResponse responseWithHTML:html]]; 95 | }]; 96 | 97 | # Handlers 98 | As shown in the examples, you can add more than one handler to an instance of OCFWebServer. The handlers are sorted and matched in a last in, first out fashion. 99 | 100 | # Requirements and Dependencies 101 | OCFWebServer runs on 102 | 103 | * OS X 10.8+ 104 | * iOS 6+ 105 | 106 | and has no third party dependencies. 107 | 108 | # Notes 109 | 110 | OCFWebServer is a fork of GCDWebServer. The author of GCDWebServer has done a fantastic job. That is why we picked GCDWebServer as the foundation for OCFWebServer. In the process of making Objective-Cloud.com we realized that GCDWebServer in an incompatible fashion in order to work better. That is why we have forked GCDWebServer and improved it. OCFWebServer is not inherently better than GCDWebServer. It is different. 111 | 112 | If you want to learn more about the architecture of OCFWebServer you can have a look at the [README of GCDWebServer](https://github.com/swisspol/GCDWebServer/blob/master/README.md). OCFWebServer has almost the same architecture than GCDWebServer. 113 | 114 | ## Asynchronous: Front to Back 115 | 116 | In OCFWebServer your request handler does not have to return anything immediately. OCFWebServer will pass the request to your request handler. The request gives you access to a lot of HTTP request specific properties. Now it is your turn to compute a response. Once that is done you should let the request object know about your response by calling `-respondWith:` (class: OCFRequest) and pass it the response. Here is an example: 117 | 118 | [server addDefaultHandlerForMethod:@"GET" 119 | requestClass:[OCFWebServerRequest class] 120 | processBlock:^void(OCFWebServerRequest *request) { 121 | dispatch_async(myQueue, ^() { 122 | OCFWebServerDataResponse *response = [OCFWebServerDataResponse responseWithHTML:@"Hello World"]; 123 | [request respondWith:response]; 124 | }); 125 | }]; 126 | 127 | As you can see your request handler can do anything it wants. You do not have to call `dispatch_async` but you can if you need to. Some APIs require you to do something asynchronously (NSURLConnection, XPC, …). 128 | 129 | 130 | By the way: Migrating your GCDWebServer related code to OCFWebServer is very easy: Simply replace `return response;` with `[request respondWith:response], return;` and you are done. 131 | 132 | ## Many concurrent requests 133 | At the time of writing GCDWebServer can only handle 16 concurrent requests. You can increase that by changing a constant in GCDWebServer's source code but in OCFWebServer the default maximum number of concurrent request is automatically set to the maximum of what is possible. If you are running OS X and not fine tune the settings this will mean that OCFWebServer can handle up to 128 concurrent requests at a time. If you tune the settings of OS X then this value can be increased and we are already working on a better queuing system which should further increase the number of concurrent requests. 134 | 135 | ## Modern code base 136 | True: This is an implementation detail but important to mention. OCFWebServer is using ARC, dispatch objects (`OS_OBJECT_USE_OBJC`), modern runtime and the existing code base of GCDWebServer has been cleaned up and made more POSIX compatible. 137 | 138 | ## No support for < OS X 10.8 and < iOS 6 139 | OCFWebServer does only support OS X 10.8+ and iOS 6+. If you want to use it on older versions of OS X/iOS then you should use GCDWebServer. 140 | 141 | # More Convenience 142 | If you want even more convenience for your HTTP server related needs you should also have a look at OCFWeb. OCFWeb is a framework that let's you develop web applications in Objective-C. OCFWeb is using OCFWebServer internally and adds a lot of nice stuff to it like a template engine, nicer syntax for handlers and a lot more. 143 | 144 | # How to contribute 145 | Development of OCFWebServer takes place on GitHub. If you find a bug, suspect a bug or have a question feel free to open an issue. Pull requests are very welcome and will be accepted as fast as possible. 146 | 147 | # License 148 | 149 | OCFWebServer is available under the New BSD License - just like GCDWebServer. 150 | 151 | This file belongs to the OCFWebServer project. 152 | OCFWebServer is a fork of GCDWebServer (originally developed by 153 | Pierre-Olivier Latour). 154 | 155 | We have forked GCDWebServer because we made extensive and 156 | incompatible changes to it. 157 | 158 | Copyright (c) 2013, Christian Kienle / chris@objective-cloud.com 159 | All rights reserved. 160 | 161 | Original Copyright Statement: 162 | Copyright (c) 2012-2013, Pierre-Olivier Latour 163 | All rights reserved. 164 | 165 | Redistribution and use in source and binary forms, with or without 166 | modification, are permitted provided that the following conditions are met: 167 | * Redistributions of source code must retain the above copyright 168 | notice, this list of conditions and the following disclaimer. 169 | * Redistributions in binary form must reproduce the above copyright 170 | notice, this list of conditions and the following disclaimer in the 171 | documentation and/or other materials provided with the distribution. 172 | * Neither the name of the nor the 173 | names of its contributors may be used to endorse or promote products 174 | derived from this software without specific prior written permission. 175 | 176 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 177 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 178 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 179 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 180 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 181 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 182 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 183 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 184 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 185 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 186 | 187 | --------------------------------------------------------------------------------