├── .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 | }
--------------------------------------------------------------------------------