├── .gitignore ├── README.md ├── android ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── me │ └── alwx │ └── HttpServer │ ├── HttpServerModule.java │ ├── HttpServerReactPackage.java │ └── Server.java ├── desktop ├── CMakeLists.txt ├── rcthttpserver.cpp └── rcthttpserver.h ├── httpServer.js ├── ios ├── RCTHttpServer.h ├── RCTHttpServer.m ├── RCTHttpServer.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── alwx.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── alwx.xcuserdatad │ │ └── xcschemes │ │ ├── RCTHttpServer.xcscheme │ │ └── xcschememanagement.plist └── WGCDWebServer │ ├── Core │ ├── WGCDWebServer.h │ ├── WGCDWebServer.m │ ├── WGCDWebServerConnection.h │ ├── WGCDWebServerConnection.m │ ├── WGCDWebServerFunctions.h │ ├── WGCDWebServerFunctions.m │ ├── WGCDWebServerHTTPStatusCodes.h │ ├── WGCDWebServerPrivate.h │ ├── WGCDWebServerRequest.h │ ├── WGCDWebServerRequest.m │ ├── WGCDWebServerResponse.h │ └── WGCDWebServerResponse.m │ ├── Requests │ ├── WGCDWebServerDataRequest.h │ ├── WGCDWebServerDataRequest.m │ ├── WGCDWebServerFileRequest.h │ ├── WGCDWebServerFileRequest.m │ ├── WGCDWebServerMultiPartFormRequest.h │ ├── WGCDWebServerMultiPartFormRequest.m │ ├── WGCDWebServerURLEncodedFormRequest.h │ └── WGCDWebServerURLEncodedFormRequest.m │ └── Responses │ ├── WGCDWebServerDataResponse.h │ ├── WGCDWebServerDataResponse.m │ ├── WGCDWebServerErrorResponse.h │ ├── WGCDWebServerErrorResponse.m │ ├── WGCDWebServerFileResponse.h │ ├── WGCDWebServerFileResponse.m │ ├── WGCDWebServerStreamedResponse.h │ └── WGCDWebServerStreamedResponse.m └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | .DS_Store 6 | *.iml 7 | /.idea 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # node-waf configuration 27 | .lock-wscript 28 | 29 | # Compiled binary addons (http://nodejs.org/api/addons.html) 30 | build/Release 31 | 32 | # Dependency directories 33 | node_modules 34 | jspm_packages 35 | 36 | # Optional npm cache directory 37 | .npm 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | 42 | # Android 43 | android/gen 44 | android/.idea 45 | android/.gradle 46 | android/build 47 | local.properties 48 | android.iml 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-http-bridge 2 | 3 | Simple HTTP server for [React Native](https://github.com/facebook/react-native). 4 | Created for [Status.im](https://github.com/status-im/status-react). 5 | 6 | Since 0.5.0 supports and handles GET, POST, PUT and DELETE requests. 7 | The library can be useful for handling requests with `application/json` content type 8 | (and this is the only content type we support at the current stage) and returning different responses. 9 | 10 | Since 0.6.0 can handle millions of requests at the same time and also includes some very basic support for [React Native QT](https://github.com/status-im/react-native-desktop). 11 | 12 | ## Install 13 | 14 | ```shell 15 | npm install --save react-native-http-bridge 16 | ``` 17 | 18 | ## Automatically link 19 | 20 | #### With React Native 0.27+ 21 | 22 | ```shell 23 | react-native link react-native-http-bridge 24 | ``` 25 | 26 | ## Example 27 | 28 | First import/require react-native-http-server: 29 | 30 | ```js 31 | 32 | var httpBridge = require('react-native-http-bridge'); 33 | 34 | ``` 35 | 36 | 37 | Initalize the server in the `componentWillMount` lifecycle method. You need to provide a `port` and a callback. 38 | 39 | ```js 40 | 41 | componentWillMount() { 42 | // initalize the server (now accessible via localhost:1234) 43 | httpBridge.start(5561, 'http_service' request => { 44 | 45 | // you can use request.url, request.type and request.postData here 46 | if (request.type === "GET" && request.url.split("/")[1] === "users") { 47 | httpBridge.respond(request.requestId, 200, "application/json", "{\"message\": \"OK\"}"); 48 | } else { 49 | httpBridge.respond(request.requestId, 400, "application/json", "{\"message\": \"Bad Request\"}"); 50 | } 51 | 52 | }); 53 | } 54 | 55 | ``` 56 | 57 | Finally, ensure that you disable the server when your component is being unmounted. 58 | 59 | ```js 60 | 61 | componentWillUnmount() { 62 | httpBridge.stop(); 63 | } 64 | 65 | ``` 66 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | dependencies { 6 | classpath 'com.android.tools.build:gradle:2.2.0' 7 | } 8 | } 9 | 10 | allprojects { 11 | repositories { 12 | jcenter() 13 | } 14 | } 15 | 16 | apply plugin: 'com.android.library' 17 | 18 | android { 19 | compileSdkVersion 26 20 | buildToolsVersion "26.0.2" 21 | 22 | defaultConfig { 23 | minSdkVersion 16 24 | targetSdkVersion 22 25 | versionCode 2 26 | versionName "1.1" 27 | ndk { 28 | abiFilters "armeabi-v7a", "x86" 29 | } 30 | } 31 | lintOptions { 32 | warning 'InvalidPackage' 33 | } 34 | } 35 | 36 | dependencies { 37 | compile 'com.facebook.react:react-native:+' 38 | compile 'com.google.android.gms:play-services-gcm:16.1.0' 39 | compile 'org.nanohttpd:nanohttpd:2.3.1' 40 | } 41 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/src/main/java/me/alwx/HttpServer/HttpServerModule.java: -------------------------------------------------------------------------------- 1 | package me.alwx.HttpServer; 2 | 3 | import android.content.Context; 4 | import com.facebook.react.bridge.ReactApplicationContext; 5 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 6 | import com.facebook.react.bridge.LifecycleEventListener; 7 | import com.facebook.react.bridge.ReactMethod; 8 | 9 | import java.io.IOException; 10 | 11 | import android.util.Log; 12 | 13 | public class HttpServerModule extends ReactContextBaseJavaModule implements LifecycleEventListener { 14 | ReactApplicationContext reactContext; 15 | 16 | private static final String MODULE_NAME = "HttpServer"; 17 | 18 | private static int port; 19 | private static Server server = null; 20 | 21 | public HttpServerModule(ReactApplicationContext reactContext) { 22 | super(reactContext); 23 | this.reactContext = reactContext; 24 | 25 | reactContext.addLifecycleEventListener(this); 26 | } 27 | 28 | @Override 29 | public String getName() { 30 | return MODULE_NAME; 31 | } 32 | 33 | @ReactMethod 34 | public void start(int port, String serviceName) { 35 | Log.d(MODULE_NAME, "Initializing server..."); 36 | this.port = port; 37 | 38 | startServer(); 39 | } 40 | 41 | @ReactMethod 42 | public void stop() { 43 | Log.d(MODULE_NAME, "Stopping server..."); 44 | 45 | stopServer(); 46 | } 47 | 48 | @ReactMethod 49 | public void respond(String requestId, int code, String type, String body) { 50 | if (server != null) { 51 | server.respond(requestId, code, type, body); 52 | } 53 | } 54 | 55 | @Override 56 | public void onHostResume() { 57 | 58 | } 59 | 60 | @Override 61 | public void onHostPause() { 62 | 63 | } 64 | 65 | @Override 66 | public void onHostDestroy() { 67 | stopServer(); 68 | } 69 | 70 | private void startServer() { 71 | if (this.port == 0) { 72 | return; 73 | } 74 | 75 | if (server == null) { 76 | server = new Server(reactContext, port); 77 | } 78 | try { 79 | server.start(); 80 | } catch (IOException e) { 81 | Log.e(MODULE_NAME, e.getMessage()); 82 | } 83 | } 84 | 85 | private void stopServer() { 86 | if (server != null) { 87 | server.stop(); 88 | server = null; 89 | port = 0; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /android/src/main/java/me/alwx/HttpServer/HttpServerReactPackage.java: -------------------------------------------------------------------------------- 1 | package me.alwx.HttpServer; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | import com.facebook.react.ReactPackage; 8 | import com.facebook.react.bridge.NativeModule; 9 | import com.facebook.react.bridge.ReactApplicationContext; 10 | import com.facebook.react.uimanager.ViewManager; 11 | import com.facebook.react.bridge.JavaScriptModule; 12 | import me.alwx.HttpServer.HttpServerModule; 13 | 14 | public class HttpServerReactPackage implements ReactPackage { 15 | @Override 16 | public List createNativeModules(ReactApplicationContext reactContext) { 17 | List modules = new ArrayList<>(); 18 | modules.add(new HttpServerModule(reactContext)); 19 | return modules; 20 | } 21 | 22 | public List> createJSModules() { 23 | return Collections.emptyList(); 24 | } 25 | 26 | @Override 27 | public List createViewManagers(ReactApplicationContext reactContext) { 28 | return Collections.emptyList(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /android/src/main/java/me/alwx/HttpServer/Server.java: -------------------------------------------------------------------------------- 1 | package me.alwx.HttpServer; 2 | 3 | import fi.iki.elonen.NanoHTTPD; 4 | import fi.iki.elonen.NanoHTTPD.Response; 5 | import fi.iki.elonen.NanoHTTPD.Response.Status; 6 | import com.facebook.react.bridge.ReactContext; 7 | import com.facebook.react.bridge.Arguments; 8 | import com.facebook.react.bridge.ReadableMap; 9 | import com.facebook.react.bridge.WritableMap; 10 | import com.facebook.react.modules.core.DeviceEventManagerModule; 11 | 12 | import java.util.Map; 13 | import java.util.Set; 14 | import java.util.HashMap; 15 | import java.util.Random; 16 | 17 | import android.support.annotation.Nullable; 18 | import android.util.Log; 19 | 20 | public class Server extends NanoHTTPD { 21 | private static final String TAG = "HttpServer"; 22 | private static final String SERVER_EVENT_ID = "httpServerResponseReceived"; 23 | 24 | private ReactContext reactContext; 25 | private Map responses; 26 | 27 | public Server(ReactContext context, int port) { 28 | super(port); 29 | reactContext = context; 30 | responses = new HashMap<>(); 31 | 32 | Log.d(TAG, "Server started"); 33 | } 34 | 35 | @Override 36 | public Response serve(IHTTPSession session) { 37 | Log.d(TAG, "Request received!"); 38 | 39 | Random rand = new Random(); 40 | String requestId = String.format("%d:%d", System.currentTimeMillis(), rand.nextInt(1000000)); 41 | 42 | WritableMap request; 43 | try { 44 | request = fillRequestMap(session, requestId); 45 | } catch (Exception e) { 46 | return newFixedLengthResponse( 47 | Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, e.getMessage() 48 | ); 49 | } 50 | 51 | this.sendEvent(reactContext, SERVER_EVENT_ID, request); 52 | 53 | while (responses.get(requestId) == null) { 54 | try { 55 | Thread.sleep(10); 56 | } catch (Exception e) { 57 | Log.d(TAG, "Exception while waiting: " + e); 58 | } 59 | } 60 | Response response = responses.get(requestId); 61 | responses.remove(requestId); 62 | return response; 63 | } 64 | 65 | public void respond(String requestId, int code, String type, String body) { 66 | responses.put(requestId, newFixedLengthResponse(Status.lookup(code), type, body)); 67 | } 68 | 69 | private WritableMap fillRequestMap(IHTTPSession session, String requestId) throws Exception { 70 | Method method = session.getMethod(); 71 | WritableMap request = Arguments.createMap(); 72 | request.putString("url", session.getUri()); 73 | request.putString("type", method.name()); 74 | request.putString("requestId", requestId); 75 | 76 | Map files = new HashMap<>(); 77 | session.parseBody(files); 78 | if (files.size() > 0) { 79 | request.putString("postData", files.get("postData")); 80 | } 81 | 82 | return request; 83 | } 84 | 85 | private void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) { 86 | reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /desktop/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 2 | 3 | set(REACT_NATIVE_DESKTOP_EXTERNAL_MODULES_TYPE_NAMES ${REACT_NATIVE_DESKTOP_EXTERNAL_MODULES_TYPE_NAMES} 4 | \"RCTHttpServer\" PARENT_SCOPE) 5 | 6 | set(REACT_NATIVE_DESKTOP_EXTERNAL_MODULES_SRC ${REACT_NATIVE_DESKTOP_EXTERNAL_MODULES_SRC} 7 | ${CMAKE_CURRENT_SOURCE_DIR}/rcthttpserver.cpp PARENT_SCOPE) -------------------------------------------------------------------------------- /desktop/rcthttpserver.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Status Research and Development GmbH. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #include "rcthttpserver.h" 12 | #include "bridge.h" 13 | #include "eventdispatcher.h" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | namespace { 21 | struct RegisterQMLMetaType { 22 | RegisterQMLMetaType() { 23 | qRegisterMetaType(); 24 | } 25 | } registerMetaType; 26 | } // namespace 27 | 28 | class RCTHttpServerPrivate { 29 | public: 30 | Bridge* bridge = nullptr; 31 | }; 32 | 33 | RCTHttpServer::RCTHttpServer(QObject* parent) : QObject(parent), d_ptr(new RCTHttpServerPrivate) {} 34 | 35 | RCTHttpServer::~RCTHttpServer() {} 36 | 37 | void RCTHttpServer::setBridge(Bridge* bridge) { 38 | Q_D(RCTHttpServer); 39 | d->bridge = bridge; 40 | } 41 | 42 | QString RCTHttpServer::moduleName() { 43 | return "RCTHttpServer"; 44 | } 45 | 46 | QList RCTHttpServer::methodsToExport() { 47 | return QList{}; 48 | } 49 | 50 | QVariantMap RCTHttpServer::constantsToExport() { 51 | return QVariantMap(); 52 | } 53 | 54 | void RCTHttpServer::start(int port, QString serviceName) { 55 | 56 | } 57 | 58 | void RCTHttpServer::stop() { 59 | 60 | } 61 | 62 | void RCTHttpServer::respond(QString requestId, int code, QString type, QString body) { 63 | 64 | } 65 | -------------------------------------------------------------------------------- /desktop/rcthttpserver.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Status Research and Development GmbH. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #ifndef RCTHTTPSERVER_H 12 | #define RCTHTTPSERVER_H 13 | 14 | #include "moduleinterface.h" 15 | 16 | #include 17 | 18 | class RCTHttpServerPrivate; 19 | class RCTHttpServer : public QObject, public ModuleInterface { 20 | Q_OBJECT 21 | Q_INTERFACES(ModuleInterface) 22 | 23 | Q_DECLARE_PRIVATE(RCTHttpServer) 24 | 25 | public: 26 | Q_INVOKABLE RCTHttpServer(QObject* parent = 0); 27 | ~RCTHttpServer(); 28 | 29 | void setBridge(Bridge* bridge) override; 30 | 31 | QString moduleName() override; 32 | QList methodsToExport() override; 33 | QVariantMap constantsToExport() override; 34 | 35 | Q_INVOKABLE void start(int port, QString serviceName); 36 | Q_INVOKABLE void stop(); 37 | Q_INVOKABLE void respond(QString requestId, int code, QString type, QString body); 38 | 39 | private: 40 | QScopedPointer d_ptr; 41 | }; 42 | 43 | #endif // RCTHTTPSERVER_H -------------------------------------------------------------------------------- /httpServer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @providesModule react-native-http-server 3 | */ 4 | 'use strict'; 5 | 6 | import {DeviceEventEmitter} from 'react-native'; 7 | import {NativeModules} from 'react-native'; 8 | var Server = NativeModules.HttpServer; 9 | 10 | module.exports = { 11 | start: function (port, serviceName, callback) { 12 | if (port == 80) { 13 | throw "Invalid server port specified. Port 80 is reserved."; 14 | } 15 | 16 | Server.start(port, serviceName); 17 | DeviceEventEmitter.addListener('httpServerResponseReceived', callback); 18 | }, 19 | 20 | stop: function () { 21 | Server.stop(); 22 | DeviceEventEmitter.removeAllListeners('httpServerResponseReceived'); 23 | }, 24 | 25 | respond: function (requestId, code, type, body) { 26 | Server.respond(requestId, code, type, body); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ios/RCTHttpServer.h: -------------------------------------------------------------------------------- 1 | #import 2 | -------------------------------------------------------------------------------- /ios/RCTHttpServer.m: -------------------------------------------------------------------------------- 1 | #import "RCTHttpServer.h" 2 | #import "React/RCTBridge.h" 3 | #import "React/RCTLog.h" 4 | #import "React/RCTEventDispatcher.h" 5 | 6 | #import "WGCDWebServer.h" 7 | #import "WGCDWebServerDataResponse.h" 8 | #import "WGCDWebServerDataRequest.h" 9 | #import "WGCDWebServerPrivate.h" 10 | #include 11 | 12 | @interface RCTHttpServer : NSObject { 13 | WGCDWebServer* _webServer; 14 | NSMutableDictionary* _completionBlocks; 15 | } 16 | @end 17 | 18 | static RCTBridge *bridge; 19 | 20 | @implementation RCTHttpServer 21 | 22 | @synthesize bridge = _bridge; 23 | 24 | RCT_EXPORT_MODULE(); 25 | 26 | 27 | - (void)initResponseReceivedFor:(WGCDWebServer *)server forType:(NSString*)type { 28 | [server addDefaultHandlerForMethod:type 29 | requestClass:[WGCDWebServerDataRequest class] 30 | asyncProcessBlock:^(WGCDWebServerRequest* request, WGCDWebServerCompletionBlock completionBlock) { 31 | 32 | long long milliseconds = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0); 33 | int r = arc4random_uniform(1000000); 34 | NSString *requestId = [NSString stringWithFormat:@"%lld:%d", milliseconds, r]; 35 | 36 | @synchronized (self) { 37 | [_completionBlocks setObject:completionBlock forKey:requestId]; 38 | } 39 | 40 | @try { 41 | if ([WGCDWebServerTruncateHeaderValue(request.contentType) isEqualToString:@"application/json"]) { 42 | WGCDWebServerDataRequest* dataRequest = (WGCDWebServerDataRequest*)request; 43 | [self.bridge.eventDispatcher sendAppEventWithName:@"httpServerResponseReceived" 44 | body:@{@"requestId": requestId, 45 | @"postData": dataRequest.jsonObject, 46 | @"type": type, 47 | @"url": request.URL.relativeString}]; 48 | } else { 49 | [self.bridge.eventDispatcher sendAppEventWithName:@"httpServerResponseReceived" 50 | body:@{@"requestId": requestId, 51 | @"type": type, 52 | @"url": request.URL.relativeString}]; 53 | } 54 | } @catch (NSException *exception) { 55 | [self.bridge.eventDispatcher sendAppEventWithName:@"httpServerResponseReceived" 56 | body:@{@"requestId": requestId, 57 | @"type": type, 58 | @"url": request.URL.relativeString}]; 59 | } 60 | }]; 61 | } 62 | 63 | RCT_EXPORT_METHOD(start:(NSInteger) port 64 | serviceName:(NSString *) serviceName) 65 | { 66 | RCTLogInfo(@"Running HTTP bridge server: %ld", port); 67 | _requestResponses = [[NSMutableDictionary alloc] init]; 68 | 69 | dispatch_sync(dispatch_get_main_queue(), ^{ 70 | _webServer = [[WGCDWebServer alloc] init]; 71 | 72 | [self initResponseReceivedFor:_webServer forType:@"POST"]; 73 | [self initResponseReceivedFor:_webServer forType:@"PUT"]; 74 | [self initResponseReceivedFor:_webServer forType:@"GET"]; 75 | [self initResponseReceivedFor:_webServer forType:@"DELETE"]; 76 | 77 | [_webServer startWithPort:port bonjourName:serviceName]; 78 | }); 79 | } 80 | 81 | RCT_EXPORT_METHOD(stop) 82 | { 83 | RCTLogInfo(@"Stopping HTTP bridge server"); 84 | 85 | if (_webServer != nil) { 86 | [_webServer stop]; 87 | [_webServer removeAllHandlers]; 88 | _webServer = nil; 89 | } 90 | } 91 | 92 | RCT_EXPORT_METHOD(respond: (NSString *) requestId 93 | code: (NSInteger) code 94 | type: (NSString *) type 95 | body: (NSString *) body) 96 | { 97 | NSData* data = [body dataUsingEncoding:NSUTF8StringEncoding]; 98 | WGCDWebServerDataResponse* requestResponse = [[WGCDWebServerDataResponse alloc] initWithData:data contentType:type]; 99 | requestResponse.statusCode = code; 100 | 101 | WGCDWebServerCompletionBlock completionBlock = nil; 102 | @synchronized (self) { 103 | completionBlock = [_completionBlocks objectForKey:requestId]; 104 | [_completionBlocks removeObjectForKey:requestId]; 105 | } 106 | 107 | completionBlock(requestResponse); 108 | } 109 | 110 | @end 111 | -------------------------------------------------------------------------------- /ios/RCTHttpServer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B232F4021E49DE0C00C8AEE0 /* WGCDWebServer.m in Sources */ = {isa = PBXBuildFile; fileRef = B232F3E51E49DE0C00C8AEE0 /* WGCDWebServer.m */; }; 11 | B232F4031E49DE0C00C8AEE0 /* WGCDWebServerConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = B232F3E71E49DE0C00C8AEE0 /* WGCDWebServerConnection.m */; }; 12 | B232F4041E49DE0C00C8AEE0 /* WGCDWebServerFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = B232F3E91E49DE0C00C8AEE0 /* WGCDWebServerFunctions.m */; }; 13 | B232F4051E49DE0C00C8AEE0 /* WGCDWebServerRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = B232F3ED1E49DE0C00C8AEE0 /* WGCDWebServerRequest.m */; }; 14 | B232F4061E49DE0C00C8AEE0 /* WGCDWebServerResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = B232F3EF1E49DE0C00C8AEE0 /* WGCDWebServerResponse.m */; }; 15 | B232F4071E49DE0C00C8AEE0 /* WGCDWebServerDataRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = B232F3F21E49DE0C00C8AEE0 /* WGCDWebServerDataRequest.m */; }; 16 | B232F4081E49DE0C00C8AEE0 /* WGCDWebServerFileRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = B232F3F41E49DE0C00C8AEE0 /* WGCDWebServerFileRequest.m */; }; 17 | B232F4091E49DE0C00C8AEE0 /* WGCDWebServerMultiPartFormRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = B232F3F61E49DE0C00C8AEE0 /* WGCDWebServerMultiPartFormRequest.m */; }; 18 | B232F40A1E49DE0C00C8AEE0 /* WGCDWebServerURLEncodedFormRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = B232F3F81E49DE0C00C8AEE0 /* WGCDWebServerURLEncodedFormRequest.m */; }; 19 | B232F40B1E49DE0C00C8AEE0 /* WGCDWebServerDataResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = B232F3FB1E49DE0C00C8AEE0 /* WGCDWebServerDataResponse.m */; }; 20 | B232F40C1E49DE0C00C8AEE0 /* WGCDWebServerErrorResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = B232F3FD1E49DE0C00C8AEE0 /* WGCDWebServerErrorResponse.m */; }; 21 | B232F40D1E49DE0C00C8AEE0 /* WGCDWebServerFileResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = B232F3FF1E49DE0C00C8AEE0 /* WGCDWebServerFileResponse.m */; }; 22 | B232F40E1E49DE0C00C8AEE0 /* WGCDWebServerStreamedResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = B232F4011E49DE0C00C8AEE0 /* WGCDWebServerStreamedResponse.m */; }; 23 | B2340C241E48BA3E0024C045 /* RCTHttpServer.m in Sources */ = {isa = PBXBuildFile; fileRef = B2340C231E48BA3E0024C045 /* RCTHttpServer.m */; }; 24 | B29ECA281E48CE1C00704A36 /* libz.1.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = B29ECA271E48CE1C00704A36 /* libz.1.dylib */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXCopyFilesBuildPhase section */ 28 | 1441618C1BD0A79300FA4F59 /* CopyFiles */ = { 29 | isa = PBXCopyFilesBuildPhase; 30 | buildActionMask = 2147483647; 31 | dstPath = "include/$(PRODUCT_NAME)"; 32 | dstSubfolderSpec = 16; 33 | files = ( 34 | ); 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | /* End PBXCopyFilesBuildPhase section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | B232F3E41E49DE0C00C8AEE0 /* WGCDWebServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WGCDWebServer.h; sourceTree = ""; }; 41 | B232F3E51E49DE0C00C8AEE0 /* WGCDWebServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WGCDWebServer.m; sourceTree = ""; }; 42 | B232F3E61E49DE0C00C8AEE0 /* WGCDWebServerConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WGCDWebServerConnection.h; sourceTree = ""; }; 43 | B232F3E71E49DE0C00C8AEE0 /* WGCDWebServerConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WGCDWebServerConnection.m; sourceTree = ""; }; 44 | B232F3E81E49DE0C00C8AEE0 /* WGCDWebServerFunctions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WGCDWebServerFunctions.h; sourceTree = ""; }; 45 | B232F3E91E49DE0C00C8AEE0 /* WGCDWebServerFunctions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WGCDWebServerFunctions.m; sourceTree = ""; }; 46 | B232F3EA1E49DE0C00C8AEE0 /* WGCDWebServerHTTPStatusCodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WGCDWebServerHTTPStatusCodes.h; sourceTree = ""; }; 47 | B232F3EB1E49DE0C00C8AEE0 /* WGCDWebServerPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WGCDWebServerPrivate.h; sourceTree = ""; }; 48 | B232F3EC1E49DE0C00C8AEE0 /* WGCDWebServerRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WGCDWebServerRequest.h; sourceTree = ""; }; 49 | B232F3ED1E49DE0C00C8AEE0 /* WGCDWebServerRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WGCDWebServerRequest.m; sourceTree = ""; }; 50 | B232F3EE1E49DE0C00C8AEE0 /* WGCDWebServerResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WGCDWebServerResponse.h; sourceTree = ""; }; 51 | B232F3EF1E49DE0C00C8AEE0 /* WGCDWebServerResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WGCDWebServerResponse.m; sourceTree = ""; }; 52 | B232F3F11E49DE0C00C8AEE0 /* WGCDWebServerDataRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WGCDWebServerDataRequest.h; sourceTree = ""; }; 53 | B232F3F21E49DE0C00C8AEE0 /* WGCDWebServerDataRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WGCDWebServerDataRequest.m; sourceTree = ""; }; 54 | B232F3F31E49DE0C00C8AEE0 /* WGCDWebServerFileRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WGCDWebServerFileRequest.h; sourceTree = ""; }; 55 | B232F3F41E49DE0C00C8AEE0 /* WGCDWebServerFileRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WGCDWebServerFileRequest.m; sourceTree = ""; }; 56 | B232F3F51E49DE0C00C8AEE0 /* WGCDWebServerMultiPartFormRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WGCDWebServerMultiPartFormRequest.h; sourceTree = ""; }; 57 | B232F3F61E49DE0C00C8AEE0 /* WGCDWebServerMultiPartFormRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WGCDWebServerMultiPartFormRequest.m; sourceTree = ""; }; 58 | B232F3F71E49DE0C00C8AEE0 /* WGCDWebServerURLEncodedFormRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WGCDWebServerURLEncodedFormRequest.h; sourceTree = ""; }; 59 | B232F3F81E49DE0C00C8AEE0 /* WGCDWebServerURLEncodedFormRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WGCDWebServerURLEncodedFormRequest.m; sourceTree = ""; }; 60 | B232F3FA1E49DE0C00C8AEE0 /* WGCDWebServerDataResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WGCDWebServerDataResponse.h; sourceTree = ""; }; 61 | B232F3FB1E49DE0C00C8AEE0 /* WGCDWebServerDataResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WGCDWebServerDataResponse.m; sourceTree = ""; }; 62 | B232F3FC1E49DE0C00C8AEE0 /* WGCDWebServerErrorResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WGCDWebServerErrorResponse.h; sourceTree = ""; }; 63 | B232F3FD1E49DE0C00C8AEE0 /* WGCDWebServerErrorResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WGCDWebServerErrorResponse.m; sourceTree = ""; }; 64 | B232F3FE1E49DE0C00C8AEE0 /* WGCDWebServerFileResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WGCDWebServerFileResponse.h; sourceTree = ""; }; 65 | B232F3FF1E49DE0C00C8AEE0 /* WGCDWebServerFileResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WGCDWebServerFileResponse.m; sourceTree = ""; }; 66 | B232F4001E49DE0C00C8AEE0 /* WGCDWebServerStreamedResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WGCDWebServerStreamedResponse.h; sourceTree = ""; }; 67 | B232F4011E49DE0C00C8AEE0 /* WGCDWebServerStreamedResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WGCDWebServerStreamedResponse.m; sourceTree = ""; }; 68 | B2340C221E48BA3E0024C045 /* RCTHttpServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTHttpServer.h; sourceTree = ""; }; 69 | B2340C231E48BA3E0024C045 /* RCTHttpServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTHttpServer.m; sourceTree = ""; }; 70 | B29EC9CC1E48BED600704A36 /* libRCTHttpServer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTHttpServer.a; sourceTree = BUILT_PRODUCTS_DIR; }; 71 | B29EC9CE1E48BF1900704A36 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 72 | B29ECA231E48CDCB00704A36 /* libz.1.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.1.tbd; path = usr/lib/libz.1.tbd; sourceTree = SDKROOT; }; 73 | B29ECA251E48CDE300704A36 /* libz.1.2.8.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.1.2.8.tbd; path = usr/lib/libz.1.2.8.tbd; sourceTree = SDKROOT; }; 74 | B29ECA271E48CE1C00704A36 /* libz.1.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.1.dylib; path = /usr/lib/libz.1.dylib; sourceTree = ""; }; 75 | /* End PBXFileReference section */ 76 | 77 | /* Begin PBXFrameworksBuildPhase section */ 78 | 1441618B1BD0A79300FA4F59 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | B29ECA281E48CE1C00704A36 /* libz.1.dylib in Frameworks */, 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | /* End PBXFrameworksBuildPhase section */ 87 | 88 | /* Begin PBXGroup section */ 89 | 145CC5671AED80100006342E = { 90 | isa = PBXGroup; 91 | children = ( 92 | B232F3E21E49DE0C00C8AEE0 /* WGCDWebServer */, 93 | B2340C221E48BA3E0024C045 /* RCTHttpServer.h */, 94 | B2340C231E48BA3E0024C045 /* RCTHttpServer.m */, 95 | B29EC9CC1E48BED600704A36 /* libRCTHttpServer.a */, 96 | B29EC9CD1E48BF1800704A36 /* Frameworks */, 97 | ); 98 | sourceTree = ""; 99 | }; 100 | B232F3E21E49DE0C00C8AEE0 /* WGCDWebServer */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | B232F3E31E49DE0C00C8AEE0 /* Core */, 104 | B232F3F01E49DE0C00C8AEE0 /* Requests */, 105 | B232F3F91E49DE0C00C8AEE0 /* Responses */, 106 | ); 107 | path = WGCDWebServer; 108 | sourceTree = ""; 109 | }; 110 | B232F3E31E49DE0C00C8AEE0 /* Core */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | B232F3E41E49DE0C00C8AEE0 /* WGCDWebServer.h */, 114 | B232F3E51E49DE0C00C8AEE0 /* WGCDWebServer.m */, 115 | B232F3E61E49DE0C00C8AEE0 /* WGCDWebServerConnection.h */, 116 | B232F3E71E49DE0C00C8AEE0 /* WGCDWebServerConnection.m */, 117 | B232F3E81E49DE0C00C8AEE0 /* WGCDWebServerFunctions.h */, 118 | B232F3E91E49DE0C00C8AEE0 /* WGCDWebServerFunctions.m */, 119 | B232F3EA1E49DE0C00C8AEE0 /* WGCDWebServerHTTPStatusCodes.h */, 120 | B232F3EB1E49DE0C00C8AEE0 /* WGCDWebServerPrivate.h */, 121 | B232F3EC1E49DE0C00C8AEE0 /* WGCDWebServerRequest.h */, 122 | B232F3ED1E49DE0C00C8AEE0 /* WGCDWebServerRequest.m */, 123 | B232F3EE1E49DE0C00C8AEE0 /* WGCDWebServerResponse.h */, 124 | B232F3EF1E49DE0C00C8AEE0 /* WGCDWebServerResponse.m */, 125 | ); 126 | path = Core; 127 | sourceTree = ""; 128 | }; 129 | B232F3F01E49DE0C00C8AEE0 /* Requests */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | B232F3F11E49DE0C00C8AEE0 /* WGCDWebServerDataRequest.h */, 133 | B232F3F21E49DE0C00C8AEE0 /* WGCDWebServerDataRequest.m */, 134 | B232F3F31E49DE0C00C8AEE0 /* WGCDWebServerFileRequest.h */, 135 | B232F3F41E49DE0C00C8AEE0 /* WGCDWebServerFileRequest.m */, 136 | B232F3F51E49DE0C00C8AEE0 /* WGCDWebServerMultiPartFormRequest.h */, 137 | B232F3F61E49DE0C00C8AEE0 /* WGCDWebServerMultiPartFormRequest.m */, 138 | B232F3F71E49DE0C00C8AEE0 /* WGCDWebServerURLEncodedFormRequest.h */, 139 | B232F3F81E49DE0C00C8AEE0 /* WGCDWebServerURLEncodedFormRequest.m */, 140 | ); 141 | path = Requests; 142 | sourceTree = ""; 143 | }; 144 | B232F3F91E49DE0C00C8AEE0 /* Responses */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | B232F3FA1E49DE0C00C8AEE0 /* WGCDWebServerDataResponse.h */, 148 | B232F3FB1E49DE0C00C8AEE0 /* WGCDWebServerDataResponse.m */, 149 | B232F3FC1E49DE0C00C8AEE0 /* WGCDWebServerErrorResponse.h */, 150 | B232F3FD1E49DE0C00C8AEE0 /* WGCDWebServerErrorResponse.m */, 151 | B232F3FE1E49DE0C00C8AEE0 /* WGCDWebServerFileResponse.h */, 152 | B232F3FF1E49DE0C00C8AEE0 /* WGCDWebServerFileResponse.m */, 153 | B232F4001E49DE0C00C8AEE0 /* WGCDWebServerStreamedResponse.h */, 154 | B232F4011E49DE0C00C8AEE0 /* WGCDWebServerStreamedResponse.m */, 155 | ); 156 | path = Responses; 157 | sourceTree = ""; 158 | }; 159 | B29EC9CD1E48BF1800704A36 /* Frameworks */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | B29ECA271E48CE1C00704A36 /* libz.1.dylib */, 163 | B29ECA251E48CDE300704A36 /* libz.1.2.8.tbd */, 164 | B29ECA231E48CDCB00704A36 /* libz.1.tbd */, 165 | B29EC9CE1E48BF1900704A36 /* libz.tbd */, 166 | ); 167 | name = Frameworks; 168 | sourceTree = ""; 169 | }; 170 | /* End PBXGroup section */ 171 | 172 | /* Begin PBXNativeTarget section */ 173 | 1441618D1BD0A79300FA4F59 /* RCTHttpServer */ = { 174 | isa = PBXNativeTarget; 175 | buildConfigurationList = 144161941BD0A79300FA4F59 /* Build configuration list for PBXNativeTarget "RCTHttpServer" */; 176 | buildPhases = ( 177 | 1441618A1BD0A79300FA4F59 /* Sources */, 178 | 1441618B1BD0A79300FA4F59 /* Frameworks */, 179 | 1441618C1BD0A79300FA4F59 /* CopyFiles */, 180 | ); 181 | buildRules = ( 182 | ); 183 | dependencies = ( 184 | ); 185 | name = RCTHttpServer; 186 | productName = RCTHttpServer; 187 | productReference = B29EC9CC1E48BED600704A36 /* libRCTHttpServer.a */; 188 | productType = "com.apple.product-type.library.static"; 189 | }; 190 | /* End PBXNativeTarget section */ 191 | 192 | /* Begin PBXProject section */ 193 | 145CC5681AED80100006342E /* Project object */ = { 194 | isa = PBXProject; 195 | attributes = { 196 | LastUpgradeCheck = 0630; 197 | ORGANIZATIONNAME = rt2zz; 198 | TargetAttributes = { 199 | 1441618D1BD0A79300FA4F59 = { 200 | CreatedOnToolsVersion = 7.0.1; 201 | }; 202 | }; 203 | }; 204 | buildConfigurationList = 145CC56B1AED80100006342E /* Build configuration list for PBXProject "RCTHttpServer" */; 205 | compatibilityVersion = "Xcode 3.2"; 206 | developmentRegion = English; 207 | hasScannedForEncodings = 0; 208 | knownRegions = ( 209 | en, 210 | ); 211 | mainGroup = 145CC5671AED80100006342E; 212 | productRefGroup = 145CC5671AED80100006342E; 213 | projectDirPath = ""; 214 | projectRoot = ""; 215 | targets = ( 216 | 1441618D1BD0A79300FA4F59 /* RCTHttpServer */, 217 | ); 218 | }; 219 | /* End PBXProject section */ 220 | 221 | /* Begin PBXSourcesBuildPhase section */ 222 | 1441618A1BD0A79300FA4F59 /* Sources */ = { 223 | isa = PBXSourcesBuildPhase; 224 | buildActionMask = 2147483647; 225 | files = ( 226 | B232F4081E49DE0C00C8AEE0 /* WGCDWebServerFileRequest.m in Sources */, 227 | B232F4071E49DE0C00C8AEE0 /* WGCDWebServerDataRequest.m in Sources */, 228 | B232F40D1E49DE0C00C8AEE0 /* WGCDWebServerFileResponse.m in Sources */, 229 | B232F40C1E49DE0C00C8AEE0 /* WGCDWebServerErrorResponse.m in Sources */, 230 | B232F40E1E49DE0C00C8AEE0 /* WGCDWebServerStreamedResponse.m in Sources */, 231 | B232F40B1E49DE0C00C8AEE0 /* WGCDWebServerDataResponse.m in Sources */, 232 | B232F4021E49DE0C00C8AEE0 /* WGCDWebServer.m in Sources */, 233 | B232F4051E49DE0C00C8AEE0 /* WGCDWebServerRequest.m in Sources */, 234 | B232F4031E49DE0C00C8AEE0 /* WGCDWebServerConnection.m in Sources */, 235 | B232F40A1E49DE0C00C8AEE0 /* WGCDWebServerURLEncodedFormRequest.m in Sources */, 236 | B232F4061E49DE0C00C8AEE0 /* WGCDWebServerResponse.m in Sources */, 237 | B232F4041E49DE0C00C8AEE0 /* WGCDWebServerFunctions.m in Sources */, 238 | B232F4091E49DE0C00C8AEE0 /* WGCDWebServerMultiPartFormRequest.m in Sources */, 239 | B2340C241E48BA3E0024C045 /* RCTHttpServer.m in Sources */, 240 | ); 241 | runOnlyForDeploymentPostprocessing = 0; 242 | }; 243 | /* End PBXSourcesBuildPhase section */ 244 | 245 | /* Begin XCBuildConfiguration section */ 246 | 144161951BD0A79300FA4F59 /* Debug */ = { 247 | isa = XCBuildConfiguration; 248 | buildSettings = { 249 | GCC_NO_COMMON_BLOCKS = NO; 250 | HEADER_SEARCH_PATHS = ( 251 | "$(inherited)", 252 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 253 | "$(SRCROOT)/../../../React/**", 254 | "$(SRCROOT)/../../react-native/React/**", 255 | "$(SDKROOT)/usr/include/libxml2/**", 256 | ); 257 | OTHER_LDFLAGS = "-ObjC"; 258 | PRODUCT_NAME = "$(TARGET_NAME)"; 259 | SKIP_INSTALL = YES; 260 | STRIP_INSTALLED_PRODUCT = NO; 261 | }; 262 | name = Debug; 263 | }; 264 | 144161961BD0A79300FA4F59 /* Release */ = { 265 | isa = XCBuildConfiguration; 266 | buildSettings = { 267 | GCC_NO_COMMON_BLOCKS = NO; 268 | HEADER_SEARCH_PATHS = ( 269 | "$(inherited)", 270 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 271 | "$(SRCROOT)/../../../React/**", 272 | "$(SRCROOT)/../../react-native/React/**", 273 | "$(SDKROOT)/usr/include/libxml2/**", 274 | ); 275 | OTHER_LDFLAGS = "-ObjC"; 276 | PRODUCT_NAME = "$(TARGET_NAME)"; 277 | SKIP_INSTALL = YES; 278 | STRIP_INSTALLED_PRODUCT = NO; 279 | }; 280 | name = Release; 281 | }; 282 | 145CC5821AED80100006342E /* Debug */ = { 283 | isa = XCBuildConfiguration; 284 | buildSettings = { 285 | ALWAYS_SEARCH_USER_PATHS = NO; 286 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 287 | CLANG_CXX_LIBRARY = "libc++"; 288 | CLANG_ENABLE_MODULES = YES; 289 | CLANG_ENABLE_OBJC_ARC = YES; 290 | CLANG_WARN_BOOL_CONVERSION = YES; 291 | CLANG_WARN_CONSTANT_CONVERSION = YES; 292 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 293 | CLANG_WARN_EMPTY_BODY = YES; 294 | CLANG_WARN_ENUM_CONVERSION = YES; 295 | CLANG_WARN_INT_CONVERSION = YES; 296 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 297 | CLANG_WARN_UNREACHABLE_CODE = YES; 298 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 299 | COPY_PHASE_STRIP = NO; 300 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 301 | ENABLE_STRICT_OBJC_MSGSEND = YES; 302 | GCC_C_LANGUAGE_STANDARD = gnu99; 303 | GCC_DYNAMIC_NO_PIC = NO; 304 | GCC_NO_COMMON_BLOCKS = YES; 305 | GCC_OPTIMIZATION_LEVEL = 0; 306 | GCC_PREPROCESSOR_DEFINITIONS = ( 307 | "DEBUG=1", 308 | "$(inherited)", 309 | ); 310 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 311 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 312 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 313 | GCC_WARN_UNDECLARED_SELECTOR = YES; 314 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 315 | GCC_WARN_UNUSED_FUNCTION = YES; 316 | GCC_WARN_UNUSED_VARIABLE = YES; 317 | HEADER_SEARCH_PATHS = ( 318 | "$(inherited)", 319 | "$(SRCROOT)/../../../React/**", 320 | "$(SRCROOT)/../../react-native/React/**", 321 | ); 322 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 323 | MTL_ENABLE_DEBUG_INFO = YES; 324 | ONLY_ACTIVE_ARCH = YES; 325 | SDKROOT = iphoneos; 326 | USER_HEADER_SEARCH_PATHS = ""; 327 | }; 328 | name = Debug; 329 | }; 330 | 145CC5831AED80100006342E /* Release */ = { 331 | isa = XCBuildConfiguration; 332 | buildSettings = { 333 | ALWAYS_SEARCH_USER_PATHS = NO; 334 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 335 | CLANG_CXX_LIBRARY = "libc++"; 336 | CLANG_ENABLE_MODULES = YES; 337 | CLANG_ENABLE_OBJC_ARC = YES; 338 | CLANG_WARN_BOOL_CONVERSION = YES; 339 | CLANG_WARN_CONSTANT_CONVERSION = YES; 340 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 341 | CLANG_WARN_EMPTY_BODY = YES; 342 | CLANG_WARN_ENUM_CONVERSION = YES; 343 | CLANG_WARN_INT_CONVERSION = YES; 344 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 345 | CLANG_WARN_UNREACHABLE_CODE = YES; 346 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 347 | COPY_PHASE_STRIP = NO; 348 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 349 | ENABLE_NS_ASSERTIONS = NO; 350 | ENABLE_STRICT_OBJC_MSGSEND = YES; 351 | GCC_C_LANGUAGE_STANDARD = gnu99; 352 | GCC_NO_COMMON_BLOCKS = YES; 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_AGGRESSIVE; 357 | GCC_WARN_UNUSED_FUNCTION = YES; 358 | GCC_WARN_UNUSED_VARIABLE = YES; 359 | HEADER_SEARCH_PATHS = ( 360 | "$(inherited)", 361 | "$(SRCROOT)/../../../React/**", 362 | "$(SRCROOT)/../../react-native/React/**", 363 | ); 364 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 365 | MTL_ENABLE_DEBUG_INFO = NO; 366 | SDKROOT = iphoneos; 367 | USER_HEADER_SEARCH_PATHS = ""; 368 | VALIDATE_PRODUCT = YES; 369 | }; 370 | name = Release; 371 | }; 372 | /* End XCBuildConfiguration section */ 373 | 374 | /* Begin XCConfigurationList section */ 375 | 144161941BD0A79300FA4F59 /* Build configuration list for PBXNativeTarget "RCTHttpServer" */ = { 376 | isa = XCConfigurationList; 377 | buildConfigurations = ( 378 | 144161951BD0A79300FA4F59 /* Debug */, 379 | 144161961BD0A79300FA4F59 /* Release */, 380 | ); 381 | defaultConfigurationIsVisible = 0; 382 | defaultConfigurationName = Release; 383 | }; 384 | 145CC56B1AED80100006342E /* Build configuration list for PBXProject "RCTHttpServer" */ = { 385 | isa = XCConfigurationList; 386 | buildConfigurations = ( 387 | 145CC5821AED80100006342E /* Debug */, 388 | 145CC5831AED80100006342E /* Release */, 389 | ); 390 | defaultConfigurationIsVisible = 0; 391 | defaultConfigurationName = Release; 392 | }; 393 | /* End XCConfigurationList section */ 394 | }; 395 | rootObject = 145CC5681AED80100006342E /* Project object */; 396 | } 397 | -------------------------------------------------------------------------------- /ios/RCTHttpServer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/RCTHttpServer.xcodeproj/project.xcworkspace/xcuserdata/alwx.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alwx/react-native-http-bridge/a6c2c705561250198fc601a92b0d2579ec50fe11/ios/RCTHttpServer.xcodeproj/project.xcworkspace/xcuserdata/alwx.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ios/RCTHttpServer.xcodeproj/xcuserdata/alwx.xcuserdatad/xcschemes/RCTHttpServer.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 55 | 61 | 62 | 63 | 64 | 65 | 66 | 72 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /ios/RCTHttpServer.xcodeproj/xcuserdata/alwx.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | RCTHttpServer.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 1441618D1BD0A79300FA4F59 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Core/WGCDWebServerConnection.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "WGCDWebServer.h" 29 | 30 | @class WGCDWebServerHandler; 31 | 32 | /** 33 | * The WGCDWebServerConnection class is instantiated by WGCDWebServer to handle 34 | * each new HTTP connection. Each instance stays alive until the connection is 35 | * closed. 36 | * 37 | * You cannot use this class directly, but it is made public so you can 38 | * subclass it to override some hooks. Use the WGCDWebServerOption_ConnectionClass 39 | * option for WGCDWebServer to install your custom subclass. 40 | * 41 | * @warning The WGCDWebServerConnection retains the WGCDWebServer until the 42 | * connection is closed. 43 | */ 44 | @interface WGCDWebServerConnection : NSObject 45 | 46 | /** 47 | * Returns the WGCDWebServer that owns the connection. 48 | */ 49 | @property(nonatomic, readonly) WGCDWebServer* server; 50 | 51 | /** 52 | * Returns YES if the connection is using IPv6. 53 | */ 54 | @property(nonatomic, readonly, getter=isUsingIPv6) BOOL usingIPv6; 55 | 56 | /** 57 | * Returns the address of the local peer (i.e. server) of the connection 58 | * as a raw "struct sockaddr". 59 | */ 60 | @property(nonatomic, readonly) NSData* localAddressData; 61 | 62 | /** 63 | * Returns the address of the local peer (i.e. server) of the connection 64 | * as a string. 65 | */ 66 | @property(nonatomic, readonly) NSString* localAddressString; 67 | 68 | /** 69 | * Returns the address of the remote peer (i.e. client) of the connection 70 | * as a raw "struct sockaddr". 71 | */ 72 | @property(nonatomic, readonly) NSData* remoteAddressData; 73 | 74 | /** 75 | * Returns the address of the remote peer (i.e. client) of the connection 76 | * as a string. 77 | */ 78 | @property(nonatomic, readonly) NSString* remoteAddressString; 79 | 80 | /** 81 | * Returns the total number of bytes received from the remote peer (i.e. client) 82 | * so far. 83 | */ 84 | @property(nonatomic, readonly) NSUInteger totalBytesRead; 85 | 86 | /** 87 | * Returns the total number of bytes sent to the remote peer (i.e. client) so far. 88 | */ 89 | @property(nonatomic, readonly) NSUInteger totalBytesWritten; 90 | 91 | @end 92 | 93 | /** 94 | * Hooks to customize the behavior of WGCDWebServer HTTP connections. 95 | * 96 | * @warning These methods can be called on any WGCD thread. 97 | * Be sure to also call "super" when overriding them. 98 | */ 99 | @interface WGCDWebServerConnection (Subclassing) 100 | 101 | /** 102 | * This method is called when the connection is opened. 103 | * 104 | * Return NO to reject the connection e.g. after validating the local 105 | * or remote address. 106 | */ 107 | - (BOOL)open; 108 | 109 | /** 110 | * This method is called whenever data has been received 111 | * from the remote peer (i.e. client). 112 | * 113 | * @warning Do not attempt to modify this data. 114 | */ 115 | - (void)didReadBytes:(const void*)bytes length:(NSUInteger)length; 116 | 117 | /** 118 | * This method is called whenever data has been sent 119 | * to the remote peer (i.e. client). 120 | * 121 | * @warning Do not attempt to modify this data. 122 | */ 123 | - (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length; 124 | 125 | /** 126 | * This method is called after the HTTP headers have been received to 127 | * allow replacing the request URL by another one. 128 | * 129 | * The default implementation returns the original URL. 130 | */ 131 | - (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary*)headers; 132 | 133 | /** 134 | * Assuming a valid HTTP request was received, this method is called before 135 | * the request is processed. 136 | * 137 | * Return a non-nil WGCDWebServerResponse to bypass the request processing entirely. 138 | * 139 | * The default implementation checks for HTTP authentication if applicable 140 | * and returns a barebone 401 status code response if authentication failed. 141 | */ 142 | - (WGCDWebServerResponse*)preflightRequest:(WGCDWebServerRequest*)request; 143 | 144 | /** 145 | * Assuming a valid HTTP request was received and -preflightRequest: returned nil, 146 | * this method is called to process the request by executing the handler's 147 | * process block. 148 | */ 149 | - (void)processRequest:(WGCDWebServerRequest*)request completion:(WGCDWebServerCompletionBlock)completion; 150 | 151 | /** 152 | * Assuming a valid HTTP request was received and either -preflightRequest: 153 | * or -processRequest:completion: returned a non-nil WGCDWebServerResponse, 154 | * this method is called to override the response. 155 | * 156 | * You can either modify the current response and return it, or return a 157 | * completely new one. 158 | * 159 | * The default implementation replaces any response matching the "ETag" or 160 | * "Last-Modified-Date" header of the request by a barebone "Not-Modified" (304) 161 | * one. 162 | */ 163 | - (WGCDWebServerResponse*)overrideResponse:(WGCDWebServerResponse*)response forRequest:(WGCDWebServerRequest*)request; 164 | 165 | /** 166 | * This method is called if any error happens while validing or processing 167 | * the request or if no WGCDWebServerResponse was generated during processing. 168 | * 169 | * @warning If the request was invalid (e.g. the HTTP headers were malformed), 170 | * the "request" argument will be nil. 171 | */ 172 | - (void)abortRequest:(WGCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode; 173 | 174 | /** 175 | * Called when the connection is closed. 176 | */ 177 | - (void)close; 178 | 179 | @end 180 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Core/WGCDWebServerFunctions.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import 29 | 30 | #ifdef __cplusplus 31 | extern "C" { 32 | #endif 33 | 34 | /** 35 | * Converts a file extension to the corresponding MIME type. 36 | * If there is no match, "application/octet-stream" is returned. 37 | */ 38 | NSString* WGCDWebServerGetMimeTypeForExtension(NSString* extension); 39 | 40 | /** 41 | * Add percent-escapes to a string so it can be used in a URL. 42 | * The legal characters ":@/?&=+" are also escaped to ensure compatibility 43 | * with URL encoded forms and URL queries. 44 | */ 45 | NSString* WGCDWebServerEscapeURLString(NSString* string); 46 | 47 | /** 48 | * Unescapes a URL percent-encoded string. 49 | */ 50 | NSString* WGCDWebServerUnescapeURLString(NSString* string); 51 | 52 | /** 53 | * Extracts the unescaped names and values from an 54 | * "application/x-www-form-urlencoded" form. 55 | * http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 56 | */ 57 | NSDictionary* WGCDWebServerParseURLEncodedForm(NSString* form); 58 | 59 | /** 60 | * On OS X, returns the IPv4 or IPv6 address as a string of the primary 61 | * connected service or nil if not available. 62 | * 63 | * On iOS, returns the IPv4 or IPv6 address as a string of the WiFi 64 | * interface if connected or nil otherwise. 65 | */ 66 | NSString* WGCDWebServerGetPrimaryIPAddress(BOOL useIPv6); 67 | 68 | /** 69 | * Converts a date into a string using RFC822 formatting. 70 | * https://tools.ietf.org/html/rfc822#section-5 71 | * https://tools.ietf.org/html/rfc1123#section-5.2.14 72 | */ 73 | NSString* WGCDWebServerFormatRFC822(NSDate* date); 74 | 75 | /** 76 | * Converts a RFC822 formatted string into a date. 77 | * https://tools.ietf.org/html/rfc822#section-5 78 | * https://tools.ietf.org/html/rfc1123#section-5.2.14 79 | * 80 | * @warning Timezones other than GMT are not supported by this function. 81 | */ 82 | NSDate* WGCDWebServerParseRFC822(NSString* string); 83 | 84 | /** 85 | * Converts a date into a string using IOS 8601 formatting. 86 | * http://tools.ietf.org/html/rfc3339#section-5.6 87 | */ 88 | NSString* WGCDWebServerFormatISO8601(NSDate* date); 89 | 90 | /** 91 | * Converts a ISO 8601 formatted string into a date. 92 | * http://tools.ietf.org/html/rfc3339#section-5.6 93 | * 94 | * @warning Only "calendar" variant is supported at this time and timezones 95 | * other than GMT are not supported either. 96 | */ 97 | NSDate* WGCDWebServerParseISO8601(NSString* string); 98 | 99 | #ifdef __cplusplus 100 | } 101 | #endif 102 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Core/WGCDWebServerFunctions.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error WGCDWebServer requires ARC 30 | #endif 31 | 32 | #import 33 | #if TARGET_OS_IPHONE 34 | #import 35 | #else 36 | #import 37 | #endif 38 | #import 39 | 40 | #import 41 | #import 42 | #import 43 | 44 | #import "WGCDWebServerPrivate.h" 45 | 46 | static NSDateFormatter* _dateFormatterRFC822 = nil; 47 | static NSDateFormatter* _dateFormatterISO8601 = nil; 48 | static dispatch_queue_t _dateFormatterQueue = NULL; 49 | 50 | // TODO: Handle RFC 850 and ANSI C's asctime() format 51 | void WGCDWebServerInitializeFunctions() { 52 | GWS_DCHECK([NSThread isMainThread]); // NSDateFormatter should be initialized on main thread 53 | if (_dateFormatterRFC822 == nil) { 54 | _dateFormatterRFC822 = [[NSDateFormatter alloc] init]; 55 | _dateFormatterRFC822.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; 56 | _dateFormatterRFC822.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'"; 57 | _dateFormatterRFC822.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; 58 | GWS_DCHECK(_dateFormatterRFC822); 59 | } 60 | if (_dateFormatterISO8601 == nil) { 61 | _dateFormatterISO8601 = [[NSDateFormatter alloc] init]; 62 | _dateFormatterISO8601.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; 63 | _dateFormatterISO8601.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'+00:00'"; 64 | _dateFormatterISO8601.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; 65 | GWS_DCHECK(_dateFormatterISO8601); 66 | } 67 | if (_dateFormatterQueue == NULL) { 68 | _dateFormatterQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); 69 | GWS_DCHECK(_dateFormatterQueue); 70 | } 71 | } 72 | 73 | NSString* WGCDWebServerNormalizeHeaderValue(NSString* value) { 74 | if (value) { 75 | NSRange range = [value rangeOfString:@";"]; // Assume part before ";" separator is case-insensitive 76 | if (range.location != NSNotFound) { 77 | value = [[[value substringToIndex:range.location] lowercaseString] stringByAppendingString:[value substringFromIndex:range.location]]; 78 | } else { 79 | value = [value lowercaseString]; 80 | } 81 | } 82 | return value; 83 | } 84 | 85 | NSString* WGCDWebServerTruncateHeaderValue(NSString* value) { 86 | NSRange range = [value rangeOfString:@";"]; 87 | return range.location != NSNotFound ? [value substringToIndex:range.location] : value; 88 | } 89 | 90 | NSString* WGCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) { 91 | NSString* parameter = nil; 92 | NSScanner* scanner = [[NSScanner alloc] initWithString:value]; 93 | [scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive 94 | NSString* string = [NSString stringWithFormat:@"%@=", name]; 95 | if ([scanner scanUpToString:string intoString:NULL]) { 96 | [scanner scanString:string intoString:NULL]; 97 | if ([scanner scanString:@"\"" intoString:NULL]) { 98 | [scanner scanUpToString:@"\"" intoString:¶meter]; 99 | } else { 100 | [scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:¶meter]; 101 | } 102 | } 103 | return parameter; 104 | } 105 | 106 | // http://www.w3schools.com/tags/ref_charactersets.asp 107 | NSStringEncoding WGCDWebServerStringEncodingFromCharset(NSString* charset) { 108 | NSStringEncoding encoding = kCFStringEncodingInvalidId; 109 | if (charset) { 110 | encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)charset)); 111 | } 112 | return (encoding != kCFStringEncodingInvalidId ? encoding : NSUTF8StringEncoding); 113 | } 114 | 115 | NSString* WGCDWebServerFormatRFC822(NSDate* date) { 116 | __block NSString* string; 117 | dispatch_sync(_dateFormatterQueue, ^{ 118 | string = [_dateFormatterRFC822 stringFromDate:date]; 119 | }); 120 | return string; 121 | } 122 | 123 | NSDate* WGCDWebServerParseRFC822(NSString* string) { 124 | __block NSDate* date; 125 | dispatch_sync(_dateFormatterQueue, ^{ 126 | date = [_dateFormatterRFC822 dateFromString:string]; 127 | }); 128 | return date; 129 | } 130 | 131 | NSString* WGCDWebServerFormatISO8601(NSDate* date) { 132 | __block NSString* string; 133 | dispatch_sync(_dateFormatterQueue, ^{ 134 | string = [_dateFormatterISO8601 stringFromDate:date]; 135 | }); 136 | return string; 137 | } 138 | 139 | NSDate* WGCDWebServerParseISO8601(NSString* string) { 140 | __block NSDate* date; 141 | dispatch_sync(_dateFormatterQueue, ^{ 142 | date = [_dateFormatterISO8601 dateFromString:string]; 143 | }); 144 | return date; 145 | } 146 | 147 | BOOL WGCDWebServerIsTextContentType(NSString* type) { 148 | return ([type hasPrefix:@"text/"] || [type hasPrefix:@"application/json"] || [type hasPrefix:@"application/xml"]); 149 | } 150 | 151 | NSString* WGCDWebServerDescribeData(NSData* data, NSString* type) { 152 | if (WGCDWebServerIsTextContentType(type)) { 153 | NSString* charset = WGCDWebServerExtractHeaderValueParameter(type, @"charset"); 154 | NSString* string = [[NSString alloc] initWithData:data encoding:WGCDWebServerStringEncodingFromCharset(charset)]; 155 | if (string) { 156 | return string; 157 | } 158 | } 159 | return [NSString stringWithFormat:@"<%lu bytes>", (unsigned long)data.length]; 160 | } 161 | 162 | NSString* WGCDWebServerGetMimeTypeForExtension(NSString* extension) { 163 | static NSDictionary* _overrides = nil; 164 | if (_overrides == nil) { 165 | _overrides = [[NSDictionary alloc] initWithObjectsAndKeys: 166 | @"text/css", @"css", 167 | nil]; 168 | } 169 | NSString* mimeType = nil; 170 | extension = [extension lowercaseString]; 171 | if (extension.length) { 172 | mimeType = [_overrides objectForKey:extension]; 173 | if (mimeType == nil) { 174 | CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL); 175 | if (uti) { 176 | mimeType = CFBridgingRelease(UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)); 177 | CFRelease(uti); 178 | } 179 | } 180 | } 181 | return mimeType ? mimeType : kWGCDWebServerDefaultMimeType; 182 | } 183 | 184 | NSString* WGCDWebServerEscapeURLString(NSString* string) { 185 | #pragma clang diagnostic push 186 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 187 | return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":@/?&=+"), kCFStringEncodingUTF8)); 188 | #pragma clang diagnostic pop 189 | } 190 | 191 | NSString* WGCDWebServerUnescapeURLString(NSString* string) { 192 | #pragma clang diagnostic push 193 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 194 | return CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""), kCFStringEncodingUTF8)); 195 | #pragma clang diagnostic pop 196 | } 197 | 198 | NSDictionary* WGCDWebServerParseURLEncodedForm(NSString* form) { 199 | NSMutableDictionary* parameters = [NSMutableDictionary dictionary]; 200 | NSScanner* scanner = [[NSScanner alloc] initWithString:form]; 201 | [scanner setCharactersToBeSkipped:nil]; 202 | while (1) { 203 | NSString* key = nil; 204 | if (![scanner scanUpToString:@"=" intoString:&key] || [scanner isAtEnd]) { 205 | break; 206 | } 207 | [scanner setScanLocation:([scanner scanLocation] + 1)]; 208 | 209 | NSString* value = nil; 210 | [scanner scanUpToString:@"&" intoString:&value]; 211 | if (value == nil) { 212 | value = @""; 213 | } 214 | 215 | key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "]; 216 | NSString* unescapedKey = key ? WGCDWebServerUnescapeURLString(key) : nil; 217 | value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "]; 218 | NSString* unescapedValue = value ? WGCDWebServerUnescapeURLString(value) : nil; 219 | if (unescapedKey && unescapedValue) { 220 | [parameters setObject:unescapedValue forKey:unescapedKey]; 221 | } else { 222 | GWS_LOG_WARNING(@"Failed parsing URL encoded form for key \"%@\" and value \"%@\"", key, value); 223 | GWS_DNOT_REACHED(); 224 | } 225 | 226 | if ([scanner isAtEnd]) { 227 | break; 228 | } 229 | [scanner setScanLocation:([scanner scanLocation] + 1)]; 230 | } 231 | return parameters; 232 | } 233 | 234 | NSString* WGCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) { 235 | NSString* string = nil; 236 | char hostBuffer[NI_MAXHOST]; 237 | char serviceBuffer[NI_MAXSERV]; 238 | if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) >= 0) { 239 | string = includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : [NSString stringWithUTF8String:hostBuffer]; 240 | } else { 241 | GWS_DNOT_REACHED(); 242 | } 243 | return string; 244 | } 245 | 246 | NSString* WGCDWebServerGetPrimaryIPAddress(BOOL useIPv6) { 247 | NSString* address = nil; 248 | #if TARGET_OS_IPHONE 249 | #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_TV 250 | const char* primaryInterface = "en0"; // WiFi interface on iOS 251 | #endif 252 | #else 253 | const char* primaryInterface = NULL; 254 | SCDynamicStoreRef store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("WGCDWebServer"), NULL, NULL); 255 | if (store) { 256 | CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4")); // There is no equivalent for IPv6 but the primary interface should be the same 257 | if (info) { 258 | primaryInterface = [[NSString stringWithString:[(__bridge NSDictionary*)info objectForKey:@"PrimaryInterface"]] UTF8String]; 259 | CFRelease(info); 260 | } 261 | CFRelease(store); 262 | } 263 | if (primaryInterface == NULL) { 264 | primaryInterface = "lo0"; 265 | } 266 | #endif 267 | struct ifaddrs* list; 268 | if (getifaddrs(&list) >= 0) { 269 | for (struct ifaddrs* ifap = list; ifap; ifap = ifap->ifa_next) { 270 | #if TARGET_IPHONE_SIMULATOR || TARGET_OS_TV 271 | // Assume en0 is Ethernet and en1 is WiFi since there is no way to use SystemConfiguration framework in iOS Simulator 272 | // Assumption holds for Apple TV running tvOS 273 | if (strcmp(ifap->ifa_name, "en0") && strcmp(ifap->ifa_name, "en1")) 274 | #else 275 | if (strcmp(ifap->ifa_name, primaryInterface)) 276 | #endif 277 | { 278 | continue; 279 | } 280 | if ((ifap->ifa_flags & IFF_UP) && ((!useIPv6 && (ifap->ifa_addr->sa_family == AF_INET)) || (useIPv6 && (ifap->ifa_addr->sa_family == AF_INET6)))) { 281 | address = WGCDWebServerStringFromSockAddr(ifap->ifa_addr, NO); 282 | break; 283 | } 284 | } 285 | freeifaddrs(list); 286 | } 287 | return address; 288 | } 289 | 290 | NSString* WGCDWebServerComputeMD5Digest(NSString* format, ...) { 291 | va_list arguments; 292 | va_start(arguments, format); 293 | const char* string = [[[NSString alloc] initWithFormat:format arguments:arguments] UTF8String]; 294 | va_end(arguments); 295 | unsigned char md5[CC_MD5_DIGEST_LENGTH]; 296 | CC_MD5(string, (CC_LONG)strlen(string), md5); 297 | char buffer[2 * CC_MD5_DIGEST_LENGTH + 1]; 298 | for (int i = 0; i < CC_MD5_DIGEST_LENGTH; ++i) { 299 | unsigned char byte = md5[i]; 300 | unsigned char byteHi = (byte & 0xF0) >> 4; 301 | buffer[2 * i + 0] = byteHi >= 10 ? 'a' + byteHi - 10 : '0' + byteHi; 302 | unsigned char byteLo = byte & 0x0F; 303 | buffer[2 * i + 1] = byteLo >= 10 ? 'a' + byteLo - 10 : '0' + byteLo; 304 | } 305 | buffer[2 * CC_MD5_DIGEST_LENGTH] = 0; 306 | return [NSString stringWithUTF8String:buffer]; 307 | } 308 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Core/WGCDWebServerHTTPStatusCodes.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 29 | // http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml 30 | 31 | #import 32 | 33 | /** 34 | * Convenience constants for "informational" HTTP status codes. 35 | */ 36 | typedef NS_ENUM(NSInteger, WGCDWebServerInformationalHTTPStatusCode) { 37 | kWGCDWebServerHTTPStatusCode_Continue = 100, 38 | kWGCDWebServerHTTPStatusCode_SwitchingProtocols = 101, 39 | kWGCDWebServerHTTPStatusCode_Processing = 102 40 | }; 41 | 42 | /** 43 | * Convenience constants for "successful" HTTP status codes. 44 | */ 45 | typedef NS_ENUM(NSInteger, WGCDWebServerSuccessfulHTTPStatusCode) { 46 | kWGCDWebServerHTTPStatusCode_OK = 200, 47 | kWGCDWebServerHTTPStatusCode_Created = 201, 48 | kWGCDWebServerHTTPStatusCode_Accepted = 202, 49 | kWGCDWebServerHTTPStatusCode_NonAuthoritativeInformation = 203, 50 | kWGCDWebServerHTTPStatusCode_NoContent = 204, 51 | kWGCDWebServerHTTPStatusCode_ResetContent = 205, 52 | kWGCDWebServerHTTPStatusCode_PartialContent = 206, 53 | kWGCDWebServerHTTPStatusCode_MultiStatus = 207, 54 | kWGCDWebServerHTTPStatusCode_AlreadyReported = 208 55 | }; 56 | 57 | /** 58 | * Convenience constants for "redirection" HTTP status codes. 59 | */ 60 | typedef NS_ENUM(NSInteger, WGCDWebServerRedirectionHTTPStatusCode) { 61 | kWGCDWebServerHTTPStatusCode_MultipleChoices = 300, 62 | kWGCDWebServerHTTPStatusCode_MovedPermanently = 301, 63 | kWGCDWebServerHTTPStatusCode_Found = 302, 64 | kWGCDWebServerHTTPStatusCode_SeeOther = 303, 65 | kWGCDWebServerHTTPStatusCode_NotModified = 304, 66 | kWGCDWebServerHTTPStatusCode_UseProxy = 305, 67 | kWGCDWebServerHTTPStatusCode_TemporaryRedirect = 307, 68 | kWGCDWebServerHTTPStatusCode_PermanentRedirect = 308 69 | }; 70 | 71 | /** 72 | * Convenience constants for "client error" HTTP status codes. 73 | */ 74 | typedef NS_ENUM(NSInteger, WGCDWebServerClientErrorHTTPStatusCode) { 75 | kWGCDWebServerHTTPStatusCode_BadRequest = 400, 76 | kWGCDWebServerHTTPStatusCode_Unauthorized = 401, 77 | kWGCDWebServerHTTPStatusCode_PaymentRequired = 402, 78 | kWGCDWebServerHTTPStatusCode_Forbidden = 403, 79 | kWGCDWebServerHTTPStatusCode_NotFound = 404, 80 | kWGCDWebServerHTTPStatusCode_MethodNotAllowed = 405, 81 | kWGCDWebServerHTTPStatusCode_NotAcceptable = 406, 82 | kWGCDWebServerHTTPStatusCode_ProxyAuthenticationRequired = 407, 83 | kWGCDWebServerHTTPStatusCode_RequestTimeout = 408, 84 | kWGCDWebServerHTTPStatusCode_Conflict = 409, 85 | kWGCDWebServerHTTPStatusCode_Gone = 410, 86 | kWGCDWebServerHTTPStatusCode_LengthRequired = 411, 87 | kWGCDWebServerHTTPStatusCode_PreconditionFailed = 412, 88 | kWGCDWebServerHTTPStatusCode_RequestEntityTooLarge = 413, 89 | kWGCDWebServerHTTPStatusCode_RequestURITooLong = 414, 90 | kWGCDWebServerHTTPStatusCode_UnsupportedMediaType = 415, 91 | kWGCDWebServerHTTPStatusCode_RequestedRangeNotSatisfiable = 416, 92 | kWGCDWebServerHTTPStatusCode_ExpectationFailed = 417, 93 | kWGCDWebServerHTTPStatusCode_UnprocessableEntity = 422, 94 | kWGCDWebServerHTTPStatusCode_Locked = 423, 95 | kWGCDWebServerHTTPStatusCode_FailedDependency = 424, 96 | kWGCDWebServerHTTPStatusCode_UpgradeRequired = 426, 97 | kWGCDWebServerHTTPStatusCode_PreconditionRequired = 428, 98 | kWGCDWebServerHTTPStatusCode_TooManyRequests = 429, 99 | kWGCDWebServerHTTPStatusCode_RequestHeaderFieldsTooLarge = 431 100 | }; 101 | 102 | /** 103 | * Convenience constants for "server error" HTTP status codes. 104 | */ 105 | typedef NS_ENUM(NSInteger, WGCDWebServerServerErrorHTTPStatusCode) { 106 | kWGCDWebServerHTTPStatusCode_InternalServerError = 500, 107 | kWGCDWebServerHTTPStatusCode_NotImplemented = 501, 108 | kWGCDWebServerHTTPStatusCode_BadGateway = 502, 109 | kWGCDWebServerHTTPStatusCode_ServiceUnavailable = 503, 110 | kWGCDWebServerHTTPStatusCode_GatewayTimeout = 504, 111 | kWGCDWebServerHTTPStatusCode_HTTPVersionNotSupported = 505, 112 | kWGCDWebServerHTTPStatusCode_InsufficientStorage = 507, 113 | kWGCDWebServerHTTPStatusCode_LoopDetected = 508, 114 | kWGCDWebServerHTTPStatusCode_NotExtended = 510, 115 | kWGCDWebServerHTTPStatusCode_NetworkAuthenticationRequired = 511 116 | }; 117 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Core/WGCDWebServerPrivate.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import 29 | #import 30 | 31 | /** 32 | * All WGCDWebServer headers. 33 | */ 34 | 35 | #import "WGCDWebServerHTTPStatusCodes.h" 36 | #import "WGCDWebServerFunctions.h" 37 | 38 | #import "WGCDWebServer.h" 39 | #import "WGCDWebServerConnection.h" 40 | 41 | #import "WGCDWebServerDataRequest.h" 42 | #import "WGCDWebServerFileRequest.h" 43 | #import "WGCDWebServerMultiPartFormRequest.h" 44 | #import "WGCDWebServerURLEncodedFormRequest.h" 45 | 46 | #import "WGCDWebServerDataResponse.h" 47 | #import "WGCDWebServerErrorResponse.h" 48 | #import "WGCDWebServerFileResponse.h" 49 | #import "WGCDWebServerStreamedResponse.h" 50 | 51 | /** 52 | * Check if a custom logging facility should be used instead. 53 | */ 54 | 55 | #if defined(__WGCDWEBSERVER_LOGGING_HEADER__) 56 | 57 | #define __WGCDWEBSERVER_LOGGING_FACILITY_CUSTOM__ 58 | 59 | #import __WGCDWEBSERVER_LOGGING_HEADER__ 60 | 61 | /** 62 | * Automatically detect if XLFacility is available and if so use it as a 63 | * logging facility. 64 | */ 65 | 66 | #elif defined(__has_include) && __has_include("XLFacilityMacros.h") 67 | 68 | #define __WGCDWEBSERVER_LOGGING_FACILITY_XLFACILITY__ 69 | 70 | #undef XLOG_TAG 71 | #define XLOG_TAG @"gcdwebserver.internal" 72 | 73 | #import "XLFacilityMacros.h" 74 | 75 | #define GWS_LOG_DEBUG(...) XLOG_DEBUG(__VA_ARGS__) 76 | #define GWS_LOG_VERBOSE(...) XLOG_VERBOSE(__VA_ARGS__) 77 | #define GWS_LOG_INFO(...) XLOG_INFO(__VA_ARGS__) 78 | #define GWS_LOG_WARNING(...) XLOG_WARNING(__VA_ARGS__) 79 | #define GWS_LOG_ERROR(...) XLOG_ERROR(__VA_ARGS__) 80 | 81 | #define GWS_DCHECK(__CONDITION__) XLOG_DEBUG_CHECK(__CONDITION__) 82 | #define GWS_DNOT_REACHED() XLOG_DEBUG_UNREACHABLE() 83 | 84 | /** 85 | * Automatically detect if CocoaLumberJack is available and if so use 86 | * it as a logging facility. 87 | */ 88 | 89 | #elif defined(__has_include) && __has_include("CocoaLumberjack/CocoaLumberjack.h") 90 | 91 | #import 92 | 93 | #define __WGCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__ 94 | 95 | #undef LOG_LEVEL_DEF 96 | #define LOG_LEVEL_DEF WGCDWebServerLogLevel 97 | extern DDLogLevel WGCDWebServerLogLevel; 98 | 99 | #define GWS_LOG_DEBUG(...) DDLogDebug(__VA_ARGS__) 100 | #define GWS_LOG_VERBOSE(...) DDLogVerbose(__VA_ARGS__) 101 | #define GWS_LOG_INFO(...) DDLogInfo(__VA_ARGS__) 102 | #define GWS_LOG_WARNING(...) DDLogWarn(__VA_ARGS__) 103 | #define GWS_LOG_ERROR(...) DDLogError(__VA_ARGS__) 104 | 105 | /** 106 | * If all of the above fail, then use WGCDWebServer built-in 107 | * logging facility. 108 | */ 109 | 110 | #else 111 | 112 | #define __WGCDWEBSERVER_LOGGING_FACILITY_BUILTIN__ 113 | 114 | typedef NS_ENUM(int, WGCDWebServerLoggingLevel) { 115 | kWGCDWebServerLoggingLevel_Debug = 0, 116 | kWGCDWebServerLoggingLevel_Verbose, 117 | kWGCDWebServerLoggingLevel_Info, 118 | kWGCDWebServerLoggingLevel_Warning, 119 | kWGCDWebServerLoggingLevel_Error 120 | }; 121 | 122 | extern WGCDWebServerLoggingLevel WGCDWebServerLogLevel; 123 | extern void WGCDWebServerLogMessage(WGCDWebServerLoggingLevel level, NSString* format, ...) NS_FORMAT_FUNCTION(2, 3); 124 | 125 | #if DEBUG 126 | #define GWS_LOG_DEBUG(...) do { if (WGCDWebServerLogLevel <= kWGCDWebServerLoggingLevel_Debug) WGCDWebServerLogMessage(kWGCDWebServerLoggingLevel_Debug, __VA_ARGS__); } while (0) 127 | #else 128 | #define GWS_LOG_DEBUG(...) 129 | #endif 130 | #define GWS_LOG_VERBOSE(...) do { if (WGCDWebServerLogLevel <= kWGCDWebServerLoggingLevel_Verbose) WGCDWebServerLogMessage(kWGCDWebServerLoggingLevel_Verbose, __VA_ARGS__); } while (0) 131 | #define GWS_LOG_INFO(...) do { if (WGCDWebServerLogLevel <= kWGCDWebServerLoggingLevel_Info) WGCDWebServerLogMessage(kWGCDWebServerLoggingLevel_Info, __VA_ARGS__); } while (0) 132 | #define GWS_LOG_WARNING(...) do { if (WGCDWebServerLogLevel <= kWGCDWebServerLoggingLevel_Warning) WGCDWebServerLogMessage(kWGCDWebServerLoggingLevel_Warning, __VA_ARGS__); } while (0) 133 | #define GWS_LOG_ERROR(...) do { if (WGCDWebServerLogLevel <= kWGCDWebServerLoggingLevel_Error) WGCDWebServerLogMessage(kWGCDWebServerLoggingLevel_Error, __VA_ARGS__); } while (0) 134 | 135 | #endif 136 | 137 | /** 138 | * Consistency check macros used when building Debug only. 139 | */ 140 | 141 | #if !defined(GWS_DCHECK) || !defined(GWS_DNOT_REACHED) 142 | 143 | #if DEBUG 144 | 145 | #define GWS_DCHECK(__CONDITION__) \ 146 | do { \ 147 | if (!(__CONDITION__)) { \ 148 | abort(); \ 149 | } \ 150 | } while (0) 151 | #define GWS_DNOT_REACHED() abort() 152 | 153 | #else 154 | 155 | #define GWS_DCHECK(__CONDITION__) 156 | #define GWS_DNOT_REACHED() 157 | 158 | #endif 159 | 160 | #endif 161 | 162 | /** 163 | * WGCDWebServer internal constants and APIs. 164 | */ 165 | 166 | #define kWGCDWebServerDefaultMimeType @"application/octet-stream" 167 | #define kWGCDWebServerErrorDomain @"WGCDWebServerErrorDomain" 168 | 169 | static inline BOOL WGCDWebServerIsValidByteRange(NSRange range) { 170 | return ((range.location != NSUIntegerMax) || (range.length > 0)); 171 | } 172 | 173 | static inline NSError* WGCDWebServerMakePosixError(int code) { 174 | return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithUTF8String:strerror(code)]}]; 175 | } 176 | 177 | extern void WGCDWebServerInitializeFunctions(); 178 | extern NSString* WGCDWebServerNormalizeHeaderValue(NSString* value); 179 | extern NSString* WGCDWebServerTruncateHeaderValue(NSString* value); 180 | extern NSString* WGCDWebServerExtractHeaderValueParameter(NSString* header, NSString* attribute); 181 | extern NSStringEncoding WGCDWebServerStringEncodingFromCharset(NSString* charset); 182 | extern BOOL WGCDWebServerIsTextContentType(NSString* type); 183 | extern NSString* WGCDWebServerDescribeData(NSData* data, NSString* contentType); 184 | extern NSString* WGCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_FUNCTION(1,2); 185 | extern NSString* WGCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService); 186 | 187 | @interface WGCDWebServerConnection () 188 | - (id)initWithServer:(WGCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket; 189 | @end 190 | 191 | @interface WGCDWebServer () 192 | @property(nonatomic, readonly) NSArray* handlers; 193 | @property(nonatomic, readonly) NSString* serverName; 194 | @property(nonatomic, readonly) NSString* authenticationRealm; 195 | @property(nonatomic, readonly) NSDictionary* authenticationBasicAccounts; 196 | @property(nonatomic, readonly) NSDictionary* authenticationDigestAccounts; 197 | @property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET; 198 | @property(nonatomic, readonly) dispatch_queue_priority_t dispatchQueuePriority; 199 | - (void)willStartConnection:(WGCDWebServerConnection*)connection; 200 | - (void)didEndConnection:(WGCDWebServerConnection*)connection; 201 | @end 202 | 203 | @interface WGCDWebServerHandler : NSObject 204 | @property(nonatomic, readonly) WGCDWebServerMatchBlock matchBlock; 205 | @property(nonatomic, readonly) WGCDWebServerAsyncProcessBlock asyncProcessBlock; 206 | @end 207 | 208 | @interface WGCDWebServerRequest () 209 | @property(nonatomic, readonly) BOOL usesChunkedTransferEncoding; 210 | @property(nonatomic, readwrite) NSData* localAddressData; 211 | @property(nonatomic, readwrite) NSData* remoteAddressData; 212 | - (void)prepareForWriting; 213 | - (BOOL)performOpen:(NSError**)error; 214 | - (BOOL)performWriteData:(NSData*)data error:(NSError**)error; 215 | - (BOOL)performClose:(NSError**)error; 216 | - (void)setAttribute:(id)attribute forKey:(NSString*)key; 217 | @end 218 | 219 | @interface WGCDWebServerResponse () 220 | @property(nonatomic, readonly) NSDictionary* additionalHeaders; 221 | @property(nonatomic, readonly) BOOL usesChunkedTransferEncoding; 222 | - (void)prepareForReading; 223 | - (BOOL)performOpen:(NSError**)error; 224 | - (void)performReadDataWithCompletion:(WGCDWebServerBodyReaderCompletionBlock)block; 225 | - (void)performClose; 226 | @end 227 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Core/WGCDWebServerRequest.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import 29 | 30 | /** 31 | * Attribute key to retrieve an NSArray containing NSStrings from a WGCDWebServerRequest 32 | * with the contents of any regular expression captures done on the request path. 33 | * 34 | * @warning This attribute will only be set on the request if adding a handler using 35 | * -addHandlerForMethod:pathRegex:requestClass:processBlock:. 36 | */ 37 | extern NSString* const WGCDWebServerRequestAttribute_RegexCaptures; 38 | 39 | /** 40 | * This protocol is used by the WGCDWebServerConnection to communicate with 41 | * the WGCDWebServerRequest and write the received HTTP body data. 42 | * 43 | * Note that multiple WGCDWebServerBodyWriter objects can be chained together 44 | * internally e.g. to automatically decode gzip encoded content before 45 | * passing it on to the WGCDWebServerRequest. 46 | * 47 | * @warning These methods can be called on any WGCD thread. 48 | */ 49 | @protocol WGCDWebServerBodyWriter 50 | 51 | /** 52 | * This method is called before any body data is received. 53 | * 54 | * It should return YES on success or NO on failure and set the "error" argument 55 | * which is guaranteed to be non-NULL. 56 | */ 57 | - (BOOL)open:(NSError**)error; 58 | 59 | /** 60 | * This method is called whenever body data has been received. 61 | * 62 | * It should return YES on success or NO on failure and set the "error" argument 63 | * which is guaranteed to be non-NULL. 64 | */ 65 | - (BOOL)writeData:(NSData*)data error:(NSError**)error; 66 | 67 | /** 68 | * This method is called after all body data has been received. 69 | * 70 | * It should return YES on success or NO on failure and set the "error" argument 71 | * which is guaranteed to be non-NULL. 72 | */ 73 | - (BOOL)close:(NSError**)error; 74 | 75 | @end 76 | 77 | /** 78 | * The WGCDWebServerRequest class is instantiated by the WGCDWebServerConnection 79 | * after the HTTP headers have been received. Each instance wraps a single HTTP 80 | * request. If a body is present, the methods from the WGCDWebServerBodyWriter 81 | * protocol will be called by the WGCDWebServerConnection to receive it. 82 | * 83 | * The default implementation of the WGCDWebServerBodyWriter protocol on the class 84 | * simply ignores the body data. 85 | * 86 | * @warning WGCDWebServerRequest instances can be created and used on any WGCD thread. 87 | */ 88 | @interface WGCDWebServerRequest : NSObject 89 | 90 | /** 91 | * Returns the HTTP method for the request. 92 | */ 93 | @property(nonatomic, readonly) NSString* method; 94 | 95 | /** 96 | * Returns the URL for the request. 97 | */ 98 | @property(nonatomic, readonly) NSURL* URL; 99 | 100 | /** 101 | * Returns the HTTP headers for the request. 102 | */ 103 | @property(nonatomic, readonly) NSDictionary* headers; 104 | 105 | /** 106 | * Returns the path component of the URL for the request. 107 | */ 108 | @property(nonatomic, readonly) NSString* path; 109 | 110 | /** 111 | * Returns the parsed and unescaped query component of the URL for the request. 112 | * 113 | * @warning This property will be nil if there is no query in the URL. 114 | */ 115 | @property(nonatomic, readonly) NSDictionary* query; 116 | 117 | /** 118 | * Returns the content type for the body of the request parsed from the 119 | * "Content-Type" header. 120 | * 121 | * This property will be nil if the request has no body or set to 122 | * "application/octet-stream" if a body is present but there was no 123 | * "Content-Type" header. 124 | */ 125 | @property(nonatomic, readonly) NSString* contentType; 126 | 127 | /** 128 | * Returns the content length for the body of the request parsed from the 129 | * "Content-Length" header. 130 | * 131 | * This property will be set to "NSUIntegerMax" if the request has no body or 132 | * if there is a body but no "Content-Length" header, typically because 133 | * chunked transfer encoding is used. 134 | */ 135 | @property(nonatomic, readonly) NSUInteger contentLength; 136 | 137 | /** 138 | * Returns the parsed "If-Modified-Since" header or nil if absent or malformed. 139 | */ 140 | @property(nonatomic, readonly) NSDate* ifModifiedSince; 141 | 142 | /** 143 | * Returns the parsed "If-None-Match" header or nil if absent or malformed. 144 | */ 145 | @property(nonatomic, readonly) NSString* ifNoneMatch; 146 | 147 | /** 148 | * Returns the parsed "Range" header or (NSUIntegerMax, 0) if absent or malformed. 149 | * The range will be set to (offset, length) if expressed from the beginning 150 | * of the entity body, or (NSUIntegerMax, length) if expressed from its end. 151 | */ 152 | @property(nonatomic, readonly) NSRange byteRange; 153 | 154 | /** 155 | * Returns YES if the client supports gzip content encoding according to the 156 | * "Accept-Encoding" header. 157 | */ 158 | @property(nonatomic, readonly) BOOL acceptsGzipContentEncoding; 159 | 160 | /** 161 | * Returns the address of the local peer (i.e. server) for the request 162 | * as a raw "struct sockaddr". 163 | */ 164 | @property(nonatomic, readonly) NSData* localAddressData; 165 | 166 | /** 167 | * Returns the address of the local peer (i.e. server) for the request 168 | * as a string. 169 | */ 170 | @property(nonatomic, readonly) NSString* localAddressString; 171 | 172 | /** 173 | * Returns the address of the remote peer (i.e. client) for the request 174 | * as a raw "struct sockaddr". 175 | */ 176 | @property(nonatomic, readonly) NSData* remoteAddressData; 177 | 178 | /** 179 | * Returns the address of the remote peer (i.e. client) for the request 180 | * as a string. 181 | */ 182 | @property(nonatomic, readonly) NSString* remoteAddressString; 183 | 184 | /** 185 | * This method is the designated initializer for the class. 186 | */ 187 | - (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query; 188 | 189 | /** 190 | * Convenience method that checks if the contentType property is defined. 191 | */ 192 | - (BOOL)hasBody; 193 | 194 | /** 195 | * Convenience method that checks if the byteRange property is defined. 196 | */ 197 | - (BOOL)hasByteRange; 198 | 199 | /** 200 | * Retrieves an attribute associated with this request using the given key. 201 | * 202 | * @return The attribute value for the key. 203 | */ 204 | - (id)attributeForKey:(NSString*)key; 205 | 206 | @end 207 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Core/WGCDWebServerRequest.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error WGCDWebServer requires ARC 30 | #endif 31 | 32 | #import 33 | 34 | #import "WGCDWebServerPrivate.h" 35 | 36 | NSString* const WGCDWebServerRequestAttribute_RegexCaptures = @"WGCDWebServerRequestAttribute_RegexCaptures"; 37 | 38 | #define kZlibErrorDomain @"ZlibErrorDomain" 39 | #define kGZipInitialBufferSize (256 * 1024) 40 | 41 | @interface WGCDWebServerBodyDecoder : NSObject 42 | - (id)initWithRequest:(WGCDWebServerRequest*)request writer:(id)writer; 43 | @end 44 | 45 | @interface WGCDWebServerGZipDecoder : WGCDWebServerBodyDecoder 46 | @end 47 | 48 | @interface WGCDWebServerBodyDecoder () { 49 | @private 50 | WGCDWebServerRequest* __unsafe_unretained _request; 51 | id __unsafe_unretained _writer; 52 | } 53 | @end 54 | 55 | @implementation WGCDWebServerBodyDecoder 56 | 57 | - (id)initWithRequest:(WGCDWebServerRequest*)request writer:(id)writer { 58 | if ((self = [super init])) { 59 | _request = request; 60 | _writer = writer; 61 | } 62 | return self; 63 | } 64 | 65 | - (BOOL)open:(NSError**)error { 66 | return [_writer open:error]; 67 | } 68 | 69 | - (BOOL)writeData:(NSData*)data error:(NSError**)error { 70 | return [_writer writeData:data error:error]; 71 | } 72 | 73 | - (BOOL)close:(NSError**)error { 74 | return [_writer close:error]; 75 | } 76 | 77 | @end 78 | 79 | @interface WGCDWebServerGZipDecoder () { 80 | @private 81 | z_stream _stream; 82 | BOOL _finished; 83 | } 84 | @end 85 | 86 | @implementation WGCDWebServerGZipDecoder 87 | 88 | - (BOOL)open:(NSError**)error { 89 | int result = inflateInit2(&_stream, 15 + 16); 90 | if (result != Z_OK) { 91 | if (error) { 92 | *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil]; 93 | } 94 | return NO; 95 | } 96 | if (![super open:error]) { 97 | inflateEnd(&_stream); 98 | return NO; 99 | } 100 | return YES; 101 | } 102 | 103 | - (BOOL)writeData:(NSData*)data error:(NSError**)error { 104 | GWS_DCHECK(!_finished); 105 | _stream.next_in = (Bytef*)data.bytes; 106 | _stream.avail_in = (uInt)data.length; 107 | NSMutableData* decodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize]; 108 | if (decodedData == nil) { 109 | GWS_DNOT_REACHED(); 110 | return NO; 111 | } 112 | NSUInteger length = 0; 113 | while (1) { 114 | NSUInteger maxLength = decodedData.length - length; 115 | _stream.next_out = (Bytef*)((char*)decodedData.mutableBytes + length); 116 | _stream.avail_out = (uInt)maxLength; 117 | int result = inflate(&_stream, Z_NO_FLUSH); 118 | if ((result != Z_OK) && (result != Z_STREAM_END)) { 119 | if (error) { 120 | *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil]; 121 | } 122 | return NO; 123 | } 124 | length += maxLength - _stream.avail_out; 125 | if (_stream.avail_out > 0) { 126 | if (result == Z_STREAM_END) { 127 | _finished = YES; 128 | } 129 | break; 130 | } 131 | decodedData.length = 2 * decodedData.length; // zlib has used all the output buffer so resize it and try again in case more data is available 132 | } 133 | decodedData.length = length; 134 | BOOL success = length ? [super writeData:decodedData error:error] : YES; // No need to call writer if we have no data yet 135 | return success; 136 | } 137 | 138 | - (BOOL)close:(NSError**)error { 139 | GWS_DCHECK(_finished); 140 | inflateEnd(&_stream); 141 | return [super close:error]; 142 | } 143 | 144 | @end 145 | 146 | @interface WGCDWebServerRequest () { 147 | @private 148 | NSString* _method; 149 | NSURL* _url; 150 | NSDictionary* _headers; 151 | NSString* _path; 152 | NSDictionary* _query; 153 | NSString* _type; 154 | BOOL _chunked; 155 | NSUInteger _length; 156 | NSDate* _modifiedSince; 157 | NSString* _noneMatch; 158 | NSRange _range; 159 | BOOL _gzipAccepted; 160 | NSData* _localAddress; 161 | NSData* _remoteAddress; 162 | 163 | BOOL _opened; 164 | NSMutableArray* _decoders; 165 | NSMutableDictionary* _attributes; 166 | id __unsafe_unretained _writer; 167 | } 168 | @end 169 | 170 | @implementation WGCDWebServerRequest : NSObject 171 | 172 | @synthesize method=_method, URL=_url, headers=_headers, path=_path, query=_query, contentType=_type, contentLength=_length, ifModifiedSince=_modifiedSince, ifNoneMatch=_noneMatch, 173 | byteRange=_range, acceptsGzipContentEncoding=_gzipAccepted, usesChunkedTransferEncoding=_chunked, localAddressData=_localAddress, remoteAddressData=_remoteAddress; 174 | 175 | - (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { 176 | if ((self = [super init])) { 177 | _method = [method copy]; 178 | _url = url; 179 | _headers = headers; 180 | _path = [path copy]; 181 | _query = query; 182 | 183 | _type = WGCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Content-Type"]); 184 | _chunked = [WGCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Transfer-Encoding"]) isEqualToString:@"chunked"]; 185 | NSString* lengthHeader = [_headers objectForKey:@"Content-Length"]; 186 | if (lengthHeader) { 187 | NSInteger length = [lengthHeader integerValue]; 188 | if (_chunked || (length < 0)) { 189 | GWS_LOG_WARNING(@"Invalid 'Content-Length' header '%@' for '%@' request on \"%@\"", lengthHeader, _method, _url); 190 | GWS_DNOT_REACHED(); 191 | return nil; 192 | } 193 | _length = length; 194 | if (_type == nil) { 195 | _type = kWGCDWebServerDefaultMimeType; 196 | } 197 | } else if (_chunked) { 198 | if (_type == nil) { 199 | _type = kWGCDWebServerDefaultMimeType; 200 | } 201 | _length = NSUIntegerMax; 202 | } else { 203 | if (_type) { 204 | GWS_LOG_WARNING(@"Ignoring 'Content-Type' header for '%@' request on \"%@\"", _method, _url); 205 | _type = nil; // Content-Type without Content-Length or chunked-encoding doesn't make sense 206 | } 207 | _length = NSUIntegerMax; 208 | } 209 | 210 | NSString* modifiedHeader = [_headers objectForKey:@"If-Modified-Since"]; 211 | if (modifiedHeader) { 212 | _modifiedSince = [WGCDWebServerParseRFC822(modifiedHeader) copy]; 213 | } 214 | _noneMatch = [_headers objectForKey:@"If-None-Match"]; 215 | 216 | _range = NSMakeRange(NSUIntegerMax, 0); 217 | NSString* rangeHeader = WGCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Range"]); 218 | if (rangeHeader) { 219 | if ([rangeHeader hasPrefix:@"bytes="]) { 220 | NSArray* components = [[rangeHeader substringFromIndex:6] componentsSeparatedByString:@","]; 221 | if (components.count == 1) { 222 | components = [[components firstObject] componentsSeparatedByString:@"-"]; 223 | if (components.count == 2) { 224 | NSString* startString = [components objectAtIndex:0]; 225 | NSInteger startValue = [startString integerValue]; 226 | NSString* endString = [components objectAtIndex:1]; 227 | NSInteger endValue = [endString integerValue]; 228 | if (startString.length && (startValue >= 0) && endString.length && (endValue >= startValue)) { // The second 500 bytes: "500-999" 229 | _range.location = startValue; 230 | _range.length = endValue - startValue + 1; 231 | } else if (startString.length && (startValue >= 0)) { // The bytes after 9500 bytes: "9500-" 232 | _range.location = startValue; 233 | _range.length = NSUIntegerMax; 234 | } else if (endString.length && (endValue > 0)) { // The final 500 bytes: "-500" 235 | _range.location = NSUIntegerMax; 236 | _range.length = endValue; 237 | } 238 | } 239 | } 240 | } 241 | if ((_range.location == NSUIntegerMax) && (_range.length == 0)) { // Ignore "Range" header if syntactically invalid 242 | GWS_LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url); 243 | } 244 | } 245 | 246 | if ([[_headers objectForKey:@"Accept-Encoding"] rangeOfString:@"gzip"].location != NSNotFound) { 247 | _gzipAccepted = YES; 248 | } 249 | 250 | _decoders = [[NSMutableArray alloc] init]; 251 | _attributes = [[NSMutableDictionary alloc] init]; 252 | } 253 | return self; 254 | } 255 | 256 | - (BOOL)hasBody { 257 | return _type ? YES : NO; 258 | } 259 | 260 | - (BOOL)hasByteRange { 261 | return WGCDWebServerIsValidByteRange(_range); 262 | } 263 | 264 | - (id)attributeForKey:(NSString*)key { 265 | return [_attributes objectForKey:key]; 266 | } 267 | 268 | - (BOOL)open:(NSError**)error { 269 | return YES; 270 | } 271 | 272 | - (BOOL)writeData:(NSData*)data error:(NSError**)error { 273 | return YES; 274 | } 275 | 276 | - (BOOL)close:(NSError**)error { 277 | return YES; 278 | } 279 | 280 | - (void)prepareForWriting { 281 | _writer = self; 282 | if ([WGCDWebServerNormalizeHeaderValue([self.headers objectForKey:@"Content-Encoding"]) isEqualToString:@"gzip"]) { 283 | WGCDWebServerGZipDecoder* decoder = [[WGCDWebServerGZipDecoder alloc] initWithRequest:self writer:_writer]; 284 | [_decoders addObject:decoder]; 285 | _writer = decoder; 286 | } 287 | } 288 | 289 | - (BOOL)performOpen:(NSError**)error { 290 | GWS_DCHECK(_type); 291 | GWS_DCHECK(_writer); 292 | if (_opened) { 293 | GWS_DNOT_REACHED(); 294 | return NO; 295 | } 296 | _opened = YES; 297 | return [_writer open:error]; 298 | } 299 | 300 | - (BOOL)performWriteData:(NSData*)data error:(NSError**)error { 301 | GWS_DCHECK(_opened); 302 | return [_writer writeData:data error:error]; 303 | } 304 | 305 | - (BOOL)performClose:(NSError**)error { 306 | GWS_DCHECK(_opened); 307 | return [_writer close:error]; 308 | } 309 | 310 | - (void)setAttribute:(id)attribute forKey:(NSString*)key { 311 | [_attributes setValue:attribute forKey:key]; 312 | } 313 | 314 | - (NSString*)localAddressString { 315 | return WGCDWebServerStringFromSockAddr(_localAddress.bytes, YES); 316 | } 317 | 318 | - (NSString*)remoteAddressString { 319 | return WGCDWebServerStringFromSockAddr(_remoteAddress.bytes, YES); 320 | } 321 | 322 | - (NSString*)description { 323 | NSMutableString* description = [NSMutableString stringWithFormat:@"%@ %@", _method, _path]; 324 | for (NSString* argument in [[_query allKeys] sortedArrayUsingSelector:@selector(compare:)]) { 325 | [description appendFormat:@"\n %@ = %@", argument, [_query objectForKey:argument]]; 326 | } 327 | [description appendString:@"\n"]; 328 | for (NSString* header in [[_headers allKeys] sortedArrayUsingSelector:@selector(compare:)]) { 329 | [description appendFormat:@"\n%@: %@", header, [_headers objectForKey:header]]; 330 | } 331 | return description; 332 | } 333 | 334 | @end 335 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Core/WGCDWebServerResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import 29 | 30 | /** 31 | * The WGCDWebServerBodyReaderCompletionBlock is passed by WGCDWebServer to the 32 | * WGCDWebServerBodyReader object when reading data from it asynchronously. 33 | */ 34 | typedef void (^WGCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* error); 35 | 36 | /** 37 | * This protocol is used by the WGCDWebServerConnection to communicate with 38 | * the WGCDWebServerResponse and read the HTTP body data to send. 39 | * 40 | * Note that multiple WGCDWebServerBodyReader objects can be chained together 41 | * internally e.g. to automatically apply gzip encoding to the content before 42 | * passing it on to the WGCDWebServerResponse. 43 | * 44 | * @warning These methods can be called on any WGCD thread. 45 | */ 46 | @protocol WGCDWebServerBodyReader 47 | 48 | @required 49 | 50 | /** 51 | * This method is called before any body data is sent. 52 | * 53 | * It should return YES on success or NO on failure and set the "error" argument 54 | * which is guaranteed to be non-NULL. 55 | */ 56 | - (BOOL)open:(NSError**)error; 57 | 58 | /** 59 | * This method is called whenever body data is sent. 60 | * 61 | * It should return a non-empty NSData if there is body data available, 62 | * or an empty NSData there is no more body data, or nil on error and set 63 | * the "error" argument which is guaranteed to be non-NULL. 64 | */ 65 | - (NSData*)readData:(NSError**)error; 66 | 67 | /** 68 | * This method is called after all body data has been sent. 69 | */ 70 | - (void)close; 71 | 72 | @optional 73 | 74 | /** 75 | * If this method is implemented, it will be preferred over -readData:. 76 | * 77 | * It must call the passed block when data is available, passing a non-empty 78 | * NSData if there is body data available, or an empty NSData there is no more 79 | * body data, or nil on error and pass an NSError along. 80 | */ 81 | - (void)asyncReadDataWithCompletion:(WGCDWebServerBodyReaderCompletionBlock)block; 82 | 83 | @end 84 | 85 | /** 86 | * The WGCDWebServerResponse class is used to wrap a single HTTP response. 87 | * It is instantiated by the handler of the WGCDWebServer that handled the request. 88 | * If a body is present, the methods from the WGCDWebServerBodyReader protocol 89 | * will be called by the WGCDWebServerConnection to send it. 90 | * 91 | * The default implementation of the WGCDWebServerBodyReader protocol 92 | * on the class simply returns an empty body. 93 | * 94 | * @warning WGCDWebServerResponse instances can be created and used on any WGCD thread. 95 | */ 96 | @interface WGCDWebServerResponse : NSObject 97 | 98 | /** 99 | * Sets the content type for the body of the response. 100 | * 101 | * The default value is nil i.e. the response has no body. 102 | * 103 | * @warning This property must be set if a body is present. 104 | */ 105 | @property(nonatomic, copy) NSString* contentType; 106 | 107 | /** 108 | * Sets the content length for the body of the response. If a body is present 109 | * but this property is set to "NSUIntegerMax", this means the length of the body 110 | * cannot be known ahead of time. Chunked transfer encoding will be 111 | * automatically enabled by the WGCDWebServerConnection to comply with HTTP/1.1 112 | * specifications. 113 | * 114 | * The default value is "NSUIntegerMax" i.e. the response has no body or its length 115 | * is undefined. 116 | */ 117 | @property(nonatomic) NSUInteger contentLength; 118 | 119 | /** 120 | * Sets the HTTP status code for the response. 121 | * 122 | * The default value is 200 i.e. "OK". 123 | */ 124 | @property(nonatomic) NSInteger statusCode; 125 | 126 | /** 127 | * Sets the caching hint for the response using the "Cache-Control" header. 128 | * This value is expressed in seconds. 129 | * 130 | * The default value is 0 i.e. "no-cache". 131 | */ 132 | @property(nonatomic) NSUInteger cacheControlMaxAge; 133 | 134 | /** 135 | * Sets the last modified date for the response using the "Last-Modified" header. 136 | * 137 | * The default value is nil. 138 | */ 139 | @property(nonatomic, retain) NSDate* lastModifiedDate; 140 | 141 | /** 142 | * Sets the ETag for the response using the "ETag" header. 143 | * 144 | * The default value is nil. 145 | */ 146 | @property(nonatomic, copy) NSString* eTag; 147 | 148 | /** 149 | * Enables gzip encoding for the response body. 150 | * 151 | * The default value is NO. 152 | * 153 | * @warning Enabling gzip encoding will remove any "Content-Length" header 154 | * since the length of the body is not known anymore. The client will still 155 | * be able to determine the body length when connection is closed per 156 | * HTTP/1.1 specifications. 157 | */ 158 | @property(nonatomic, getter=isGZipContentEncodingEnabled) BOOL gzipContentEncodingEnabled; 159 | 160 | /** 161 | * Creates an empty response. 162 | */ 163 | + (instancetype)response; 164 | 165 | /** 166 | * This method is the designated initializer for the class. 167 | */ 168 | - (instancetype)init; 169 | 170 | /** 171 | * Sets an additional HTTP header on the response. 172 | * Pass a nil value to remove an additional header. 173 | * 174 | * @warning Do not attempt to override the primary headers used 175 | * by WGCDWebServerResponse like "Content-Type", "ETag", etc... 176 | */ 177 | - (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header; 178 | 179 | /** 180 | * Convenience method that checks if the contentType property is defined. 181 | */ 182 | - (BOOL)hasBody; 183 | 184 | @end 185 | 186 | @interface WGCDWebServerResponse (Extensions) 187 | 188 | /** 189 | * Creates a empty response with a specific HTTP status code. 190 | */ 191 | + (instancetype)responseWithStatusCode:(NSInteger)statusCode; 192 | 193 | /** 194 | * Creates an HTTP redirect response to a new URL. 195 | */ 196 | + (instancetype)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent; 197 | 198 | /** 199 | * Initializes an empty response with a specific HTTP status code. 200 | */ 201 | - (instancetype)initWithStatusCode:(NSInteger)statusCode; 202 | 203 | /** 204 | * Initializes an HTTP redirect response to a new URL. 205 | */ 206 | - (instancetype)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent; 207 | 208 | @end 209 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Core/WGCDWebServerResponse.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error WGCDWebServer requires ARC 30 | #endif 31 | 32 | #import 33 | 34 | #import "WGCDWebServerPrivate.h" 35 | 36 | #define kZlibErrorDomain @"ZlibErrorDomain" 37 | #define kGZipInitialBufferSize (256 * 1024) 38 | 39 | @interface WGCDWebServerBodyEncoder : NSObject 40 | - (id)initWithResponse:(WGCDWebServerResponse*)response reader:(id)reader; 41 | @end 42 | 43 | @interface WGCDWebServerGZipEncoder : WGCDWebServerBodyEncoder 44 | @end 45 | 46 | @interface WGCDWebServerBodyEncoder () { 47 | @private 48 | WGCDWebServerResponse* __unsafe_unretained _response; 49 | id __unsafe_unretained _reader; 50 | } 51 | @end 52 | 53 | @implementation WGCDWebServerBodyEncoder 54 | 55 | - (id)initWithResponse:(WGCDWebServerResponse*)response reader:(id)reader { 56 | if ((self = [super init])) { 57 | _response = response; 58 | _reader = reader; 59 | } 60 | return self; 61 | } 62 | 63 | - (BOOL)open:(NSError**)error { 64 | return [_reader open:error]; 65 | } 66 | 67 | - (NSData*)readData:(NSError**)error { 68 | return [_reader readData:error]; 69 | } 70 | 71 | - (void)close { 72 | [_reader close]; 73 | } 74 | 75 | @end 76 | 77 | @interface WGCDWebServerGZipEncoder () { 78 | @private 79 | z_stream _stream; 80 | BOOL _finished; 81 | } 82 | @end 83 | 84 | @implementation WGCDWebServerGZipEncoder 85 | 86 | - (id)initWithResponse:(WGCDWebServerResponse*)response reader:(id)reader { 87 | if ((self = [super initWithResponse:response reader:reader])) { 88 | response.contentLength = NSUIntegerMax; // Make sure "Content-Length" header is not set since we don't know it 89 | [response setValue:@"gzip" forAdditionalHeader:@"Content-Encoding"]; 90 | } 91 | return self; 92 | } 93 | 94 | - (BOOL)open:(NSError**)error { 95 | int result = deflateInit2(&_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY); 96 | if (result != Z_OK) { 97 | if (error) { 98 | *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil]; 99 | } 100 | return NO; 101 | } 102 | if (![super open:error]) { 103 | deflateEnd(&_stream); 104 | return NO; 105 | } 106 | return YES; 107 | } 108 | 109 | - (NSData*)readData:(NSError**)error { 110 | NSMutableData* encodedData; 111 | if (_finished) { 112 | encodedData = [[NSMutableData alloc] init]; 113 | } else { 114 | encodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize]; 115 | if (encodedData == nil) { 116 | GWS_DNOT_REACHED(); 117 | return nil; 118 | } 119 | NSUInteger length = 0; 120 | do { 121 | NSData* data = [super readData:error]; 122 | if (data == nil) { 123 | return nil; 124 | } 125 | _stream.next_in = (Bytef*)data.bytes; 126 | _stream.avail_in = (uInt)data.length; 127 | while (1) { 128 | NSUInteger maxLength = encodedData.length - length; 129 | _stream.next_out = (Bytef*)((char*)encodedData.mutableBytes + length); 130 | _stream.avail_out = (uInt)maxLength; 131 | int result = deflate(&_stream, data.length ? Z_NO_FLUSH : Z_FINISH); 132 | if (result == Z_STREAM_END) { 133 | _finished = YES; 134 | } else if (result != Z_OK) { 135 | if (error) { 136 | *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil]; 137 | } 138 | return nil; 139 | } 140 | length += maxLength - _stream.avail_out; 141 | if (_stream.avail_out > 0) { 142 | break; 143 | } 144 | encodedData.length = 2 * encodedData.length; // zlib has used all the output buffer so resize it and try again in case more data is available 145 | } 146 | GWS_DCHECK(_stream.avail_in == 0); 147 | } while (length == 0); // Make sure we don't return an empty NSData if not in finished state 148 | encodedData.length = length; 149 | } 150 | return encodedData; 151 | } 152 | 153 | - (void)close { 154 | deflateEnd(&_stream); 155 | [super close]; 156 | } 157 | 158 | @end 159 | 160 | @interface WGCDWebServerResponse () { 161 | @private 162 | NSString* _type; 163 | NSUInteger _length; 164 | NSInteger _status; 165 | NSUInteger _maxAge; 166 | NSDate* _lastModified; 167 | NSString* _eTag; 168 | NSMutableDictionary* _headers; 169 | BOOL _chunked; 170 | BOOL _gzipped; 171 | 172 | BOOL _opened; 173 | NSMutableArray* _encoders; 174 | id __unsafe_unretained _reader; 175 | } 176 | @end 177 | 178 | @implementation WGCDWebServerResponse 179 | 180 | @synthesize contentType=_type, contentLength=_length, statusCode=_status, cacheControlMaxAge=_maxAge, lastModifiedDate=_lastModified, eTag=_eTag, 181 | gzipContentEncodingEnabled=_gzipped, additionalHeaders=_headers; 182 | 183 | + (instancetype)response { 184 | return [[[self class] alloc] init]; 185 | } 186 | 187 | - (instancetype)init { 188 | if ((self = [super init])) { 189 | _type = nil; 190 | _length = NSUIntegerMax; 191 | _status = kWGCDWebServerHTTPStatusCode_OK; 192 | _maxAge = 0; 193 | _headers = [[NSMutableDictionary alloc] init]; 194 | _encoders = [[NSMutableArray alloc] init]; 195 | } 196 | return self; 197 | } 198 | 199 | - (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header { 200 | [_headers setValue:value forKey:header]; 201 | } 202 | 203 | - (BOOL)hasBody { 204 | return _type ? YES : NO; 205 | } 206 | 207 | - (BOOL)usesChunkedTransferEncoding { 208 | return (_type != nil) && (_length == NSUIntegerMax); 209 | } 210 | 211 | - (BOOL)open:(NSError**)error { 212 | return YES; 213 | } 214 | 215 | - (NSData*)readData:(NSError**)error { 216 | return [NSData data]; 217 | } 218 | 219 | - (void)close { 220 | ; 221 | } 222 | 223 | - (void)prepareForReading { 224 | _reader = self; 225 | if (_gzipped) { 226 | WGCDWebServerGZipEncoder* encoder = [[WGCDWebServerGZipEncoder alloc] initWithResponse:self reader:_reader]; 227 | [_encoders addObject:encoder]; 228 | _reader = encoder; 229 | } 230 | } 231 | 232 | - (BOOL)performOpen:(NSError**)error { 233 | GWS_DCHECK(_type); 234 | GWS_DCHECK(_reader); 235 | if (_opened) { 236 | GWS_DNOT_REACHED(); 237 | return NO; 238 | } 239 | _opened = YES; 240 | return [_reader open:error]; 241 | } 242 | 243 | - (void)performReadDataWithCompletion:(WGCDWebServerBodyReaderCompletionBlock)block { 244 | GWS_DCHECK(_opened); 245 | if ([_reader respondsToSelector:@selector(asyncReadDataWithCompletion:)]) { 246 | [_reader asyncReadDataWithCompletion:[block copy]]; 247 | } else { 248 | NSError* error = nil; 249 | NSData* data = [_reader readData:&error]; 250 | block(data, error); 251 | } 252 | } 253 | 254 | - (void)performClose { 255 | GWS_DCHECK(_opened); 256 | [_reader close]; 257 | } 258 | 259 | - (NSString*)description { 260 | NSMutableString* description = [NSMutableString stringWithFormat:@"Status Code = %i", (int)_status]; 261 | if (_type) { 262 | [description appendFormat:@"\nContent Type = %@", _type]; 263 | } 264 | if (_length != NSUIntegerMax) { 265 | [description appendFormat:@"\nContent Length = %lu", (unsigned long)_length]; 266 | } 267 | [description appendFormat:@"\nCache Control Max Age = %lu", (unsigned long)_maxAge]; 268 | if (_lastModified) { 269 | [description appendFormat:@"\nLast Modified Date = %@", _lastModified]; 270 | } 271 | if (_eTag) { 272 | [description appendFormat:@"\nETag = %@", _eTag]; 273 | } 274 | if (_headers.count) { 275 | [description appendString:@"\n"]; 276 | for (NSString* header in [[_headers allKeys] sortedArrayUsingSelector:@selector(compare:)]) { 277 | [description appendFormat:@"\n%@: %@", header, [_headers objectForKey:header]]; 278 | } 279 | } 280 | return description; 281 | } 282 | 283 | @end 284 | 285 | @implementation WGCDWebServerResponse (Extensions) 286 | 287 | + (instancetype)responseWithStatusCode:(NSInteger)statusCode { 288 | return [[self alloc] initWithStatusCode:statusCode]; 289 | } 290 | 291 | + (instancetype)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent { 292 | return [[self alloc] initWithRedirect:location permanent:permanent]; 293 | } 294 | 295 | - (instancetype)initWithStatusCode:(NSInteger)statusCode { 296 | if ((self = [self init])) { 297 | self.statusCode = statusCode; 298 | } 299 | return self; 300 | } 301 | 302 | - (instancetype)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent { 303 | if ((self = [self init])) { 304 | self.statusCode = permanent ? kWGCDWebServerHTTPStatusCode_MovedPermanently : kWGCDWebServerHTTPStatusCode_TemporaryRedirect; 305 | [self setValue:[location absoluteString] forAdditionalHeader:@"Location"]; 306 | } 307 | return self; 308 | } 309 | 310 | @end 311 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Requests/WGCDWebServerDataRequest.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "WGCDWebServerRequest.h" 29 | 30 | /** 31 | * The WGCDWebServerDataRequest subclass of WGCDWebServerRequest stores the body 32 | * of the HTTP request in memory. 33 | */ 34 | @interface WGCDWebServerDataRequest : WGCDWebServerRequest 35 | 36 | /** 37 | * Returns the data for the request body. 38 | */ 39 | @property(nonatomic, readonly) NSData* data; 40 | 41 | @end 42 | 43 | @interface WGCDWebServerDataRequest (Extensions) 44 | 45 | /** 46 | * Returns the data for the request body interpreted as text. If the content 47 | * type of the body is not a text one, or if an error occurs, nil is returned. 48 | * 49 | * The text encoding used to interpret the data is extracted from the 50 | * "Content-Type" header or defaults to UTF-8. 51 | */ 52 | @property(nonatomic, readonly) NSString* text; 53 | 54 | /** 55 | * Returns the data for the request body interpreted as a JSON object. If the 56 | * content type of the body is not JSON, or if an error occurs, nil is returned. 57 | */ 58 | @property(nonatomic, readonly) id jsonObject; 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Requests/WGCDWebServerDataRequest.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error WGCDWebServer requires ARC 30 | #endif 31 | 32 | #import "WGCDWebServerPrivate.h" 33 | 34 | @interface WGCDWebServerDataRequest () { 35 | @private 36 | NSMutableData* _data; 37 | 38 | NSString* _text; 39 | id _jsonObject; 40 | } 41 | @end 42 | 43 | @implementation WGCDWebServerDataRequest 44 | 45 | @synthesize data=_data; 46 | 47 | - (BOOL)open:(NSError**)error { 48 | if (self.contentLength != NSUIntegerMax) { 49 | _data = [[NSMutableData alloc] initWithCapacity:self.contentLength]; 50 | } else { 51 | _data = [[NSMutableData alloc] init]; 52 | } 53 | if (_data == nil) { 54 | if (error) { 55 | *error = [NSError errorWithDomain:kWGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed allocating memory"}]; 56 | } 57 | return NO; 58 | } 59 | return YES; 60 | } 61 | 62 | - (BOOL)writeData:(NSData*)data error:(NSError**)error { 63 | [_data appendData:data]; 64 | return YES; 65 | } 66 | 67 | - (BOOL)close:(NSError**)error { 68 | return YES; 69 | } 70 | 71 | - (NSString*)description { 72 | NSMutableString* description = [NSMutableString stringWithString:[super description]]; 73 | if (_data) { 74 | [description appendString:@"\n\n"]; 75 | [description appendString:WGCDWebServerDescribeData(_data, self.contentType)]; 76 | } 77 | return description; 78 | } 79 | 80 | @end 81 | 82 | @implementation WGCDWebServerDataRequest (Extensions) 83 | 84 | - (NSString*)text { 85 | if (_text == nil) { 86 | if ([self.contentType hasPrefix:@"text/"]) { 87 | NSString* charset = WGCDWebServerExtractHeaderValueParameter(self.contentType, @"charset"); 88 | _text = [[NSString alloc] initWithData:self.data encoding:WGCDWebServerStringEncodingFromCharset(charset)]; 89 | } else { 90 | GWS_DNOT_REACHED(); 91 | } 92 | } 93 | return _text; 94 | } 95 | 96 | - (id)jsonObject { 97 | if (_jsonObject == nil) { 98 | NSString* mimeType = WGCDWebServerTruncateHeaderValue(self.contentType); 99 | if ([mimeType isEqualToString:@"application/json"] || [mimeType isEqualToString:@"text/json"] || [mimeType isEqualToString:@"text/javascript"]) { 100 | _jsonObject = [NSJSONSerialization JSONObjectWithData:_data options:0 error:NULL]; 101 | } else { 102 | GWS_DNOT_REACHED(); 103 | } 104 | } 105 | return _jsonObject; 106 | } 107 | 108 | @end 109 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Requests/WGCDWebServerFileRequest.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "WGCDWebServerRequest.h" 29 | 30 | /** 31 | * The WGCDWebServerFileRequest subclass of WGCDWebServerRequest stores the body 32 | * of the HTTP request to a file on disk. 33 | */ 34 | @interface WGCDWebServerFileRequest : WGCDWebServerRequest 35 | 36 | /** 37 | * Returns the path to the temporary file containing the request body. 38 | * 39 | * @warning This temporary file will be automatically deleted when the 40 | * WGCDWebServerFileRequest is deallocated. If you want to preserve this file, 41 | * you must move it to a different location beforehand. 42 | */ 43 | @property(nonatomic, readonly) NSString* temporaryPath; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Requests/WGCDWebServerFileRequest.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error WGCDWebServer requires ARC 30 | #endif 31 | 32 | #import "WGCDWebServerPrivate.h" 33 | 34 | @interface WGCDWebServerFileRequest () { 35 | @private 36 | NSString* _temporaryPath; 37 | int _file; 38 | } 39 | @end 40 | 41 | @implementation WGCDWebServerFileRequest 42 | 43 | @synthesize temporaryPath=_temporaryPath; 44 | 45 | - (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { 46 | if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) { 47 | _temporaryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; 48 | } 49 | return self; 50 | } 51 | 52 | - (void)dealloc { 53 | unlink([_temporaryPath fileSystemRepresentation]); 54 | } 55 | 56 | - (BOOL)open:(NSError**)error { 57 | _file = open([_temporaryPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 58 | if (_file <= 0) { 59 | if (error) { 60 | *error = WGCDWebServerMakePosixError(errno); 61 | } 62 | return NO; 63 | } 64 | return YES; 65 | } 66 | 67 | - (BOOL)writeData:(NSData*)data error:(NSError**)error { 68 | if (write(_file, data.bytes, data.length) != (ssize_t)data.length) { 69 | if (error) { 70 | *error = WGCDWebServerMakePosixError(errno); 71 | } 72 | return NO; 73 | } 74 | return YES; 75 | } 76 | 77 | - (BOOL)close:(NSError**)error { 78 | if (close(_file) < 0) { 79 | if (error) { 80 | *error = WGCDWebServerMakePosixError(errno); 81 | } 82 | return NO; 83 | } 84 | #ifdef __WGCDWEBSERVER_ENABLE_TESTING__ 85 | NSString* creationDateHeader = [self.headers objectForKey:@"X-WGCDWebServer-CreationDate"]; 86 | if (creationDateHeader) { 87 | NSDate* date = WGCDWebServerParseISO8601(creationDateHeader); 88 | if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileCreationDate: date} ofItemAtPath:_temporaryPath error:error]) { 89 | return NO; 90 | } 91 | } 92 | NSString* modifiedDateHeader = [self.headers objectForKey:@"X-WGCDWebServer-ModifiedDate"]; 93 | if (modifiedDateHeader) { 94 | NSDate* date = WGCDWebServerParseRFC822(modifiedDateHeader); 95 | if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileModificationDate: date} ofItemAtPath:_temporaryPath error:error]) { 96 | return NO; 97 | } 98 | } 99 | #endif 100 | return YES; 101 | } 102 | 103 | - (NSString*)description { 104 | NSMutableString* description = [NSMutableString stringWithString:[super description]]; 105 | [description appendFormat:@"\n\n{%@}", _temporaryPath]; 106 | return description; 107 | } 108 | 109 | @end 110 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Requests/WGCDWebServerMultiPartFormRequest.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "WGCDWebServerRequest.h" 29 | 30 | /** 31 | * The WGCDWebServerMultiPart class is an abstract class that wraps the content 32 | * of a part. 33 | */ 34 | @interface WGCDWebServerMultiPart : NSObject 35 | 36 | /** 37 | * Returns the control name retrieved from the part headers. 38 | */ 39 | @property(nonatomic, readonly) NSString* controlName; 40 | 41 | /** 42 | * Returns the content type retrieved from the part headers or "text/plain" 43 | * if not available (per HTTP specifications). 44 | */ 45 | @property(nonatomic, readonly) NSString* contentType; 46 | 47 | /** 48 | * Returns the MIME type component of the content type for the part. 49 | */ 50 | @property(nonatomic, readonly) NSString* mimeType; 51 | 52 | @end 53 | 54 | /** 55 | * The WGCDWebServerMultiPartArgument subclass of WGCDWebServerMultiPart wraps 56 | * the content of a part as data in memory. 57 | */ 58 | @interface WGCDWebServerMultiPartArgument : WGCDWebServerMultiPart 59 | 60 | /** 61 | * Returns the data for the part. 62 | */ 63 | @property(nonatomic, readonly) NSData* data; 64 | 65 | /** 66 | * Returns the data for the part interpreted as text. If the content 67 | * type of the part is not a text one, or if an error occurs, nil is returned. 68 | * 69 | * The text encoding used to interpret the data is extracted from the 70 | * "Content-Type" header or defaults to UTF-8. 71 | */ 72 | @property(nonatomic, readonly) NSString* string; 73 | 74 | @end 75 | 76 | /** 77 | * The WGCDWebServerMultiPartFile subclass of WGCDWebServerMultiPart wraps 78 | * the content of a part as a file on disk. 79 | */ 80 | @interface WGCDWebServerMultiPartFile : WGCDWebServerMultiPart 81 | 82 | /** 83 | * Returns the file name retrieved from the part headers. 84 | */ 85 | @property(nonatomic, readonly) NSString* fileName; 86 | 87 | /** 88 | * Returns the path to the temporary file containing the part data. 89 | * 90 | * @warning This temporary file will be automatically deleted when the 91 | * WGCDWebServerMultiPartFile is deallocated. If you want to preserve this file, 92 | * you must move it to a different location beforehand. 93 | */ 94 | @property(nonatomic, readonly) NSString* temporaryPath; 95 | 96 | @end 97 | 98 | /** 99 | * The WGCDWebServerMultiPartFormRequest subclass of WGCDWebServerRequest 100 | * parses the body of the HTTP request as a multipart encoded form. 101 | */ 102 | @interface WGCDWebServerMultiPartFormRequest : WGCDWebServerRequest 103 | 104 | /** 105 | * Returns the argument parts from the multipart encoded form as 106 | * name / WGCDWebServerMultiPartArgument pairs. 107 | */ 108 | @property(nonatomic, readonly) NSArray* arguments; 109 | 110 | /** 111 | * Returns the files parts from the multipart encoded form as 112 | * name / WGCDWebServerMultiPartFile pairs. 113 | */ 114 | @property(nonatomic, readonly) NSArray* files; 115 | 116 | /** 117 | * Returns the MIME type for multipart encoded forms 118 | * i.e. "multipart/form-data". 119 | */ 120 | + (NSString*)mimeType; 121 | 122 | /** 123 | * Returns the first argument for a given control name or nil if not found. 124 | */ 125 | - (WGCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name; 126 | 127 | /** 128 | * Returns the first file for a given control name or nil if not found. 129 | */ 130 | - (WGCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name; 131 | 132 | @end 133 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Requests/WGCDWebServerMultiPartFormRequest.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error WGCDWebServer requires ARC 30 | #endif 31 | 32 | #import "WGCDWebServerPrivate.h" 33 | 34 | #define kMultiPartBufferSize (256 * 1024) 35 | 36 | typedef enum { 37 | kParserState_Undefined = 0, 38 | kParserState_Start, 39 | kParserState_Headers, 40 | kParserState_Content, 41 | kParserState_End 42 | } ParserState; 43 | 44 | @interface WGCDWebServerMIMEStreamParser : NSObject 45 | - (id)initWithBoundary:(NSString*)boundary defaultControlName:(NSString*)name arguments:(NSMutableArray*)arguments files:(NSMutableArray*)files; 46 | - (BOOL)appendBytes:(const void*)bytes length:(NSUInteger)length; 47 | - (BOOL)isAtEnd; 48 | @end 49 | 50 | static NSData* _newlineData = nil; 51 | static NSData* _newlinesData = nil; 52 | static NSData* _dashNewlineData = nil; 53 | 54 | @interface WGCDWebServerMultiPart () { 55 | @private 56 | NSString* _controlName; 57 | NSString* _contentType; 58 | NSString* _mimeType; 59 | } 60 | @end 61 | 62 | @implementation WGCDWebServerMultiPart 63 | 64 | @synthesize controlName=_controlName, contentType=_contentType, mimeType=_mimeType; 65 | 66 | - (id)initWithControlName:(NSString*)name contentType:(NSString*)type { 67 | if ((self = [super init])) { 68 | _controlName = [name copy]; 69 | _contentType = [type copy]; 70 | _mimeType = WGCDWebServerTruncateHeaderValue(_contentType); 71 | } 72 | return self; 73 | } 74 | 75 | @end 76 | 77 | @interface WGCDWebServerMultiPartArgument () { 78 | @private 79 | NSData* _data; 80 | NSString* _string; 81 | } 82 | @end 83 | 84 | @implementation WGCDWebServerMultiPartArgument 85 | 86 | @synthesize data=_data, string=_string; 87 | 88 | - (id)initWithControlName:(NSString*)name contentType:(NSString*)type data:(NSData*)data { 89 | if ((self = [super initWithControlName:name contentType:type])) { 90 | _data = data; 91 | 92 | if ([self.contentType hasPrefix:@"text/"]) { 93 | NSString* charset = WGCDWebServerExtractHeaderValueParameter(self.contentType, @"charset"); 94 | _string = [[NSString alloc] initWithData:_data encoding:WGCDWebServerStringEncodingFromCharset(charset)]; 95 | } 96 | } 97 | return self; 98 | } 99 | 100 | - (NSString*)description { 101 | return [NSString stringWithFormat:@"<%@ | '%@' | %lu bytes>", [self class], self.mimeType, (unsigned long)_data.length]; 102 | } 103 | 104 | @end 105 | 106 | @interface WGCDWebServerMultiPartFile () { 107 | @private 108 | NSString* _fileName; 109 | NSString* _temporaryPath; 110 | } 111 | @end 112 | 113 | @implementation WGCDWebServerMultiPartFile 114 | 115 | @synthesize fileName=_fileName, temporaryPath=_temporaryPath; 116 | 117 | - (id)initWithControlName:(NSString*)name contentType:(NSString*)type fileName:(NSString*)fileName temporaryPath:(NSString*)temporaryPath { 118 | if ((self = [super initWithControlName:name contentType:type])) { 119 | _fileName = [fileName copy]; 120 | _temporaryPath = [temporaryPath copy]; 121 | } 122 | return self; 123 | } 124 | 125 | - (void)dealloc { 126 | unlink([_temporaryPath fileSystemRepresentation]); 127 | } 128 | 129 | - (NSString*)description { 130 | return [NSString stringWithFormat:@"<%@ | '%@' | '%@>'", [self class], self.mimeType, _fileName]; 131 | } 132 | 133 | @end 134 | 135 | @interface WGCDWebServerMIMEStreamParser () { 136 | @private 137 | NSData* _boundary; 138 | NSString* _defaultcontrolName; 139 | ParserState _state; 140 | NSMutableData* _data; 141 | NSMutableArray* _arguments; 142 | NSMutableArray* _files; 143 | 144 | NSString* _controlName; 145 | NSString* _fileName; 146 | NSString* _contentType; 147 | NSString* _tmpPath; 148 | int _tmpFile; 149 | WGCDWebServerMIMEStreamParser* _subParser; 150 | } 151 | @end 152 | 153 | @implementation WGCDWebServerMIMEStreamParser 154 | 155 | + (void)initialize { 156 | if (_newlineData == nil) { 157 | _newlineData = [[NSData alloc] initWithBytes:"\r\n" length:2]; 158 | GWS_DCHECK(_newlineData); 159 | } 160 | if (_newlinesData == nil) { 161 | _newlinesData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4]; 162 | GWS_DCHECK(_newlinesData); 163 | } 164 | if (_dashNewlineData == nil) { 165 | _dashNewlineData = [[NSData alloc] initWithBytes:"--\r\n" length:4]; 166 | GWS_DCHECK(_dashNewlineData); 167 | } 168 | } 169 | 170 | - (id)initWithBoundary:(NSString*)boundary defaultControlName:(NSString*)name arguments:(NSMutableArray*)arguments files:(NSMutableArray*)files { 171 | NSData* data = boundary.length ? [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding] : nil; 172 | if (data == nil) { 173 | GWS_DNOT_REACHED(); 174 | return nil; 175 | } 176 | if ((self = [super init])) { 177 | _boundary = data; 178 | _defaultcontrolName = name; 179 | _arguments = arguments; 180 | _files = files; 181 | _data = [[NSMutableData alloc] initWithCapacity:kMultiPartBufferSize]; 182 | _state = kParserState_Start; 183 | } 184 | return self; 185 | } 186 | 187 | - (void)dealloc { 188 | if (_tmpFile > 0) { 189 | close(_tmpFile); 190 | unlink([_tmpPath fileSystemRepresentation]); 191 | } 192 | } 193 | 194 | // http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 195 | - (BOOL)_parseData { 196 | BOOL success = YES; 197 | 198 | if (_state == kParserState_Headers) { 199 | NSRange range = [_data rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _data.length)]; 200 | if (range.location != NSNotFound) { 201 | 202 | _controlName = nil; 203 | _fileName = nil; 204 | _contentType = nil; 205 | _tmpPath = nil; 206 | _subParser = nil; 207 | NSString* headers = [[NSString alloc] initWithData:[_data subdataWithRange:NSMakeRange(0, range.location)] encoding:NSUTF8StringEncoding]; 208 | if (headers) { 209 | for (NSString* header in [headers componentsSeparatedByString:@"\r\n"]) { 210 | NSRange subRange = [header rangeOfString:@":"]; 211 | if (subRange.location != NSNotFound) { 212 | NSString* name = [header substringToIndex:subRange.location]; 213 | NSString* value = [[header substringFromIndex:(subRange.location + subRange.length)] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 214 | if ([name caseInsensitiveCompare:@"Content-Type"] == NSOrderedSame) { 215 | _contentType = WGCDWebServerNormalizeHeaderValue(value); 216 | } else if ([name caseInsensitiveCompare:@"Content-Disposition"] == NSOrderedSame) { 217 | NSString* contentDisposition = WGCDWebServerNormalizeHeaderValue(value); 218 | if ([WGCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"form-data"]) { 219 | _controlName = WGCDWebServerExtractHeaderValueParameter(contentDisposition, @"name"); 220 | _fileName = WGCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename"); 221 | } else if ([WGCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"file"]) { 222 | _controlName = _defaultcontrolName; 223 | _fileName = WGCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename"); 224 | } 225 | } 226 | } else { 227 | GWS_DNOT_REACHED(); 228 | } 229 | } 230 | if (_contentType == nil) { 231 | _contentType = @"text/plain"; 232 | } 233 | } else { 234 | GWS_LOG_ERROR(@"Failed decoding headers in part of 'multipart/form-data'"); 235 | GWS_DNOT_REACHED(); 236 | } 237 | if (_controlName) { 238 | if ([WGCDWebServerTruncateHeaderValue(_contentType) isEqualToString:@"multipart/mixed"]) { 239 | NSString* boundary = WGCDWebServerExtractHeaderValueParameter(_contentType, @"boundary"); 240 | _subParser = [[WGCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:_controlName arguments:_arguments files:_files]; 241 | if (_subParser == nil) { 242 | GWS_DNOT_REACHED(); 243 | success = NO; 244 | } 245 | } else if (_fileName) { 246 | NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; 247 | _tmpFile = open([path fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 248 | if (_tmpFile > 0) { 249 | _tmpPath = [path copy]; 250 | } else { 251 | GWS_DNOT_REACHED(); 252 | success = NO; 253 | } 254 | } 255 | } else { 256 | GWS_DNOT_REACHED(); 257 | success = NO; 258 | } 259 | 260 | [_data replaceBytesInRange:NSMakeRange(0, range.location + range.length) withBytes:NULL length:0]; 261 | _state = kParserState_Content; 262 | } 263 | } 264 | 265 | if ((_state == kParserState_Start) || (_state == kParserState_Content)) { 266 | NSRange range = [_data rangeOfData:_boundary options:0 range:NSMakeRange(0, _data.length)]; 267 | if (range.location != NSNotFound) { 268 | NSRange subRange = NSMakeRange(range.location + range.length, _data.length - range.location - range.length); 269 | NSRange subRange1 = [_data rangeOfData:_newlineData options:NSDataSearchAnchored range:subRange]; 270 | NSRange subRange2 = [_data rangeOfData:_dashNewlineData options:NSDataSearchAnchored range:subRange]; 271 | if ((subRange1.location != NSNotFound) || (subRange2.location != NSNotFound)) { 272 | 273 | if (_state == kParserState_Content) { 274 | const void* dataBytes = _data.bytes; 275 | NSUInteger dataLength = range.location - 2; 276 | if (_subParser) { 277 | if (![_subParser appendBytes:dataBytes length:(dataLength + 2)] || ![_subParser isAtEnd]) { 278 | GWS_DNOT_REACHED(); 279 | success = NO; 280 | } 281 | _subParser = nil; 282 | } else if (_tmpPath) { 283 | ssize_t result = write(_tmpFile, dataBytes, dataLength); 284 | if (result == (ssize_t)dataLength) { 285 | if (close(_tmpFile) == 0) { 286 | _tmpFile = 0; 287 | WGCDWebServerMultiPartFile* file = [[WGCDWebServerMultiPartFile alloc] initWithControlName:_controlName contentType:_contentType fileName:_fileName temporaryPath:_tmpPath]; 288 | [_files addObject:file]; 289 | } else { 290 | GWS_DNOT_REACHED(); 291 | success = NO; 292 | } 293 | } else { 294 | GWS_DNOT_REACHED(); 295 | success = NO; 296 | } 297 | _tmpPath = nil; 298 | } else { 299 | NSData* data = [[NSData alloc] initWithBytes:(void*)dataBytes length:dataLength]; 300 | WGCDWebServerMultiPartArgument* argument = [[WGCDWebServerMultiPartArgument alloc] initWithControlName:_controlName contentType:_contentType data:data]; 301 | [_arguments addObject:argument]; 302 | } 303 | } 304 | 305 | if (subRange1.location != NSNotFound) { 306 | [_data replaceBytesInRange:NSMakeRange(0, subRange1.location + subRange1.length) withBytes:NULL length:0]; 307 | _state = kParserState_Headers; 308 | success = [self _parseData]; 309 | } else { 310 | _state = kParserState_End; 311 | } 312 | } 313 | } else { 314 | NSUInteger margin = 2 * _boundary.length; 315 | if (_data.length > margin) { 316 | NSUInteger length = _data.length - margin; 317 | if (_subParser) { 318 | if ([_subParser appendBytes:_data.bytes length:length]) { 319 | [_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0]; 320 | } else { 321 | GWS_DNOT_REACHED(); 322 | success = NO; 323 | } 324 | } else if (_tmpPath) { 325 | ssize_t result = write(_tmpFile, _data.bytes, length); 326 | if (result == (ssize_t)length) { 327 | [_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0]; 328 | } else { 329 | GWS_DNOT_REACHED(); 330 | success = NO; 331 | } 332 | } 333 | } 334 | } 335 | } 336 | 337 | return success; 338 | } 339 | 340 | - (BOOL)appendBytes:(const void*)bytes length:(NSUInteger)length { 341 | [_data appendBytes:bytes length:length]; 342 | return [self _parseData]; 343 | } 344 | 345 | - (BOOL)isAtEnd { 346 | return (_state == kParserState_End); 347 | } 348 | 349 | @end 350 | 351 | @interface WGCDWebServerMultiPartFormRequest () { 352 | @private 353 | WGCDWebServerMIMEStreamParser* _parser; 354 | NSMutableArray* _arguments; 355 | NSMutableArray* _files; 356 | } 357 | @end 358 | 359 | @implementation WGCDWebServerMultiPartFormRequest 360 | 361 | @synthesize arguments=_arguments, files=_files; 362 | 363 | + (NSString*)mimeType { 364 | return @"multipart/form-data"; 365 | } 366 | 367 | - (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { 368 | if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) { 369 | _arguments = [[NSMutableArray alloc] init]; 370 | _files = [[NSMutableArray alloc] init]; 371 | } 372 | return self; 373 | } 374 | 375 | - (BOOL)open:(NSError**)error { 376 | NSString* boundary = WGCDWebServerExtractHeaderValueParameter(self.contentType, @"boundary"); 377 | _parser = [[WGCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:nil arguments:_arguments files:_files]; 378 | if (_parser == nil) { 379 | if (error) { 380 | *error = [NSError errorWithDomain:kWGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed starting to parse multipart form data"}]; 381 | } 382 | return NO; 383 | } 384 | return YES; 385 | } 386 | 387 | - (BOOL)writeData:(NSData*)data error:(NSError**)error { 388 | if (![_parser appendBytes:data.bytes length:data.length]) { 389 | if (error) { 390 | *error = [NSError errorWithDomain:kWGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed continuing to parse multipart form data"}]; 391 | } 392 | return NO; 393 | } 394 | return YES; 395 | } 396 | 397 | - (BOOL)close:(NSError**)error { 398 | BOOL atEnd = [_parser isAtEnd]; 399 | _parser = nil; 400 | if (!atEnd) { 401 | if (error) { 402 | *error = [NSError errorWithDomain:kWGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed finishing to parse multipart form data"}]; 403 | } 404 | return NO; 405 | } 406 | return YES; 407 | } 408 | 409 | - (WGCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name { 410 | for (WGCDWebServerMultiPartArgument* argument in _arguments) { 411 | if ([argument.controlName isEqualToString:name]) { 412 | return argument; 413 | } 414 | } 415 | return nil; 416 | } 417 | 418 | - (WGCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name { 419 | for (WGCDWebServerMultiPartFile* file in _files) { 420 | if ([file.controlName isEqualToString:name]) { 421 | return file; 422 | } 423 | } 424 | return nil; 425 | } 426 | 427 | - (NSString*)description { 428 | NSMutableString* description = [NSMutableString stringWithString:[super description]]; 429 | if (_arguments.count) { 430 | [description appendString:@"\n"]; 431 | for (WGCDWebServerMultiPartArgument* argument in _arguments) { 432 | [description appendFormat:@"\n%@ (%@)\n", argument.controlName, argument.contentType]; 433 | [description appendString:WGCDWebServerDescribeData(argument.data, argument.contentType)]; 434 | } 435 | } 436 | if (_files.count) { 437 | [description appendString:@"\n"]; 438 | for (WGCDWebServerMultiPartFile* file in _files) { 439 | [description appendFormat:@"\n%@ (%@): %@\n{%@}", file.controlName, file.contentType, file.fileName, file.temporaryPath]; 440 | } 441 | } 442 | return description; 443 | } 444 | 445 | @end 446 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Requests/WGCDWebServerURLEncodedFormRequest.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "WGCDWebServerDataRequest.h" 29 | 30 | /** 31 | * The WGCDWebServerURLEncodedFormRequest subclass of WGCDWebServerRequest 32 | * parses the body of the HTTP request as a URL encoded form using 33 | * WGCDWebServerParseURLEncodedForm(). 34 | */ 35 | @interface WGCDWebServerURLEncodedFormRequest : WGCDWebServerDataRequest 36 | 37 | /** 38 | * Returns the unescaped control names and values for the URL encoded form. 39 | * 40 | * The text encoding used to interpret the data is extracted from the 41 | * "Content-Type" header or defaults to UTF-8. 42 | */ 43 | @property(nonatomic, readonly) NSDictionary* arguments; 44 | 45 | /** 46 | * Returns the MIME type for URL encoded forms 47 | * i.e. "application/x-www-form-urlencoded". 48 | */ 49 | + (NSString*)mimeType; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Requests/WGCDWebServerURLEncodedFormRequest.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error WGCDWebServer requires ARC 30 | #endif 31 | 32 | #import "WGCDWebServerPrivate.h" 33 | 34 | @interface WGCDWebServerURLEncodedFormRequest () { 35 | @private 36 | NSDictionary* _arguments; 37 | } 38 | @end 39 | 40 | @implementation WGCDWebServerURLEncodedFormRequest 41 | 42 | @synthesize arguments=_arguments; 43 | 44 | + (NSString*)mimeType { 45 | return @"application/x-www-form-urlencoded"; 46 | } 47 | 48 | - (BOOL)close:(NSError**)error { 49 | if (![super close:error]) { 50 | return NO; 51 | } 52 | 53 | NSString* charset = WGCDWebServerExtractHeaderValueParameter(self.contentType, @"charset"); 54 | NSString* string = [[NSString alloc] initWithData:self.data encoding:WGCDWebServerStringEncodingFromCharset(charset)]; 55 | _arguments = WGCDWebServerParseURLEncodedForm(string); 56 | GWS_DCHECK(_arguments); 57 | 58 | return YES; 59 | } 60 | 61 | - (NSString*)description { 62 | NSMutableString* description = [NSMutableString stringWithString:[super description]]; 63 | [description appendString:@"\n"]; 64 | for (NSString* argument in [[_arguments allKeys] sortedArrayUsingSelector:@selector(compare:)]) { 65 | [description appendFormat:@"\n%@ = %@", argument, [_arguments objectForKey:argument]]; 66 | } 67 | return description; 68 | } 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Responses/WGCDWebServerDataResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "WGCDWebServerResponse.h" 29 | 30 | /** 31 | * The WGCDWebServerDataResponse subclass of WGCDWebServerResponse reads the body 32 | * of the HTTP response from memory. 33 | */ 34 | @interface WGCDWebServerDataResponse : WGCDWebServerResponse 35 | 36 | /** 37 | * Creates a response with data in memory and a given content type. 38 | */ 39 | + (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type; 40 | 41 | /** 42 | * This method is the designated initializer for the class. 43 | */ 44 | - (instancetype)initWithData:(NSData*)data contentType:(NSString*)type; 45 | 46 | @end 47 | 48 | @interface WGCDWebServerDataResponse (Extensions) 49 | 50 | /** 51 | * Creates a data response from text encoded using UTF-8. 52 | */ 53 | + (instancetype)responseWithText:(NSString*)text; 54 | 55 | /** 56 | * Creates a data response from HTML encoded using UTF-8. 57 | */ 58 | + (instancetype)responseWithHTML:(NSString*)html; 59 | 60 | /** 61 | * Creates a data response from an HTML template encoded using UTF-8. 62 | * See -initWithHTMLTemplate:variables: for details. 63 | */ 64 | + (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; 65 | 66 | /** 67 | * Creates a data response from a serialized JSON object and the default 68 | * "application/json" content type. 69 | */ 70 | + (instancetype)responseWithJSONObject:(id)object; 71 | 72 | /** 73 | * Creates a data response from a serialized JSON object and a custom 74 | * content type. 75 | */ 76 | + (instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type; 77 | 78 | /** 79 | * Initializes a data response from text encoded using UTF-8. 80 | */ 81 | - (instancetype)initWithText:(NSString*)text; 82 | 83 | /** 84 | * Initializes a data response from HTML encoded using UTF-8. 85 | */ 86 | - (instancetype)initWithHTML:(NSString*)html; 87 | 88 | /** 89 | * Initializes a data response from an HTML template encoded using UTF-8. 90 | * 91 | * All occurences of "%variable%" within the HTML template are replaced with 92 | * their corresponding values. 93 | */ 94 | - (instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; 95 | 96 | /** 97 | * Initializes a data response from a serialized JSON object and the default 98 | * "application/json" content type. 99 | */ 100 | - (instancetype)initWithJSONObject:(id)object; 101 | 102 | /** 103 | * Initializes a data response from a serialized JSON object and a custom 104 | * content type. 105 | */ 106 | - (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type; 107 | 108 | @end 109 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Responses/WGCDWebServerDataResponse.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error WGCDWebServer requires ARC 30 | #endif 31 | 32 | #import "WGCDWebServerPrivate.h" 33 | 34 | @interface WGCDWebServerDataResponse () { 35 | @private 36 | NSData* _data; 37 | BOOL _done; 38 | } 39 | @end 40 | 41 | @implementation WGCDWebServerDataResponse 42 | 43 | + (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type { 44 | return [[[self class] alloc] initWithData:data contentType:type]; 45 | } 46 | 47 | - (instancetype)initWithData:(NSData*)data contentType:(NSString*)type { 48 | if (data == nil) { 49 | GWS_DNOT_REACHED(); 50 | return nil; 51 | } 52 | 53 | if ((self = [super init])) { 54 | _data = data; 55 | 56 | self.contentType = type; 57 | self.contentLength = data.length; 58 | } 59 | return self; 60 | } 61 | 62 | - (NSData*)readData:(NSError**)error { 63 | NSData* data; 64 | if (_done) { 65 | data = [NSData data]; 66 | } else { 67 | data = _data; 68 | _done = YES; 69 | } 70 | return data; 71 | } 72 | 73 | - (NSString*)description { 74 | NSMutableString* description = [NSMutableString stringWithString:[super description]]; 75 | [description appendString:@"\n\n"]; 76 | [description appendString:WGCDWebServerDescribeData(_data, self.contentType)]; 77 | return description; 78 | } 79 | 80 | @end 81 | 82 | @implementation WGCDWebServerDataResponse (Extensions) 83 | 84 | + (instancetype)responseWithText:(NSString*)text { 85 | return [[self alloc] initWithText:text]; 86 | } 87 | 88 | + (instancetype)responseWithHTML:(NSString*)html { 89 | return [[self alloc] initWithHTML:html]; 90 | } 91 | 92 | + (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables { 93 | return [[self alloc] initWithHTMLTemplate:path variables:variables]; 94 | } 95 | 96 | + (instancetype)responseWithJSONObject:(id)object { 97 | return [[self alloc] initWithJSONObject:object]; 98 | } 99 | 100 | + (instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type { 101 | return [[self alloc] initWithJSONObject:object contentType:type]; 102 | } 103 | 104 | - (instancetype)initWithText:(NSString*)text { 105 | NSData* data = [text dataUsingEncoding:NSUTF8StringEncoding]; 106 | if (data == nil) { 107 | GWS_DNOT_REACHED(); 108 | return nil; 109 | } 110 | return [self initWithData:data contentType:@"text/plain; charset=utf-8"]; 111 | } 112 | 113 | - (instancetype)initWithHTML:(NSString*)html { 114 | NSData* data = [html dataUsingEncoding:NSUTF8StringEncoding]; 115 | if (data == nil) { 116 | GWS_DNOT_REACHED(); 117 | return nil; 118 | } 119 | return [self initWithData:data contentType:@"text/html; charset=utf-8"]; 120 | } 121 | 122 | - (instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables { 123 | NSMutableString* html = [[NSMutableString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; 124 | [variables enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL* stop) { 125 | [html replaceOccurrencesOfString:[NSString stringWithFormat:@"%%%@%%", key] withString:value options:0 range:NSMakeRange(0, html.length)]; 126 | }]; 127 | id response = [self initWithHTML:html]; 128 | return response; 129 | } 130 | 131 | - (instancetype)initWithJSONObject:(id)object { 132 | return [self initWithJSONObject:object contentType:@"application/json"]; 133 | } 134 | 135 | - (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type { 136 | NSData* data = [NSJSONSerialization dataWithJSONObject:object options:0 error:NULL]; 137 | if (data == nil) { 138 | return nil; 139 | } 140 | return [self initWithData:data contentType:type]; 141 | } 142 | 143 | @end 144 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Responses/WGCDWebServerErrorResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "WGCDWebServerDataResponse.h" 29 | #import "WGCDWebServerHTTPStatusCodes.h" 30 | 31 | /** 32 | * The WGCDWebServerDataResponse subclass of WGCDWebServerDataResponse generates 33 | * an HTML body from an HTTP status code and an error message. 34 | */ 35 | @interface WGCDWebServerErrorResponse : WGCDWebServerDataResponse 36 | 37 | /** 38 | * Creates a client error response with the corresponding HTTP status code. 39 | */ 40 | + (instancetype)responseWithClientError:(WGCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2,3); 41 | 42 | /** 43 | * Creates a server error response with the corresponding HTTP status code. 44 | */ 45 | + (instancetype)responseWithServerError:(WGCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2,3); 46 | 47 | /** 48 | * Creates a client error response with the corresponding HTTP status code 49 | * and an underlying NSError. 50 | */ 51 | + (instancetype)responseWithClientError:(WGCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4); 52 | 53 | /** 54 | * Creates a server error response with the corresponding HTTP status code 55 | * and an underlying NSError. 56 | */ 57 | + (instancetype)responseWithServerError:(WGCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4); 58 | 59 | /** 60 | * Initializes a client error response with the corresponding HTTP status code. 61 | */ 62 | - (instancetype)initWithClientError:(WGCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2,3); 63 | 64 | /** 65 | * Initializes a server error response with the corresponding HTTP status code. 66 | */ 67 | - (instancetype)initWithServerError:(WGCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2,3); 68 | 69 | /** 70 | * Initializes a client error response with the corresponding HTTP status code 71 | * and an underlying NSError. 72 | */ 73 | - (instancetype)initWithClientError:(WGCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4); 74 | 75 | /** 76 | * Initializes a server error response with the corresponding HTTP status code 77 | * and an underlying NSError. 78 | */ 79 | - (instancetype)initWithServerError:(WGCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4); 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Responses/WGCDWebServerErrorResponse.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error WGCDWebServer requires ARC 30 | #endif 31 | 32 | #import "WGCDWebServerPrivate.h" 33 | 34 | @interface WGCDWebServerErrorResponse () 35 | - (instancetype)initWithStatusCode:(NSInteger)statusCode underlyingError:(NSError*)underlyingError messageFormat:(NSString*)format arguments:(va_list)arguments; 36 | @end 37 | 38 | @implementation WGCDWebServerErrorResponse 39 | 40 | + (instancetype)responseWithClientError:(WGCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... { 41 | GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500)); 42 | va_list arguments; 43 | va_start(arguments, format); 44 | WGCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments]; 45 | va_end(arguments); 46 | return response; 47 | } 48 | 49 | + (instancetype)responseWithServerError:(WGCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... { 50 | GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600)); 51 | va_list arguments; 52 | va_start(arguments, format); 53 | WGCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments]; 54 | va_end(arguments); 55 | return response; 56 | } 57 | 58 | + (instancetype)responseWithClientError:(WGCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... { 59 | GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500)); 60 | va_list arguments; 61 | va_start(arguments, format); 62 | WGCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments]; 63 | va_end(arguments); 64 | return response; 65 | } 66 | 67 | + (instancetype)responseWithServerError:(WGCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... { 68 | GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600)); 69 | va_list arguments; 70 | va_start(arguments, format); 71 | WGCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments]; 72 | va_end(arguments); 73 | return response; 74 | } 75 | 76 | static inline NSString* _EscapeHTMLString(NSString* string) { 77 | return [string stringByReplacingOccurrencesOfString:@"\"" withString:@"""]; 78 | } 79 | 80 | - (instancetype)initWithStatusCode:(NSInteger)statusCode underlyingError:(NSError*)underlyingError messageFormat:(NSString*)format arguments:(va_list)arguments { 81 | NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments]; 82 | NSString* title = [NSString stringWithFormat:@"HTTP Error %i", (int)statusCode]; 83 | NSString* error = underlyingError ? [NSString stringWithFormat:@"[%@] %@ (%li)", underlyingError.domain, _EscapeHTMLString(underlyingError.localizedDescription), (long)underlyingError.code] : @""; 84 | NSString* html = [NSString stringWithFormat:@"%@

%@: %@

%@

", 85 | title, title, _EscapeHTMLString(message), error]; 86 | if ((self = [self initWithHTML:html])) { 87 | self.statusCode = statusCode; 88 | } 89 | return self; 90 | } 91 | 92 | - (instancetype)initWithClientError:(WGCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... { 93 | GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500)); 94 | va_list arguments; 95 | va_start(arguments, format); 96 | self = [self initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments]; 97 | va_end(arguments); 98 | return self; 99 | } 100 | 101 | - (instancetype)initWithServerError:(WGCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... { 102 | GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600)); 103 | va_list arguments; 104 | va_start(arguments, format); 105 | self = [self initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments]; 106 | va_end(arguments); 107 | return self; 108 | } 109 | 110 | - (instancetype)initWithClientError:(WGCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... { 111 | GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500)); 112 | va_list arguments; 113 | va_start(arguments, format); 114 | self = [self initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments]; 115 | va_end(arguments); 116 | return self; 117 | } 118 | 119 | - (instancetype)initWithServerError:(WGCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... { 120 | GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600)); 121 | va_list arguments; 122 | va_start(arguments, format); 123 | self = [self initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments]; 124 | va_end(arguments); 125 | return self; 126 | } 127 | 128 | @end 129 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Responses/WGCDWebServerFileResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "WGCDWebServerResponse.h" 29 | 30 | /** 31 | * The WGCDWebServerFileResponse subclass of WGCDWebServerResponse reads the body 32 | * of the HTTP response from a file on disk. 33 | * 34 | * It will automatically set the contentType, lastModifiedDate and eTag 35 | * properties of the WGCDWebServerResponse according to the file extension and 36 | * metadata. 37 | */ 38 | @interface WGCDWebServerFileResponse : WGCDWebServerResponse 39 | 40 | /** 41 | * Creates a response with the contents of a file. 42 | */ 43 | + (instancetype)responseWithFile:(NSString*)path; 44 | 45 | /** 46 | * Creates a response like +responseWithFile: and sets the "Content-Disposition" 47 | * HTTP header for a download if the "attachment" argument is YES. 48 | */ 49 | + (instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment; 50 | 51 | /** 52 | * Creates a response like +responseWithFile: but restricts the file contents 53 | * to a specific byte range. 54 | * 55 | * See -initWithFile:byteRange: for details. 56 | */ 57 | + (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range; 58 | 59 | /** 60 | * Creates a response like +responseWithFile:byteRange: and sets the 61 | * "Content-Disposition" HTTP header for a download if the "attachment" 62 | * argument is YES. 63 | */ 64 | + (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment; 65 | 66 | /** 67 | * Initializes a response with the contents of a file. 68 | */ 69 | - (instancetype)initWithFile:(NSString*)path; 70 | 71 | /** 72 | * Initializes a response like +responseWithFile: and sets the 73 | * "Content-Disposition" HTTP header for a download if the "attachment" 74 | * argument is YES. 75 | */ 76 | - (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment; 77 | 78 | /** 79 | * Initializes a response like -initWithFile: but restricts the file contents 80 | * to a specific byte range. This range should be set to (NSUIntegerMax, 0) for 81 | * the full file, (offset, length) if expressed from the beginning of the file, 82 | * or (NSUIntegerMax, length) if expressed from the end of the file. The "offset" 83 | * and "length" values will be automatically adjusted to be compatible with the 84 | * actual size of the file. 85 | * 86 | * This argument would typically be set to the value of the byteRange property 87 | * of the current WGCDWebServerRequest. 88 | */ 89 | - (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range; 90 | 91 | /** 92 | * This method is the designated initializer for the class. 93 | */ 94 | - (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment; 95 | 96 | @end 97 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Responses/WGCDWebServerFileResponse.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error WGCDWebServer requires ARC 30 | #endif 31 | 32 | #import 33 | 34 | #import "WGCDWebServerPrivate.h" 35 | 36 | #define kFileReadBufferSize (32 * 1024) 37 | 38 | @interface WGCDWebServerFileResponse () { 39 | @private 40 | NSString* _path; 41 | NSUInteger _offset; 42 | NSUInteger _size; 43 | int _file; 44 | } 45 | @end 46 | 47 | @implementation WGCDWebServerFileResponse 48 | 49 | + (instancetype)responseWithFile:(NSString*)path { 50 | return [[[self class] alloc] initWithFile:path]; 51 | } 52 | 53 | + (instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment { 54 | return [[[self class] alloc] initWithFile:path isAttachment:attachment]; 55 | } 56 | 57 | + (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range { 58 | return [[[self class] alloc] initWithFile:path byteRange:range]; 59 | } 60 | 61 | + (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment { 62 | return [[[self class] alloc] initWithFile:path byteRange:range isAttachment:attachment]; 63 | } 64 | 65 | - (instancetype)initWithFile:(NSString*)path { 66 | return [self initWithFile:path byteRange:NSMakeRange(NSUIntegerMax, 0) isAttachment:NO]; 67 | } 68 | 69 | - (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment { 70 | return [self initWithFile:path byteRange:NSMakeRange(NSUIntegerMax, 0) isAttachment:attachment]; 71 | } 72 | 73 | - (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range { 74 | return [self initWithFile:path byteRange:range isAttachment:NO]; 75 | } 76 | 77 | static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) { 78 | return [NSDate dateWithTimeIntervalSince1970:((NSTimeInterval)t->tv_sec + (NSTimeInterval)t->tv_nsec / 1000000000.0)]; 79 | } 80 | 81 | - (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment { 82 | struct stat info; 83 | if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) { 84 | GWS_DNOT_REACHED(); 85 | return nil; 86 | } 87 | #ifndef __LP64__ 88 | if (info.st_size >= (off_t)4294967295) { // In 32 bit mode, we can't handle files greater than 4 GiBs (don't use "NSUIntegerMax" here to avoid potential unsigned to signed conversion issues) 89 | GWS_DNOT_REACHED(); 90 | return nil; 91 | } 92 | #endif 93 | NSUInteger fileSize = (NSUInteger)info.st_size; 94 | 95 | BOOL hasByteRange = WGCDWebServerIsValidByteRange(range); 96 | if (hasByteRange) { 97 | if (range.location != NSUIntegerMax) { 98 | range.location = MIN(range.location, fileSize); 99 | range.length = MIN(range.length, fileSize - range.location); 100 | } else { 101 | range.length = MIN(range.length, fileSize); 102 | range.location = fileSize - range.length; 103 | } 104 | if (range.length == 0) { 105 | return nil; // TODO: Return 416 status code and "Content-Range: bytes */{file length}" header 106 | } 107 | } else { 108 | range.location = 0; 109 | range.length = fileSize; 110 | } 111 | 112 | if ((self = [super init])) { 113 | _path = [path copy]; 114 | _offset = range.location; 115 | _size = range.length; 116 | if (hasByteRange) { 117 | [self setStatusCode:kWGCDWebServerHTTPStatusCode_PartialContent]; 118 | [self setValue:[NSString stringWithFormat:@"bytes %lu-%lu/%lu", (unsigned long)_offset, (unsigned long)(_offset + _size - 1), (unsigned long)fileSize] forAdditionalHeader:@"Content-Range"]; 119 | GWS_LOG_DEBUG(@"Using content bytes range [%lu-%lu] for file \"%@\"", (unsigned long)_offset, (unsigned long)(_offset + _size - 1), path); 120 | } 121 | 122 | if (attachment) { 123 | NSString* fileName = [path lastPathComponent]; 124 | NSData* data = [[fileName stringByReplacingOccurrencesOfString:@"\"" withString:@""] dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:YES]; 125 | NSString* lossyFileName = data ? [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding] : nil; 126 | if (lossyFileName) { 127 | NSString* value = [NSString stringWithFormat:@"attachment; filename=\"%@\"; filename*=UTF-8''%@", lossyFileName, WGCDWebServerEscapeURLString(fileName)]; 128 | [self setValue:value forAdditionalHeader:@"Content-Disposition"]; 129 | } else { 130 | GWS_DNOT_REACHED(); 131 | } 132 | } 133 | 134 | self.contentType = WGCDWebServerGetMimeTypeForExtension([_path pathExtension]); 135 | self.contentLength = _size; 136 | self.lastModifiedDate = _NSDateFromTimeSpec(&info.st_mtimespec); 137 | self.eTag = [NSString stringWithFormat:@"%llu/%li/%li", info.st_ino, info.st_mtimespec.tv_sec, info.st_mtimespec.tv_nsec]; 138 | } 139 | return self; 140 | } 141 | 142 | - (BOOL)open:(NSError**)error { 143 | _file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY); 144 | if (_file <= 0) { 145 | if (error) { 146 | *error = WGCDWebServerMakePosixError(errno); 147 | } 148 | return NO; 149 | } 150 | if (lseek(_file, _offset, SEEK_SET) != (off_t)_offset) { 151 | if (error) { 152 | *error = WGCDWebServerMakePosixError(errno); 153 | } 154 | close(_file); 155 | return NO; 156 | } 157 | return YES; 158 | } 159 | 160 | - (NSData*)readData:(NSError**)error { 161 | size_t length = MIN((NSUInteger)kFileReadBufferSize, _size); 162 | NSMutableData* data = [[NSMutableData alloc] initWithLength:length]; 163 | ssize_t result = read(_file, data.mutableBytes, length); 164 | if (result < 0) { 165 | if (error) { 166 | *error = WGCDWebServerMakePosixError(errno); 167 | } 168 | return nil; 169 | } 170 | if (result > 0) { 171 | [data setLength:result]; 172 | _size -= result; 173 | } 174 | return data; 175 | } 176 | 177 | - (void)close { 178 | close(_file); 179 | } 180 | 181 | - (NSString*)description { 182 | NSMutableString* description = [NSMutableString stringWithString:[super description]]; 183 | [description appendFormat:@"\n\n{%@}", _path]; 184 | return description; 185 | } 186 | 187 | @end 188 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Responses/WGCDWebServerStreamedResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "WGCDWebServerResponse.h" 29 | 30 | /** 31 | * The WGCDWebServerStreamBlock is called to stream the data for the HTTP body. 32 | * The block must return either a chunk of data, an empty NSData when done, or 33 | * nil on error and set the "error" argument which is guaranteed to be non-NULL. 34 | */ 35 | typedef NSData* (^WGCDWebServerStreamBlock)(NSError** error); 36 | 37 | /** 38 | * The WGCDWebServerAsyncStreamBlock works like the WGCDWebServerStreamBlock 39 | * except the streamed data can be returned at a later time allowing for 40 | * truly asynchronous generation of the data. 41 | * 42 | * The block must call "completionBlock" passing the new chunk of data when ready, 43 | * an empty NSData when done, or nil on error and pass a NSError. 44 | * 45 | * The block cannot call "completionBlock" more than once per invocation. 46 | */ 47 | typedef void (^WGCDWebServerAsyncStreamBlock)(WGCDWebServerBodyReaderCompletionBlock completionBlock); 48 | 49 | /** 50 | * The WGCDWebServerStreamedResponse subclass of WGCDWebServerResponse streams 51 | * the body of the HTTP response using a WGCD block. 52 | */ 53 | @interface WGCDWebServerStreamedResponse : WGCDWebServerResponse 54 | 55 | /** 56 | * Creates a response with streamed data and a given content type. 57 | */ 58 | + (instancetype)responseWithContentType:(NSString*)type streamBlock:(WGCDWebServerStreamBlock)block; 59 | 60 | /** 61 | * Creates a response with async streamed data and a given content type. 62 | */ 63 | + (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(WGCDWebServerAsyncStreamBlock)block; 64 | 65 | /** 66 | * Initializes a response with streamed data and a given content type. 67 | */ 68 | - (instancetype)initWithContentType:(NSString*)type streamBlock:(WGCDWebServerStreamBlock)block; 69 | 70 | /** 71 | * This method is the designated initializer for the class. 72 | */ 73 | - (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(WGCDWebServerAsyncStreamBlock)block; 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /ios/WGCDWebServer/Responses/WGCDWebServerStreamedResponse.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error WGCDWebServer requires ARC 30 | #endif 31 | 32 | #import "WGCDWebServerPrivate.h" 33 | 34 | @interface WGCDWebServerStreamedResponse () { 35 | @private 36 | WGCDWebServerAsyncStreamBlock _block; 37 | } 38 | @end 39 | 40 | @implementation WGCDWebServerStreamedResponse 41 | 42 | + (instancetype)responseWithContentType:(NSString*)type streamBlock:(WGCDWebServerStreamBlock)block { 43 | return [[[self class] alloc] initWithContentType:type streamBlock:block]; 44 | } 45 | 46 | + (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(WGCDWebServerAsyncStreamBlock)block { 47 | return [[[self class] alloc] initWithContentType:type asyncStreamBlock:block]; 48 | } 49 | 50 | - (instancetype)initWithContentType:(NSString*)type streamBlock:(WGCDWebServerStreamBlock)block { 51 | return [self initWithContentType:type asyncStreamBlock:^(WGCDWebServerBodyReaderCompletionBlock completionBlock) { 52 | 53 | NSError* error = nil; 54 | NSData* data = block(&error); 55 | completionBlock(data, error); 56 | 57 | }]; 58 | } 59 | 60 | - (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(WGCDWebServerAsyncStreamBlock)block { 61 | if ((self = [super init])) { 62 | _block = [block copy]; 63 | 64 | self.contentType = type; 65 | } 66 | return self; 67 | } 68 | 69 | - (void)asyncReadDataWithCompletion:(WGCDWebServerBodyReaderCompletionBlock)block { 70 | _block(block); 71 | } 72 | 73 | - (NSString*)description { 74 | NSMutableString* description = [NSMutableString stringWithString:[super description]]; 75 | [description appendString:@"\n\n"]; 76 | return description; 77 | } 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "name": "react-native-http-bridge", 4 | "version": "0.6.1", 5 | "description": "A simple HTTP debug server for React Native apps", 6 | "main": "httpServer.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/alwx/react-native-http-bridge.git" 10 | }, 11 | "keywords": [ 12 | "react-component", 13 | "react-native", 14 | "nanohttpd", 15 | "gcdhttpserver", 16 | "http", 17 | "server", 18 | "bridge" 19 | ], 20 | "author": "Alexander Pantyukhov", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/alwx/react-native-http-bridge/issues" 24 | }, 25 | "homepage": "https://github.com/alwx/react-native-http-bridge#readme" 26 | } --------------------------------------------------------------------------------