mirror of
https://github.com/swisspol/GCDWebServer.git
synced 2026-02-11 00:00:07 +08:00
Compare commits
205 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6d118f4ec | ||
|
|
0da9ee6afe | ||
|
|
6c93927e12 | ||
|
|
f4cf591e50 | ||
|
|
5c737549fc | ||
|
|
1c36bf07c8 | ||
|
|
df8d66f6c8 | ||
|
|
45432e6563 | ||
|
|
7720b1363d | ||
|
|
a7a6cfdbc7 | ||
|
|
70f687e34b | ||
|
|
11967061a1 | ||
|
|
2d8dc8775d | ||
|
|
9cb7caacbd | ||
|
|
ac10c0c5b0 | ||
|
|
acdb5c9262 | ||
|
|
f54cc20bd6 | ||
|
|
02738433bf | ||
|
|
c9563db0a6 | ||
|
|
cd1eea5612 | ||
|
|
f7c1c4eff5 | ||
|
|
fdc0feddf0 | ||
|
|
25dbee032d | ||
|
|
c3c7aaad00 | ||
|
|
2c53064f5d | ||
|
|
8d0a3599ee | ||
|
|
653dfb727b | ||
|
|
7e4dd53c98 | ||
|
|
4739d208c0 | ||
|
|
f57c307e7d | ||
|
|
466b1f8444 | ||
|
|
fd565421dc | ||
|
|
8811d2233e | ||
|
|
e561389d33 | ||
|
|
32cf20a1d8 | ||
|
|
3c6c2a2b5d | ||
|
|
2bf2dc72c5 | ||
|
|
0001648879 | ||
|
|
cbbf5483e8 | ||
|
|
edd1f2850b | ||
|
|
5eb5e14b70 | ||
|
|
3e46e12648 | ||
|
|
ee4395e67d | ||
|
|
34884f273a | ||
|
|
87745c0fde | ||
|
|
ec800b43d5 | ||
|
|
79d9fb389c | ||
|
|
33d14f22e0 | ||
|
|
b060305d6d | ||
|
|
561f56e7fb | ||
|
|
e9fdd19830 | ||
|
|
03fae468d1 | ||
|
|
0a7d185417 | ||
|
|
1e29a0195b | ||
|
|
4e29da53a2 | ||
|
|
acc54ceac3 | ||
|
|
c46a2ddb22 | ||
|
|
71e972084c | ||
|
|
e8c872b286 | ||
|
|
fc928d0e2b | ||
|
|
e65f0eeaf1 | ||
|
|
79ae63a4c0 | ||
|
|
21cc6bfb35 | ||
|
|
faf28fe0f9 | ||
|
|
bac5b680df | ||
|
|
7df465336e | ||
|
|
11254331d1 | ||
|
|
9f345c6858 | ||
|
|
a554893844 | ||
|
|
06569fe2cc | ||
|
|
e812d3f43c | ||
|
|
d30c88729d | ||
|
|
1a7dc913f1 | ||
|
|
47ed2cc593 | ||
|
|
033b0d3e56 | ||
|
|
6ed51907da | ||
|
|
8ed23992d8 | ||
|
|
b4927bdd75 | ||
|
|
44ffec1cf6 | ||
|
|
257ed94b65 | ||
|
|
4ea0a12317 | ||
|
|
ac788ca906 | ||
|
|
9f9509a05e | ||
|
|
48777cc151 | ||
|
|
c35d514b08 | ||
|
|
a013f9cebb | ||
|
|
1e28aef262 | ||
|
|
09851dd71b | ||
|
|
23f77b5765 | ||
|
|
7de8b0c643 | ||
|
|
2685819b61 | ||
|
|
fbcf3fa96c | ||
|
|
0f8e4f57e0 | ||
|
|
750214b5d5 | ||
|
|
ce5310f2ce | ||
|
|
83f1c062b5 | ||
|
|
0c51a9b071 | ||
|
|
3b5bc40e5e | ||
|
|
3178baa86e | ||
|
|
9a97e44d97 | ||
|
|
6c2b11e7b6 | ||
|
|
56b8e1befe | ||
|
|
4231465c58 | ||
|
|
4958eb8c72 | ||
|
|
43555c6662 | ||
|
|
7c08c54156 | ||
|
|
5834770e08 | ||
|
|
52406a12c2 | ||
|
|
e83814b0fa | ||
|
|
fba593a602 | ||
|
|
eec17d5c7b | ||
|
|
32bc72113a | ||
|
|
200d21c3fa | ||
|
|
3042b853bc | ||
|
|
50eb0437c1 | ||
|
|
00cc560b8e | ||
|
|
fa41f26b30 | ||
|
|
845969ec0d | ||
|
|
629df7895d | ||
|
|
55104e5b1e | ||
|
|
fcc95fdc11 | ||
|
|
10a94e36fd | ||
|
|
010ef9b8bc | ||
|
|
5ca7c27a07 | ||
|
|
c6632633f8 | ||
|
|
5b6eebbb9e | ||
|
|
a4c61bd071 | ||
|
|
2543279a6d | ||
|
|
95231b1a66 | ||
|
|
5f2877b85f | ||
|
|
47a51c3d42 | ||
|
|
3873dd1ad3 | ||
|
|
2ff10258e7 | ||
|
|
4360c4f7db | ||
|
|
8a6a139687 | ||
|
|
4eba86f348 | ||
|
|
ea973735c1 | ||
|
|
5707076e8d | ||
|
|
e1fb807a93 | ||
|
|
71575729e9 | ||
|
|
94e30f6442 | ||
|
|
c98941121a | ||
|
|
4ee9c30911 | ||
|
|
48cf20bb55 | ||
|
|
ac9b8a5f47 | ||
|
|
cad428ca6f | ||
|
|
bb5c1a5195 | ||
|
|
bef95231d2 | ||
|
|
0192c364b6 | ||
|
|
062a0dcee4 | ||
|
|
21d9fc2f62 | ||
|
|
614ff58be5 | ||
|
|
7b0477b1e0 | ||
|
|
a674614713 | ||
|
|
b549f1197d | ||
|
|
9d38bb4f94 | ||
|
|
44c6a8adcf | ||
|
|
aaf8679308 | ||
|
|
81d74b46b8 | ||
|
|
f7de5cac09 | ||
|
|
a1c68352a4 | ||
|
|
e70a3338a5 | ||
|
|
3c33e9f056 | ||
|
|
d160e5ff91 | ||
|
|
2d2343ab34 | ||
|
|
f6783daadd | ||
|
|
99cae36644 | ||
|
|
b292710102 | ||
|
|
b8b4a35178 | ||
|
|
ecc572a934 | ||
|
|
3a02341b0c | ||
|
|
e792fe8eb6 | ||
|
|
4c8ec1d685 | ||
|
|
f7bb5babf8 | ||
|
|
ae88198f20 | ||
|
|
d71c0d493f | ||
|
|
d611ae0cbe | ||
|
|
93287edfd5 | ||
|
|
dc287906d6 | ||
|
|
ab9459a67a | ||
|
|
aa8fc97b9b | ||
|
|
863febed62 | ||
|
|
2ff117dbf3 | ||
|
|
4838d0def9 | ||
|
|
c394ae8bf5 | ||
|
|
bdfe6728ae | ||
|
|
b1ab7479b3 | ||
|
|
03a0ac32ee | ||
|
|
bd2c292cb6 | ||
|
|
e8b67264ab | ||
|
|
3d5fd0b828 | ||
|
|
9524d31b1b | ||
|
|
a3606d6027 | ||
|
|
00b2c38109 | ||
|
|
0a9d3105fc | ||
|
|
0f0a9840e4 | ||
|
|
047fdddb0e | ||
|
|
79d6075a84 | ||
|
|
594497d234 | ||
|
|
1f7c0366f0 | ||
|
|
fe472cdd54 | ||
|
|
9c33c83351 | ||
|
|
9719406303 | ||
|
|
0b8f7ff6ad | ||
|
|
71c08cff73 |
16
.clang-format
Normal file
16
.clang-format
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
BasedOnStyle: Google
|
||||||
|
Standard: Cpp11
|
||||||
|
ColumnLimit: 0
|
||||||
|
AlignTrailingComments: false
|
||||||
|
NamespaceIndentation: All
|
||||||
|
DerivePointerAlignment: false
|
||||||
|
AlwaysBreakBeforeMultilineStrings: false
|
||||||
|
AccessModifierOffset: -2
|
||||||
|
ObjCSpaceBeforeProtocolList: true
|
||||||
|
SortIncludes: false
|
||||||
|
---
|
||||||
|
Language: Cpp
|
||||||
|
---
|
||||||
|
Language: ObjC
|
||||||
|
...
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,5 +1,5 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
xcuserdata
|
xcuserdata
|
||||||
project.xcworkspace
|
project.xcworkspace
|
||||||
|
/build
|
||||||
Tests/Payload
|
/Carthage/Build
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
language: objective-c
|
language: objective-c
|
||||||
script: ./Run-Tests.sh
|
script: ./Run-Tests.sh
|
||||||
|
osx_image: xcode11.3
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -25,46 +25,28 @@
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import "AppDelegate.h"
|
// GCDWebServer Core
|
||||||
|
#import "GCDWebServer.h"
|
||||||
|
#import "GCDWebServerConnection.h"
|
||||||
|
#import "GCDWebServerFunctions.h"
|
||||||
|
#import "GCDWebServerHTTPStatusCodes.h"
|
||||||
|
#import "GCDWebServerResponse.h"
|
||||||
|
#import "GCDWebServerRequest.h"
|
||||||
|
|
||||||
|
// GCDWebServer Requests
|
||||||
|
#import "GCDWebServerDataRequest.h"
|
||||||
|
#import "GCDWebServerFileRequest.h"
|
||||||
|
#import "GCDWebServerMultiPartFormRequest.h"
|
||||||
|
#import "GCDWebServerURLEncodedFormRequest.h"
|
||||||
|
|
||||||
|
// GCDWebServer Responses
|
||||||
|
#import "GCDWebServerDataResponse.h"
|
||||||
|
#import "GCDWebServerErrorResponse.h"
|
||||||
|
#import "GCDWebServerFileResponse.h"
|
||||||
|
#import "GCDWebServerStreamedResponse.h"
|
||||||
|
|
||||||
|
// GCDWebUploader
|
||||||
#import "GCDWebUploader.h"
|
#import "GCDWebUploader.h"
|
||||||
|
|
||||||
@interface AppDelegate () <GCDWebUploaderDelegate> {
|
// GCDWebDAVServer
|
||||||
@private
|
#import "GCDWebDAVServer.h"
|
||||||
GCDWebUploader* _webServer;
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation AppDelegate
|
|
||||||
|
|
||||||
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
|
||||||
CGRect bounds = ([UIScreen instancesRespondToSelector:@selector(nativeBounds)] ? [[UIScreen mainScreen] nativeBounds] : [[UIScreen mainScreen] bounds]);
|
|
||||||
_window = [[UIWindow alloc] initWithFrame:bounds];
|
|
||||||
_window.backgroundColor = [UIColor whiteColor];
|
|
||||||
[_window makeKeyAndVisible];
|
|
||||||
|
|
||||||
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
|
|
||||||
_webServer = [[GCDWebUploader alloc] initWithUploadDirectory:documentsPath];
|
|
||||||
_webServer.delegate = self;
|
|
||||||
_webServer.allowHiddenItems = YES;
|
|
||||||
[_webServer start];
|
|
||||||
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)webUploader:(GCDWebUploader*)uploader didUploadFileAtPath:(NSString*)path {
|
|
||||||
NSLog(@"[UPLOAD] %@", path);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)webUploader:(GCDWebUploader*)uploader didMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath {
|
|
||||||
NSLog(@"[MOVE] %@ -> %@", fromPath, toPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)webUploader:(GCDWebUploader*)uploader didDeleteItemAtPath:(NSString*)path {
|
|
||||||
NSLog(@"[DELETE] %@", path);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)webUploader:(GCDWebUploader*)uploader didCreateDirectoryAtPath:(NSString*)path {
|
|
||||||
NSLog(@"[CREATE] %@", path);
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
22
Frameworks/Info.plist
Normal file
22
Frameworks/Info.plist
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>${BUNDLE_VERSION_STRING}</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>${BUNDLE_VERSION_STRING}</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
43
Frameworks/Tests.m
Normal file
43
Frameworks/Tests.m
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#import <GCDWebServers/GCDWebServers.h>
|
||||||
|
#import <XCTest/XCTest.h>
|
||||||
|
|
||||||
|
#pragma clang diagnostic ignored "-Weverything" // Prevent "messaging to unqualified id" warnings
|
||||||
|
|
||||||
|
@interface Tests : XCTestCase
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation Tests
|
||||||
|
|
||||||
|
- (void)testWebServer {
|
||||||
|
GCDWebServer* server = [[GCDWebServer alloc] init];
|
||||||
|
XCTAssertNotNil(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testDAVServer {
|
||||||
|
GCDWebDAVServer* server = [[GCDWebDAVServer alloc] init];
|
||||||
|
XCTAssertNotNil(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testWebUploader {
|
||||||
|
GCDWebUploader* server = [[GCDWebUploader alloc] init];
|
||||||
|
XCTAssertNotNil(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testPaths {
|
||||||
|
XCTAssertEqualObjects(GCDWebServerNormalizePath(@""), @"");
|
||||||
|
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"/foo/"), @"/foo");
|
||||||
|
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"foo/bar"), @"foo/bar");
|
||||||
|
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"foo//bar"), @"foo/bar");
|
||||||
|
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"foo/bar//"), @"foo/bar");
|
||||||
|
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"foo/./bar"), @"foo/bar");
|
||||||
|
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"foo/bar/."), @"foo/bar");
|
||||||
|
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"foo/../bar"), @"bar");
|
||||||
|
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"/foo/../bar"), @"/bar");
|
||||||
|
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"/foo/.."), @"/");
|
||||||
|
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"/.."), @"/");
|
||||||
|
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"."), @"");
|
||||||
|
XCTAssertEqualObjects(GCDWebServerNormalizePath(@".."), @"");
|
||||||
|
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"../.."), @"");
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
3
Frameworks/module.modulemap
Normal file
3
Frameworks/module.modulemap
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
framework module GCDWebServers {
|
||||||
|
umbrella header "GCDWebServers.h"
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
#import "GCDWebServer.h"
|
#import "GCDWebServer.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
@class GCDWebDAVServer;
|
@class GCDWebDAVServer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,14 +88,14 @@
|
|||||||
/**
|
/**
|
||||||
* Sets the delegate for the server.
|
* Sets the delegate for the server.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, assign) id<GCDWebDAVServerDelegate> delegate;
|
@property(nonatomic, weak, nullable) id<GCDWebDAVServerDelegate> delegate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets which files are allowed to be operated on depending on their extension.
|
* Sets which files are allowed to be operated on depending on their extension.
|
||||||
*
|
*
|
||||||
* The default value is nil i.e. all file extensions are allowed.
|
* The default value is nil i.e. all file extensions are allowed.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, copy) NSArray* allowedFileExtensions;
|
@property(nonatomic, copy) NSArray<NSString*>* allowedFileExtensions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets if files and directories whose name start with a period are allowed to
|
* Sets if files and directories whose name start with a period are allowed to
|
||||||
@@ -154,3 +156,5 @@
|
|||||||
- (BOOL)shouldCreateDirectoryAtPath:(NSString*)path;
|
- (BOOL)shouldCreateDirectoryAtPath:(NSString*)path;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -55,23 +55,110 @@ typedef NS_ENUM(NSInteger, DAVProperties) {
|
|||||||
kDAVAllProperties = kDAVProperty_ResourceType | kDAVProperty_CreationDate | kDAVProperty_LastModified | kDAVProperty_ContentLength
|
kDAVAllProperties = kDAVProperty_ResourceType | kDAVProperty_CreationDate | kDAVProperty_LastModified | kDAVProperty_ContentLength
|
||||||
};
|
};
|
||||||
|
|
||||||
@interface GCDWebDAVServer () {
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
@private
|
|
||||||
NSString* _uploadDirectory;
|
@interface GCDWebDAVServer (Methods)
|
||||||
NSArray* _allowedExtensions;
|
- (nullable GCDWebServerResponse*)performOPTIONS:(GCDWebServerRequest*)request;
|
||||||
BOOL _allowHidden;
|
- (nullable GCDWebServerResponse*)performGET:(GCDWebServerRequest*)request;
|
||||||
|
- (nullable GCDWebServerResponse*)performPUT:(GCDWebServerFileRequest*)request;
|
||||||
|
- (nullable GCDWebServerResponse*)performDELETE:(GCDWebServerRequest*)request;
|
||||||
|
- (nullable GCDWebServerResponse*)performMKCOL:(GCDWebServerDataRequest*)request;
|
||||||
|
- (nullable GCDWebServerResponse*)performCOPY:(GCDWebServerRequest*)request isMove:(BOOL)isMove;
|
||||||
|
- (nullable GCDWebServerResponse*)performPROPFIND:(GCDWebServerDataRequest*)request;
|
||||||
|
- (nullable GCDWebServerResponse*)performLOCK:(GCDWebServerDataRequest*)request;
|
||||||
|
- (nullable GCDWebServerResponse*)performUNLOCK:(GCDWebServerRequest*)request;
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
||||||
|
@implementation GCDWebDAVServer
|
||||||
|
|
||||||
|
@dynamic delegate;
|
||||||
|
|
||||||
|
- (instancetype)initWithUploadDirectory:(NSString*)path {
|
||||||
|
if ((self = [super init])) {
|
||||||
|
_uploadDirectory = [path copy];
|
||||||
|
GCDWebDAVServer* __unsafe_unretained server = self;
|
||||||
|
|
||||||
|
// 9.1 PROPFIND method
|
||||||
|
[self addDefaultHandlerForMethod:@"PROPFIND"
|
||||||
|
requestClass:[GCDWebServerDataRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
return [server performPROPFIND:(GCDWebServerDataRequest*)request];
|
||||||
|
}];
|
||||||
|
|
||||||
|
// 9.3 MKCOL Method
|
||||||
|
[self addDefaultHandlerForMethod:@"MKCOL"
|
||||||
|
requestClass:[GCDWebServerDataRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
return [server performMKCOL:(GCDWebServerDataRequest*)request];
|
||||||
|
}];
|
||||||
|
|
||||||
|
// 9.4 GET & HEAD methods
|
||||||
|
[self addDefaultHandlerForMethod:@"GET"
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
return [server performGET:request];
|
||||||
|
}];
|
||||||
|
|
||||||
|
// 9.6 DELETE method
|
||||||
|
[self addDefaultHandlerForMethod:@"DELETE"
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
return [server performDELETE:request];
|
||||||
|
}];
|
||||||
|
|
||||||
|
// 9.7 PUT method
|
||||||
|
[self addDefaultHandlerForMethod:@"PUT"
|
||||||
|
requestClass:[GCDWebServerFileRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
return [server performPUT:(GCDWebServerFileRequest*)request];
|
||||||
|
}];
|
||||||
|
|
||||||
|
// 9.8 COPY method
|
||||||
|
[self addDefaultHandlerForMethod:@"COPY"
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
return [server performCOPY:request isMove:NO];
|
||||||
|
}];
|
||||||
|
|
||||||
|
// 9.9 MOVE method
|
||||||
|
[self addDefaultHandlerForMethod:@"MOVE"
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
return [server performCOPY:request isMove:YES];
|
||||||
|
}];
|
||||||
|
|
||||||
|
// 9.10 LOCK method
|
||||||
|
[self addDefaultHandlerForMethod:@"LOCK"
|
||||||
|
requestClass:[GCDWebServerDataRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
return [server performLOCK:(GCDWebServerDataRequest*)request];
|
||||||
|
}];
|
||||||
|
|
||||||
|
// 9.11 UNLOCK method
|
||||||
|
[self addDefaultHandlerForMethod:@"UNLOCK"
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
return [server performUNLOCK:request];
|
||||||
|
}];
|
||||||
|
|
||||||
|
// 10.1 OPTIONS method / DAV Header
|
||||||
|
[self addDefaultHandlerForMethod:@"OPTIONS"
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
return [server performOPTIONS:request];
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation GCDWebDAVServer (Methods)
|
@implementation GCDWebDAVServer (Methods)
|
||||||
|
|
||||||
// Must match implementation in GCDWebUploader
|
|
||||||
- (BOOL)_checkSandboxedPath:(NSString*)path {
|
|
||||||
return [[path stringByStandardizingPath] hasPrefix:_uploadDirectory];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)_checkFileExtension:(NSString*)fileName {
|
- (BOOL)_checkFileExtension:(NSString*)fileName {
|
||||||
if (_allowedExtensions && ![_allowedExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
|
if (_allowedFileExtensions && ![_allowedFileExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
return YES;
|
return YES;
|
||||||
@@ -94,14 +181,14 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
|
|||||||
|
|
||||||
- (GCDWebServerResponse*)performGET:(GCDWebServerRequest*)request {
|
- (GCDWebServerResponse*)performGET:(GCDWebServerRequest*)request {
|
||||||
NSString* relativePath = request.path;
|
NSString* relativePath = request.path;
|
||||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
|
||||||
BOOL isDirectory = NO;
|
BOOL isDirectory = NO;
|
||||||
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString* itemName = [absolutePath lastPathComponent];
|
NSString* itemName = [absolutePath lastPathComponent];
|
||||||
if (([itemName hasPrefix:@"."] && !_allowHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) {
|
if (([itemName hasPrefix:@"."] && !_allowHiddenItems) || (!isDirectory && ![self _checkFileExtension:itemName])) {
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Downlading item name \"%@\" is not allowed", itemName];
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Downlading item name \"%@\" is not allowed", itemName];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,6 +202,11 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
|
|||||||
[self.delegate davServer:self didDownloadFileAtPath:absolutePath];
|
[self.delegate davServer:self didDownloadFileAtPath:absolutePath];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ([request hasByteRange]) {
|
||||||
|
return [GCDWebServerFileResponse responseWithFile:absolutePath byteRange:request.byteRange];
|
||||||
|
}
|
||||||
|
|
||||||
return [GCDWebServerFileResponse responseWithFile:absolutePath];
|
return [GCDWebServerFileResponse responseWithFile:absolutePath];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,10 +216,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSString* relativePath = request.path;
|
NSString* relativePath = request.path;
|
||||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
|
||||||
if (![self _checkSandboxedPath:absolutePath]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
|
||||||
}
|
|
||||||
BOOL isDirectory;
|
BOOL isDirectory;
|
||||||
if (![[NSFileManager defaultManager] fileExistsAtPath:[absolutePath stringByDeletingLastPathComponent] isDirectory:&isDirectory] || !isDirectory) {
|
if (![[NSFileManager defaultManager] fileExistsAtPath:[absolutePath stringByDeletingLastPathComponent] isDirectory:&isDirectory] || !isDirectory) {
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Conflict message:@"Missing intermediate collection(s) for \"%@\"", relativePath];
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Conflict message:@"Missing intermediate collection(s) for \"%@\"", relativePath];
|
||||||
@@ -139,7 +228,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSString* fileName = [absolutePath lastPathComponent];
|
NSString* fileName = [absolutePath lastPathComponent];
|
||||||
if (([fileName hasPrefix:@"."] && !_allowHidden) || ![self _checkFileExtension:fileName]) {
|
if (([fileName hasPrefix:@"."] && !_allowHiddenItems) || ![self _checkFileExtension:fileName]) {
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file name \"%@\" is not allowed", fileName];
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file name \"%@\" is not allowed", fileName];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,14 +257,14 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSString* relativePath = request.path;
|
NSString* relativePath = request.path;
|
||||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
|
||||||
BOOL isDirectory = NO;
|
BOOL isDirectory = NO;
|
||||||
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString* itemName = [absolutePath lastPathComponent];
|
NSString* itemName = [absolutePath lastPathComponent];
|
||||||
if (([itemName hasPrefix:@"."] && !_allowHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) {
|
if (([itemName hasPrefix:@"."] && !_allowHiddenItems) || (!isDirectory && ![self _checkFileExtension:itemName])) {
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting item name \"%@\" is not allowed", itemName];
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting item name \"%@\" is not allowed", itemName];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,17 +291,14 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSString* relativePath = request.path;
|
NSString* relativePath = request.path;
|
||||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
|
||||||
if (![self _checkSandboxedPath:absolutePath]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
|
||||||
}
|
|
||||||
BOOL isDirectory;
|
BOOL isDirectory;
|
||||||
if (![[NSFileManager defaultManager] fileExistsAtPath:[absolutePath stringByDeletingLastPathComponent] isDirectory:&isDirectory] || !isDirectory) {
|
if (![[NSFileManager defaultManager] fileExistsAtPath:[absolutePath stringByDeletingLastPathComponent] isDirectory:&isDirectory] || !isDirectory) {
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Conflict message:@"Missing intermediate collection(s) for \"%@\"", relativePath];
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Conflict message:@"Missing intermediate collection(s) for \"%@\"", relativePath];
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString* directoryName = [absolutePath lastPathComponent];
|
NSString* directoryName = [absolutePath lastPathComponent];
|
||||||
if (!_allowHidden && [directoryName hasPrefix:@"."]) {
|
if (!_allowHiddenItems && [directoryName hasPrefix:@"."]) {
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory name \"%@\" is not allowed", directoryName];
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory name \"%@\" is not allowed", directoryName];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,19 +337,19 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSString* srcRelativePath = request.path;
|
NSString* srcRelativePath = request.path;
|
||||||
NSString* srcAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:srcRelativePath];
|
NSString* srcAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(srcRelativePath)];
|
||||||
if (![self _checkSandboxedPath:srcAbsolutePath]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", srcRelativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* dstRelativePath = [request.headers objectForKey:@"Destination"];
|
NSString* dstRelativePath = [request.headers objectForKey:@"Destination"];
|
||||||
NSRange range = [dstRelativePath rangeOfString:[request.headers objectForKey:@"Host"]];
|
NSRange range = [dstRelativePath rangeOfString:(NSString*)[request.headers objectForKey:@"Host"]];
|
||||||
if ((dstRelativePath == nil) || (range.location == NSNotFound)) {
|
if ((dstRelativePath == nil) || (range.location == NSNotFound)) {
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Malformed 'Destination' header: %@", dstRelativePath];
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Malformed 'Destination' header: %@", dstRelativePath];
|
||||||
}
|
}
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
dstRelativePath = [[dstRelativePath substringFromIndex:(range.location + range.length)] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
dstRelativePath = [[dstRelativePath substringFromIndex:(range.location + range.length)] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
||||||
NSString* dstAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:dstRelativePath];
|
#pragma clang diagnostic pop
|
||||||
if (![self _checkSandboxedPath:dstAbsolutePath]) {
|
NSString* dstAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(dstRelativePath)];
|
||||||
|
if (!dstAbsolutePath) {
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", srcRelativePath];
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", srcRelativePath];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,9 +358,14 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
|
|||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Conflict message:@"Invalid destination \"%@\"", dstRelativePath];
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Conflict message:@"Invalid destination \"%@\"", dstRelativePath];
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString* itemName = [dstAbsolutePath lastPathComponent];
|
NSString* srcName = [srcAbsolutePath lastPathComponent];
|
||||||
if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
|
if ((!_allowHiddenItems && [srcName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:srcName])) {
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"%@ to item name \"%@\" is not allowed", isMove ? @"Moving" : @"Copying", itemName];
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"%@ from item name \"%@\" is not allowed", isMove ? @"Moving" : @"Copying", srcName];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString* dstName = [dstAbsolutePath lastPathComponent];
|
||||||
|
if ((!_allowHiddenItems && [dstName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:dstName])) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"%@ to item name \"%@\" is not allowed", isMove ? @"Moving" : @"Copying", dstName];
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString* overwriteHeader = [request.headers objectForKey:@"Overwrite"];
|
NSString* overwriteHeader = [request.headers objectForKey:@"Overwrite"];
|
||||||
@@ -333,7 +424,10 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)_addPropertyResponseForItem:(NSString*)itemPath resource:(NSString*)resourcePath properties:(DAVProperties)properties xmlString:(NSMutableString*)xmlString {
|
- (void)_addPropertyResponseForItem:(NSString*)itemPath resource:(NSString*)resourcePath properties:(DAVProperties)properties xmlString:(NSMutableString*)xmlString {
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
CFStringRef escapedPath = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)resourcePath, NULL, CFSTR("<&>?+"), kCFStringEncodingUTF8);
|
CFStringRef escapedPath = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)resourcePath, NULL, CFSTR("<&>?+"), kCFStringEncodingUTF8);
|
||||||
|
#pragma clang diagnostic pop
|
||||||
if (escapedPath) {
|
if (escapedPath) {
|
||||||
NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:itemPath error:NULL];
|
NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:itemPath error:NULL];
|
||||||
NSString* type = [attributes objectForKey:NSFileType];
|
NSString* type = [attributes objectForKey:NSFileType];
|
||||||
@@ -354,11 +448,11 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((properties & kDAVProperty_CreationDate) && [attributes objectForKey:NSFileCreationDate]) {
|
if ((properties & kDAVProperty_CreationDate) && [attributes objectForKey:NSFileCreationDate]) {
|
||||||
[xmlString appendFormat:@"<D:creationdate>%@</D:creationdate>", GCDWebServerFormatISO8601([attributes fileCreationDate])];
|
[xmlString appendFormat:@"<D:creationdate>%@</D:creationdate>", GCDWebServerFormatISO8601((NSDate*)[attributes fileCreationDate])];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((properties & kDAVProperty_LastModified) && isFile && [attributes objectForKey:NSFileModificationDate]) { // Last modification date is not useful for directories as it changes implicitely and 'Last-Modified' header is not provided for directories anyway
|
if ((properties & kDAVProperty_LastModified) && isFile && [attributes objectForKey:NSFileModificationDate]) { // Last modification date is not useful for directories as it changes implicitely and 'Last-Modified' header is not provided for directories anyway
|
||||||
[xmlString appendFormat:@"<D:getlastmodified>%@</D:getlastmodified>", GCDWebServerFormatRFC822([attributes fileModificationDate])];
|
[xmlString appendFormat:@"<D:getlastmodified>%@</D:getlastmodified>", GCDWebServerFormatRFC822((NSDate*)[attributes fileModificationDate])];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((properties & kDAVProperty_ContentLength) && !isDirectory && [attributes objectForKey:NSFileSize]) {
|
if ((properties & kDAVProperty_ContentLength) && !isDirectory && [attributes objectForKey:NSFileSize]) {
|
||||||
@@ -429,21 +523,21 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSString* relativePath = request.path;
|
NSString* relativePath = request.path;
|
||||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
|
||||||
BOOL isDirectory = NO;
|
BOOL isDirectory = NO;
|
||||||
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString* itemName = [absolutePath lastPathComponent];
|
NSString* itemName = [absolutePath lastPathComponent];
|
||||||
if (([itemName hasPrefix:@"."] && !_allowHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) {
|
if (([itemName hasPrefix:@"."] && !_allowHiddenItems) || (!isDirectory && ![self _checkFileExtension:itemName])) {
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Retrieving properties for item name \"%@\" is not allowed", itemName];
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Retrieving properties for item name \"%@\" is not allowed", itemName];
|
||||||
}
|
}
|
||||||
|
|
||||||
NSArray* items = nil;
|
NSArray* items = nil;
|
||||||
if (isDirectory) {
|
if (isDirectory) {
|
||||||
NSError* error = nil;
|
NSError* error = nil;
|
||||||
items = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:absolutePath error:&error];
|
items = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:absolutePath error:&error] sortedArrayUsingSelector:@selector(localizedStandardCompare:)];
|
||||||
if (items == nil) {
|
if (items == nil) {
|
||||||
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed listing directory \"%@\"", relativePath];
|
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed listing directory \"%@\"", relativePath];
|
||||||
}
|
}
|
||||||
@@ -460,14 +554,14 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
|||||||
relativePath = [relativePath stringByAppendingString:@"/"];
|
relativePath = [relativePath stringByAppendingString:@"/"];
|
||||||
}
|
}
|
||||||
for (NSString* item in items) {
|
for (NSString* item in items) {
|
||||||
if (_allowHidden || ![item hasPrefix:@"."]) {
|
if (_allowHiddenItems || ![item hasPrefix:@"."]) {
|
||||||
[self _addPropertyResponseForItem:[absolutePath stringByAppendingPathComponent:item] resource:[relativePath stringByAppendingString:item] properties:properties xmlString:xmlString];
|
[self _addPropertyResponseForItem:[absolutePath stringByAppendingPathComponent:item] resource:[relativePath stringByAppendingString:item] properties:properties xmlString:xmlString];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[xmlString appendString:@"</D:multistatus>"];
|
[xmlString appendString:@"</D:multistatus>"];
|
||||||
|
|
||||||
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:[xmlString dataUsingEncoding:NSUTF8StringEncoding]
|
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:(NSData*)[xmlString dataUsingEncoding:NSUTF8StringEncoding]
|
||||||
contentType:@"application/xml; charset=\"utf-8\""];
|
contentType:@"application/xml; charset=\"utf-8\""];
|
||||||
response.statusCode = kGCDWebServerHTTPStatusCode_MultiStatus;
|
response.statusCode = kGCDWebServerHTTPStatusCode_MultiStatus;
|
||||||
return response;
|
return response;
|
||||||
@@ -479,9 +573,9 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSString* relativePath = request.path;
|
NSString* relativePath = request.path;
|
||||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
|
||||||
BOOL isDirectory = NO;
|
BOOL isDirectory = NO;
|
||||||
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,7 +622,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSString* itemName = [absolutePath lastPathComponent];
|
NSString* itemName = [absolutePath lastPathComponent];
|
||||||
if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
|
if ((!_allowHiddenItems && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Locking item name \"%@\" is not allowed", itemName];
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Locking item name \"%@\" is not allowed", itemName];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,13 +653,13 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
|||||||
[xmlString appendFormat:@"<D:timeout>%@</D:timeout>\n", timeoutHeader];
|
[xmlString appendFormat:@"<D:timeout>%@</D:timeout>\n", timeoutHeader];
|
||||||
}
|
}
|
||||||
[xmlString appendFormat:@"<D:locktoken><D:href>%@</D:href></D:locktoken>\n", token];
|
[xmlString appendFormat:@"<D:locktoken><D:href>%@</D:href></D:locktoken>\n", token];
|
||||||
NSString* lockroot = [@"http://" stringByAppendingString:[[request.headers objectForKey:@"Host"] stringByAppendingString:[@"/" stringByAppendingString:relativePath]]];
|
NSString* lockroot = [@"http://" stringByAppendingString:[(NSString*)[request.headers objectForKey:@"Host"] stringByAppendingString:[@"/" stringByAppendingString:relativePath]]];
|
||||||
[xmlString appendFormat:@"<D:lockroot><D:href>%@</D:href></D:lockroot>\n", lockroot];
|
[xmlString appendFormat:@"<D:lockroot><D:href>%@</D:href></D:lockroot>\n", lockroot];
|
||||||
[xmlString appendString:@"</D:activelock>\n</D:lockdiscovery>\n"];
|
[xmlString appendString:@"</D:activelock>\n</D:lockdiscovery>\n"];
|
||||||
[xmlString appendString:@"</D:prop>"];
|
[xmlString appendString:@"</D:prop>"];
|
||||||
|
|
||||||
[self logVerbose:@"WebDAV pretending to lock \"%@\"", relativePath];
|
[self logVerbose:@"WebDAV pretending to lock \"%@\"", relativePath];
|
||||||
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:[xmlString dataUsingEncoding:NSUTF8StringEncoding]
|
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:(NSData*)[xmlString dataUsingEncoding:NSUTF8StringEncoding]
|
||||||
contentType:@"application/xml; charset=\"utf-8\""];
|
contentType:@"application/xml; charset=\"utf-8\""];
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@@ -576,9 +670,9 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSString* relativePath = request.path;
|
NSString* relativePath = request.path;
|
||||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
|
||||||
BOOL isDirectory = NO;
|
BOOL isDirectory = NO;
|
||||||
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -588,7 +682,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSString* itemName = [absolutePath lastPathComponent];
|
NSString* itemName = [absolutePath lastPathComponent];
|
||||||
if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
|
if ((!_allowHiddenItems && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Unlocking item name \"%@\" is not allowed", itemName];
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Unlocking item name \"%@\" is not allowed", itemName];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -598,71 +692,6 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation GCDWebDAVServer
|
|
||||||
|
|
||||||
@synthesize uploadDirectory=_uploadDirectory, allowedFileExtensions=_allowedExtensions, allowHiddenItems=_allowHidden;
|
|
||||||
|
|
||||||
- (instancetype)initWithUploadDirectory:(NSString*)path {
|
|
||||||
if ((self = [super init])) {
|
|
||||||
_uploadDirectory = [[path stringByStandardizingPath] copy];
|
|
||||||
GCDWebDAVServer* __unsafe_unretained server = self;
|
|
||||||
|
|
||||||
// 9.1 PROPFIND method
|
|
||||||
[self addDefaultHandlerForMethod:@"PROPFIND" requestClass:[GCDWebServerDataRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
|
||||||
return [server performPROPFIND:(GCDWebServerDataRequest*)request];
|
|
||||||
}];
|
|
||||||
|
|
||||||
// 9.3 MKCOL Method
|
|
||||||
[self addDefaultHandlerForMethod:@"MKCOL" requestClass:[GCDWebServerDataRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
|
||||||
return [server performMKCOL:(GCDWebServerDataRequest*)request];
|
|
||||||
}];
|
|
||||||
|
|
||||||
// 9.4 GET & HEAD methods
|
|
||||||
[self addDefaultHandlerForMethod:@"GET" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
|
||||||
return [server performGET:request];
|
|
||||||
}];
|
|
||||||
|
|
||||||
// 9.6 DELETE method
|
|
||||||
[self addDefaultHandlerForMethod:@"DELETE" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
|
||||||
return [server performDELETE:request];
|
|
||||||
}];
|
|
||||||
|
|
||||||
// 9.7 PUT method
|
|
||||||
[self addDefaultHandlerForMethod:@"PUT" requestClass:[GCDWebServerFileRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
|
||||||
return [server performPUT:(GCDWebServerFileRequest*)request];
|
|
||||||
}];
|
|
||||||
|
|
||||||
// 9.8 COPY method
|
|
||||||
[self addDefaultHandlerForMethod:@"COPY" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
|
||||||
return [server performCOPY:request isMove:NO];
|
|
||||||
}];
|
|
||||||
|
|
||||||
// 9.9 MOVE method
|
|
||||||
[self addDefaultHandlerForMethod:@"MOVE" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
|
||||||
return [server performCOPY:request isMove:YES];
|
|
||||||
}];
|
|
||||||
|
|
||||||
// 9.10 LOCK method
|
|
||||||
[self addDefaultHandlerForMethod:@"LOCK" requestClass:[GCDWebServerDataRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
|
||||||
return [server performLOCK:(GCDWebServerDataRequest*)request];
|
|
||||||
}];
|
|
||||||
|
|
||||||
// 9.11 UNLOCK method
|
|
||||||
[self addDefaultHandlerForMethod:@"UNLOCK" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
|
||||||
return [server performUNLOCK:request];
|
|
||||||
}];
|
|
||||||
|
|
||||||
// 10.1 OPTIONS method / DAV Header
|
|
||||||
[self addDefaultHandlerForMethod:@"OPTIONS" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
|
||||||
return [server performOPTIONS:request];
|
|
||||||
}];
|
|
||||||
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebDAVServer (Subclassing)
|
@implementation GCDWebDAVServer (Subclassing)
|
||||||
|
|
||||||
- (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath {
|
- (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath {
|
||||||
|
|||||||
@@ -2,19 +2,20 @@
|
|||||||
# http://guides.cocoapods.org/making/getting-setup-with-trunk.html
|
# http://guides.cocoapods.org/making/getting-setup-with-trunk.html
|
||||||
# $ sudo gem update cocoapods
|
# $ sudo gem update cocoapods
|
||||||
# (optional) $ pod trunk register {email} {name} --description={computer}
|
# (optional) $ pod trunk register {email} {name} --description={computer}
|
||||||
# $ pod trunk push
|
# $ pod trunk --verbose push
|
||||||
# DELETE THIS SECTION BEFORE PROCEEDING!
|
# DELETE THIS SECTION BEFORE PROCEEDING!
|
||||||
|
|
||||||
Pod::Spec.new do |s|
|
Pod::Spec.new do |s|
|
||||||
s.name = 'GCDWebServer'
|
s.name = 'GCDWebServer'
|
||||||
s.version = '3.2.2'
|
s.version = '3.5.4'
|
||||||
s.author = { 'Pierre-Olivier Latour' => 'info@pol-online.net' }
|
s.author = { 'Pierre-Olivier Latour' => 'info@pol-online.net' }
|
||||||
s.license = { :type => 'BSD', :file => 'LICENSE' }
|
s.license = { :type => 'BSD', :file => 'LICENSE' }
|
||||||
s.homepage = 'https://github.com/swisspol/GCDWebServer'
|
s.homepage = 'https://github.com/swisspol/GCDWebServer'
|
||||||
s.summary = 'Lightweight GCD based HTTP server for OS X & iOS (includes web based uploader & WebDAV server)'
|
s.summary = 'Lightweight GCD based HTTP server for OS X & iOS (includes web based uploader & WebDAV server)'
|
||||||
|
|
||||||
s.source = { :git => 'https://github.com/swisspol/GCDWebServer.git', :tag => s.version.to_s }
|
s.source = { :git => 'https://github.com/swisspol/GCDWebServer.git', :tag => s.version.to_s }
|
||||||
s.ios.deployment_target = '5.0'
|
s.ios.deployment_target = '8.0'
|
||||||
|
s.tvos.deployment_target = '9.0'
|
||||||
s.osx.deployment_target = '10.7'
|
s.osx.deployment_target = '10.7'
|
||||||
s.requires_arc = true
|
s.requires_arc = true
|
||||||
|
|
||||||
@@ -25,7 +26,9 @@ Pod::Spec.new do |s|
|
|||||||
cs.private_header_files = "GCDWebServer/Core/GCDWebServerPrivate.h"
|
cs.private_header_files = "GCDWebServer/Core/GCDWebServerPrivate.h"
|
||||||
cs.requires_arc = true
|
cs.requires_arc = true
|
||||||
cs.ios.library = 'z'
|
cs.ios.library = 'z'
|
||||||
cs.ios.frameworks = 'MobileCoreServices', 'CFNetwork'
|
cs.ios.frameworks = 'CoreServices', 'CFNetwork'
|
||||||
|
cs.tvos.library = 'z'
|
||||||
|
cs.tvos.frameworks = 'CoreServices', 'CFNetwork'
|
||||||
cs.osx.library = 'z'
|
cs.osx.library = 'z'
|
||||||
cs.osx.framework = 'SystemConfiguration'
|
cs.osx.framework = 'SystemConfiguration'
|
||||||
end
|
end
|
||||||
@@ -35,6 +38,7 @@ Pod::Spec.new do |s|
|
|||||||
cs.source_files = 'GCDWebDAVServer/*.{h,m}'
|
cs.source_files = 'GCDWebDAVServer/*.{h,m}'
|
||||||
cs.requires_arc = true
|
cs.requires_arc = true
|
||||||
cs.ios.library = 'xml2'
|
cs.ios.library = 'xml2'
|
||||||
|
cs.tvos.library = 'xml2'
|
||||||
cs.osx.library = 'xml2'
|
cs.osx.library = 'xml2'
|
||||||
cs.compiler_flags = '-I$(SDKROOT)/usr/include/libxml2'
|
cs.compiler_flags = '-I$(SDKROOT)/usr/include/libxml2'
|
||||||
end
|
end
|
||||||
@@ -45,5 +49,4 @@ Pod::Spec.new do |s|
|
|||||||
cs.requires_arc = true
|
cs.requires_arc = true
|
||||||
cs.resource = "GCDWebUploader/GCDWebUploader.bundle"
|
cs.resource = "GCDWebUploader/GCDWebUploader.bundle"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1130"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "NO"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "CEE28CD01AE004D800F4023C"
|
||||||
|
BuildableName = "GCDWebServers.framework"
|
||||||
|
BlueprintName = "GCDWebServers (Mac)"
|
||||||
|
ReferencedContainer = "container:GCDWebServer.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
enableAddressSanitizer = "YES">
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "E24039241BA09207000B7089"
|
||||||
|
BuildableName = "Tests.xctest"
|
||||||
|
BlueprintName = "Tests (Mac)"
|
||||||
|
ReferencedContainer = "container:GCDWebServer.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "CEE28CD01AE004D800F4023C"
|
||||||
|
BuildableName = "GCDWebServers.framework"
|
||||||
|
BlueprintName = "GCDWebServers (Mac)"
|
||||||
|
ReferencedContainer = "container:GCDWebServer.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "CEE28CD01AE004D800F4023C"
|
||||||
|
BuildableName = "GCDWebServers.framework"
|
||||||
|
BlueprintName = "GCDWebServers (Mac)"
|
||||||
|
ReferencedContainer = "container:GCDWebServer.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "0610"
|
LastUpgradeVersion = "1130"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
@@ -10,74 +10,73 @@
|
|||||||
buildForTesting = "NO"
|
buildForTesting = "NO"
|
||||||
buildForRunning = "YES"
|
buildForRunning = "YES"
|
||||||
buildForProfiling = "YES"
|
buildForProfiling = "YES"
|
||||||
buildForArchiving = "NO"
|
buildForArchiving = "YES"
|
||||||
buildForAnalyzing = "YES">
|
buildForAnalyzing = "YES">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "8DD76FA90486AB0100D96B5E"
|
BlueprintIdentifier = "CEE28CEE1AE0051F00F4023C"
|
||||||
BuildableName = "GCDWebServer"
|
BuildableName = "GCDWebServers.framework"
|
||||||
BlueprintName = "GCDWebServer (Mac)"
|
BlueprintName = "GCDWebServers (iOS)"
|
||||||
ReferencedContainer = "container:GCDWebServer.xcodeproj">
|
ReferencedContainer = "container:GCDWebServer.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildActionEntry>
|
</BuildActionEntry>
|
||||||
</BuildActionEntries>
|
</BuildActionEntries>
|
||||||
</BuildAction>
|
</BuildAction>
|
||||||
<TestAction
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
buildConfiguration = "Debug">
|
|
||||||
<Testables>
|
|
||||||
</Testables>
|
|
||||||
<MacroExpansion>
|
<MacroExpansion>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "8DD76FA90486AB0100D96B5E"
|
BlueprintIdentifier = "CEE28CEE1AE0051F00F4023C"
|
||||||
BuildableName = "GCDWebServer"
|
BuildableName = "GCDWebServers.framework"
|
||||||
BlueprintName = "GCDWebServer (Mac)"
|
BlueprintName = "GCDWebServers (iOS)"
|
||||||
ReferencedContainer = "container:GCDWebServer.xcodeproj">
|
ReferencedContainer = "container:GCDWebServer.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</MacroExpansion>
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
</Testables>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
buildConfiguration = "Debug"
|
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
allowLocationSimulation = "YES">
|
allowLocationSimulation = "YES">
|
||||||
<BuildableProductRunnable>
|
<MacroExpansion>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "8DD76FA90486AB0100D96B5E"
|
BlueprintIdentifier = "CEE28CEE1AE0051F00F4023C"
|
||||||
BuildableName = "GCDWebServer"
|
BuildableName = "GCDWebServers.framework"
|
||||||
BlueprintName = "GCDWebServer (Mac)"
|
BlueprintName = "GCDWebServers (iOS)"
|
||||||
ReferencedContainer = "container:GCDWebServer.xcodeproj">
|
ReferencedContainer = "container:GCDWebServer.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</MacroExpansion>
|
||||||
<AdditionalOptions>
|
|
||||||
</AdditionalOptions>
|
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
savedToolIdentifier = ""
|
savedToolIdentifier = ""
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
buildConfiguration = "Release"
|
|
||||||
debugDocumentVersioning = "YES">
|
debugDocumentVersioning = "YES">
|
||||||
<BuildableProductRunnable>
|
<MacroExpansion>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "8DD76FA90486AB0100D96B5E"
|
BlueprintIdentifier = "CEE28CEE1AE0051F00F4023C"
|
||||||
BuildableName = "GCDWebServer"
|
BuildableName = "GCDWebServers.framework"
|
||||||
BlueprintName = "GCDWebServer (Mac)"
|
BlueprintName = "GCDWebServers (iOS)"
|
||||||
ReferencedContainer = "container:GCDWebServer.xcodeproj">
|
ReferencedContainer = "container:GCDWebServer.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</MacroExpansion>
|
||||||
</ProfileAction>
|
</ProfileAction>
|
||||||
<AnalyzeAction
|
<AnalyzeAction
|
||||||
buildConfiguration = "Release">
|
buildConfiguration = "Debug">
|
||||||
</AnalyzeAction>
|
</AnalyzeAction>
|
||||||
<ArchiveAction
|
<ArchiveAction
|
||||||
buildConfiguration = "Release"
|
buildConfiguration = "Release"
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1130"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "NO"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "E2DDD18A1BE69404002CE867"
|
||||||
|
BuildableName = "GCDWebServers.framework"
|
||||||
|
BlueprintName = "GCDWebServers (tvOS)"
|
||||||
|
ReferencedContainer = "container:GCDWebServer.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "E2DDD18A1BE69404002CE867"
|
||||||
|
BuildableName = "GCDWebServers.framework"
|
||||||
|
BlueprintName = "GCDWebServers (tvOS)"
|
||||||
|
ReferencedContainer = "container:GCDWebServer.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "E2DDD18A1BE69404002CE867"
|
||||||
|
BuildableName = "GCDWebServers.framework"
|
||||||
|
BlueprintName = "GCDWebServers (tvOS)"
|
||||||
|
ReferencedContainer = "container:GCDWebServer.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "E2DDD18A1BE69404002CE867"
|
||||||
|
BuildableName = "GCDWebServers.framework"
|
||||||
|
BlueprintName = "GCDWebServers (tvOS)"
|
||||||
|
ReferencedContainer = "container:GCDWebServer.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -30,6 +30,8 @@
|
|||||||
#import "GCDWebServerRequest.h"
|
#import "GCDWebServerRequest.h"
|
||||||
#import "GCDWebServerResponse.h"
|
#import "GCDWebServerResponse.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GCDWebServerMatchBlock is called for every handler added to the
|
* The GCDWebServerMatchBlock is called for every handler added to the
|
||||||
* GCDWebServer whenever a new HTTP request has started (i.e. HTTP headers have
|
* GCDWebServer whenever a new HTTP request has started (i.e. HTTP headers have
|
||||||
@@ -40,7 +42,7 @@
|
|||||||
* GCDWebServerRequest instance created with the same basic info.
|
* GCDWebServerRequest instance created with the same basic info.
|
||||||
* Otherwise, it simply returns nil.
|
* Otherwise, it simply returns nil.
|
||||||
*/
|
*/
|
||||||
typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery);
|
typedef GCDWebServerRequest* _Nullable (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary<NSString*, NSString*>* requestHeaders, NSString* urlPath, NSDictionary<NSString*, NSString*>* urlQuery);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GCDWebServerProcessBlock is called after the HTTP request has been fully
|
* The GCDWebServerProcessBlock is called after the HTTP request has been fully
|
||||||
@@ -52,7 +54,7 @@ typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod,
|
|||||||
* recommended to return a GCDWebServerErrorResponse on error so more useful
|
* recommended to return a GCDWebServerErrorResponse on error so more useful
|
||||||
* information can be returned to the client.
|
* information can be returned to the client.
|
||||||
*/
|
*/
|
||||||
typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* request);
|
typedef GCDWebServerResponse* _Nullable (^GCDWebServerProcessBlock)(__kindof GCDWebServerRequest* request);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GCDWebServerAsynchronousProcessBlock works like the GCDWebServerProcessBlock
|
* The GCDWebServerAsynchronousProcessBlock works like the GCDWebServerProcessBlock
|
||||||
@@ -64,8 +66,15 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* r
|
|||||||
* It's however recommended to return a GCDWebServerErrorResponse on error so more
|
* It's however recommended to return a GCDWebServerErrorResponse on error so more
|
||||||
* useful information can be returned to the client.
|
* useful information can be returned to the client.
|
||||||
*/
|
*/
|
||||||
typedef void (^GCDWebServerCompletionBlock)(GCDWebServerResponse* response);
|
typedef void (^GCDWebServerCompletionBlock)(GCDWebServerResponse* _Nullable response);
|
||||||
typedef void (^GCDWebServerAsyncProcessBlock)(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock);
|
typedef void (^GCDWebServerAsyncProcessBlock)(__kindof GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GCDWebServerBuiltInLoggerBlock is used to override the built-in logger at runtime.
|
||||||
|
* The block will be passed the log level and the log message, see setLogLevel for
|
||||||
|
* documentation of the log levels for the built-in logger.
|
||||||
|
*/
|
||||||
|
typedef void (^GCDWebServerBuiltInLoggerBlock)(int level, NSString* _Nonnull message);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The port used by the GCDWebServer (NSNumber / NSUInteger).
|
* The port used by the GCDWebServer (NSNumber / NSUInteger).
|
||||||
@@ -83,6 +92,13 @@ extern NSString* const GCDWebServerOption_Port;
|
|||||||
*/
|
*/
|
||||||
extern NSString* const GCDWebServerOption_BonjourName;
|
extern NSString* const GCDWebServerOption_BonjourName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Bonjour TXT Data used by the GCDWebServer (NSDictionary<NSString, NSString>).
|
||||||
|
*
|
||||||
|
* The default value is nil.
|
||||||
|
*/
|
||||||
|
extern NSString* const GCDWebServerOption_BonjourTXTData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Bonjour service type used by the GCDWebServer (NSString).
|
* The Bonjour service type used by the GCDWebServer (NSString).
|
||||||
*
|
*
|
||||||
@@ -90,14 +106,26 @@ extern NSString* const GCDWebServerOption_BonjourName;
|
|||||||
*/
|
*/
|
||||||
extern NSString* const GCDWebServerOption_BonjourType;
|
extern NSString* const GCDWebServerOption_BonjourType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request a port mapping in the NAT gateway (NSNumber / BOOL).
|
||||||
|
*
|
||||||
|
* This uses the DNSService API under the hood which supports IPv4 mappings only.
|
||||||
|
*
|
||||||
|
* The default value is NO.
|
||||||
|
*
|
||||||
|
* @warning The external port set up by the NAT gateway may be different than
|
||||||
|
* the one used by the GCDWebServer.
|
||||||
|
*/
|
||||||
|
extern NSString* const GCDWebServerOption_RequestNATPortMapping;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only accept HTTP requests coming from localhost i.e. not from the outside
|
* Only accept HTTP requests coming from localhost i.e. not from the outside
|
||||||
* network (NSNumber / BOOL).
|
* network (NSNumber / BOOL).
|
||||||
*
|
*
|
||||||
* The default value is NO.
|
* The default value is NO.
|
||||||
*
|
*
|
||||||
* @warning Bonjour should be disabled if using this option since the server
|
* @warning Bonjour and NAT port mapping should be disabled if using this option
|
||||||
* will not be reachable from the outside network anyway.
|
* since the server will not be reachable from the outside network anyway.
|
||||||
*/
|
*/
|
||||||
extern NSString* const GCDWebServerOption_BindToLocalhost;
|
extern NSString* const GCDWebServerOption_BindToLocalhost;
|
||||||
|
|
||||||
@@ -164,6 +192,15 @@ extern NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET;
|
|||||||
*/
|
*/
|
||||||
extern NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval;
|
extern NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the dispatch queue priority on which server connection will be
|
||||||
|
* run (NSNumber / long).
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The default value is DISPATCH_QUEUE_PRIORITY_DEFAULT.
|
||||||
|
*/
|
||||||
|
extern NSString* const GCDWebServerOption_DispatchQueuePriority;
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -213,9 +250,21 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
/**
|
/**
|
||||||
* This method is called after the Bonjour registration for the server has
|
* This method is called after the Bonjour registration for the server has
|
||||||
* successfully completed.
|
* successfully completed.
|
||||||
|
*
|
||||||
|
* Use the "bonjourServerURL" property to retrieve the Bonjour address of the
|
||||||
|
* server.
|
||||||
*/
|
*/
|
||||||
- (void)webServerDidCompleteBonjourRegistration:(GCDWebServer*)server;
|
- (void)webServerDidCompleteBonjourRegistration:(GCDWebServer*)server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called after the NAT port mapping for the server has been
|
||||||
|
* updated.
|
||||||
|
*
|
||||||
|
* Use the "publicServerURL" property to retrieve the public address of the
|
||||||
|
* server.
|
||||||
|
*/
|
||||||
|
- (void)webServerDidUpdateNATPortMapping:(GCDWebServer*)server;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called when the first GCDWebServerConnection is opened by the
|
* This method is called when the first GCDWebServerConnection is opened by the
|
||||||
* server to serve a series of HTTP requests.
|
* server to serve a series of HTTP requests.
|
||||||
@@ -262,7 +311,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
/**
|
/**
|
||||||
* Sets the delegate for the server.
|
* Sets the delegate for the server.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, assign) id<GCDWebServerDelegate> delegate;
|
@property(nonatomic, weak, nullable) id<GCDWebServerDelegate> delegate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns YES if the server is currently running.
|
* Returns YES if the server is currently running.
|
||||||
@@ -282,7 +331,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
* @warning This property is only valid if the server is running and Bonjour
|
* @warning This property is only valid if the server is running and Bonjour
|
||||||
* registration has successfully completed, which can take up to a few seconds.
|
* registration has successfully completed, which can take up to a few seconds.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, readonly) NSString* bonjourName;
|
@property(nonatomic, readonly, nullable) NSString* bonjourName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the Bonjour service type used by the server.
|
* Returns the Bonjour service type used by the server.
|
||||||
@@ -290,7 +339,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
* @warning This property is only valid if the server is running and Bonjour
|
* @warning This property is only valid if the server is running and Bonjour
|
||||||
* registration has successfully completed, which can take up to a few seconds.
|
* registration has successfully completed, which can take up to a few seconds.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, readonly) NSString* bonjourType;
|
@property(nonatomic, readonly, nullable) NSString* bonjourType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is the designated initializer for the class.
|
* This method is the designated initializer for the class.
|
||||||
@@ -330,7 +379,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
*
|
*
|
||||||
* Returns NO if the server failed to start and sets "error" argument if not NULL.
|
* Returns NO if the server failed to start and sets "error" argument if not NULL.
|
||||||
*/
|
*/
|
||||||
- (BOOL)startWithOptions:(NSDictionary*)options error:(NSError**)error;
|
- (BOOL)startWithOptions:(nullable NSDictionary<NSString*, id>*)options error:(NSError** _Nullable)error;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops the server and prevents it to accepts new HTTP requests.
|
* Stops the server and prevents it to accepts new HTTP requests.
|
||||||
@@ -350,7 +399,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
*
|
*
|
||||||
* @warning This property is only valid if the server is running.
|
* @warning This property is only valid if the server is running.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, readonly) NSURL* serverURL;
|
@property(nonatomic, readonly, nullable) NSURL* serverURL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the server's Bonjour URL.
|
* Returns the server's Bonjour URL.
|
||||||
@@ -360,7 +409,15 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
* Also be aware this property will not automatically update if the Bonjour hostname
|
* Also be aware this property will not automatically update if the Bonjour hostname
|
||||||
* has been dynamically changed after the server started running (this should be rare).
|
* has been dynamically changed after the server started running (this should be rare).
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, readonly) NSURL* bonjourServerURL;
|
@property(nonatomic, readonly, nullable) NSURL* bonjourServerURL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the server's public URL.
|
||||||
|
*
|
||||||
|
* @warning This property is only valid if the server is running and NAT port
|
||||||
|
* mapping is active.
|
||||||
|
*/
|
||||||
|
@property(nonatomic, readonly, nullable) NSURL* publicServerURL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the server on port 8080 (OS X & iOS Simulator) or port 80 (iOS)
|
* Starts the server on port 8080 (OS X & iOS Simulator) or port 80 (iOS)
|
||||||
@@ -377,7 +434,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
*
|
*
|
||||||
* Returns NO if the server failed to start.
|
* Returns NO if the server failed to start.
|
||||||
*/
|
*/
|
||||||
- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name;
|
- (BOOL)startWithPort:(NSUInteger)port bonjourName:(nullable NSString*)name;
|
||||||
|
|
||||||
#if !TARGET_OS_IPHONE
|
#if !TARGET_OS_IPHONE
|
||||||
|
|
||||||
@@ -390,7 +447,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
*
|
*
|
||||||
* @warning This method must be used from the main thread only.
|
* @warning This method must be used from the main thread only.
|
||||||
*/
|
*/
|
||||||
- (BOOL)runWithPort:(NSUInteger)port bonjourName:(NSString*)name;
|
- (BOOL)runWithPort:(NSUInteger)port bonjourName:(nullable NSString*)name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the server synchronously using -startWithOptions: until a SIGTERM or
|
* Runs the server synchronously using -startWithOptions: until a SIGTERM or
|
||||||
@@ -401,7 +458,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
*
|
*
|
||||||
* @warning This method must be used from the main thread only.
|
* @warning This method must be used from the main thread only.
|
||||||
*/
|
*/
|
||||||
- (BOOL)runWithOptions:(NSDictionary*)options error:(NSError**)error;
|
- (BOOL)runWithOptions:(nullable NSDictionary<NSString*, id>*)options error:(NSError** _Nullable)error;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -457,7 +514,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
* Adds a handler to the server to respond to incoming "GET" HTTP requests
|
* Adds a handler to the server to respond to incoming "GET" HTTP requests
|
||||||
* with a specific case-insensitive path with in-memory data.
|
* with a specific case-insensitive path with in-memory data.
|
||||||
*/
|
*/
|
||||||
- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge;
|
- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(nullable NSString*)contentType cacheAge:(NSUInteger)cacheAge;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a handler to the server to respond to incoming "GET" HTTP requests
|
* Adds a handler to the server to respond to incoming "GET" HTTP requests
|
||||||
@@ -474,7 +531,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
* The "indexFilename" argument allows to specify an "index" file name to use
|
* The "indexFilename" argument allows to specify an "index" file name to use
|
||||||
* when the request path corresponds to a directory.
|
* when the request path corresponds to a directory.
|
||||||
*/
|
*/
|
||||||
- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests;
|
- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(nullable NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -489,11 +546,10 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
*
|
*
|
||||||
* Currently supported third-party logging facilities are:
|
* Currently supported third-party logging facilities are:
|
||||||
* - XLFacility (by the same author as GCDWebServer): https://github.com/swisspol/XLFacility
|
* - XLFacility (by the same author as GCDWebServer): https://github.com/swisspol/XLFacility
|
||||||
* - CocoaLumberjack: https://github.com/CocoaLumberjack/CocoaLumberjack
|
|
||||||
*
|
*
|
||||||
* For both the built-in logging facility and CocoaLumberjack, the default
|
* For the built-in logging facility, the default logging level is INFO
|
||||||
* logging level is INFO (or DEBUG if the preprocessor constant "DEBUG"
|
* (or DEBUG if the preprocessor constant "DEBUG" evaluates to non-zero at
|
||||||
* evaluates to non-zero at compile time).
|
* compile time).
|
||||||
*
|
*
|
||||||
* It's possible to have GCDWebServer use a custom logging facility by defining
|
* It's possible to have GCDWebServer use a custom logging facility by defining
|
||||||
* the "__GCDWEBSERVER_LOGGING_HEADER__" preprocessor constant in Xcode build
|
* the "__GCDWEBSERVER_LOGGING_HEADER__" preprocessor constant in Xcode build
|
||||||
@@ -505,12 +561,10 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
* GWS_LOG_INFO(...)
|
* GWS_LOG_INFO(...)
|
||||||
* GWS_LOG_WARNING(...)
|
* GWS_LOG_WARNING(...)
|
||||||
* GWS_LOG_ERROR(...)
|
* GWS_LOG_ERROR(...)
|
||||||
* GWS_LOG_EXCEPTION(__EXCEPTION__)
|
|
||||||
*
|
*
|
||||||
* IMPORTANT: Except for GWS_LOG_EXCEPTION() which gets passed an NSException,
|
* IMPORTANT: These macros must behave like NSLog(). Furthermore the GWS_LOG_DEBUG()
|
||||||
* these macros must behave like NSLog(). Furthermore the GWS_LOG_DEBUG() macro
|
* macro should not do anything unless the preprocessor constant "DEBUG" evaluates
|
||||||
* should not do anything unless the preprocessor constant "DEBUG" evaluates to
|
* to non-zero.
|
||||||
* non-zero.
|
|
||||||
*
|
*
|
||||||
* The logging methods below send log messages to the same logging facility
|
* The logging methods below send log messages to the same logging facility
|
||||||
* used by GCDWebServer. They can be used for consistency wherever you interact
|
* used by GCDWebServer. They can be used for consistency wherever you interact
|
||||||
@@ -530,10 +584,17 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
* INFO = 2
|
* INFO = 2
|
||||||
* WARNING = 3
|
* WARNING = 3
|
||||||
* ERROR = 4
|
* ERROR = 4
|
||||||
* EXCEPTION = 5
|
|
||||||
*/
|
*/
|
||||||
+ (void)setLogLevel:(int)level;
|
+ (void)setLogLevel:(int)level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a logger to be used instead of the built-in logger which logs to stderr.
|
||||||
|
*
|
||||||
|
* IMPORTANT: In order for this override to work, you should not be specifying
|
||||||
|
* a custom logger at compile time with "__GCDWEBSERVER_LOGGING_HEADER__".
|
||||||
|
*/
|
||||||
|
+ (void)setBuiltInLogger:(GCDWebServerBuiltInLoggerBlock)block;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs a message to the logging facility at the VERBOSE level.
|
* Logs a message to the logging facility at the VERBOSE level.
|
||||||
*/
|
*/
|
||||||
@@ -554,11 +615,6 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
*/
|
*/
|
||||||
- (void)logError:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2);
|
- (void)logError:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2);
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs an exception to the logging facility at the EXCEPTION level.
|
|
||||||
*/
|
|
||||||
- (void)logException:(NSException*)exception;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
||||||
@@ -579,8 +635,10 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
*
|
*
|
||||||
* Returns the number of failed tests or -1 if server failed to start.
|
* Returns the number of failed tests or -1 if server failed to start.
|
||||||
*/
|
*/
|
||||||
- (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path;
|
- (NSInteger)runTestsWithOptions:(nullable NSDictionary<NSString*, id>*)options inDirectory:(NSString*)path;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#import <netinet/in.h>
|
#import <netinet/in.h>
|
||||||
|
#import <dns_sd.h>
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
#import "GCDWebServerPrivate.h"
|
||||||
|
|
||||||
@@ -47,9 +48,13 @@
|
|||||||
#define kDefaultPort 8080
|
#define kDefaultPort 8080
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define kBonjourResolutionTimeout 5.0
|
||||||
|
|
||||||
NSString* const GCDWebServerOption_Port = @"Port";
|
NSString* const GCDWebServerOption_Port = @"Port";
|
||||||
NSString* const GCDWebServerOption_BonjourName = @"BonjourName";
|
NSString* const GCDWebServerOption_BonjourName = @"BonjourName";
|
||||||
NSString* const GCDWebServerOption_BonjourType = @"BonjourType";
|
NSString* const GCDWebServerOption_BonjourType = @"BonjourType";
|
||||||
|
NSString* const GCDWebServerOption_BonjourTXTData = @"BonjourTXTData";
|
||||||
|
NSString* const GCDWebServerOption_RequestNATPortMapping = @"RequestNATPortMapping";
|
||||||
NSString* const GCDWebServerOption_BindToLocalhost = @"BindToLocalhost";
|
NSString* const GCDWebServerOption_BindToLocalhost = @"BindToLocalhost";
|
||||||
NSString* const GCDWebServerOption_MaxPendingConnections = @"MaxPendingConnections";
|
NSString* const GCDWebServerOption_MaxPendingConnections = @"MaxPendingConnections";
|
||||||
NSString* const GCDWebServerOption_ServerName = @"ServerName";
|
NSString* const GCDWebServerOption_ServerName = @"ServerName";
|
||||||
@@ -59,6 +64,7 @@ NSString* const GCDWebServerOption_AuthenticationAccounts = @"AuthenticationAcco
|
|||||||
NSString* const GCDWebServerOption_ConnectionClass = @"ConnectionClass";
|
NSString* const GCDWebServerOption_ConnectionClass = @"ConnectionClass";
|
||||||
NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET = @"AutomaticallyMapHEADToGET";
|
NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET = @"AutomaticallyMapHEADToGET";
|
||||||
NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval = @"ConnectedStateCoalescingInterval";
|
NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval = @"ConnectedStateCoalescingInterval";
|
||||||
|
NSString* const GCDWebServerOption_DispatchQueuePriority = @"DispatchQueuePriority";
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
NSString* const GCDWebServerOption_AutomaticallySuspendInBackground = @"AutomaticallySuspendInBackground";
|
NSString* const GCDWebServerOption_AutomaticallySuspendInBackground = @"AutomaticallySuspendInBackground";
|
||||||
#endif
|
#endif
|
||||||
@@ -72,12 +78,6 @@ GCDWebServerLoggingLevel GCDWebServerLogLevel = kGCDWebServerLoggingLevel_Debug;
|
|||||||
#else
|
#else
|
||||||
GCDWebServerLoggingLevel GCDWebServerLogLevel = kGCDWebServerLoggingLevel_Info;
|
GCDWebServerLoggingLevel GCDWebServerLogLevel = kGCDWebServerLoggingLevel_Info;
|
||||||
#endif
|
#endif
|
||||||
#elif defined(__GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__)
|
|
||||||
#if DEBUG
|
|
||||||
int GCDWebServerLogLevel = LOG_LEVEL_DEBUG;
|
|
||||||
#else
|
|
||||||
int GCDWebServerLogLevel = LOG_LEVEL_INFO;
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !TARGET_OS_IPHONE
|
#if !TARGET_OS_IPHONE
|
||||||
@@ -86,20 +86,26 @@ static BOOL _run;
|
|||||||
|
|
||||||
#ifdef __GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__
|
#ifdef __GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__
|
||||||
|
|
||||||
|
static GCDWebServerBuiltInLoggerBlock _builtInLoggerBlock;
|
||||||
|
|
||||||
void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* format, ...) {
|
void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* format, ...) {
|
||||||
static const char* levelNames[] = {"DEBUG", "VERBOSE", "INFO", "WARNING", "ERROR", "EXCEPTION"};
|
static const char* levelNames[] = {"DEBUG", "VERBOSE", "INFO", "WARNING", "ERROR"};
|
||||||
static int enableLogging = -1;
|
static int enableLogging = -1;
|
||||||
if (enableLogging < 0) {
|
if (enableLogging < 0) {
|
||||||
enableLogging = (isatty(STDERR_FILENO) ? 1 : 0);
|
enableLogging = (isatty(STDERR_FILENO) ? 1 : 0);
|
||||||
}
|
}
|
||||||
if (enableLogging) {
|
if (_builtInLoggerBlock || enableLogging) {
|
||||||
va_list arguments;
|
va_list arguments;
|
||||||
va_start(arguments, format);
|
va_start(arguments, format);
|
||||||
NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments];
|
NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments];
|
||||||
va_end(arguments);
|
va_end(arguments);
|
||||||
|
if (_builtInLoggerBlock) {
|
||||||
|
_builtInLoggerBlock(level, message);
|
||||||
|
} else {
|
||||||
fprintf(stderr, "[%s] %s\n", levelNames[level], [message UTF8String]);
|
fprintf(stderr, "[%s] %s\n", levelNames[level], [message UTF8String]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -127,18 +133,9 @@ static void _ExecuteMainThreadRunLoopSources() {
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@interface GCDWebServerHandler () {
|
|
||||||
@private
|
|
||||||
GCDWebServerMatchBlock _matchBlock;
|
|
||||||
GCDWebServerAsyncProcessBlock _asyncProcessBlock;
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerHandler
|
@implementation GCDWebServerHandler
|
||||||
|
|
||||||
@synthesize matchBlock=_matchBlock, asyncProcessBlock=_asyncProcessBlock;
|
- (instancetype)initWithMatchBlock:(GCDWebServerMatchBlock _Nonnull)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock _Nonnull)processBlock {
|
||||||
|
|
||||||
- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock {
|
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
_matchBlock = [matchBlock copy];
|
_matchBlock = [matchBlock copy];
|
||||||
_asyncProcessBlock = [processBlock copy];
|
_asyncProcessBlock = [processBlock copy];
|
||||||
@@ -148,29 +145,29 @@ static void _ExecuteMainThreadRunLoopSources() {
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServer () {
|
@implementation GCDWebServer {
|
||||||
@private
|
|
||||||
id<GCDWebServerDelegate> __unsafe_unretained _delegate;
|
|
||||||
dispatch_queue_t _syncQueue;
|
dispatch_queue_t _syncQueue;
|
||||||
dispatch_group_t _sourceGroup;
|
dispatch_group_t _sourceGroup;
|
||||||
NSMutableArray* _handlers;
|
NSMutableArray<GCDWebServerHandler*>* _handlers;
|
||||||
NSInteger _activeConnections; // Accessed through _syncQueue only
|
NSInteger _activeConnections; // Accessed through _syncQueue only
|
||||||
BOOL _connected; // Accessed on main thread only
|
BOOL _connected; // Accessed on main thread only
|
||||||
CFRunLoopTimerRef _disconnectTimer; // Accessed on main thread only
|
CFRunLoopTimerRef _disconnectTimer; // Accessed on main thread only
|
||||||
|
|
||||||
NSDictionary* _options;
|
NSDictionary<NSString*, id>* _options;
|
||||||
NSString* _serverName;
|
NSMutableDictionary<NSString*, NSString*>* _authenticationBasicAccounts;
|
||||||
NSString* _authenticationRealm;
|
NSMutableDictionary<NSString*, NSString*>* _authenticationDigestAccounts;
|
||||||
NSMutableDictionary* _authenticationBasicAccounts;
|
|
||||||
NSMutableDictionary* _authenticationDigestAccounts;
|
|
||||||
Class _connectionClass;
|
Class _connectionClass;
|
||||||
BOOL _mapHEADToGET;
|
|
||||||
CFTimeInterval _disconnectDelay;
|
CFTimeInterval _disconnectDelay;
|
||||||
NSUInteger _port;
|
|
||||||
dispatch_source_t _source4;
|
dispatch_source_t _source4;
|
||||||
dispatch_source_t _source6;
|
dispatch_source_t _source6;
|
||||||
CFNetServiceRef _registrationService;
|
CFNetServiceRef _registrationService;
|
||||||
CFNetServiceRef _resolutionService;
|
CFNetServiceRef _resolutionService;
|
||||||
|
DNSServiceRef _dnsService;
|
||||||
|
CFSocketRef _dnsSocket;
|
||||||
|
CFRunLoopSourceRef _dnsSource;
|
||||||
|
NSString* _dnsAddress;
|
||||||
|
NSUInteger _dnsPort;
|
||||||
|
BOOL _bindToLocalhost;
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
BOOL _suspendInBackground;
|
BOOL _suspendInBackground;
|
||||||
UIBackgroundTaskIdentifier _backgroundTask;
|
UIBackgroundTaskIdentifier _backgroundTask;
|
||||||
@@ -179,13 +176,6 @@ static void _ExecuteMainThreadRunLoopSources() {
|
|||||||
BOOL _recording;
|
BOOL _recording;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServer
|
|
||||||
|
|
||||||
@synthesize delegate=_delegate, handlers=_handlers, port=_port, serverName=_serverName, authenticationRealm=_authenticationRealm,
|
|
||||||
authenticationBasicAccounts=_authenticationBasicAccounts, authenticationDigestAccounts=_authenticationDigestAccounts,
|
|
||||||
shouldAutomaticallyMapHEADToGET=_mapHEADToGET;
|
|
||||||
|
|
||||||
+ (void)initialize {
|
+ (void)initialize {
|
||||||
GCDWebServerInitializeFunctions();
|
GCDWebServerInitializeFunctions();
|
||||||
@@ -223,10 +213,8 @@ static void _ExecuteMainThreadRunLoopSources() {
|
|||||||
if (_backgroundTask == UIBackgroundTaskInvalid) {
|
if (_backgroundTask == UIBackgroundTaskInvalid) {
|
||||||
GWS_LOG_DEBUG(@"Did start background task");
|
GWS_LOG_DEBUG(@"Did start background task");
|
||||||
_backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
|
_backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
|
||||||
|
|
||||||
GWS_LOG_WARNING(@"Application is being suspended while %@ is still connected", [self class]);
|
GWS_LOG_WARNING(@"Application is being suspended while %@ is still connected", [self class]);
|
||||||
[self _endBackgroundTask];
|
[self _endBackgroundTask];
|
||||||
|
|
||||||
}];
|
}];
|
||||||
} else {
|
} else {
|
||||||
GWS_DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
@@ -243,7 +231,9 @@ static void _ExecuteMainThreadRunLoopSources() {
|
|||||||
GWS_LOG_DEBUG(@"Did connect");
|
GWS_LOG_DEBUG(@"Did connect");
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
|
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
|
||||||
[self _startBackgroundTask];
|
[self _startBackgroundTask];
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if ([_delegate respondsToSelector:@selector(webServerDidConnect:)]) {
|
if ([_delegate respondsToSelector:@selector(webServerDidConnect:)]) {
|
||||||
@@ -253,22 +243,20 @@ static void _ExecuteMainThreadRunLoopSources() {
|
|||||||
|
|
||||||
- (void)willStartConnection:(GCDWebServerConnection*)connection {
|
- (void)willStartConnection:(GCDWebServerConnection*)connection {
|
||||||
dispatch_sync(_syncQueue, ^{
|
dispatch_sync(_syncQueue, ^{
|
||||||
|
GWS_DCHECK(self->_activeConnections >= 0);
|
||||||
GWS_DCHECK(_activeConnections >= 0);
|
if (self->_activeConnections == 0) {
|
||||||
if (_activeConnections == 0) {
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
if (_disconnectTimer) {
|
if (self->_disconnectTimer) {
|
||||||
CFRunLoopTimerInvalidate(_disconnectTimer);
|
CFRunLoopTimerInvalidate(self->_disconnectTimer);
|
||||||
CFRelease(_disconnectTimer);
|
CFRelease(self->_disconnectTimer);
|
||||||
_disconnectTimer = NULL;
|
self->_disconnectTimer = NULL;
|
||||||
}
|
}
|
||||||
if (_connected == NO) {
|
if (self->_connected == NO) {
|
||||||
[self _didConnect];
|
[self _didConnect];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
_activeConnections += 1;
|
self->_activeConnections += 1;
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,22 +295,22 @@ static void _ExecuteMainThreadRunLoopSources() {
|
|||||||
|
|
||||||
- (void)didEndConnection:(GCDWebServerConnection*)connection {
|
- (void)didEndConnection:(GCDWebServerConnection*)connection {
|
||||||
dispatch_sync(_syncQueue, ^{
|
dispatch_sync(_syncQueue, ^{
|
||||||
GWS_DCHECK(_activeConnections > 0);
|
GWS_DCHECK(self->_activeConnections > 0);
|
||||||
_activeConnections -= 1;
|
self->_activeConnections -= 1;
|
||||||
if (_activeConnections == 0) {
|
if (self->_activeConnections == 0) {
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
if ((_disconnectDelay > 0.0) && (_source4 != NULL)) {
|
if ((self->_disconnectDelay > 0.0) && (self->_source4 != NULL)) {
|
||||||
if (_disconnectTimer) {
|
if (self->_disconnectTimer) {
|
||||||
CFRunLoopTimerInvalidate(_disconnectTimer);
|
CFRunLoopTimerInvalidate(self->_disconnectTimer);
|
||||||
CFRelease(_disconnectTimer);
|
CFRelease(self->_disconnectTimer);
|
||||||
}
|
}
|
||||||
_disconnectTimer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + _disconnectDelay, 0.0, 0, 0, ^(CFRunLoopTimerRef timer) {
|
self->_disconnectTimer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + self->_disconnectDelay, 0.0, 0, 0, ^(CFRunLoopTimerRef timer) {
|
||||||
GWS_DCHECK([NSThread isMainThread]);
|
GWS_DCHECK([NSThread isMainThread]);
|
||||||
[self _didDisconnect];
|
[self _didDisconnect];
|
||||||
CFRelease(_disconnectTimer);
|
CFRelease(self->_disconnectTimer);
|
||||||
_disconnectTimer = NULL;
|
self->_disconnectTimer = NULL;
|
||||||
});
|
});
|
||||||
CFRunLoopAddTimer(CFRunLoopGetMain(), _disconnectTimer, kCFRunLoopCommonModes);
|
CFRunLoopAddTimer(CFRunLoopGetMain(), self->_disconnectTimer, kCFRunLoopCommonModes);
|
||||||
} else {
|
} else {
|
||||||
[self _didDisconnect];
|
[self _didDisconnect];
|
||||||
}
|
}
|
||||||
@@ -342,7 +330,8 @@ static void _ExecuteMainThreadRunLoopSources() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock {
|
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock {
|
||||||
[self addHandlerWithMatchBlock:matchBlock asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
|
[self addHandlerWithMatchBlock:matchBlock
|
||||||
|
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
|
||||||
completionBlock(processBlock(request));
|
completionBlock(processBlock(request));
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
@@ -366,7 +355,10 @@ static void _NetServiceRegisterCallBack(CFNetServiceRef service, CFStreamError*
|
|||||||
} else {
|
} else {
|
||||||
GCDWebServer* server = (__bridge GCDWebServer*)info;
|
GCDWebServer* server = (__bridge GCDWebServer*)info;
|
||||||
GWS_LOG_VERBOSE(@"Bonjour registration complete for %@", [server class]);
|
GWS_LOG_VERBOSE(@"Bonjour registration complete for %@", [server class]);
|
||||||
CFNetServiceResolveWithTimeout(server->_resolutionService, 1.0, NULL);
|
if (!CFNetServiceResolveWithTimeout(server->_resolutionService, kBonjourResolutionTimeout, NULL)) {
|
||||||
|
GWS_LOG_ERROR(@"Failed starting Bonjour resolution");
|
||||||
|
GWS_DNOT_REACHED();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -380,7 +372,7 @@ static void _NetServiceResolveCallBack(CFNetServiceRef service, CFStreamError* e
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
GCDWebServer* server = (__bridge GCDWebServer*)info;
|
GCDWebServer* server = (__bridge GCDWebServer*)info;
|
||||||
GWS_LOG_INFO(@"%@ now reachable at %@", [server class], server.bonjourServerURL);
|
GWS_LOG_INFO(@"%@ now locally reachable at %@", [server class], server.bonjourServerURL);
|
||||||
if ([server.delegate respondsToSelector:@selector(webServerDidCompleteBonjourRegistration:)]) {
|
if ([server.delegate respondsToSelector:@selector(webServerDidCompleteBonjourRegistration:)]) {
|
||||||
[server.delegate webServerDidCompleteBonjourRegistration:server];
|
[server.delegate webServerDidCompleteBonjourRegistration:server];
|
||||||
}
|
}
|
||||||
@@ -388,19 +380,56 @@ static void _NetServiceResolveCallBack(CFNetServiceRef service, CFStreamError* e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline id _GetOption(NSDictionary* options, NSString* key, id defaultValue) {
|
static void _DNSServiceCallBack(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, uint32_t externalAddress, DNSServiceProtocol protocol, uint16_t internalPort, uint16_t externalPort, uint32_t ttl, void* context) {
|
||||||
|
GWS_DCHECK([NSThread isMainThread]);
|
||||||
|
@autoreleasepool {
|
||||||
|
GCDWebServer* server = (__bridge GCDWebServer*)context;
|
||||||
|
if ((errorCode == kDNSServiceErr_NoError) || (errorCode == kDNSServiceErr_DoubleNAT)) {
|
||||||
|
struct sockaddr_in addr4;
|
||||||
|
bzero(&addr4, sizeof(addr4));
|
||||||
|
addr4.sin_len = sizeof(addr4);
|
||||||
|
addr4.sin_family = AF_INET;
|
||||||
|
addr4.sin_addr.s_addr = externalAddress; // Already in network byte order
|
||||||
|
server->_dnsAddress = GCDWebServerStringFromSockAddr((const struct sockaddr*)&addr4, NO);
|
||||||
|
server->_dnsPort = ntohs(externalPort);
|
||||||
|
GWS_LOG_INFO(@"%@ now publicly reachable at %@", [server class], server.publicServerURL);
|
||||||
|
} else {
|
||||||
|
GWS_LOG_ERROR(@"DNS service error %i", errorCode);
|
||||||
|
server->_dnsAddress = nil;
|
||||||
|
server->_dnsPort = 0;
|
||||||
|
}
|
||||||
|
if ([server.delegate respondsToSelector:@selector(webServerDidUpdateNATPortMapping:)]) {
|
||||||
|
[server.delegate webServerDidUpdateNATPortMapping:server];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _SocketCallBack(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void* data, void* info) {
|
||||||
|
GWS_DCHECK([NSThread isMainThread]);
|
||||||
|
@autoreleasepool {
|
||||||
|
GCDWebServer* server = (__bridge GCDWebServer*)info;
|
||||||
|
DNSServiceErrorType status = DNSServiceProcessResult(server->_dnsService);
|
||||||
|
if (status != kDNSServiceErr_NoError) {
|
||||||
|
GWS_LOG_ERROR(@"DNS service error %i", status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline id _GetOption(NSDictionary<NSString*, id>* options, NSString* key, id defaultValue) {
|
||||||
id value = [options objectForKey:key];
|
id value = [options objectForKey:key];
|
||||||
return value ? value : defaultValue;
|
return value ? value : defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline NSString* _EncodeBase64(NSString* string) {
|
static inline NSString* _EncodeBase64(NSString* string) {
|
||||||
NSData* data = [string dataUsingEncoding:NSUTF8StringEncoding];
|
NSData* data = [string dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
#if (TARGET_OS_IPHONE && !(__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0)) || (!TARGET_OS_IPHONE && !(__MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9))
|
#if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9)
|
||||||
if (![data respondsToSelector:@selector(base64EncodedDataWithOptions:)]) {
|
|
||||||
return [data base64Encoding];
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return [[NSString alloc] initWithData:[data base64EncodedDataWithOptions:0] encoding:NSASCIIStringEncoding];
|
return [[NSString alloc] initWithData:[data base64EncodedDataWithOptions:0] encoding:NSASCIIStringEncoding];
|
||||||
|
#else
|
||||||
|
if (@available(macOS 10.9, *)) {
|
||||||
|
return [[NSString alloc] initWithData:[data base64EncodedDataWithOptions:0] encoding:NSASCIIStringEncoding];
|
||||||
|
}
|
||||||
|
return [data base64Encoding];
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
- (int)_createListeningSocket:(BOOL)useIPv6
|
- (int)_createListeningSocket:(BOOL)useIPv6
|
||||||
@@ -443,9 +472,8 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
|
|
||||||
- (dispatch_source_t)_createDispatchSourceWithListeningSocket:(int)listeningSocket isIPv6:(BOOL)isIPv6 {
|
- (dispatch_source_t)_createDispatchSourceWithListeningSocket:(int)listeningSocket isIPv6:(BOOL)isIPv6 {
|
||||||
dispatch_group_enter(_sourceGroup);
|
dispatch_group_enter(_sourceGroup);
|
||||||
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, kGCDWebServerGCDQueue);
|
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, dispatch_get_global_queue(_dispatchQueuePriority, 0));
|
||||||
dispatch_source_set_cancel_handler(source, ^{
|
dispatch_source_set_cancel_handler(source, ^{
|
||||||
|
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
int result = close(listeningSocket);
|
int result = close(listeningSocket);
|
||||||
if (result != 0) {
|
if (result != 0) {
|
||||||
@@ -454,24 +482,22 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
GWS_LOG_DEBUG(@"Did close %s listening socket %i", isIPv6 ? "IPv6" : "IPv4", listeningSocket);
|
GWS_LOG_DEBUG(@"Did close %s listening socket %i", isIPv6 ? "IPv6" : "IPv4", listeningSocket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dispatch_group_leave(_sourceGroup);
|
dispatch_group_leave(self->_sourceGroup);
|
||||||
|
|
||||||
});
|
});
|
||||||
dispatch_source_set_event_handler(source, ^{
|
dispatch_source_set_event_handler(source, ^{
|
||||||
|
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
struct sockaddr remoteSockAddr;
|
struct sockaddr_storage remoteSockAddr;
|
||||||
socklen_t remoteAddrLen = sizeof(remoteSockAddr);
|
socklen_t remoteAddrLen = sizeof(remoteSockAddr);
|
||||||
int socket = accept(listeningSocket, &remoteSockAddr, &remoteAddrLen);
|
int socket = accept(listeningSocket, (struct sockaddr*)&remoteSockAddr, &remoteAddrLen);
|
||||||
if (socket > 0) {
|
if (socket > 0) {
|
||||||
NSData* remoteAddress = [NSData dataWithBytes:&remoteSockAddr length:remoteAddrLen];
|
NSData* remoteAddress = [NSData dataWithBytes:&remoteSockAddr length:remoteAddrLen];
|
||||||
|
|
||||||
struct sockaddr localSockAddr;
|
struct sockaddr_storage localSockAddr;
|
||||||
socklen_t localAddrLen = sizeof(localSockAddr);
|
socklen_t localAddrLen = sizeof(localSockAddr);
|
||||||
NSData* localAddress = nil;
|
NSData* localAddress = nil;
|
||||||
if (getsockname(socket, &localSockAddr, &localAddrLen) == 0) {
|
if (getsockname(socket, (struct sockaddr*)&localSockAddr, &localAddrLen) == 0) {
|
||||||
localAddress = [NSData dataWithBytes:&localSockAddr length:localAddrLen];
|
localAddress = [NSData dataWithBytes:&localSockAddr length:localAddrLen];
|
||||||
GWS_DCHECK((!isIPv6 && localSockAddr.sa_family == AF_INET) || (isIPv6 && localSockAddr.sa_family == AF_INET6));
|
GWS_DCHECK((!isIPv6 && localSockAddr.ss_family == AF_INET) || (isIPv6 && localSockAddr.ss_family == AF_INET6));
|
||||||
} else {
|
} else {
|
||||||
GWS_DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
}
|
}
|
||||||
@@ -479,13 +505,12 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
int noSigPipe = 1;
|
int noSigPipe = 1;
|
||||||
setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)); // Make sure this socket cannot generate SIG_PIPE
|
setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)); // Make sure this socket cannot generate SIG_PIPE
|
||||||
|
|
||||||
GCDWebServerConnection* connection = [[_connectionClass alloc] initWithServer:self localAddress:localAddress remoteAddress:remoteAddress socket:socket]; // Connection will automatically retain itself while opened
|
GCDWebServerConnection* connection = [(GCDWebServerConnection*)[self->_connectionClass alloc] initWithServer:self localAddress:localAddress remoteAddress:remoteAddress socket:socket]; // Connection will automatically retain itself while opened
|
||||||
[connection self]; // Prevent compiler from complaining about unused variable / useless statement
|
[connection self]; // Prevent compiler from complaining about unused variable / useless statement
|
||||||
} else {
|
} else {
|
||||||
GWS_LOG_ERROR(@"Failed accepting %s socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
|
GWS_LOG_ERROR(@"Failed accepting %s socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
@@ -493,9 +518,9 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
- (BOOL)_start:(NSError**)error {
|
- (BOOL)_start:(NSError**)error {
|
||||||
GWS_DCHECK(_source4 == NULL);
|
GWS_DCHECK(_source4 == NULL);
|
||||||
|
|
||||||
NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue];
|
NSUInteger port = [(NSNumber*)_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue];
|
||||||
BOOL bindToLocalhost = [_GetOption(_options, GCDWebServerOption_BindToLocalhost, @NO) boolValue];
|
BOOL bindToLocalhost = [(NSNumber*)_GetOption(_options, GCDWebServerOption_BindToLocalhost, @NO) boolValue];
|
||||||
NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue];
|
NSUInteger maxPendingConnections = [(NSNumber*)_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue];
|
||||||
|
|
||||||
struct sockaddr_in addr4;
|
struct sockaddr_in addr4;
|
||||||
bzero(&addr4, sizeof(addr4));
|
bzero(&addr4, sizeof(addr4));
|
||||||
@@ -508,11 +533,10 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
if (port == 0) {
|
if (port == 0) {
|
||||||
struct sockaddr addr;
|
struct sockaddr_in addr;
|
||||||
socklen_t addrlen = sizeof(addr);
|
socklen_t addrlen = sizeof(addr);
|
||||||
if (getsockname(listeningSocket4, &addr, &addrlen) == 0) {
|
if (getsockname(listeningSocket4, (struct sockaddr*)&addr, &addrlen) == 0) {
|
||||||
struct sockaddr_in* sockaddr = (struct sockaddr_in*)&addr;
|
port = ntohs(addr.sin_port);
|
||||||
port = ntohs(sockaddr->sin_port);
|
|
||||||
} else {
|
} else {
|
||||||
GWS_LOG_ERROR(@"Failed retrieving socket address: %s (%i)", strerror(errno), errno);
|
GWS_LOG_ERROR(@"Failed retrieving socket address: %s (%i)", strerror(errno), errno);
|
||||||
}
|
}
|
||||||
@@ -530,30 +554,32 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
_serverName = [_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy];
|
_serverName = [(NSString*)_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy];
|
||||||
NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil);
|
NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil);
|
||||||
if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) {
|
if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) {
|
||||||
_authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
|
_authenticationRealm = [(NSString*)_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
|
||||||
_authenticationBasicAccounts = [[NSMutableDictionary alloc] init];
|
_authenticationBasicAccounts = [[NSMutableDictionary alloc] init];
|
||||||
NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
|
NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
|
||||||
[accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
|
[accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
|
||||||
[_authenticationBasicAccounts setObject:_EncodeBase64([NSString stringWithFormat:@"%@:%@", username, password]) forKey:username];
|
[self->_authenticationBasicAccounts setObject:_EncodeBase64([NSString stringWithFormat:@"%@:%@", username, password]) forKey:username];
|
||||||
}];
|
}];
|
||||||
} else if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_DigestAccess]) {
|
} else if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_DigestAccess]) {
|
||||||
_authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
|
_authenticationRealm = [(NSString*)_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
|
||||||
_authenticationDigestAccounts = [[NSMutableDictionary alloc] init];
|
_authenticationDigestAccounts = [[NSMutableDictionary alloc] init];
|
||||||
NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
|
NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
|
||||||
[accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
|
[accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
|
||||||
[_authenticationDigestAccounts setObject:GCDWebServerComputeMD5Digest(@"%@:%@:%@", username, _authenticationRealm, password) forKey:username];
|
[self->_authenticationDigestAccounts setObject:GCDWebServerComputeMD5Digest(@"%@:%@:%@", username, self->_authenticationRealm, password) forKey:username];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
_connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]);
|
_connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]);
|
||||||
_mapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue];
|
_shouldAutomaticallyMapHEADToGET = [(NSNumber*)_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue];
|
||||||
_disconnectDelay = [_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue];
|
_disconnectDelay = [(NSNumber*)_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue];
|
||||||
|
_dispatchQueuePriority = [(NSNumber*)_GetOption(_options, GCDWebServerOption_DispatchQueuePriority, @(DISPATCH_QUEUE_PRIORITY_DEFAULT)) longValue];
|
||||||
|
|
||||||
_source4 = [self _createDispatchSourceWithListeningSocket:listeningSocket4 isIPv6:NO];
|
_source4 = [self _createDispatchSourceWithListeningSocket:listeningSocket4 isIPv6:NO];
|
||||||
_source6 = [self _createDispatchSourceWithListeningSocket:listeningSocket6 isIPv6:YES];
|
_source6 = [self _createDispatchSourceWithListeningSocket:listeningSocket6 isIPv6:YES];
|
||||||
_port = port;
|
_port = port;
|
||||||
|
_bindToLocalhost = bindToLocalhost;
|
||||||
|
|
||||||
NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, nil);
|
NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, nil);
|
||||||
NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp");
|
NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp");
|
||||||
@@ -565,15 +591,63 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
CFNetServiceSetClient(_registrationService, _NetServiceRegisterCallBack, &context);
|
CFNetServiceSetClient(_registrationService, _NetServiceRegisterCallBack, &context);
|
||||||
CFNetServiceScheduleWithRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
|
CFNetServiceScheduleWithRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
|
||||||
CFStreamError streamError = {0};
|
CFStreamError streamError = {0};
|
||||||
|
|
||||||
|
NSDictionary* txtDataDictionary = _GetOption(_options, GCDWebServerOption_BonjourTXTData, nil);
|
||||||
|
if (txtDataDictionary != nil) {
|
||||||
|
NSUInteger count = txtDataDictionary.count;
|
||||||
|
CFStringRef keys[count];
|
||||||
|
CFStringRef values[count];
|
||||||
|
NSUInteger index = 0;
|
||||||
|
for (NSString *key in txtDataDictionary) {
|
||||||
|
NSString *value = txtDataDictionary[key];
|
||||||
|
keys[index] = (__bridge CFStringRef)(key);
|
||||||
|
values[index] = (__bridge CFStringRef)(value);
|
||||||
|
index ++;
|
||||||
|
}
|
||||||
|
CFDictionaryRef txtDictionary = CFDictionaryCreate(CFAllocatorGetDefault(), (void *)keys, (void *)values, count, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||||
|
if (txtDictionary != NULL) {
|
||||||
|
CFDataRef txtData = CFNetServiceCreateTXTDataWithDictionary(nil, txtDictionary);
|
||||||
|
Boolean setTXTDataResult = CFNetServiceSetTXTData(_registrationService, txtData);
|
||||||
|
if (!setTXTDataResult) {
|
||||||
|
GWS_LOG_ERROR(@"Failed setting TXTData");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CFNetServiceRegisterWithOptions(_registrationService, 0, &streamError);
|
CFNetServiceRegisterWithOptions(_registrationService, 0, &streamError);
|
||||||
|
|
||||||
_resolutionService = CFNetServiceCreateCopy(kCFAllocatorDefault, _registrationService);
|
_resolutionService = CFNetServiceCreateCopy(kCFAllocatorDefault, _registrationService);
|
||||||
if (_resolutionService) {
|
if (_resolutionService) {
|
||||||
CFNetServiceSetClient(_resolutionService, _NetServiceResolveCallBack, &context);
|
CFNetServiceSetClient(_resolutionService, _NetServiceResolveCallBack, &context);
|
||||||
CFNetServiceScheduleWithRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
|
CFNetServiceScheduleWithRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
|
||||||
|
} else {
|
||||||
|
GWS_LOG_ERROR(@"Failed creating CFNetService for resolution");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
GWS_LOG_ERROR(@"Failed creating CFNetService");
|
GWS_LOG_ERROR(@"Failed creating CFNetService for registration");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([(NSNumber*)_GetOption(_options, GCDWebServerOption_RequestNATPortMapping, @NO) boolValue]) {
|
||||||
|
DNSServiceErrorType status = DNSServiceNATPortMappingCreate(&_dnsService, 0, 0, kDNSServiceProtocol_TCP, htons(port), htons(port), 0, _DNSServiceCallBack, (__bridge void*)self);
|
||||||
|
if (status == kDNSServiceErr_NoError) {
|
||||||
|
CFSocketContext context = {0, (__bridge void*)self, NULL, NULL, NULL};
|
||||||
|
_dnsSocket = CFSocketCreateWithNative(kCFAllocatorDefault, DNSServiceRefSockFD(_dnsService), kCFSocketReadCallBack, _SocketCallBack, &context);
|
||||||
|
if (_dnsSocket) {
|
||||||
|
CFSocketSetSocketFlags(_dnsSocket, CFSocketGetSocketFlags(_dnsSocket) & ~kCFSocketCloseOnInvalidate);
|
||||||
|
_dnsSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _dnsSocket, 0);
|
||||||
|
if (_dnsSource) {
|
||||||
|
CFRunLoopAddSource(CFRunLoopGetMain(), _dnsSource, kCFRunLoopCommonModes);
|
||||||
|
} else {
|
||||||
|
GWS_LOG_ERROR(@"Failed creating CFRunLoopSource");
|
||||||
|
GWS_DNOT_REACHED();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GWS_LOG_ERROR(@"Failed creating CFSocket");
|
||||||
|
GWS_DNOT_REACHED();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GWS_LOG_ERROR(@"Failed creating NAT port mapping (%i)", status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,7 +656,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
GWS_LOG_INFO(@"%@ started on port %i and reachable at %@", [self class], (int)_port, self.serverURL);
|
GWS_LOG_INFO(@"%@ started on port %i and reachable at %@", [self class], (int)_port, self.serverURL);
|
||||||
if ([_delegate respondsToSelector:@selector(webServerDidStart:)]) {
|
if ([_delegate respondsToSelector:@selector(webServerDidStart:)]) {
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
[_delegate webServerDidStart:self];
|
[self->_delegate webServerDidStart:self];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,6 +666,22 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
- (void)_stop {
|
- (void)_stop {
|
||||||
GWS_DCHECK(_source4 != NULL);
|
GWS_DCHECK(_source4 != NULL);
|
||||||
|
|
||||||
|
if (_dnsService) {
|
||||||
|
_dnsAddress = nil;
|
||||||
|
_dnsPort = 0;
|
||||||
|
if (_dnsSource) {
|
||||||
|
CFRunLoopSourceInvalidate(_dnsSource);
|
||||||
|
CFRelease(_dnsSource);
|
||||||
|
_dnsSource = NULL;
|
||||||
|
}
|
||||||
|
if (_dnsSocket) {
|
||||||
|
CFRelease(_dnsSocket);
|
||||||
|
_dnsSocket = NULL;
|
||||||
|
}
|
||||||
|
DNSServiceRefDeallocate(_dnsService);
|
||||||
|
_dnsService = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (_registrationService) {
|
if (_registrationService) {
|
||||||
if (_resolutionService) {
|
if (_resolutionService) {
|
||||||
CFNetServiceUnscheduleFromRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
|
CFNetServiceUnscheduleFromRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
|
||||||
@@ -619,6 +709,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
#endif
|
#endif
|
||||||
_source4 = NULL;
|
_source4 = NULL;
|
||||||
_port = 0;
|
_port = 0;
|
||||||
|
_bindToLocalhost = NO;
|
||||||
|
|
||||||
_serverName = nil;
|
_serverName = nil;
|
||||||
_authenticationRealm = nil;
|
_authenticationRealm = nil;
|
||||||
@@ -626,10 +717,10 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
_authenticationDigestAccounts = nil;
|
_authenticationDigestAccounts = nil;
|
||||||
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
if (_disconnectTimer) {
|
if (self->_disconnectTimer) {
|
||||||
CFRunLoopTimerInvalidate(_disconnectTimer);
|
CFRunLoopTimerInvalidate(self->_disconnectTimer);
|
||||||
CFRelease(_disconnectTimer);
|
CFRelease(self->_disconnectTimer);
|
||||||
_disconnectTimer = NULL;
|
self->_disconnectTimer = NULL;
|
||||||
[self _didDisconnect];
|
[self _didDisconnect];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -637,7 +728,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
GWS_LOG_INFO(@"%@ stopped", [self class]);
|
GWS_LOG_INFO(@"%@ stopped", [self class]);
|
||||||
if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) {
|
if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) {
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
[_delegate webServerDidStop:self];
|
[self->_delegate webServerDidStop:self];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -662,11 +753,11 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
- (BOOL)startWithOptions:(NSDictionary*)options error:(NSError**)error {
|
- (BOOL)startWithOptions:(NSDictionary<NSString*, id>*)options error:(NSError**)error {
|
||||||
if (_options == nil) {
|
if (_options == nil) {
|
||||||
_options = [options copy];
|
_options = options ? [options copy] : @{};
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
_suspendInBackground = [_GetOption(_options, GCDWebServerOption_AutomaticallySuspendInBackground, @YES) boolValue];
|
_suspendInBackground = [(NSNumber*)_GetOption(_options, GCDWebServerOption_AutomaticallySuspendInBackground, @YES) boolValue];
|
||||||
if (((_suspendInBackground == NO) || ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground)) && ![self _start:error])
|
if (((_suspendInBackground == NO) || ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground)) && ![self _start:error])
|
||||||
#else
|
#else
|
||||||
if (![self _start:error])
|
if (![self _start:error])
|
||||||
@@ -715,7 +806,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
|
|
||||||
- (NSURL*)serverURL {
|
- (NSURL*)serverURL {
|
||||||
if (_source4) {
|
if (_source4) {
|
||||||
NSString* ipAddress = GCDWebServerGetPrimaryIPAddress(NO); // We can't really use IPv6 anyway as it doesn't work great with HTTP URLs in practice
|
NSString* ipAddress = _bindToLocalhost ? @"localhost" : GCDWebServerGetPrimaryIPAddress(NO); // We can't really use IPv6 anyway as it doesn't work great with HTTP URLs in practice
|
||||||
if (ipAddress) {
|
if (ipAddress) {
|
||||||
if (_port != 80) {
|
if (_port != 80) {
|
||||||
return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", ipAddress, (int)_port]];
|
return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", ipAddress, (int)_port]];
|
||||||
@@ -742,6 +833,17 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSURL*)publicServerURL {
|
||||||
|
if (_source4 && _dnsService && _dnsAddress && _dnsPort) {
|
||||||
|
if (_dnsPort != 80) {
|
||||||
|
return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", _dnsAddress, (int)_dnsPort]];
|
||||||
|
} else {
|
||||||
|
return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", _dnsAddress]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
- (BOOL)start {
|
- (BOOL)start {
|
||||||
return [self startWithPort:kDefaultPort bonjourName:@""];
|
return [self startWithPort:kDefaultPort bonjourName:@""];
|
||||||
}
|
}
|
||||||
@@ -762,7 +864,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
return [self runWithOptions:options error:NULL];
|
return [self runWithOptions:options error:NULL];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)runWithOptions:(NSDictionary*)options error:(NSError**)error {
|
- (BOOL)runWithOptions:(NSDictionary<NSString*, id>*)options error:(NSError**)error {
|
||||||
GWS_DCHECK([NSThread isMainThread]);
|
GWS_DCHECK([NSThread isMainThread]);
|
||||||
BOOL success = NO;
|
BOOL success = NO;
|
||||||
_run = YES;
|
_run = YES;
|
||||||
@@ -790,48 +892,56 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
@implementation GCDWebServer (Handlers)
|
@implementation GCDWebServer (Handlers)
|
||||||
|
|
||||||
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
|
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
|
||||||
[self addDefaultHandlerForMethod:method requestClass:aClass asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
|
[self addDefaultHandlerForMethod:method
|
||||||
|
requestClass:aClass
|
||||||
|
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
|
||||||
completionBlock(block(request));
|
completionBlock(block(request));
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
|
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
|
||||||
[self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
|
[self
|
||||||
|
addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary<NSString*, NSString*>* requestHeaders, NSString* urlPath, NSDictionary<NSString*, NSString*>* urlQuery) {
|
||||||
if (![requestMethod isEqualToString:method]) {
|
if (![requestMethod isEqualToString:method]) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
return [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
|
return [(GCDWebServerRequest*)[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
|
||||||
|
}
|
||||||
} asyncProcessBlock:block];
|
asyncProcessBlock:block];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
|
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
|
||||||
[self addHandlerForMethod:method path:path requestClass:aClass asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
|
[self addHandlerForMethod:method
|
||||||
|
path:path
|
||||||
|
requestClass:aClass
|
||||||
|
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
|
||||||
completionBlock(block(request));
|
completionBlock(block(request));
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
|
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
|
||||||
if ([path hasPrefix:@"/"] && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) {
|
if ([path hasPrefix:@"/"] && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) {
|
||||||
[self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
|
[self
|
||||||
|
addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary<NSString*, NSString*>* requestHeaders, NSString* urlPath, NSDictionary<NSString*, NSString*>* urlQuery) {
|
||||||
if (![requestMethod isEqualToString:method]) {
|
if (![requestMethod isEqualToString:method]) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
if ([urlPath caseInsensitiveCompare:path] != NSOrderedSame) {
|
if ([urlPath caseInsensitiveCompare:path] != NSOrderedSame) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
return [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
|
return [(GCDWebServerRequest*)[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
|
||||||
|
}
|
||||||
} asyncProcessBlock:block];
|
asyncProcessBlock:block];
|
||||||
} else {
|
} else {
|
||||||
GWS_DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
|
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
|
||||||
[self addHandlerForMethod:method pathRegex:regex requestClass:aClass asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
|
[self addHandlerForMethod:method
|
||||||
|
pathRegex:regex
|
||||||
|
requestClass:aClass
|
||||||
|
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
|
||||||
completionBlock(block(request));
|
completionBlock(block(request));
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
@@ -839,8 +949,8 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
|
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
|
||||||
NSRegularExpression* expression = [NSRegularExpression regularExpressionWithPattern:regex options:NSRegularExpressionCaseInsensitive error:NULL];
|
NSRegularExpression* expression = [NSRegularExpression regularExpressionWithPattern:regex options:NSRegularExpressionCaseInsensitive error:NULL];
|
||||||
if (expression && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) {
|
if (expression && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) {
|
||||||
[self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
|
[self
|
||||||
|
addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary<NSString*, NSString*>* requestHeaders, NSString* urlPath, NSDictionary<NSString*, NSString*>* urlQuery) {
|
||||||
if (![requestMethod isEqualToString:method]) {
|
if (![requestMethod isEqualToString:method]) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
@@ -854,15 +964,20 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
for (NSTextCheckingResult* result in matches) {
|
for (NSTextCheckingResult* result in matches) {
|
||||||
// Start at 1; index 0 is the whole string
|
// Start at 1; index 0 is the whole string
|
||||||
for (NSUInteger i = 1; i < result.numberOfRanges; i++) {
|
for (NSUInteger i = 1; i < result.numberOfRanges; i++) {
|
||||||
[captures addObject:[urlPath substringWithRange:[result rangeAtIndex:i]]];
|
NSRange range = [result rangeAtIndex:i];
|
||||||
|
// range is {NSNotFound, 0} "if one of the capture groups did not participate in this particular match"
|
||||||
|
// see discussion in -[NSRegularExpression firstMatchInString:options:range:]
|
||||||
|
if (range.location != NSNotFound) {
|
||||||
|
[captures addObject:[urlPath substringWithRange:range]];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GCDWebServerRequest* request = [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
|
GCDWebServerRequest* request = [(GCDWebServerRequest*)[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
|
||||||
[request setAttribute:captures forKey:GCDWebServerRequestAttribute_RegexCaptures];
|
[request setAttribute:captures forKey:GCDWebServerRequestAttribute_RegexCaptures];
|
||||||
return request;
|
return request;
|
||||||
|
}
|
||||||
} asyncProcessBlock:block];
|
asyncProcessBlock:block];
|
||||||
} else {
|
} else {
|
||||||
GWS_DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
}
|
}
|
||||||
@@ -873,18 +988,21 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
@implementation GCDWebServer (GETHandlers)
|
@implementation GCDWebServer (GETHandlers)
|
||||||
|
|
||||||
- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge {
|
- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge {
|
||||||
[self addHandlerForMethod:@"GET" path:path requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
[self addHandlerForMethod:@"GET"
|
||||||
|
path:path
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
GCDWebServerResponse* response = [GCDWebServerDataResponse responseWithData:staticData contentType:contentType];
|
GCDWebServerResponse* response = [GCDWebServerDataResponse responseWithData:staticData contentType:contentType];
|
||||||
response.cacheControlMaxAge = cacheAge;
|
response.cacheControlMaxAge = cacheAge;
|
||||||
return response;
|
return response;
|
||||||
|
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests {
|
- (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests {
|
||||||
[self addHandlerForMethod:@"GET" path:path requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
[self addHandlerForMethod:@"GET"
|
||||||
|
path:path
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
GCDWebServerResponse* response = nil;
|
GCDWebServerResponse* response = nil;
|
||||||
if (allowRangeRequests) {
|
if (allowRangeRequests) {
|
||||||
response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange isAttachment:isAttachment];
|
response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange isAttachment:isAttachment];
|
||||||
@@ -894,31 +1012,33 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
}
|
}
|
||||||
response.cacheControlMaxAge = cacheAge;
|
response.cacheControlMaxAge = cacheAge;
|
||||||
return response;
|
return response;
|
||||||
|
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (GCDWebServerResponse*)_responseWithContentsOfDirectory:(NSString*)path {
|
- (GCDWebServerResponse*)_responseWithContentsOfDirectory:(NSString*)path {
|
||||||
NSDirectoryEnumerator* enumerator = [[NSFileManager defaultManager] enumeratorAtPath:path];
|
NSArray* contents = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL] sortedArrayUsingSelector:@selector(localizedStandardCompare:)];
|
||||||
if (enumerator == nil) {
|
if (contents == nil) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
NSMutableString* html = [NSMutableString string];
|
NSMutableString* html = [NSMutableString string];
|
||||||
[html appendString:@"<!DOCTYPE html>\n"];
|
[html appendString:@"<!DOCTYPE html>\n"];
|
||||||
[html appendString:@"<html><head><meta charset=\"utf-8\"></head><body>\n"];
|
[html appendString:@"<html><head><meta charset=\"utf-8\"></head><body>\n"];
|
||||||
[html appendString:@"<ul>\n"];
|
[html appendString:@"<ul>\n"];
|
||||||
for (NSString* file in enumerator) {
|
for (NSString* entry in contents) {
|
||||||
if (![file hasPrefix:@"."]) {
|
if (![entry hasPrefix:@"."]) {
|
||||||
NSString* type = [[enumerator fileAttributes] objectForKey:NSFileType];
|
NSString* type = [[[NSFileManager defaultManager] attributesOfItemAtPath:[path stringByAppendingPathComponent:entry] error:NULL] objectForKey:NSFileType];
|
||||||
NSString* escapedFile = [file stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
GWS_DCHECK(type);
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
|
NSString* escapedFile = [entry stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
#pragma clang diagnostic pop
|
||||||
GWS_DCHECK(escapedFile);
|
GWS_DCHECK(escapedFile);
|
||||||
if ([type isEqualToString:NSFileTypeRegular]) {
|
if ([type isEqualToString:NSFileTypeRegular]) {
|
||||||
[html appendFormat:@"<li><a href=\"%@\">%@</a></li>\n", escapedFile, file];
|
[html appendFormat:@"<li><a href=\"%@\">%@</a></li>\n", escapedFile, entry];
|
||||||
} else if ([type isEqualToString:NSFileTypeDirectory]) {
|
} else if ([type isEqualToString:NSFileTypeDirectory]) {
|
||||||
[html appendFormat:@"<li><a href=\"%@/\">%@/</a></li>\n", escapedFile, file];
|
[html appendFormat:@"<li><a href=\"%@/\">%@/</a></li>\n", escapedFile, entry];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[enumerator skipDescendents];
|
|
||||||
}
|
}
|
||||||
[html appendString:@"</ul>\n"];
|
[html appendString:@"</ul>\n"];
|
||||||
[html appendString:@"</body></html>\n"];
|
[html appendString:@"</body></html>\n"];
|
||||||
@@ -928,8 +1048,8 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests {
|
- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests {
|
||||||
if ([basePath hasPrefix:@"/"] && [basePath hasSuffix:@"/"]) {
|
if ([basePath hasPrefix:@"/"] && [basePath hasSuffix:@"/"]) {
|
||||||
GCDWebServer* __unsafe_unretained server = self;
|
GCDWebServer* __unsafe_unretained server = self;
|
||||||
[self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
|
[self
|
||||||
|
addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary<NSString*, NSString*>* requestHeaders, NSString* urlPath, NSDictionary<NSString*, NSString*>* urlQuery) {
|
||||||
if (![requestMethod isEqualToString:@"GET"]) {
|
if (![requestMethod isEqualToString:@"GET"]) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
@@ -937,11 +1057,10 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
return [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
|
return [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
|
||||||
|
}
|
||||||
} processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
|
||||||
GCDWebServerResponse* response = nil;
|
GCDWebServerResponse* response = nil;
|
||||||
NSString* filePath = [directoryPath stringByAppendingPathComponent:[request.path substringFromIndex:basePath.length]];
|
NSString* filePath = [directoryPath stringByAppendingPathComponent:GCDWebServerNormalizePath([request.path substringFromIndex:basePath.length])];
|
||||||
NSString* fileType = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL] fileType];
|
NSString* fileType = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL] fileType];
|
||||||
if (fileType) {
|
if (fileType) {
|
||||||
if ([fileType isEqualToString:NSFileTypeDirectory]) {
|
if ([fileType isEqualToString:NSFileTypeDirectory]) {
|
||||||
@@ -968,7 +1087,6 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_NotFound];
|
response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_NotFound];
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
|
|
||||||
}];
|
}];
|
||||||
} else {
|
} else {
|
||||||
GWS_DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
@@ -982,13 +1100,19 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
+ (void)setLogLevel:(int)level {
|
+ (void)setLogLevel:(int)level {
|
||||||
#if defined(__GCDWEBSERVER_LOGGING_FACILITY_XLFACILITY__)
|
#if defined(__GCDWEBSERVER_LOGGING_FACILITY_XLFACILITY__)
|
||||||
[XLSharedFacility setMinLogLevel:level];
|
[XLSharedFacility setMinLogLevel:level];
|
||||||
#elif defined(__GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__)
|
|
||||||
GCDWebServerLogLevel = level;
|
|
||||||
#elif defined(__GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__)
|
#elif defined(__GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__)
|
||||||
GCDWebServerLogLevel = level;
|
GCDWebServerLogLevel = level;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ (void)setBuiltInLogger:(GCDWebServerBuiltInLoggerBlock)block {
|
||||||
|
#if defined(__GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__)
|
||||||
|
_builtInLoggerBlock = block;
|
||||||
|
#else
|
||||||
|
GWS_DNOT_REACHED(); // Built-in logger must be enabled in order to override
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
- (void)logVerbose:(NSString*)format, ... {
|
- (void)logVerbose:(NSString*)format, ... {
|
||||||
va_list arguments;
|
va_list arguments;
|
||||||
va_start(arguments, format);
|
va_start(arguments, format);
|
||||||
@@ -1017,10 +1141,6 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
va_end(arguments);
|
va_end(arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)logException:(NSException*)exception {
|
|
||||||
GWS_LOG_EXCEPTION(exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
||||||
@@ -1050,9 +1170,9 @@ static CFHTTPMessageRef _CreateHTTPMessageFromPerformingRequest(NSData* inData,
|
|||||||
if (httpSocket > 0) {
|
if (httpSocket > 0) {
|
||||||
struct sockaddr_in addr4;
|
struct sockaddr_in addr4;
|
||||||
bzero(&addr4, sizeof(addr4));
|
bzero(&addr4, sizeof(addr4));
|
||||||
addr4.sin_len = sizeof(port);
|
addr4.sin_len = sizeof(addr4);
|
||||||
addr4.sin_family = AF_INET;
|
addr4.sin_family = AF_INET;
|
||||||
addr4.sin_port = htons(8080);
|
addr4.sin_port = htons(port);
|
||||||
addr4.sin_addr.s_addr = htonl(INADDR_ANY);
|
addr4.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
if (connect(httpSocket, (void*)&addr4, sizeof(addr4)) == 0) {
|
if (connect(httpSocket, (void*)&addr4, sizeof(addr4)) == 0) {
|
||||||
if (write(httpSocket, inData.bytes, inData.length) == (ssize_t)inData.length) {
|
if (write(httpSocket, inData.bytes, inData.length) == (ssize_t)inData.length) {
|
||||||
@@ -1092,7 +1212,7 @@ static void _LogResult(NSString* format, ...) {
|
|||||||
fprintf(stdout, "%s\n", [message UTF8String]);
|
fprintf(stdout, "%s\n", [message UTF8String]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path {
|
- (NSInteger)runTestsWithOptions:(NSDictionary<NSString*, id>*)options inDirectory:(NSString*)path {
|
||||||
GWS_DCHECK([NSThread isMainThread]);
|
GWS_DCHECK([NSThread isMainThread]);
|
||||||
NSArray* ignoredHeaders = @[ @"Date", @"Etag" ]; // Dates are always different by definition and ETags depend on file system node IDs
|
NSArray* ignoredHeaders = @[ @"Date", @"Etag" ]; // Dates are always different by definition and ETags depend on file system node IDs
|
||||||
NSInteger result = -1;
|
NSInteger result = -1;
|
||||||
@@ -1100,7 +1220,7 @@ static void _LogResult(NSString* format, ...) {
|
|||||||
_ExecuteMainThreadRunLoopSources();
|
_ExecuteMainThreadRunLoopSources();
|
||||||
|
|
||||||
result = 0;
|
result = 0;
|
||||||
NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL];
|
NSArray* files = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL] sortedArrayUsingSelector:@selector(localizedStandardCompare:)];
|
||||||
for (NSString* requestFile in files) {
|
for (NSString* requestFile in files) {
|
||||||
if (![requestFile hasSuffix:@".request"]) {
|
if (![requestFile hasSuffix:@".request"]) {
|
||||||
continue;
|
continue;
|
||||||
@@ -1160,14 +1280,14 @@ static void _LogResult(NSString* format, ...) {
|
|||||||
if ([actualContentLength isEqualToString:expectedContentLength] && (actualBody.length > expectedBody.length)) { // Handle web browser closing connection before retrieving entire body (e.g. when playing a video file)
|
if ([actualContentLength isEqualToString:expectedContentLength] && (actualBody.length > expectedBody.length)) { // Handle web browser closing connection before retrieving entire body (e.g. when playing a video file)
|
||||||
actualBody = [actualBody subdataWithRange:NSMakeRange(0, expectedBody.length)];
|
actualBody = [actualBody subdataWithRange:NSMakeRange(0, expectedBody.length)];
|
||||||
}
|
}
|
||||||
if (![actualBody isEqualToData:expectedBody]) {
|
if ((actualBody && expectedBody && ![actualBody isEqualToData:expectedBody]) || (actualBody && !expectedBody) || (!actualBody && expectedBody)) {
|
||||||
_LogResult(@" Bodies not matching:\n Expected: %lu bytes\n Actual: %lu bytes", (unsigned long)expectedBody.length, (unsigned long)actualBody.length);
|
_LogResult(@" Bodies not matching:\n Expected: %lu bytes\n Actual: %lu bytes", (unsigned long)expectedBody.length, (unsigned long)actualBody.length);
|
||||||
success = NO;
|
success = NO;
|
||||||
#if !TARGET_OS_IPHONE
|
#if !TARGET_OS_IPHONE
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (GCDWebServerIsTextContentType([expectedHeaders objectForKey:@"Content-Type"])) {
|
if (GCDWebServerIsTextContentType((NSString*)[expectedHeaders objectForKey:@"Content-Type"])) {
|
||||||
NSString* expectedPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
|
NSString* expectedPath = [NSTemporaryDirectory() stringByAppendingPathComponent:(NSString*)[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
|
||||||
NSString* actualPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
|
NSString* actualPath = [NSTemporaryDirectory() stringByAppendingPathComponent:(NSString*)[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
|
||||||
if ([expectedBody writeToFile:expectedPath atomically:YES] && [actualBody writeToFile:actualPath atomically:YES]) {
|
if ([expectedBody writeToFile:expectedPath atomically:YES] && [actualBody writeToFile:actualPath atomically:YES]) {
|
||||||
NSTask* task = [[NSTask alloc] init];
|
NSTask* task = [[NSTask alloc] init];
|
||||||
[task setLaunchPath:@"/usr/bin/opendiff"];
|
[task setLaunchPath:@"/usr/bin/opendiff"];
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
#import "GCDWebServer.h"
|
#import "GCDWebServer.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
@class GCDWebServerHandler;
|
@class GCDWebServerHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -128,7 +130,7 @@
|
|||||||
*
|
*
|
||||||
* The default implementation returns the original URL.
|
* The default implementation returns the original URL.
|
||||||
*/
|
*/
|
||||||
- (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary*)headers;
|
- (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary<NSString*, NSString*>*)headers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assuming a valid HTTP request was received, this method is called before
|
* Assuming a valid HTTP request was received, this method is called before
|
||||||
@@ -139,7 +141,7 @@
|
|||||||
* The default implementation checks for HTTP authentication if applicable
|
* The default implementation checks for HTTP authentication if applicable
|
||||||
* and returns a barebone 401 status code response if authentication failed.
|
* and returns a barebone 401 status code response if authentication failed.
|
||||||
*/
|
*/
|
||||||
- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request;
|
- (nullable GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assuming a valid HTTP request was received and -preflightRequest: returned nil,
|
* Assuming a valid HTTP request was received and -preflightRequest: returned nil,
|
||||||
@@ -169,7 +171,7 @@
|
|||||||
* @warning If the request was invalid (e.g. the HTTP headers were malformed),
|
* @warning If the request was invalid (e.g. the HTTP headers were malformed),
|
||||||
* the "request" argument will be nil.
|
* the "request" argument will be nil.
|
||||||
*/
|
*/
|
||||||
- (void)abortRequest:(GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode;
|
- (void)abortRequest:(nullable GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the connection is closed.
|
* Called when the connection is closed.
|
||||||
@@ -177,3 +179,5 @@
|
|||||||
- (void)close;
|
- (void)close;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
@@ -34,27 +36,31 @@ extern "C" {
|
|||||||
/**
|
/**
|
||||||
* Converts a file extension to the corresponding MIME type.
|
* Converts a file extension to the corresponding MIME type.
|
||||||
* If there is no match, "application/octet-stream" is returned.
|
* If there is no match, "application/octet-stream" is returned.
|
||||||
|
*
|
||||||
|
* Overrides allow to customize the built-in mapping from extensions to MIME
|
||||||
|
* types. Keys of the dictionary must be lowercased file extensions without
|
||||||
|
* the period, and the values must be the corresponding MIME types.
|
||||||
*/
|
*/
|
||||||
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension);
|
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary<NSString*, NSString*>* _Nullable overrides);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add percent-escapes to a string so it can be used in a URL.
|
* Add percent-escapes to a string so it can be used in a URL.
|
||||||
* The legal characters ":@/?&=+" are also escaped to ensure compatibility
|
* The legal characters ":@/?&=+" are also escaped to ensure compatibility
|
||||||
* with URL encoded forms and URL queries.
|
* with URL encoded forms and URL queries.
|
||||||
*/
|
*/
|
||||||
NSString* GCDWebServerEscapeURLString(NSString* string);
|
NSString* _Nullable GCDWebServerEscapeURLString(NSString* string);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unescapes a URL percent-encoded string.
|
* Unescapes a URL percent-encoded string.
|
||||||
*/
|
*/
|
||||||
NSString* GCDWebServerUnescapeURLString(NSString* string);
|
NSString* _Nullable GCDWebServerUnescapeURLString(NSString* string);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the unescaped names and values from an
|
* Extracts the unescaped names and values from an
|
||||||
* "application/x-www-form-urlencoded" form.
|
* "application/x-www-form-urlencoded" form.
|
||||||
* http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
|
* http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
|
||||||
*/
|
*/
|
||||||
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
|
NSDictionary<NSString*, NSString*>* GCDWebServerParseURLEncodedForm(NSString* form);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On OS X, returns the IPv4 or IPv6 address as a string of the primary
|
* On OS X, returns the IPv4 or IPv6 address as a string of the primary
|
||||||
@@ -63,7 +69,7 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
|
|||||||
* On iOS, returns the IPv4 or IPv6 address as a string of the WiFi
|
* On iOS, returns the IPv4 or IPv6 address as a string of the WiFi
|
||||||
* interface if connected or nil otherwise.
|
* interface if connected or nil otherwise.
|
||||||
*/
|
*/
|
||||||
NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6);
|
NSString* _Nullable GCDWebServerGetPrimaryIPAddress(BOOL useIPv6);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a date into a string using RFC822 formatting.
|
* Converts a date into a string using RFC822 formatting.
|
||||||
@@ -79,7 +85,7 @@ NSString* GCDWebServerFormatRFC822(NSDate* date);
|
|||||||
*
|
*
|
||||||
* @warning Timezones other than GMT are not supported by this function.
|
* @warning Timezones other than GMT are not supported by this function.
|
||||||
*/
|
*/
|
||||||
NSDate* GCDWebServerParseRFC822(NSString* string);
|
NSDate* _Nullable GCDWebServerParseRFC822(NSString* string);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a date into a string using IOS 8601 formatting.
|
* Converts a date into a string using IOS 8601 formatting.
|
||||||
@@ -94,8 +100,15 @@ NSString* GCDWebServerFormatISO8601(NSDate* date);
|
|||||||
* @warning Only "calendar" variant is supported at this time and timezones
|
* @warning Only "calendar" variant is supported at this time and timezones
|
||||||
* other than GMT are not supported either.
|
* other than GMT are not supported either.
|
||||||
*/
|
*/
|
||||||
NSDate* GCDWebServerParseISO8601(NSString* string);
|
NSDate* _Nullable GCDWebServerParseISO8601(NSString* string);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes "//", "/./" and "/../" components from path as well as any trailing slash.
|
||||||
|
*/
|
||||||
|
NSString* GCDWebServerNormalizePath(NSString* path);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
#import <TargetConditionals.h>
|
#import <TargetConditionals.h>
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
#import <MobileCoreServices/MobileCoreServices.h>
|
#import <CoreServices/CoreServices.h>
|
||||||
#else
|
#else
|
||||||
#import <SystemConfiguration/SystemConfiguration.h>
|
#import <SystemConfiguration/SystemConfiguration.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -83,12 +83,18 @@ NSString* GCDWebServerNormalizeHeaderValue(NSString* value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSString* GCDWebServerTruncateHeaderValue(NSString* value) {
|
NSString* GCDWebServerTruncateHeaderValue(NSString* value) {
|
||||||
|
if (value) {
|
||||||
NSRange range = [value rangeOfString:@";"];
|
NSRange range = [value rangeOfString:@";"];
|
||||||
return range.location != NSNotFound ? [value substringToIndex:range.location] : value;
|
if (range.location != NSNotFound) {
|
||||||
|
return [value substringToIndex:range.location];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) {
|
NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) {
|
||||||
NSString* parameter = nil;
|
NSString* parameter = nil;
|
||||||
|
if (value) {
|
||||||
NSScanner* scanner = [[NSScanner alloc] initWithString:value];
|
NSScanner* scanner = [[NSScanner alloc] initWithString:value];
|
||||||
[scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive
|
[scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive
|
||||||
NSString* string = [NSString stringWithFormat:@"%@=", name];
|
NSString* string = [NSString stringWithFormat:@"%@=", name];
|
||||||
@@ -100,6 +106,7 @@ NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* nam
|
|||||||
[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:¶meter];
|
[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:¶meter];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return parameter;
|
return parameter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,17 +166,15 @@ NSString* GCDWebServerDescribeData(NSData* data, NSString* type) {
|
|||||||
return [NSString stringWithFormat:@"<%lu bytes>", (unsigned long)data.length];
|
return [NSString stringWithFormat:@"<%lu bytes>", (unsigned long)data.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) {
|
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary<NSString*, NSString*>* overrides) {
|
||||||
static NSDictionary* _overrides = nil;
|
NSDictionary* builtInOverrides = @{@"css" : @"text/css"};
|
||||||
if (_overrides == nil) {
|
|
||||||
_overrides = [[NSDictionary alloc] initWithObjectsAndKeys:
|
|
||||||
@"text/css", @"css",
|
|
||||||
nil];
|
|
||||||
}
|
|
||||||
NSString* mimeType = nil;
|
NSString* mimeType = nil;
|
||||||
extension = [extension lowercaseString];
|
extension = [extension lowercaseString];
|
||||||
if (extension.length) {
|
if (extension.length) {
|
||||||
mimeType = [_overrides objectForKey:extension];
|
mimeType = [overrides objectForKey:extension];
|
||||||
|
if (mimeType == nil) {
|
||||||
|
mimeType = [builtInOverrides objectForKey:extension];
|
||||||
|
}
|
||||||
if (mimeType == nil) {
|
if (mimeType == nil) {
|
||||||
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
|
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
|
||||||
if (uti) {
|
if (uti) {
|
||||||
@@ -182,14 +187,20 @@ NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSString* GCDWebServerEscapeURLString(NSString* string) {
|
NSString* GCDWebServerEscapeURLString(NSString* string) {
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":@/?&=+"), kCFStringEncodingUTF8));
|
return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":@/?&=+"), kCFStringEncodingUTF8));
|
||||||
|
#pragma clang diagnostic pop
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString* GCDWebServerUnescapeURLString(NSString* string) {
|
NSString* GCDWebServerUnescapeURLString(NSString* string) {
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
return CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""), kCFStringEncodingUTF8));
|
return CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""), kCFStringEncodingUTF8));
|
||||||
|
#pragma clang diagnostic pop
|
||||||
}
|
}
|
||||||
|
|
||||||
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
|
NSDictionary<NSString*, NSString*>* GCDWebServerParseURLEncodedForm(NSString* form) {
|
||||||
NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
|
NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
|
||||||
NSScanner* scanner = [[NSScanner alloc] initWithString:form];
|
NSScanner* scanner = [[NSScanner alloc] initWithString:form];
|
||||||
[scanner setCharactersToBeSkipped:nil];
|
[scanner setCharactersToBeSkipped:nil];
|
||||||
@@ -226,21 +237,22 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) {
|
NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) {
|
||||||
NSString* string = nil;
|
|
||||||
char hostBuffer[NI_MAXHOST];
|
char hostBuffer[NI_MAXHOST];
|
||||||
char serviceBuffer[NI_MAXSERV];
|
char serviceBuffer[NI_MAXSERV];
|
||||||
if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) >= 0) {
|
if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) != 0) {
|
||||||
string = includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : [NSString stringWithUTF8String:hostBuffer];
|
#if DEBUG
|
||||||
} else {
|
|
||||||
GWS_DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
|
#else
|
||||||
|
return @"";
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
return string;
|
return includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : (NSString*)[NSString stringWithUTF8String:hostBuffer];
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
|
NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
|
||||||
NSString* address = nil;
|
NSString* address = nil;
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
#if !TARGET_IPHONE_SIMULATOR
|
#if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_TV
|
||||||
const char* primaryInterface = "en0"; // WiFi interface on iOS
|
const char* primaryInterface = "en0"; // WiFi interface on iOS
|
||||||
#endif
|
#endif
|
||||||
#else
|
#else
|
||||||
@@ -249,7 +261,10 @@ NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
|
|||||||
if (store) {
|
if (store) {
|
||||||
CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4")); // There is no equivalent for IPv6 but the primary interface should be the same
|
CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4")); // There is no equivalent for IPv6 but the primary interface should be the same
|
||||||
if (info) {
|
if (info) {
|
||||||
primaryInterface = [[NSString stringWithString:[(__bridge NSDictionary*)info objectForKey:@"PrimaryInterface"]] UTF8String];
|
NSString* interface = [(__bridge NSDictionary*)info objectForKey:@"PrimaryInterface"];
|
||||||
|
if (interface) {
|
||||||
|
primaryInterface = [[NSString stringWithString:interface] UTF8String]; // Copy string to auto-release pool
|
||||||
|
}
|
||||||
CFRelease(info);
|
CFRelease(info);
|
||||||
}
|
}
|
||||||
CFRelease(store);
|
CFRelease(store);
|
||||||
@@ -261,8 +276,10 @@ NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
|
|||||||
struct ifaddrs* list;
|
struct ifaddrs* list;
|
||||||
if (getifaddrs(&list) >= 0) {
|
if (getifaddrs(&list) >= 0) {
|
||||||
for (struct ifaddrs* ifap = list; ifap; ifap = ifap->ifa_next) {
|
for (struct ifaddrs* ifap = list; ifap; ifap = ifap->ifa_next) {
|
||||||
#if TARGET_IPHONE_SIMULATOR
|
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_TV
|
||||||
if (strcmp(ifap->ifa_name, "en0") && strcmp(ifap->ifa_name, "en1")) // Assume en0 is Ethernet and en1 is WiFi since there is no way to use SystemConfiguration framework in iOS Simulator
|
// Assume en0 is Ethernet and en1 is WiFi since there is no way to use SystemConfiguration framework in iOS Simulator
|
||||||
|
// Assumption holds for Apple TV running tvOS
|
||||||
|
if (strcmp(ifap->ifa_name, "en0") && strcmp(ifap->ifa_name, "en1"))
|
||||||
#else
|
#else
|
||||||
if (strcmp(ifap->ifa_name, primaryInterface))
|
if (strcmp(ifap->ifa_name, primaryInterface))
|
||||||
#endif
|
#endif
|
||||||
@@ -285,7 +302,10 @@ NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) {
|
|||||||
const char* string = [[[NSString alloc] initWithFormat:format arguments:arguments] UTF8String];
|
const char* string = [[[NSString alloc] initWithFormat:format arguments:arguments] UTF8String];
|
||||||
va_end(arguments);
|
va_end(arguments);
|
||||||
unsigned char md5[CC_MD5_DIGEST_LENGTH];
|
unsigned char md5[CC_MD5_DIGEST_LENGTH];
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
CC_MD5(string, (CC_LONG)strlen(string), md5);
|
CC_MD5(string, (CC_LONG)strlen(string), md5);
|
||||||
|
#pragma clang diagnostic pop
|
||||||
char buffer[2 * CC_MD5_DIGEST_LENGTH + 1];
|
char buffer[2 * CC_MD5_DIGEST_LENGTH + 1];
|
||||||
for (int i = 0; i < CC_MD5_DIGEST_LENGTH; ++i) {
|
for (int i = 0; i < CC_MD5_DIGEST_LENGTH; ++i) {
|
||||||
unsigned char byte = md5[i];
|
unsigned char byte = md5[i];
|
||||||
@@ -295,5 +315,20 @@ NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) {
|
|||||||
buffer[2 * i + 1] = byteLo >= 10 ? 'a' + byteLo - 10 : '0' + byteLo;
|
buffer[2 * i + 1] = byteLo >= 10 ? 'a' + byteLo - 10 : '0' + byteLo;
|
||||||
}
|
}
|
||||||
buffer[2 * CC_MD5_DIGEST_LENGTH] = 0;
|
buffer[2 * CC_MD5_DIGEST_LENGTH] = 0;
|
||||||
return [NSString stringWithUTF8String:buffer];
|
return (NSString*)[NSString stringWithUTF8String:buffer];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString* GCDWebServerNormalizePath(NSString* path) {
|
||||||
|
NSMutableArray* components = [[NSMutableArray alloc] init];
|
||||||
|
for (NSString* component in [path componentsSeparatedByString:@"/"]) {
|
||||||
|
if ([component isEqualToString:@".."]) {
|
||||||
|
[components removeLastObject];
|
||||||
|
} else if (component.length && ![component isEqualToString:@"."]) {
|
||||||
|
[components addObject:component];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (path.length && ([path characterAtIndex:0] == '/')) {
|
||||||
|
return [@"/" stringByAppendingString:[components componentsJoinedByString:@"/"]]; // Preserve initial slash
|
||||||
|
}
|
||||||
|
return [components componentsJoinedByString:@"/"];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -77,33 +77,10 @@
|
|||||||
#define GWS_LOG_INFO(...) XLOG_INFO(__VA_ARGS__)
|
#define GWS_LOG_INFO(...) XLOG_INFO(__VA_ARGS__)
|
||||||
#define GWS_LOG_WARNING(...) XLOG_WARNING(__VA_ARGS__)
|
#define GWS_LOG_WARNING(...) XLOG_WARNING(__VA_ARGS__)
|
||||||
#define GWS_LOG_ERROR(...) XLOG_ERROR(__VA_ARGS__)
|
#define GWS_LOG_ERROR(...) XLOG_ERROR(__VA_ARGS__)
|
||||||
#define GWS_LOG_EXCEPTION(__EXCEPTION__) XLOG_EXCEPTION(__EXCEPTION__)
|
|
||||||
|
|
||||||
#define GWS_DCHECK(__CONDITION__) XLOG_DEBUG_CHECK(__CONDITION__)
|
#define GWS_DCHECK(__CONDITION__) XLOG_DEBUG_CHECK(__CONDITION__)
|
||||||
#define GWS_DNOT_REACHED() XLOG_DEBUG_UNREACHABLE()
|
#define GWS_DNOT_REACHED() XLOG_DEBUG_UNREACHABLE()
|
||||||
|
|
||||||
/**
|
|
||||||
* Automatically detect if CocoaLumberJack is available and if so use
|
|
||||||
* it as a logging facility.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#elif defined(__has_include) && __has_include("DDLogMacros.h")
|
|
||||||
|
|
||||||
#import "DDLogMacros.h"
|
|
||||||
|
|
||||||
#define __GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__
|
|
||||||
|
|
||||||
#undef LOG_LEVEL_DEF
|
|
||||||
#define LOG_LEVEL_DEF GCDWebServerLogLevel
|
|
||||||
extern int GCDWebServerLogLevel;
|
|
||||||
|
|
||||||
#define GWS_LOG_DEBUG(...) DDLogDebug(__VA_ARGS__)
|
|
||||||
#define GWS_LOG_VERBOSE(...) DDLogVerbose(__VA_ARGS__)
|
|
||||||
#define GWS_LOG_INFO(...) DDLogInfo(__VA_ARGS__)
|
|
||||||
#define GWS_LOG_WARNING(...) DDLogWarn(__VA_ARGS__)
|
|
||||||
#define GWS_LOG_ERROR(...) DDLogError(__VA_ARGS__)
|
|
||||||
#define GWS_LOG_EXCEPTION(__EXCEPTION__) DDLogError(@"%@", __EXCEPTION__)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If all of the above fail, then use GCDWebServer built-in
|
* If all of the above fail, then use GCDWebServer built-in
|
||||||
* logging facility.
|
* logging facility.
|
||||||
@@ -118,23 +95,36 @@ typedef NS_ENUM(int, GCDWebServerLoggingLevel) {
|
|||||||
kGCDWebServerLoggingLevel_Verbose,
|
kGCDWebServerLoggingLevel_Verbose,
|
||||||
kGCDWebServerLoggingLevel_Info,
|
kGCDWebServerLoggingLevel_Info,
|
||||||
kGCDWebServerLoggingLevel_Warning,
|
kGCDWebServerLoggingLevel_Warning,
|
||||||
kGCDWebServerLoggingLevel_Error,
|
kGCDWebServerLoggingLevel_Error
|
||||||
kGCDWebServerLoggingLevel_Exception
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern GCDWebServerLoggingLevel GCDWebServerLogLevel;
|
extern GCDWebServerLoggingLevel GCDWebServerLogLevel;
|
||||||
extern void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* format, ...) NS_FORMAT_FUNCTION(2, 3);
|
extern void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* _Nonnull format, ...) NS_FORMAT_FUNCTION(2, 3);
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
#define GWS_LOG_DEBUG(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Debug) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Debug, __VA_ARGS__); } while (0)
|
#define GWS_LOG_DEBUG(...) \
|
||||||
|
do { \
|
||||||
|
if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Debug) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Debug, __VA_ARGS__); \
|
||||||
|
} while (0)
|
||||||
#else
|
#else
|
||||||
#define GWS_LOG_DEBUG(...)
|
#define GWS_LOG_DEBUG(...)
|
||||||
#endif
|
#endif
|
||||||
#define GWS_LOG_VERBOSE(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Verbose) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Verbose, __VA_ARGS__); } while (0)
|
#define GWS_LOG_VERBOSE(...) \
|
||||||
#define GWS_LOG_INFO(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Info) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Info, __VA_ARGS__); } while (0)
|
do { \
|
||||||
#define GWS_LOG_WARNING(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Warning) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Warning, __VA_ARGS__); } while (0)
|
if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Verbose) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Verbose, __VA_ARGS__); \
|
||||||
#define GWS_LOG_ERROR(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Error) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Error, __VA_ARGS__); } while (0)
|
} while (0)
|
||||||
#define GWS_LOG_EXCEPTION(__EXCEPTION__) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Exception) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Exception, @"%@", __EXCEPTION__); } while (0)
|
#define GWS_LOG_INFO(...) \
|
||||||
|
do { \
|
||||||
|
if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Info) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Info, __VA_ARGS__); \
|
||||||
|
} while (0)
|
||||||
|
#define GWS_LOG_WARNING(...) \
|
||||||
|
do { \
|
||||||
|
if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Warning) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Warning, __VA_ARGS__); \
|
||||||
|
} while (0)
|
||||||
|
#define GWS_LOG_ERROR(...) \
|
||||||
|
do { \
|
||||||
|
if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Error) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Error, __VA_ARGS__); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -163,12 +153,13 @@ extern void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* for
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GCDWebServer internal constants and APIs.
|
* GCDWebServer internal constants and APIs.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define kGCDWebServerDefaultMimeType @"application/octet-stream"
|
#define kGCDWebServerDefaultMimeType @"application/octet-stream"
|
||||||
#define kGCDWebServerGCDQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
|
|
||||||
#define kGCDWebServerErrorDomain @"GCDWebServerErrorDomain"
|
#define kGCDWebServerErrorDomain @"GCDWebServerErrorDomain"
|
||||||
|
|
||||||
static inline BOOL GCDWebServerIsValidByteRange(NSRange range) {
|
static inline BOOL GCDWebServerIsValidByteRange(NSRange range) {
|
||||||
@@ -176,13 +167,13 @@ static inline BOOL GCDWebServerIsValidByteRange(NSRange range) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline NSError* GCDWebServerMakePosixError(int code) {
|
static inline NSError* GCDWebServerMakePosixError(int code) {
|
||||||
return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithUTF8String:strerror(code)]}];
|
return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : (NSString*)[NSString stringWithUTF8String:strerror(code)]}];
|
||||||
}
|
}
|
||||||
|
|
||||||
extern void GCDWebServerInitializeFunctions();
|
extern void GCDWebServerInitializeFunctions(void);
|
||||||
extern NSString* GCDWebServerNormalizeHeaderValue(NSString* value);
|
extern NSString* _Nullable GCDWebServerNormalizeHeaderValue(NSString* _Nullable value);
|
||||||
extern NSString* GCDWebServerTruncateHeaderValue(NSString* value);
|
extern NSString* _Nullable GCDWebServerTruncateHeaderValue(NSString* _Nullable value);
|
||||||
extern NSString* GCDWebServerExtractHeaderValueParameter(NSString* header, NSString* attribute);
|
extern NSString* _Nullable GCDWebServerExtractHeaderValueParameter(NSString* _Nullable value, NSString* attribute);
|
||||||
extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset);
|
extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset);
|
||||||
extern BOOL GCDWebServerIsTextContentType(NSString* type);
|
extern BOOL GCDWebServerIsTextContentType(NSString* type);
|
||||||
extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType);
|
extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType);
|
||||||
@@ -190,16 +181,17 @@ extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_F
|
|||||||
extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService);
|
extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService);
|
||||||
|
|
||||||
@interface GCDWebServerConnection ()
|
@interface GCDWebServerConnection ()
|
||||||
- (id)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket;
|
- (instancetype)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServer ()
|
@interface GCDWebServer ()
|
||||||
@property(nonatomic, readonly) NSArray* handlers;
|
@property(nonatomic, readonly) NSMutableArray<GCDWebServerHandler*>* handlers;
|
||||||
@property(nonatomic, readonly) NSString* serverName;
|
@property(nonatomic, readonly, nullable) NSString* serverName;
|
||||||
@property(nonatomic, readonly) NSString* authenticationRealm;
|
@property(nonatomic, readonly, nullable) NSString* authenticationRealm;
|
||||||
@property(nonatomic, readonly) NSDictionary* authenticationBasicAccounts;
|
@property(nonatomic, readonly, nullable) NSMutableDictionary<NSString*, NSString*>* authenticationBasicAccounts;
|
||||||
@property(nonatomic, readonly) NSDictionary* authenticationDigestAccounts;
|
@property(nonatomic, readonly, nullable) NSMutableDictionary<NSString*, NSString*>* authenticationDigestAccounts;
|
||||||
@property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET;
|
@property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET;
|
||||||
|
@property(nonatomic, readonly) dispatch_queue_priority_t dispatchQueuePriority;
|
||||||
- (void)willStartConnection:(GCDWebServerConnection*)connection;
|
- (void)willStartConnection:(GCDWebServerConnection*)connection;
|
||||||
- (void)didEndConnection:(GCDWebServerConnection*)connection;
|
- (void)didEndConnection:(GCDWebServerConnection*)connection;
|
||||||
@end
|
@end
|
||||||
@@ -211,18 +203,22 @@ extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOO
|
|||||||
|
|
||||||
@interface GCDWebServerRequest ()
|
@interface GCDWebServerRequest ()
|
||||||
@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
|
@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
|
||||||
|
@property(nonatomic) NSData* localAddressData;
|
||||||
|
@property(nonatomic) NSData* remoteAddressData;
|
||||||
- (void)prepareForWriting;
|
- (void)prepareForWriting;
|
||||||
- (BOOL)performOpen:(NSError**)error;
|
- (BOOL)performOpen:(NSError**)error;
|
||||||
- (BOOL)performWriteData:(NSData*)data error:(NSError**)error;
|
- (BOOL)performWriteData:(NSData*)data error:(NSError**)error;
|
||||||
- (BOOL)performClose:(NSError**)error;
|
- (BOOL)performClose:(NSError**)error;
|
||||||
- (void)setAttribute:(id)attribute forKey:(NSString*)key;
|
- (void)setAttribute:(nullable id)attribute forKey:(NSString*)key;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerResponse ()
|
@interface GCDWebServerResponse ()
|
||||||
@property(nonatomic, readonly) NSDictionary* additionalHeaders;
|
@property(nonatomic, readonly) NSDictionary<NSString*, NSString*>* additionalHeaders;
|
||||||
@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
|
@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
|
||||||
- (void)prepareForReading;
|
- (void)prepareForReading;
|
||||||
- (BOOL)performOpen:(NSError**)error;
|
- (BOOL)performOpen:(NSError**)error;
|
||||||
- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block;
|
- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block;
|
||||||
- (void)performClose;
|
- (void)performClose;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attribute key to retrieve an NSArray containing NSStrings from a GCDWebServerRequest
|
* Attribute key to retrieve an NSArray containing NSStrings from a GCDWebServerRequest
|
||||||
* with the contents of any regular expression captures done on the request path.
|
* with the contents of any regular expression captures done on the request path.
|
||||||
@@ -100,7 +102,7 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
|
|||||||
/**
|
/**
|
||||||
* Returns the HTTP headers for the request.
|
* Returns the HTTP headers for the request.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, readonly) NSDictionary* headers;
|
@property(nonatomic, readonly) NSDictionary<NSString*, NSString*>* headers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the path component of the URL for the request.
|
* Returns the path component of the URL for the request.
|
||||||
@@ -112,7 +114,7 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
|
|||||||
*
|
*
|
||||||
* @warning This property will be nil if there is no query in the URL.
|
* @warning This property will be nil if there is no query in the URL.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, readonly) NSDictionary* query;
|
@property(nonatomic, readonly, nullable) NSDictionary<NSString*, NSString*>* query;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the content type for the body of the request parsed from the
|
* Returns the content type for the body of the request parsed from the
|
||||||
@@ -122,7 +124,7 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
|
|||||||
* "application/octet-stream" if a body is present but there was no
|
* "application/octet-stream" if a body is present but there was no
|
||||||
* "Content-Type" header.
|
* "Content-Type" header.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, readonly) NSString* contentType;
|
@property(nonatomic, readonly, nullable) NSString* contentType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the content length for the body of the request parsed from the
|
* Returns the content length for the body of the request parsed from the
|
||||||
@@ -137,12 +139,12 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
|
|||||||
/**
|
/**
|
||||||
* Returns the parsed "If-Modified-Since" header or nil if absent or malformed.
|
* Returns the parsed "If-Modified-Since" header or nil if absent or malformed.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, readonly) NSDate* ifModifiedSince;
|
@property(nonatomic, readonly, nullable) NSDate* ifModifiedSince;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the parsed "If-None-Match" header or nil if absent or malformed.
|
* Returns the parsed "If-None-Match" header or nil if absent or malformed.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, readonly) NSString* ifNoneMatch;
|
@property(nonatomic, readonly, nullable) NSString* ifNoneMatch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the parsed "Range" header or (NSUIntegerMax, 0) if absent or malformed.
|
* Returns the parsed "Range" header or (NSUIntegerMax, 0) if absent or malformed.
|
||||||
@@ -157,10 +159,34 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
|
|||||||
*/
|
*/
|
||||||
@property(nonatomic, readonly) BOOL acceptsGzipContentEncoding;
|
@property(nonatomic, readonly) BOOL acceptsGzipContentEncoding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the address of the local peer (i.e. server) for the request
|
||||||
|
* as a raw "struct sockaddr".
|
||||||
|
*/
|
||||||
|
@property(nonatomic, readonly) NSData* localAddressData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the address of the local peer (i.e. server) for the request
|
||||||
|
* as a string.
|
||||||
|
*/
|
||||||
|
@property(nonatomic, readonly) NSString* localAddressString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the address of the remote peer (i.e. client) for the request
|
||||||
|
* as a raw "struct sockaddr".
|
||||||
|
*/
|
||||||
|
@property(nonatomic, readonly) NSData* remoteAddressData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the address of the remote peer (i.e. client) for the request
|
||||||
|
* as a string.
|
||||||
|
*/
|
||||||
|
@property(nonatomic, readonly) NSString* remoteAddressString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is the designated initializer for the class.
|
* This method is the designated initializer for the class.
|
||||||
*/
|
*/
|
||||||
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query;
|
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary<NSString*, NSString*>*)headers path:(NSString*)path query:(nullable NSDictionary<NSString*, NSString*>*)query;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method that checks if the contentType property is defined.
|
* Convenience method that checks if the contentType property is defined.
|
||||||
@@ -177,6 +203,8 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
|
|||||||
*
|
*
|
||||||
* @return The attribute value for the key.
|
* @return The attribute value for the key.
|
||||||
*/
|
*/
|
||||||
- (id)attributeForKey:(NSString*)key;
|
- (nullable id)attributeForKey:(NSString*)key;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -39,22 +39,17 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
|
|||||||
#define kGZipInitialBufferSize (256 * 1024)
|
#define kGZipInitialBufferSize (256 * 1024)
|
||||||
|
|
||||||
@interface GCDWebServerBodyDecoder : NSObject <GCDWebServerBodyWriter>
|
@interface GCDWebServerBodyDecoder : NSObject <GCDWebServerBodyWriter>
|
||||||
- (id)initWithRequest:(GCDWebServerRequest*)request writer:(id<GCDWebServerBodyWriter>)writer;
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerGZipDecoder : GCDWebServerBodyDecoder
|
@interface GCDWebServerGZipDecoder : GCDWebServerBodyDecoder
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerBodyDecoder () {
|
@implementation GCDWebServerBodyDecoder {
|
||||||
@private
|
|
||||||
GCDWebServerRequest* __unsafe_unretained _request;
|
GCDWebServerRequest* __unsafe_unretained _request;
|
||||||
id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
|
id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
|
||||||
}
|
}
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerBodyDecoder
|
- (instancetype)initWithRequest:(GCDWebServerRequest* _Nonnull)request writer:(id<GCDWebServerBodyWriter> _Nonnull)writer {
|
||||||
|
|
||||||
- (id)initWithRequest:(GCDWebServerRequest*)request writer:(id<GCDWebServerBodyWriter>)writer {
|
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
_request = request;
|
_request = request;
|
||||||
_writer = writer;
|
_writer = writer;
|
||||||
@@ -76,14 +71,10 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerGZipDecoder () {
|
@implementation GCDWebServerGZipDecoder {
|
||||||
@private
|
|
||||||
z_stream _stream;
|
z_stream _stream;
|
||||||
BOOL _finished;
|
BOOL _finished;
|
||||||
}
|
}
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerGZipDecoder
|
|
||||||
|
|
||||||
- (BOOL)open:(NSError**)error {
|
- (BOOL)open:(NSError**)error {
|
||||||
int result = inflateInit2(&_stream, 15 + 16);
|
int result = inflateInit2(&_stream, 15 + 16);
|
||||||
@@ -94,7 +85,7 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
|
|||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
if (![super open:error]) {
|
if (![super open:error]) {
|
||||||
deflateEnd(&_stream);
|
inflateEnd(&_stream);
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
return YES;
|
return YES;
|
||||||
@@ -143,105 +134,86 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerRequest () {
|
@implementation GCDWebServerRequest {
|
||||||
@private
|
|
||||||
NSString* _method;
|
|
||||||
NSURL* _url;
|
|
||||||
NSDictionary* _headers;
|
|
||||||
NSString* _path;
|
|
||||||
NSDictionary* _query;
|
|
||||||
NSString* _type;
|
|
||||||
BOOL _chunked;
|
|
||||||
NSUInteger _length;
|
|
||||||
NSDate* _modifiedSince;
|
|
||||||
NSString* _noneMatch;
|
|
||||||
NSRange _range;
|
|
||||||
BOOL _gzipAccepted;
|
|
||||||
|
|
||||||
BOOL _opened;
|
BOOL _opened;
|
||||||
NSMutableArray* _decoders;
|
NSMutableArray<GCDWebServerBodyDecoder*>* _decoders;
|
||||||
NSMutableDictionary* _attributes;
|
|
||||||
id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
|
id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
|
||||||
|
NSMutableDictionary<NSString*, id>* _attributes;
|
||||||
}
|
}
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerRequest : NSObject
|
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary<NSString*, NSString*>*)headers path:(NSString*)path query:(NSDictionary<NSString*, NSString*>*)query {
|
||||||
|
|
||||||
@synthesize method=_method, URL=_url, headers=_headers, path=_path, query=_query, contentType=_type, contentLength=_length, ifModifiedSince=_modifiedSince, ifNoneMatch=_noneMatch,
|
|
||||||
byteRange=_range, acceptsGzipContentEncoding=_gzipAccepted, usesChunkedTransferEncoding=_chunked;
|
|
||||||
|
|
||||||
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
|
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
_method = [method copy];
|
_method = [method copy];
|
||||||
_url = url;
|
_URL = url;
|
||||||
_headers = headers;
|
_headers = headers;
|
||||||
_path = [path copy];
|
_path = [path copy];
|
||||||
_query = query;
|
_query = query;
|
||||||
|
|
||||||
_type = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Content-Type"]);
|
_contentType = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Content-Type"]);
|
||||||
_chunked = [GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Transfer-Encoding"]) isEqualToString:@"chunked"];
|
_usesChunkedTransferEncoding = [GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Transfer-Encoding"]) isEqualToString:@"chunked"];
|
||||||
NSString* lengthHeader = [_headers objectForKey:@"Content-Length"];
|
NSString* lengthHeader = [_headers objectForKey:@"Content-Length"];
|
||||||
if (lengthHeader) {
|
if (lengthHeader) {
|
||||||
NSInteger length = [lengthHeader integerValue];
|
NSInteger length = [lengthHeader integerValue];
|
||||||
if (_chunked || (length < 0)) {
|
if (_usesChunkedTransferEncoding || (length < 0)) {
|
||||||
|
GWS_LOG_WARNING(@"Invalid 'Content-Length' header '%@' for '%@' request on \"%@\"", lengthHeader, _method, _URL);
|
||||||
GWS_DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
_length = length;
|
_contentLength = length;
|
||||||
if (_type == nil) {
|
if (_contentType == nil) {
|
||||||
_type = kGCDWebServerDefaultMimeType;
|
_contentType = kGCDWebServerDefaultMimeType;
|
||||||
}
|
}
|
||||||
} else if (_chunked) {
|
} else if (_usesChunkedTransferEncoding) {
|
||||||
if (_type == nil) {
|
if (_contentType == nil) {
|
||||||
_type = kGCDWebServerDefaultMimeType;
|
_contentType = kGCDWebServerDefaultMimeType;
|
||||||
}
|
}
|
||||||
_length = NSUIntegerMax;
|
_contentLength = NSUIntegerMax;
|
||||||
} else {
|
} else {
|
||||||
if (_type) {
|
if (_contentType) {
|
||||||
GWS_DNOT_REACHED();
|
GWS_LOG_WARNING(@"Ignoring 'Content-Type' header for '%@' request on \"%@\"", _method, _URL);
|
||||||
return nil;
|
_contentType = nil; // Content-Type without Content-Length or chunked-encoding doesn't make sense
|
||||||
}
|
}
|
||||||
_length = NSUIntegerMax;
|
_contentLength = NSUIntegerMax;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString* modifiedHeader = [_headers objectForKey:@"If-Modified-Since"];
|
NSString* modifiedHeader = [_headers objectForKey:@"If-Modified-Since"];
|
||||||
if (modifiedHeader) {
|
if (modifiedHeader) {
|
||||||
_modifiedSince = [GCDWebServerParseRFC822(modifiedHeader) copy];
|
_ifModifiedSince = [GCDWebServerParseRFC822(modifiedHeader) copy];
|
||||||
}
|
}
|
||||||
_noneMatch = [_headers objectForKey:@"If-None-Match"];
|
_ifNoneMatch = [_headers objectForKey:@"If-None-Match"];
|
||||||
|
|
||||||
_range = NSMakeRange(NSUIntegerMax, 0);
|
_byteRange = NSMakeRange(NSUIntegerMax, 0);
|
||||||
NSString* rangeHeader = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Range"]);
|
NSString* rangeHeader = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Range"]);
|
||||||
if (rangeHeader) {
|
if (rangeHeader) {
|
||||||
if ([rangeHeader hasPrefix:@"bytes="]) {
|
if ([rangeHeader hasPrefix:@"bytes="]) {
|
||||||
NSArray* components = [[rangeHeader substringFromIndex:6] componentsSeparatedByString:@","];
|
NSArray* components = [[rangeHeader substringFromIndex:6] componentsSeparatedByString:@","];
|
||||||
if (components.count == 1) {
|
if (components.count == 1) {
|
||||||
components = [[components firstObject] componentsSeparatedByString:@"-"];
|
components = [(NSString*)[components firstObject] componentsSeparatedByString:@"-"];
|
||||||
if (components.count == 2) {
|
if (components.count == 2) {
|
||||||
NSString* startString = [components objectAtIndex:0];
|
NSString* startString = [components objectAtIndex:0];
|
||||||
NSInteger startValue = [startString integerValue];
|
NSInteger startValue = [startString integerValue];
|
||||||
NSString* endString = [components objectAtIndex:1];
|
NSString* endString = [components objectAtIndex:1];
|
||||||
NSInteger endValue = [endString integerValue];
|
NSInteger endValue = [endString integerValue];
|
||||||
if (startString.length && (startValue >= 0) && endString.length && (endValue >= startValue)) { // The second 500 bytes: "500-999"
|
if (startString.length && (startValue >= 0) && endString.length && (endValue >= startValue)) { // The second 500 bytes: "500-999"
|
||||||
_range.location = startValue;
|
_byteRange.location = startValue;
|
||||||
_range.length = endValue - startValue + 1;
|
_byteRange.length = endValue - startValue + 1;
|
||||||
} else if (startString.length && (startValue >= 0)) { // The bytes after 9500 bytes: "9500-"
|
} else if (startString.length && (startValue >= 0)) { // The bytes after 9500 bytes: "9500-"
|
||||||
_range.location = startValue;
|
_byteRange.location = startValue;
|
||||||
_range.length = NSUIntegerMax;
|
_byteRange.length = NSUIntegerMax;
|
||||||
} else if (endString.length && (endValue > 0)) { // The final 500 bytes: "-500"
|
} else if (endString.length && (endValue > 0)) { // The final 500 bytes: "-500"
|
||||||
_range.location = NSUIntegerMax;
|
_byteRange.location = NSUIntegerMax;
|
||||||
_range.length = endValue;
|
_byteRange.length = endValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((_range.location == NSUIntegerMax) && (_range.length == 0)) { // Ignore "Range" header if syntactically invalid
|
if ((_byteRange.location == NSUIntegerMax) && (_byteRange.length == 0)) { // Ignore "Range" header if syntactically invalid
|
||||||
GWS_LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url);
|
GWS_LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([[_headers objectForKey:@"Accept-Encoding"] rangeOfString:@"gzip"].location != NSNotFound) {
|
if ([[_headers objectForKey:@"Accept-Encoding"] rangeOfString:@"gzip"].location != NSNotFound) {
|
||||||
_gzipAccepted = YES;
|
_acceptsGzipContentEncoding = YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
_decoders = [[NSMutableArray alloc] init];
|
_decoders = [[NSMutableArray alloc] init];
|
||||||
@@ -251,11 +223,11 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)hasBody {
|
- (BOOL)hasBody {
|
||||||
return _type ? YES : NO;
|
return _contentType ? YES : NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)hasByteRange {
|
- (BOOL)hasByteRange {
|
||||||
return GCDWebServerIsValidByteRange(_range);
|
return GCDWebServerIsValidByteRange(_byteRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)attributeForKey:(NSString*)key {
|
- (id)attributeForKey:(NSString*)key {
|
||||||
@@ -284,7 +256,7 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)performOpen:(NSError**)error {
|
- (BOOL)performOpen:(NSError**)error {
|
||||||
GWS_DCHECK(_type);
|
GWS_DCHECK(_contentType);
|
||||||
GWS_DCHECK(_writer);
|
GWS_DCHECK(_writer);
|
||||||
if (_opened) {
|
if (_opened) {
|
||||||
GWS_DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
@@ -308,6 +280,14 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
|
|||||||
[_attributes setValue:attribute forKey:key];
|
[_attributes setValue:attribute forKey:key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSString*)localAddressString {
|
||||||
|
return GCDWebServerStringFromSockAddr(_localAddressData.bytes, YES);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString*)remoteAddressString {
|
||||||
|
return GCDWebServerStringFromSockAddr(_remoteAddressData.bytes, YES);
|
||||||
|
}
|
||||||
|
|
||||||
- (NSString*)description {
|
- (NSString*)description {
|
||||||
NSMutableString* description = [NSMutableString stringWithFormat:@"%@ %@", _method, _path];
|
NSMutableString* description = [NSMutableString stringWithFormat:@"%@ %@", _method, _path];
|
||||||
for (NSString* argument in [[_query allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
|
for (NSString* argument in [[_query allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -27,11 +27,13 @@
|
|||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GCDWebServerBodyReaderCompletionBlock is passed by GCDWebServer to the
|
* The GCDWebServerBodyReaderCompletionBlock is passed by GCDWebServer to the
|
||||||
* GCDWebServerBodyReader object when reading data from it asynchronously.
|
* GCDWebServerBodyReader object when reading data from it asynchronously.
|
||||||
*/
|
*/
|
||||||
typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* error);
|
typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* _Nullable data, NSError* _Nullable error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This protocol is used by the GCDWebServerConnection to communicate with
|
* This protocol is used by the GCDWebServerConnection to communicate with
|
||||||
@@ -62,7 +64,7 @@ typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* err
|
|||||||
* or an empty NSData there is no more body data, or nil on error and set
|
* or an empty NSData there is no more body data, or nil on error and set
|
||||||
* the "error" argument which is guaranteed to be non-NULL.
|
* the "error" argument which is guaranteed to be non-NULL.
|
||||||
*/
|
*/
|
||||||
- (NSData*)readData:(NSError**)error;
|
- (nullable NSData*)readData:(NSError**)error;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called after all body data has been sent.
|
* This method is called after all body data has been sent.
|
||||||
@@ -102,7 +104,7 @@ typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* err
|
|||||||
*
|
*
|
||||||
* @warning This property must be set if a body is present.
|
* @warning This property must be set if a body is present.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, copy) NSString* contentType;
|
@property(nonatomic, copy, nullable) NSString* contentType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the content length for the body of the response. If a body is present
|
* Sets the content length for the body of the response. If a body is present
|
||||||
@@ -136,14 +138,14 @@ typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* err
|
|||||||
*
|
*
|
||||||
* The default value is nil.
|
* The default value is nil.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, retain) NSDate* lastModifiedDate;
|
@property(nonatomic, nullable) NSDate* lastModifiedDate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the ETag for the response using the "ETag" header.
|
* Sets the ETag for the response using the "ETag" header.
|
||||||
*
|
*
|
||||||
* The default value is nil.
|
* The default value is nil.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, copy) NSString* eTag;
|
@property(nonatomic, copy, nullable) NSString* eTag;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables gzip encoding for the response body.
|
* Enables gzip encoding for the response body.
|
||||||
@@ -174,7 +176,7 @@ typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* err
|
|||||||
* @warning Do not attempt to override the primary headers used
|
* @warning Do not attempt to override the primary headers used
|
||||||
* by GCDWebServerResponse like "Content-Type", "ETag", etc...
|
* by GCDWebServerResponse like "Content-Type", "ETag", etc...
|
||||||
*/
|
*/
|
||||||
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header;
|
- (void)setValue:(nullable NSString*)value forAdditionalHeader:(NSString*)header;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method that checks if the contentType property is defined.
|
* Convenience method that checks if the contentType property is defined.
|
||||||
@@ -206,3 +208,5 @@ typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* err
|
|||||||
- (instancetype)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent;
|
- (instancetype)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -37,22 +37,17 @@
|
|||||||
#define kGZipInitialBufferSize (256 * 1024)
|
#define kGZipInitialBufferSize (256 * 1024)
|
||||||
|
|
||||||
@interface GCDWebServerBodyEncoder : NSObject <GCDWebServerBodyReader>
|
@interface GCDWebServerBodyEncoder : NSObject <GCDWebServerBodyReader>
|
||||||
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader;
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerGZipEncoder : GCDWebServerBodyEncoder
|
@interface GCDWebServerGZipEncoder : GCDWebServerBodyEncoder
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerBodyEncoder () {
|
@implementation GCDWebServerBodyEncoder {
|
||||||
@private
|
|
||||||
GCDWebServerResponse* __unsafe_unretained _response;
|
GCDWebServerResponse* __unsafe_unretained _response;
|
||||||
id<GCDWebServerBodyReader> __unsafe_unretained _reader;
|
id<GCDWebServerBodyReader> __unsafe_unretained _reader;
|
||||||
}
|
}
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerBodyEncoder
|
- (instancetype)initWithResponse:(GCDWebServerResponse* _Nonnull)response reader:(id<GCDWebServerBodyReader> _Nonnull)reader {
|
||||||
|
|
||||||
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader {
|
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
_response = response;
|
_response = response;
|
||||||
_reader = reader;
|
_reader = reader;
|
||||||
@@ -74,16 +69,12 @@
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerGZipEncoder () {
|
@implementation GCDWebServerGZipEncoder {
|
||||||
@private
|
|
||||||
z_stream _stream;
|
z_stream _stream;
|
||||||
BOOL _finished;
|
BOOL _finished;
|
||||||
}
|
}
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerGZipEncoder
|
- (instancetype)initWithResponse:(GCDWebServerResponse* _Nonnull)response reader:(id<GCDWebServerBodyReader> _Nonnull)reader {
|
||||||
|
|
||||||
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader {
|
|
||||||
if ((self = [super initWithResponse:response reader:reader])) {
|
if ((self = [super initWithResponse:response reader:reader])) {
|
||||||
response.contentLength = NSUIntegerMax; // Make sure "Content-Length" header is not set since we don't know it
|
response.contentLength = NSUIntegerMax; // Make sure "Content-Length" header is not set since we don't know it
|
||||||
[response setValue:@"gzip" forAdditionalHeader:@"Content-Encoding"];
|
[response setValue:@"gzip" forAdditionalHeader:@"Content-Encoding"];
|
||||||
@@ -157,55 +148,38 @@
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerResponse () {
|
@implementation GCDWebServerResponse {
|
||||||
@private
|
|
||||||
NSString* _type;
|
|
||||||
NSUInteger _length;
|
|
||||||
NSInteger _status;
|
|
||||||
NSUInteger _maxAge;
|
|
||||||
NSDate* _lastModified;
|
|
||||||
NSString* _eTag;
|
|
||||||
NSMutableDictionary* _headers;
|
|
||||||
BOOL _chunked;
|
|
||||||
BOOL _gzipped;
|
|
||||||
|
|
||||||
BOOL _opened;
|
BOOL _opened;
|
||||||
NSMutableArray* _encoders;
|
NSMutableArray<GCDWebServerBodyEncoder*>* _encoders;
|
||||||
id<GCDWebServerBodyReader> __unsafe_unretained _reader;
|
id<GCDWebServerBodyReader> __unsafe_unretained _reader;
|
||||||
}
|
}
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerResponse
|
|
||||||
|
|
||||||
@synthesize contentType=_type, contentLength=_length, statusCode=_status, cacheControlMaxAge=_maxAge, lastModifiedDate=_lastModified, eTag=_eTag,
|
|
||||||
gzipContentEncodingEnabled=_gzipped, additionalHeaders=_headers;
|
|
||||||
|
|
||||||
+ (instancetype)response {
|
+ (instancetype)response {
|
||||||
return [[[self class] alloc] init];
|
return [(GCDWebServerResponse*)[[self class] alloc] init];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)init {
|
- (instancetype)init {
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
_type = nil;
|
_contentType = nil;
|
||||||
_length = NSUIntegerMax;
|
_contentLength = NSUIntegerMax;
|
||||||
_status = kGCDWebServerHTTPStatusCode_OK;
|
_statusCode = kGCDWebServerHTTPStatusCode_OK;
|
||||||
_maxAge = 0;
|
_cacheControlMaxAge = 0;
|
||||||
_headers = [[NSMutableDictionary alloc] init];
|
_additionalHeaders = [[NSMutableDictionary alloc] init];
|
||||||
_encoders = [[NSMutableArray alloc] init];
|
_encoders = [[NSMutableArray alloc] init];
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header {
|
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header {
|
||||||
[_headers setValue:value forKey:header];
|
[_additionalHeaders setValue:value forKey:header];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)hasBody {
|
- (BOOL)hasBody {
|
||||||
return _type ? YES : NO;
|
return _contentType ? YES : NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)usesChunkedTransferEncoding {
|
- (BOOL)usesChunkedTransferEncoding {
|
||||||
return (_type != nil) && (_length == NSUIntegerMax);
|
return (_contentType != nil) && (_contentLength == NSUIntegerMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)open:(NSError**)error {
|
- (BOOL)open:(NSError**)error {
|
||||||
@@ -222,7 +196,7 @@
|
|||||||
|
|
||||||
- (void)prepareForReading {
|
- (void)prepareForReading {
|
||||||
_reader = self;
|
_reader = self;
|
||||||
if (_gzipped) {
|
if (_gzipContentEncodingEnabled) {
|
||||||
GCDWebServerGZipEncoder* encoder = [[GCDWebServerGZipEncoder alloc] initWithResponse:self reader:_reader];
|
GCDWebServerGZipEncoder* encoder = [[GCDWebServerGZipEncoder alloc] initWithResponse:self reader:_reader];
|
||||||
[_encoders addObject:encoder];
|
[_encoders addObject:encoder];
|
||||||
_reader = encoder;
|
_reader = encoder;
|
||||||
@@ -230,7 +204,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)performOpen:(NSError**)error {
|
- (BOOL)performOpen:(NSError**)error {
|
||||||
GWS_DCHECK(_type);
|
GWS_DCHECK(_contentType);
|
||||||
GWS_DCHECK(_reader);
|
GWS_DCHECK(_reader);
|
||||||
if (_opened) {
|
if (_opened) {
|
||||||
GWS_DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
@@ -241,8 +215,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block {
|
- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block {
|
||||||
|
GWS_DCHECK(_opened);
|
||||||
if ([_reader respondsToSelector:@selector(asyncReadDataWithCompletion:)]) {
|
if ([_reader respondsToSelector:@selector(asyncReadDataWithCompletion:)]) {
|
||||||
[_reader asyncReadDataWithCompletion:block];
|
[_reader asyncReadDataWithCompletion:[block copy]];
|
||||||
} else {
|
} else {
|
||||||
NSError* error = nil;
|
NSError* error = nil;
|
||||||
NSData* data = [_reader readData:&error];
|
NSData* data = [_reader readData:&error];
|
||||||
@@ -256,24 +231,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (NSString*)description {
|
- (NSString*)description {
|
||||||
NSMutableString* description = [NSMutableString stringWithFormat:@"Status Code = %i", (int)_status];
|
NSMutableString* description = [NSMutableString stringWithFormat:@"Status Code = %i", (int)_statusCode];
|
||||||
if (_type) {
|
if (_contentType) {
|
||||||
[description appendFormat:@"\nContent Type = %@", _type];
|
[description appendFormat:@"\nContent Type = %@", _contentType];
|
||||||
}
|
}
|
||||||
if (_length != NSUIntegerMax) {
|
if (_contentLength != NSUIntegerMax) {
|
||||||
[description appendFormat:@"\nContent Length = %lu", (unsigned long)_length];
|
[description appendFormat:@"\nContent Length = %lu", (unsigned long)_contentLength];
|
||||||
}
|
}
|
||||||
[description appendFormat:@"\nCache Control Max Age = %lu", (unsigned long)_maxAge];
|
[description appendFormat:@"\nCache Control Max Age = %lu", (unsigned long)_cacheControlMaxAge];
|
||||||
if (_lastModified) {
|
if (_lastModifiedDate) {
|
||||||
[description appendFormat:@"\nLast Modified Date = %@", _lastModified];
|
[description appendFormat:@"\nLast Modified Date = %@", _lastModifiedDate];
|
||||||
}
|
}
|
||||||
if (_eTag) {
|
if (_eTag) {
|
||||||
[description appendFormat:@"\nETag = %@", _eTag];
|
[description appendFormat:@"\nETag = %@", _eTag];
|
||||||
}
|
}
|
||||||
if (_headers.count) {
|
if (_additionalHeaders.count) {
|
||||||
[description appendString:@"\n"];
|
[description appendString:@"\n"];
|
||||||
for (NSString* header in [[_headers allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
|
for (NSString* header in [[_additionalHeaders allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
|
||||||
[description appendFormat:@"\n%@: %@", header, [_headers objectForKey:header]];
|
[description appendFormat:@"\n%@: %@", header, [_additionalHeaders objectForKey:header]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return description;
|
return description;
|
||||||
@@ -284,11 +259,11 @@
|
|||||||
@implementation GCDWebServerResponse (Extensions)
|
@implementation GCDWebServerResponse (Extensions)
|
||||||
|
|
||||||
+ (instancetype)responseWithStatusCode:(NSInteger)statusCode {
|
+ (instancetype)responseWithStatusCode:(NSInteger)statusCode {
|
||||||
return [[self alloc] initWithStatusCode:statusCode];
|
return [(GCDWebServerResponse*)[self alloc] initWithStatusCode:statusCode];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent {
|
+ (instancetype)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent {
|
||||||
return [[self alloc] initWithRedirect:location permanent:permanent];
|
return [(GCDWebServerResponse*)[self alloc] initWithRedirect:location permanent:permanent];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithStatusCode:(NSInteger)statusCode {
|
- (instancetype)initWithStatusCode:(NSInteger)statusCode {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
#import "GCDWebServerRequest.h"
|
#import "GCDWebServerRequest.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GCDWebServerDataRequest subclass of GCDWebServerRequest stores the body
|
* The GCDWebServerDataRequest subclass of GCDWebServerRequest stores the body
|
||||||
* of the HTTP request in memory.
|
* of the HTTP request in memory.
|
||||||
@@ -49,12 +51,14 @@
|
|||||||
* The text encoding used to interpret the data is extracted from the
|
* The text encoding used to interpret the data is extracted from the
|
||||||
* "Content-Type" header or defaults to UTF-8.
|
* "Content-Type" header or defaults to UTF-8.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, readonly) NSString* text;
|
@property(nonatomic, readonly, nullable) NSString* text;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the data for the request body interpreted as a JSON object. If the
|
* Returns the data for the request body interpreted as a JSON object. If the
|
||||||
* content type of the body is not JSON, or if an error occurs, nil is returned.
|
* content type of the body is not JSON, or if an error occurs, nil is returned.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, readonly) id jsonObject;
|
@property(nonatomic, readonly, nullable) id jsonObject;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -31,18 +31,14 @@
|
|||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
#import "GCDWebServerPrivate.h"
|
||||||
|
|
||||||
@interface GCDWebServerDataRequest () {
|
@interface GCDWebServerDataRequest ()
|
||||||
@private
|
@property(nonatomic) NSMutableData* data;
|
||||||
NSMutableData* _data;
|
@end
|
||||||
|
|
||||||
|
@implementation GCDWebServerDataRequest {
|
||||||
NSString* _text;
|
NSString* _text;
|
||||||
id _jsonObject;
|
id _jsonObject;
|
||||||
}
|
}
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerDataRequest
|
|
||||||
|
|
||||||
@synthesize data=_data;
|
|
||||||
|
|
||||||
- (BOOL)open:(NSError**)error {
|
- (BOOL)open:(NSError**)error {
|
||||||
if (self.contentLength != NSUIntegerMax) {
|
if (self.contentLength != NSUIntegerMax) {
|
||||||
@@ -72,7 +68,7 @@
|
|||||||
NSMutableString* description = [NSMutableString stringWithString:[super description]];
|
NSMutableString* description = [NSMutableString stringWithString:[super description]];
|
||||||
if (_data) {
|
if (_data) {
|
||||||
[description appendString:@"\n\n"];
|
[description appendString:@"\n\n"];
|
||||||
[description appendString:GCDWebServerDescribeData(_data, self.contentType)];
|
[description appendString:GCDWebServerDescribeData(_data, (NSString*)self.contentType)];
|
||||||
}
|
}
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
#import "GCDWebServerRequest.h"
|
#import "GCDWebServerRequest.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GCDWebServerFileRequest subclass of GCDWebServerRequest stores the body
|
* The GCDWebServerFileRequest subclass of GCDWebServerRequest stores the body
|
||||||
* of the HTTP request to a file on disk.
|
* of the HTTP request to a file on disk.
|
||||||
@@ -43,3 +45,5 @@
|
|||||||
@property(nonatomic, readonly) NSString* temporaryPath;
|
@property(nonatomic, readonly) NSString* temporaryPath;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -31,18 +31,11 @@
|
|||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
#import "GCDWebServerPrivate.h"
|
||||||
|
|
||||||
@interface GCDWebServerFileRequest () {
|
@implementation GCDWebServerFileRequest {
|
||||||
@private
|
|
||||||
NSString* _temporaryPath;
|
|
||||||
int _file;
|
int _file;
|
||||||
}
|
}
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerFileRequest
|
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary<NSString*, NSString*>*)headers path:(NSString*)path query:(NSDictionary<NSString*, NSString*>*)query {
|
||||||
|
|
||||||
@synthesize temporaryPath=_temporaryPath;
|
|
||||||
|
|
||||||
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
|
|
||||||
if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) {
|
if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) {
|
||||||
_temporaryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
|
_temporaryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
#import "GCDWebServerRequest.h"
|
#import "GCDWebServerRequest.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GCDWebServerMultiPart class is an abstract class that wraps the content
|
* The GCDWebServerMultiPart class is an abstract class that wraps the content
|
||||||
* of a part.
|
* of a part.
|
||||||
@@ -69,7 +71,7 @@
|
|||||||
* The text encoding used to interpret the data is extracted from the
|
* The text encoding used to interpret the data is extracted from the
|
||||||
* "Content-Type" header or defaults to UTF-8.
|
* "Content-Type" header or defaults to UTF-8.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, readonly) NSString* string;
|
@property(nonatomic, readonly, nullable) NSString* string;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -105,13 +107,13 @@
|
|||||||
* Returns the argument parts from the multipart encoded form as
|
* Returns the argument parts from the multipart encoded form as
|
||||||
* name / GCDWebServerMultiPartArgument pairs.
|
* name / GCDWebServerMultiPartArgument pairs.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, readonly) NSArray* arguments;
|
@property(nonatomic, readonly) NSArray<GCDWebServerMultiPartArgument*>* arguments;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the files parts from the multipart encoded form as
|
* Returns the files parts from the multipart encoded form as
|
||||||
* name / GCDWebServerMultiPartFile pairs.
|
* name / GCDWebServerMultiPartFile pairs.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, readonly) NSArray* files;
|
@property(nonatomic, readonly) NSArray<GCDWebServerMultiPartFile*>* files;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the MIME type for multipart encoded forms
|
* Returns the MIME type for multipart encoded forms
|
||||||
@@ -122,11 +124,13 @@
|
|||||||
/**
|
/**
|
||||||
* Returns the first argument for a given control name or nil if not found.
|
* Returns the first argument for a given control name or nil if not found.
|
||||||
*/
|
*/
|
||||||
- (GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name;
|
- (nullable GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the first file for a given control name or nil if not found.
|
* Returns the first file for a given control name or nil if not found.
|
||||||
*/
|
*/
|
||||||
- (GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name;
|
- (nullable GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -42,50 +42,28 @@ typedef enum {
|
|||||||
} ParserState;
|
} ParserState;
|
||||||
|
|
||||||
@interface GCDWebServerMIMEStreamParser : NSObject
|
@interface GCDWebServerMIMEStreamParser : NSObject
|
||||||
- (id)initWithBoundary:(NSString*)boundary defaultControlName:(NSString*)name arguments:(NSMutableArray*)arguments files:(NSMutableArray*)files;
|
|
||||||
- (BOOL)appendBytes:(const void*)bytes length:(NSUInteger)length;
|
|
||||||
- (BOOL)isAtEnd;
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
static NSData* _newlineData = nil;
|
static NSData* _newlineData = nil;
|
||||||
static NSData* _newlinesData = nil;
|
static NSData* _newlinesData = nil;
|
||||||
static NSData* _dashNewlineData = nil;
|
static NSData* _dashNewlineData = nil;
|
||||||
|
|
||||||
@interface GCDWebServerMultiPart () {
|
|
||||||
@private
|
|
||||||
NSString* _controlName;
|
|
||||||
NSString* _contentType;
|
|
||||||
NSString* _mimeType;
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerMultiPart
|
@implementation GCDWebServerMultiPart
|
||||||
|
|
||||||
@synthesize controlName=_controlName, contentType=_contentType, mimeType=_mimeType;
|
- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type {
|
||||||
|
|
||||||
- (id)initWithControlName:(NSString*)name contentType:(NSString*)type {
|
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
_controlName = [name copy];
|
_controlName = [name copy];
|
||||||
_contentType = [type copy];
|
_contentType = [type copy];
|
||||||
_mimeType = GCDWebServerTruncateHeaderValue(_contentType);
|
_mimeType = (NSString*)GCDWebServerTruncateHeaderValue(_contentType);
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerMultiPartArgument () {
|
|
||||||
@private
|
|
||||||
NSData* _data;
|
|
||||||
NSString* _string;
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerMultiPartArgument
|
@implementation GCDWebServerMultiPartArgument
|
||||||
|
|
||||||
@synthesize data=_data, string=_string;
|
- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type data:(NSData* _Nonnull)data {
|
||||||
|
|
||||||
- (id)initWithControlName:(NSString*)name contentType:(NSString*)type data:(NSData*)data {
|
|
||||||
if ((self = [super initWithControlName:name contentType:type])) {
|
if ((self = [super initWithControlName:name contentType:type])) {
|
||||||
_data = data;
|
_data = data;
|
||||||
|
|
||||||
@@ -103,18 +81,9 @@ static NSData* _dashNewlineData = nil;
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerMultiPartFile () {
|
|
||||||
@private
|
|
||||||
NSString* _fileName;
|
|
||||||
NSString* _temporaryPath;
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerMultiPartFile
|
@implementation GCDWebServerMultiPartFile
|
||||||
|
|
||||||
@synthesize fileName=_fileName, temporaryPath=_temporaryPath;
|
- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type fileName:(NSString* _Nonnull)fileName temporaryPath:(NSString* _Nonnull)temporaryPath {
|
||||||
|
|
||||||
- (id)initWithControlName:(NSString*)name contentType:(NSString*)type fileName:(NSString*)fileName temporaryPath:(NSString*)temporaryPath {
|
|
||||||
if ((self = [super initWithControlName:name contentType:type])) {
|
if ((self = [super initWithControlName:name contentType:type])) {
|
||||||
_fileName = [fileName copy];
|
_fileName = [fileName copy];
|
||||||
_temporaryPath = [temporaryPath copy];
|
_temporaryPath = [temporaryPath copy];
|
||||||
@@ -132,14 +101,13 @@ static NSData* _dashNewlineData = nil;
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerMIMEStreamParser () {
|
@implementation GCDWebServerMIMEStreamParser {
|
||||||
@private
|
|
||||||
NSData* _boundary;
|
NSData* _boundary;
|
||||||
NSString* _defaultcontrolName;
|
NSString* _defaultcontrolName;
|
||||||
ParserState _state;
|
ParserState _state;
|
||||||
NSMutableData* _data;
|
NSMutableData* _data;
|
||||||
NSMutableArray* _arguments;
|
NSMutableArray<GCDWebServerMultiPartArgument*>* _arguments;
|
||||||
NSMutableArray* _files;
|
NSMutableArray<GCDWebServerMultiPartFile*>* _files;
|
||||||
|
|
||||||
NSString* _controlName;
|
NSString* _controlName;
|
||||||
NSString* _fileName;
|
NSString* _fileName;
|
||||||
@@ -148,9 +116,6 @@ static NSData* _dashNewlineData = nil;
|
|||||||
int _tmpFile;
|
int _tmpFile;
|
||||||
GCDWebServerMIMEStreamParser* _subParser;
|
GCDWebServerMIMEStreamParser* _subParser;
|
||||||
}
|
}
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerMIMEStreamParser
|
|
||||||
|
|
||||||
+ (void)initialize {
|
+ (void)initialize {
|
||||||
if (_newlineData == nil) {
|
if (_newlineData == nil) {
|
||||||
@@ -167,7 +132,7 @@ static NSData* _dashNewlineData = nil;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)initWithBoundary:(NSString*)boundary defaultControlName:(NSString*)name arguments:(NSMutableArray*)arguments files:(NSMutableArray*)files {
|
- (instancetype)initWithBoundary:(NSString* _Nonnull)boundary defaultControlName:(NSString* _Nullable)name arguments:(NSMutableArray<GCDWebServerMultiPartArgument*>* _Nonnull)arguments files:(NSMutableArray<GCDWebServerMultiPartFile*>* _Nonnull)files {
|
||||||
NSData* data = boundary.length ? [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding] : nil;
|
NSData* data = boundary.length ? [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding] : nil;
|
||||||
if (data == nil) {
|
if (data == nil) {
|
||||||
GWS_DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
@@ -198,7 +163,6 @@ static NSData* _dashNewlineData = nil;
|
|||||||
if (_state == kParserState_Headers) {
|
if (_state == kParserState_Headers) {
|
||||||
NSRange range = [_data rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _data.length)];
|
NSRange range = [_data rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _data.length)];
|
||||||
if (range.location != NSNotFound) {
|
if (range.location != NSNotFound) {
|
||||||
|
|
||||||
_controlName = nil;
|
_controlName = nil;
|
||||||
_fileName = nil;
|
_fileName = nil;
|
||||||
_contentType = nil;
|
_contentType = nil;
|
||||||
@@ -269,7 +233,6 @@ static NSData* _dashNewlineData = nil;
|
|||||||
NSRange subRange1 = [_data rangeOfData:_newlineData options:NSDataSearchAnchored range:subRange];
|
NSRange subRange1 = [_data rangeOfData:_newlineData options:NSDataSearchAnchored range:subRange];
|
||||||
NSRange subRange2 = [_data rangeOfData:_dashNewlineData options:NSDataSearchAnchored range:subRange];
|
NSRange subRange2 = [_data rangeOfData:_dashNewlineData options:NSDataSearchAnchored range:subRange];
|
||||||
if ((subRange1.location != NSNotFound) || (subRange2.location != NSNotFound)) {
|
if ((subRange1.location != NSNotFound) || (subRange2.location != NSNotFound)) {
|
||||||
|
|
||||||
if (_state == kParserState_Content) {
|
if (_state == kParserState_Content) {
|
||||||
const void* dataBytes = _data.bytes;
|
const void* dataBytes = _data.bytes;
|
||||||
NSUInteger dataLength = range.location - 2;
|
NSUInteger dataLength = range.location - 2;
|
||||||
@@ -348,23 +311,20 @@ static NSData* _dashNewlineData = nil;
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerMultiPartFormRequest () {
|
@interface GCDWebServerMultiPartFormRequest ()
|
||||||
@private
|
@property(nonatomic) NSMutableArray<GCDWebServerMultiPartArgument*>* arguments;
|
||||||
GCDWebServerMIMEStreamParser* _parser;
|
@property(nonatomic) NSMutableArray<GCDWebServerMultiPartFile*>* files;
|
||||||
NSMutableArray* _arguments;
|
|
||||||
NSMutableArray* _files;
|
|
||||||
}
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation GCDWebServerMultiPartFormRequest
|
@implementation GCDWebServerMultiPartFormRequest {
|
||||||
|
GCDWebServerMIMEStreamParser* _parser;
|
||||||
@synthesize arguments=_arguments, files=_files;
|
}
|
||||||
|
|
||||||
+ (NSString*)mimeType {
|
+ (NSString*)mimeType {
|
||||||
return @"multipart/form-data";
|
return @"multipart/form-data";
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
|
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary<NSString*, NSString*>*)headers path:(NSString*)path query:(NSDictionary<NSString*, NSString*>*)query {
|
||||||
if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) {
|
if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) {
|
||||||
_arguments = [[NSMutableArray alloc] init];
|
_arguments = [[NSMutableArray alloc] init];
|
||||||
_files = [[NSMutableArray alloc] init];
|
_files = [[NSMutableArray alloc] init];
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
#import "GCDWebServerDataRequest.h"
|
#import "GCDWebServerDataRequest.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GCDWebServerURLEncodedFormRequest subclass of GCDWebServerRequest
|
* The GCDWebServerURLEncodedFormRequest subclass of GCDWebServerRequest
|
||||||
* parses the body of the HTTP request as a URL encoded form using
|
* parses the body of the HTTP request as a URL encoded form using
|
||||||
@@ -40,7 +42,7 @@
|
|||||||
* The text encoding used to interpret the data is extracted from the
|
* The text encoding used to interpret the data is extracted from the
|
||||||
* "Content-Type" header or defaults to UTF-8.
|
* "Content-Type" header or defaults to UTF-8.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, readonly) NSDictionary* arguments;
|
@property(nonatomic, readonly) NSDictionary<NSString*, NSString*>* arguments;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the MIME type for URL encoded forms
|
* Returns the MIME type for URL encoded forms
|
||||||
@@ -49,3 +51,5 @@
|
|||||||
+ (NSString*)mimeType;
|
+ (NSString*)mimeType;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -31,16 +31,8 @@
|
|||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
#import "GCDWebServerPrivate.h"
|
||||||
|
|
||||||
@interface GCDWebServerURLEncodedFormRequest () {
|
|
||||||
@private
|
|
||||||
NSDictionary* _arguments;
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerURLEncodedFormRequest
|
@implementation GCDWebServerURLEncodedFormRequest
|
||||||
|
|
||||||
@synthesize arguments=_arguments;
|
|
||||||
|
|
||||||
+ (NSString*)mimeType {
|
+ (NSString*)mimeType {
|
||||||
return @"application/x-www-form-urlencoded";
|
return @"application/x-www-form-urlencoded";
|
||||||
}
|
}
|
||||||
@@ -53,8 +45,6 @@
|
|||||||
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
|
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
|
||||||
NSString* string = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)];
|
NSString* string = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)];
|
||||||
_arguments = GCDWebServerParseURLEncodedForm(string);
|
_arguments = GCDWebServerParseURLEncodedForm(string);
|
||||||
GWS_DCHECK(_arguments);
|
|
||||||
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -27,11 +27,14 @@
|
|||||||
|
|
||||||
#import "GCDWebServerResponse.h"
|
#import "GCDWebServerResponse.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GCDWebServerDataResponse subclass of GCDWebServerResponse reads the body
|
* The GCDWebServerDataResponse subclass of GCDWebServerResponse reads the body
|
||||||
* of the HTTP response from memory.
|
* of the HTTP response from memory.
|
||||||
*/
|
*/
|
||||||
@interface GCDWebServerDataResponse : GCDWebServerResponse
|
@interface GCDWebServerDataResponse : GCDWebServerResponse
|
||||||
|
@property(nonatomic, copy) NSString* contentType; // Redeclare as non-null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a response with data in memory and a given content type.
|
* Creates a response with data in memory and a given content type.
|
||||||
@@ -50,40 +53,40 @@
|
|||||||
/**
|
/**
|
||||||
* Creates a data response from text encoded using UTF-8.
|
* Creates a data response from text encoded using UTF-8.
|
||||||
*/
|
*/
|
||||||
+ (instancetype)responseWithText:(NSString*)text;
|
+ (nullable instancetype)responseWithText:(NSString*)text;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a data response from HTML encoded using UTF-8.
|
* Creates a data response from HTML encoded using UTF-8.
|
||||||
*/
|
*/
|
||||||
+ (instancetype)responseWithHTML:(NSString*)html;
|
+ (nullable instancetype)responseWithHTML:(NSString*)html;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a data response from an HTML template encoded using UTF-8.
|
* Creates a data response from an HTML template encoded using UTF-8.
|
||||||
* See -initWithHTMLTemplate:variables: for details.
|
* See -initWithHTMLTemplate:variables: for details.
|
||||||
*/
|
*/
|
||||||
+ (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables;
|
+ (nullable instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary<NSString*, NSString*>*)variables;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a data response from a serialized JSON object and the default
|
* Creates a data response from a serialized JSON object and the default
|
||||||
* "application/json" content type.
|
* "application/json" content type.
|
||||||
*/
|
*/
|
||||||
+ (instancetype)responseWithJSONObject:(id)object;
|
+ (nullable instancetype)responseWithJSONObject:(id)object;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a data response from a serialized JSON object and a custom
|
* Creates a data response from a serialized JSON object and a custom
|
||||||
* content type.
|
* content type.
|
||||||
*/
|
*/
|
||||||
+ (instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type;
|
+ (nullable instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a data response from text encoded using UTF-8.
|
* Initializes a data response from text encoded using UTF-8.
|
||||||
*/
|
*/
|
||||||
- (instancetype)initWithText:(NSString*)text;
|
- (nullable instancetype)initWithText:(NSString*)text;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a data response from HTML encoded using UTF-8.
|
* Initializes a data response from HTML encoded using UTF-8.
|
||||||
*/
|
*/
|
||||||
- (instancetype)initWithHTML:(NSString*)html;
|
- (nullable instancetype)initWithHTML:(NSString*)html;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a data response from an HTML template encoded using UTF-8.
|
* Initializes a data response from an HTML template encoded using UTF-8.
|
||||||
@@ -91,18 +94,20 @@
|
|||||||
* All occurences of "%variable%" within the HTML template are replaced with
|
* All occurences of "%variable%" within the HTML template are replaced with
|
||||||
* their corresponding values.
|
* their corresponding values.
|
||||||
*/
|
*/
|
||||||
- (instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables;
|
- (nullable instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary<NSString*, NSString*>*)variables;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a data response from a serialized JSON object and the default
|
* Initializes a data response from a serialized JSON object and the default
|
||||||
* "application/json" content type.
|
* "application/json" content type.
|
||||||
*/
|
*/
|
||||||
- (instancetype)initWithJSONObject:(id)object;
|
- (nullable instancetype)initWithJSONObject:(id)object;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a data response from a serialized JSON object and a custom
|
* Initializes a data response from a serialized JSON object and a custom
|
||||||
* content type.
|
* content type.
|
||||||
*/
|
*/
|
||||||
- (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type;
|
- (nullable instancetype)initWithJSONObject:(id)object contentType:(NSString*)type;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -31,25 +31,18 @@
|
|||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
#import "GCDWebServerPrivate.h"
|
||||||
|
|
||||||
@interface GCDWebServerDataResponse () {
|
@implementation GCDWebServerDataResponse {
|
||||||
@private
|
|
||||||
NSData* _data;
|
NSData* _data;
|
||||||
BOOL _done;
|
BOOL _done;
|
||||||
}
|
}
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerDataResponse
|
@dynamic contentType;
|
||||||
|
|
||||||
+ (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type {
|
+ (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type {
|
||||||
return [[[self class] alloc] initWithData:data contentType:type];
|
return [(GCDWebServerDataResponse*)[[self class] alloc] initWithData:data contentType:type];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithData:(NSData*)data contentType:(NSString*)type {
|
- (instancetype)initWithData:(NSData*)data contentType:(NSString*)type {
|
||||||
if (data == nil) {
|
|
||||||
GWS_DNOT_REACHED();
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
_data = data;
|
_data = data;
|
||||||
|
|
||||||
@@ -82,23 +75,23 @@
|
|||||||
@implementation GCDWebServerDataResponse (Extensions)
|
@implementation GCDWebServerDataResponse (Extensions)
|
||||||
|
|
||||||
+ (instancetype)responseWithText:(NSString*)text {
|
+ (instancetype)responseWithText:(NSString*)text {
|
||||||
return [[self alloc] initWithText:text];
|
return [(GCDWebServerDataResponse*)[self alloc] initWithText:text];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)responseWithHTML:(NSString*)html {
|
+ (instancetype)responseWithHTML:(NSString*)html {
|
||||||
return [[self alloc] initWithHTML:html];
|
return [(GCDWebServerDataResponse*)[self alloc] initWithHTML:html];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables {
|
+ (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary<NSString*, NSString*>*)variables {
|
||||||
return [[self alloc] initWithHTMLTemplate:path variables:variables];
|
return [(GCDWebServerDataResponse*)[self alloc] initWithHTMLTemplate:path variables:variables];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)responseWithJSONObject:(id)object {
|
+ (instancetype)responseWithJSONObject:(id)object {
|
||||||
return [[self alloc] initWithJSONObject:object];
|
return [(GCDWebServerDataResponse*)[self alloc] initWithJSONObject:object];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type {
|
+ (instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type {
|
||||||
return [[self alloc] initWithJSONObject:object contentType:type];
|
return [(GCDWebServerDataResponse*)[self alloc] initWithJSONObject:object contentType:type];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithText:(NSString*)text {
|
- (instancetype)initWithText:(NSString*)text {
|
||||||
@@ -119,13 +112,12 @@
|
|||||||
return [self initWithData:data contentType:@"text/html; charset=utf-8"];
|
return [self initWithData:data contentType:@"text/html; charset=utf-8"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables {
|
- (instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary<NSString*, NSString*>*)variables {
|
||||||
NSMutableString* html = [[NSMutableString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
|
NSMutableString* html = [[NSMutableString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
|
||||||
[variables enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL* stop) {
|
[variables enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL* stop) {
|
||||||
[html replaceOccurrencesOfString:[NSString stringWithFormat:@"%%%@%%", key] withString:value options:0 range:NSMakeRange(0, html.length)];
|
[html replaceOccurrencesOfString:[NSString stringWithFormat:@"%%%@%%", key] withString:value options:0 range:NSMakeRange(0, html.length)];
|
||||||
}];
|
}];
|
||||||
id response = [self initWithHTML:html];
|
return [self initWithHTML:html];
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithJSONObject:(id)object {
|
- (instancetype)initWithJSONObject:(id)object {
|
||||||
@@ -135,6 +127,7 @@
|
|||||||
- (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type {
|
- (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type {
|
||||||
NSData* data = [NSJSONSerialization dataWithJSONObject:object options:0 error:NULL];
|
NSData* data = [NSJSONSerialization dataWithJSONObject:object options:0 error:NULL];
|
||||||
if (data == nil) {
|
if (data == nil) {
|
||||||
|
GWS_DNOT_REACHED();
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
return [self initWithData:data contentType:type];
|
return [self initWithData:data contentType:type];
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -28,6 +28,8 @@
|
|||||||
#import "GCDWebServerDataResponse.h"
|
#import "GCDWebServerDataResponse.h"
|
||||||
#import "GCDWebServerHTTPStatusCodes.h"
|
#import "GCDWebServerHTTPStatusCodes.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GCDWebServerDataResponse subclass of GCDWebServerDataResponse generates
|
* The GCDWebServerDataResponse subclass of GCDWebServerDataResponse generates
|
||||||
* an HTML body from an HTTP status code and an error message.
|
* an HTML body from an HTTP status code and an error message.
|
||||||
@@ -48,13 +50,13 @@
|
|||||||
* Creates a client error response with the corresponding HTTP status code
|
* Creates a client error response with the corresponding HTTP status code
|
||||||
* and an underlying NSError.
|
* and an underlying NSError.
|
||||||
*/
|
*/
|
||||||
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4);
|
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a server error response with the corresponding HTTP status code
|
* Creates a server error response with the corresponding HTTP status code
|
||||||
* and an underlying NSError.
|
* and an underlying NSError.
|
||||||
*/
|
*/
|
||||||
+ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4);
|
+ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a client error response with the corresponding HTTP status code.
|
* Initializes a client error response with the corresponding HTTP status code.
|
||||||
@@ -70,12 +72,14 @@
|
|||||||
* Initializes a client error response with the corresponding HTTP status code
|
* Initializes a client error response with the corresponding HTTP status code
|
||||||
* and an underlying NSError.
|
* and an underlying NSError.
|
||||||
*/
|
*/
|
||||||
- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4);
|
- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a server error response with the corresponding HTTP status code
|
* Initializes a server error response with the corresponding HTTP status code
|
||||||
* and an underlying NSError.
|
* and an underlying NSError.
|
||||||
*/
|
*/
|
||||||
- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4);
|
- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4);
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -31,17 +31,13 @@
|
|||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
#import "GCDWebServerPrivate.h"
|
||||||
|
|
||||||
@interface GCDWebServerErrorResponse ()
|
|
||||||
- (instancetype)initWithStatusCode:(NSInteger)statusCode underlyingError:(NSError*)underlyingError messageFormat:(NSString*)format arguments:(va_list)arguments;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerErrorResponse
|
@implementation GCDWebServerErrorResponse
|
||||||
|
|
||||||
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
|
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
|
||||||
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
||||||
va_list arguments;
|
va_list arguments;
|
||||||
va_start(arguments, format);
|
va_start(arguments, format);
|
||||||
GCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
|
GCDWebServerErrorResponse* response = [(GCDWebServerErrorResponse*)[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
|
||||||
va_end(arguments);
|
va_end(arguments);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@@ -50,7 +46,7 @@
|
|||||||
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
||||||
va_list arguments;
|
va_list arguments;
|
||||||
va_start(arguments, format);
|
va_start(arguments, format);
|
||||||
GCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
|
GCDWebServerErrorResponse* response = [(GCDWebServerErrorResponse*)[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
|
||||||
va_end(arguments);
|
va_end(arguments);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@@ -59,7 +55,7 @@
|
|||||||
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
||||||
va_list arguments;
|
va_list arguments;
|
||||||
va_start(arguments, format);
|
va_start(arguments, format);
|
||||||
GCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
|
GCDWebServerErrorResponse* response = [(GCDWebServerErrorResponse*)[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
|
||||||
va_end(arguments);
|
va_end(arguments);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@@ -68,7 +64,7 @@
|
|||||||
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
||||||
va_list arguments;
|
va_list arguments;
|
||||||
va_start(arguments, format);
|
va_start(arguments, format);
|
||||||
GCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
|
GCDWebServerErrorResponse* response = [(GCDWebServerErrorResponse*)[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
|
||||||
va_end(arguments);
|
va_end(arguments);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
#import "GCDWebServerResponse.h"
|
#import "GCDWebServerResponse.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GCDWebServerFileResponse subclass of GCDWebServerResponse reads the body
|
* The GCDWebServerFileResponse subclass of GCDWebServerResponse reads the body
|
||||||
* of the HTTP response from a file on disk.
|
* of the HTTP response from a file on disk.
|
||||||
@@ -36,17 +38,20 @@
|
|||||||
* metadata.
|
* metadata.
|
||||||
*/
|
*/
|
||||||
@interface GCDWebServerFileResponse : GCDWebServerResponse
|
@interface GCDWebServerFileResponse : GCDWebServerResponse
|
||||||
|
@property(nonatomic, copy) NSString* contentType; // Redeclare as non-null
|
||||||
|
@property(nonatomic) NSDate* lastModifiedDate; // Redeclare as non-null
|
||||||
|
@property(nonatomic, copy) NSString* eTag; // Redeclare as non-null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a response with the contents of a file.
|
* Creates a response with the contents of a file.
|
||||||
*/
|
*/
|
||||||
+ (instancetype)responseWithFile:(NSString*)path;
|
+ (nullable instancetype)responseWithFile:(NSString*)path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a response like +responseWithFile: and sets the "Content-Disposition"
|
* Creates a response like +responseWithFile: and sets the "Content-Disposition"
|
||||||
* HTTP header for a download if the "attachment" argument is YES.
|
* HTTP header for a download if the "attachment" argument is YES.
|
||||||
*/
|
*/
|
||||||
+ (instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment;
|
+ (nullable instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a response like +responseWithFile: but restricts the file contents
|
* Creates a response like +responseWithFile: but restricts the file contents
|
||||||
@@ -54,26 +59,26 @@
|
|||||||
*
|
*
|
||||||
* See -initWithFile:byteRange: for details.
|
* See -initWithFile:byteRange: for details.
|
||||||
*/
|
*/
|
||||||
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range;
|
+ (nullable instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a response like +responseWithFile:byteRange: and sets the
|
* Creates a response like +responseWithFile:byteRange: and sets the
|
||||||
* "Content-Disposition" HTTP header for a download if the "attachment"
|
* "Content-Disposition" HTTP header for a download if the "attachment"
|
||||||
* argument is YES.
|
* argument is YES.
|
||||||
*/
|
*/
|
||||||
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
|
+ (nullable instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a response with the contents of a file.
|
* Initializes a response with the contents of a file.
|
||||||
*/
|
*/
|
||||||
- (instancetype)initWithFile:(NSString*)path;
|
- (nullable instancetype)initWithFile:(NSString*)path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a response like +responseWithFile: and sets the
|
* Initializes a response like +responseWithFile: and sets the
|
||||||
* "Content-Disposition" HTTP header for a download if the "attachment"
|
* "Content-Disposition" HTTP header for a download if the "attachment"
|
||||||
* argument is YES.
|
* argument is YES.
|
||||||
*/
|
*/
|
||||||
- (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment;
|
- (nullable instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a response like -initWithFile: but restricts the file contents
|
* Initializes a response like -initWithFile: but restricts the file contents
|
||||||
@@ -86,11 +91,18 @@
|
|||||||
* This argument would typically be set to the value of the byteRange property
|
* This argument would typically be set to the value of the byteRange property
|
||||||
* of the current GCDWebServerRequest.
|
* of the current GCDWebServerRequest.
|
||||||
*/
|
*/
|
||||||
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range;
|
- (nullable instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is the designated initializer for the class.
|
* This method is the designated initializer for the class.
|
||||||
|
*
|
||||||
|
* If MIME type overrides are specified, they allow to customize the built-in
|
||||||
|
* mapping from extensions to MIME types. Keys of the dictionary must be lowercased
|
||||||
|
* file extensions without the period, and the values must be the corresponding
|
||||||
|
* MIME types.
|
||||||
*/
|
*/
|
||||||
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
|
- (nullable instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment mimeTypeOverrides:(nullable NSDictionary<NSString*, NSString*>*)overrides;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -35,50 +35,48 @@
|
|||||||
|
|
||||||
#define kFileReadBufferSize (32 * 1024)
|
#define kFileReadBufferSize (32 * 1024)
|
||||||
|
|
||||||
@interface GCDWebServerFileResponse () {
|
@implementation GCDWebServerFileResponse {
|
||||||
@private
|
|
||||||
NSString* _path;
|
NSString* _path;
|
||||||
NSUInteger _offset;
|
NSUInteger _offset;
|
||||||
NSUInteger _size;
|
NSUInteger _size;
|
||||||
int _file;
|
int _file;
|
||||||
}
|
}
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerFileResponse
|
@dynamic contentType, lastModifiedDate, eTag;
|
||||||
|
|
||||||
+ (instancetype)responseWithFile:(NSString*)path {
|
+ (instancetype)responseWithFile:(NSString*)path {
|
||||||
return [[[self class] alloc] initWithFile:path];
|
return [(GCDWebServerFileResponse*)[[self class] alloc] initWithFile:path];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment {
|
+ (instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment {
|
||||||
return [[[self class] alloc] initWithFile:path isAttachment:attachment];
|
return [(GCDWebServerFileResponse*)[[self class] alloc] initWithFile:path isAttachment:attachment];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range {
|
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range {
|
||||||
return [[[self class] alloc] initWithFile:path byteRange:range];
|
return [(GCDWebServerFileResponse*)[[self class] alloc] initWithFile:path byteRange:range];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment {
|
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment {
|
||||||
return [[[self class] alloc] initWithFile:path byteRange:range isAttachment:attachment];
|
return [(GCDWebServerFileResponse*)[[self class] alloc] initWithFile:path byteRange:range isAttachment:attachment mimeTypeOverrides:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithFile:(NSString*)path {
|
- (instancetype)initWithFile:(NSString*)path {
|
||||||
return [self initWithFile:path byteRange:NSMakeRange(NSUIntegerMax, 0) isAttachment:NO];
|
return [self initWithFile:path byteRange:NSMakeRange(NSUIntegerMax, 0) isAttachment:NO mimeTypeOverrides:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment {
|
- (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment {
|
||||||
return [self initWithFile:path byteRange:NSMakeRange(NSUIntegerMax, 0) isAttachment:attachment];
|
return [self initWithFile:path byteRange:NSMakeRange(NSUIntegerMax, 0) isAttachment:attachment mimeTypeOverrides:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range {
|
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range {
|
||||||
return [self initWithFile:path byteRange:range isAttachment:NO];
|
return [self initWithFile:path byteRange:range isAttachment:NO mimeTypeOverrides:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
|
static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
|
||||||
return [NSDate dateWithTimeIntervalSince1970:((NSTimeInterval)t->tv_sec + (NSTimeInterval)t->tv_nsec / 1000000000.0)];
|
return [NSDate dateWithTimeIntervalSince1970:((NSTimeInterval)t->tv_sec + (NSTimeInterval)t->tv_nsec / 1000000000.0)];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment {
|
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment mimeTypeOverrides:(NSDictionary<NSString*, NSString*>*)overrides {
|
||||||
struct stat info;
|
struct stat info;
|
||||||
if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) {
|
if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) {
|
||||||
GWS_DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
@@ -131,7 +129,7 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.contentType = GCDWebServerGetMimeTypeForExtension([_path pathExtension]);
|
self.contentType = GCDWebServerGetMimeTypeForExtension([_path pathExtension], overrides);
|
||||||
self.contentLength = _size;
|
self.contentLength = _size;
|
||||||
self.lastModifiedDate = _NSDateFromTimeSpec(&info.st_mtimespec);
|
self.lastModifiedDate = _NSDateFromTimeSpec(&info.st_mtimespec);
|
||||||
self.eTag = [NSString stringWithFormat:@"%llu/%li/%li", info.st_ino, info.st_mtimespec.tv_sec, info.st_mtimespec.tv_nsec];
|
self.eTag = [NSString stringWithFormat:@"%llu/%li/%li", info.st_ino, info.st_mtimespec.tv_sec, info.st_mtimespec.tv_nsec];
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -27,12 +27,14 @@
|
|||||||
|
|
||||||
#import "GCDWebServerResponse.h"
|
#import "GCDWebServerResponse.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GCDWebServerStreamBlock is called to stream the data for the HTTP body.
|
* The GCDWebServerStreamBlock is called to stream the data for the HTTP body.
|
||||||
* The block must return either a chunk of data, an empty NSData when done, or
|
* The block must return either a chunk of data, an empty NSData when done, or
|
||||||
* nil on error and set the "error" argument which is guaranteed to be non-NULL.
|
* nil on error and set the "error" argument which is guaranteed to be non-NULL.
|
||||||
*/
|
*/
|
||||||
typedef NSData* (^GCDWebServerStreamBlock)(NSError** error);
|
typedef NSData* _Nullable (^GCDWebServerStreamBlock)(NSError** error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GCDWebServerAsyncStreamBlock works like the GCDWebServerStreamBlock
|
* The GCDWebServerAsyncStreamBlock works like the GCDWebServerStreamBlock
|
||||||
@@ -51,6 +53,7 @@ typedef void (^GCDWebServerAsyncStreamBlock)(GCDWebServerBodyReaderCompletionBlo
|
|||||||
* the body of the HTTP response using a GCD block.
|
* the body of the HTTP response using a GCD block.
|
||||||
*/
|
*/
|
||||||
@interface GCDWebServerStreamedResponse : GCDWebServerResponse
|
@interface GCDWebServerStreamedResponse : GCDWebServerResponse
|
||||||
|
@property(nonatomic, copy) NSString* contentType; // Redeclare as non-null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a response with streamed data and a given content type.
|
* Creates a response with streamed data and a given content type.
|
||||||
@@ -73,3 +76,5 @@ typedef void (^GCDWebServerAsyncStreamBlock)(GCDWebServerBodyReaderCompletionBlo
|
|||||||
- (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block;
|
- (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -31,29 +31,26 @@
|
|||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
#import "GCDWebServerPrivate.h"
|
||||||
|
|
||||||
@interface GCDWebServerStreamedResponse () {
|
@implementation GCDWebServerStreamedResponse {
|
||||||
@private
|
|
||||||
GCDWebServerAsyncStreamBlock _block;
|
GCDWebServerAsyncStreamBlock _block;
|
||||||
}
|
}
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerStreamedResponse
|
@dynamic contentType;
|
||||||
|
|
||||||
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
|
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
|
||||||
return [[[self class] alloc] initWithContentType:type streamBlock:block];
|
return [(GCDWebServerStreamedResponse*)[[self class] alloc] initWithContentType:type streamBlock:block];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block {
|
+ (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block {
|
||||||
return [[[self class] alloc] initWithContentType:type asyncStreamBlock:block];
|
return [(GCDWebServerStreamedResponse*)[[self class] alloc] initWithContentType:type asyncStreamBlock:block];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
|
- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
|
||||||
return [self initWithContentType:type asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) {
|
return [self initWithContentType:type
|
||||||
|
asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) {
|
||||||
NSError* error = nil;
|
NSError* error = nil;
|
||||||
NSData* data = block(&error);
|
NSData* data = block(&error);
|
||||||
completionBlock(data, error);
|
completionBlock(data, error);
|
||||||
|
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
@@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
0
GCDWebUploader/GCDWebUploader.bundle/js/html5shiv.min.js → GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/js/html5shiv.min.js
vendored
Executable file → Normal file
0
GCDWebUploader/GCDWebUploader.bundle/js/html5shiv.min.js → GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/js/html5shiv.min.js
vendored
Executable file → Normal file
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
0
GCDWebUploader/GCDWebUploader.bundle/js/respond.min.js → GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/js/respond.min.js
vendored
Executable file → Normal file
0
GCDWebUploader/GCDWebUploader.bundle/js/respond.min.js → GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/js/respond.min.js
vendored
Executable file → Normal file
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
#import "GCDWebServer.h"
|
#import "GCDWebServer.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
@class GCDWebUploader;
|
@class GCDWebUploader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,14 +86,14 @@
|
|||||||
/**
|
/**
|
||||||
* Sets the delegate for the uploader.
|
* Sets the delegate for the uploader.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, assign) id<GCDWebUploaderDelegate> delegate;
|
@property(nonatomic, weak, nullable) id<GCDWebUploaderDelegate> delegate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets which files are allowed to be operated on depending on their extension.
|
* Sets which files are allowed to be operated on depending on their extension.
|
||||||
*
|
*
|
||||||
* The default value is nil i.e. all file extensions are allowed.
|
* The default value is nil i.e. all file extensions are allowed.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, copy) NSArray* allowedFileExtensions;
|
@property(nonatomic, copy) NSArray<NSString*>* allowedFileExtensions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets if files and directories whose name start with a period are allowed to
|
* Sets if files and directories whose name start with a period are allowed to
|
||||||
@@ -195,3 +197,5 @@
|
|||||||
- (BOOL)shouldCreateDirectoryAtPath:(NSString*)path;
|
- (BOOL)shouldCreateDirectoryAtPath:(NSString*)path;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#import "GCDWebUploader.h"
|
#import "GCDWebUploader.h"
|
||||||
|
#import "GCDWebServerFunctions.h"
|
||||||
|
|
||||||
#import "GCDWebServerDataRequest.h"
|
#import "GCDWebServerDataRequest.h"
|
||||||
#import "GCDWebServerMultiPartFormRequest.h"
|
#import "GCDWebServerMultiPartFormRequest.h"
|
||||||
@@ -46,266 +47,44 @@
|
|||||||
#import "GCDWebServerErrorResponse.h"
|
#import "GCDWebServerErrorResponse.h"
|
||||||
#import "GCDWebServerFileResponse.h"
|
#import "GCDWebServerFileResponse.h"
|
||||||
|
|
||||||
@interface GCDWebUploader () {
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
@private
|
|
||||||
NSString* _uploadDirectory;
|
@interface GCDWebUploader (Methods)
|
||||||
NSArray* _allowedExtensions;
|
- (nullable GCDWebServerResponse*)listDirectory:(GCDWebServerRequest*)request;
|
||||||
BOOL _allowHidden;
|
- (nullable GCDWebServerResponse*)downloadFile:(GCDWebServerRequest*)request;
|
||||||
NSString* _title;
|
- (nullable GCDWebServerResponse*)uploadFile:(GCDWebServerMultiPartFormRequest*)request;
|
||||||
NSString* _header;
|
- (nullable GCDWebServerResponse*)moveItem:(GCDWebServerURLEncodedFormRequest*)request;
|
||||||
NSString* _prologue;
|
- (nullable GCDWebServerResponse*)deleteItem:(GCDWebServerURLEncodedFormRequest*)request;
|
||||||
NSString* _epilogue;
|
- (nullable GCDWebServerResponse*)createDirectory:(GCDWebServerURLEncodedFormRequest*)request;
|
||||||
NSString* _footer;
|
|
||||||
}
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation GCDWebUploader (Methods)
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
||||||
// Must match implementation in GCDWebDAVServer
|
|
||||||
- (BOOL)_checkSandboxedPath:(NSString*)path {
|
|
||||||
return [[path stringByStandardizingPath] hasPrefix:_uploadDirectory];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)_checkFileExtension:(NSString*)fileName {
|
|
||||||
if (_allowedExtensions && ![_allowedExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString*) _uniquePathForPath:(NSString*)path {
|
|
||||||
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
|
||||||
NSString* directory = [path stringByDeletingLastPathComponent];
|
|
||||||
NSString* file = [path lastPathComponent];
|
|
||||||
NSString* base = [file stringByDeletingPathExtension];
|
|
||||||
NSString* extension = [file pathExtension];
|
|
||||||
int retries = 0;
|
|
||||||
do {
|
|
||||||
if (extension.length) {
|
|
||||||
path = [directory stringByAppendingPathComponent:[[base stringByAppendingFormat:@" (%i)", ++retries] stringByAppendingPathExtension:extension]];
|
|
||||||
} else {
|
|
||||||
path = [directory stringByAppendingPathComponent:[base stringByAppendingFormat:@" (%i)", ++retries]];
|
|
||||||
}
|
|
||||||
} while ([[NSFileManager defaultManager] fileExistsAtPath:path]);
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (GCDWebServerResponse*)listDirectory:(GCDWebServerRequest*)request {
|
|
||||||
NSString* relativePath = [[request query] objectForKey:@"path"];
|
|
||||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
|
||||||
BOOL isDirectory = NO;
|
|
||||||
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
|
||||||
}
|
|
||||||
if (!isDirectory) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"\"%@\" is not a directory", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* directoryName = [absolutePath lastPathComponent];
|
|
||||||
if (!_allowHidden && [directoryName hasPrefix:@"."]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Listing directory name \"%@\" is not allowed", directoryName];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSError* error = nil;
|
|
||||||
NSArray* contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:absolutePath error:&error];
|
|
||||||
if (contents == nil) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed listing directory \"%@\"", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSMutableArray* array = [NSMutableArray array];
|
|
||||||
for (NSString* item in [contents sortedArrayUsingSelector:@selector(localizedStandardCompare:)]) {
|
|
||||||
if (_allowHidden || ![item hasPrefix:@"."]) {
|
|
||||||
NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[absolutePath stringByAppendingPathComponent:item] error:NULL];
|
|
||||||
NSString* type = [attributes objectForKey:NSFileType];
|
|
||||||
if ([type isEqualToString:NSFileTypeRegular] && [self _checkFileExtension:item]) {
|
|
||||||
[array addObject:@{
|
|
||||||
@"path": [relativePath stringByAppendingPathComponent:item],
|
|
||||||
@"name": item,
|
|
||||||
@"size": [attributes objectForKey:NSFileSize]
|
|
||||||
}];
|
|
||||||
} else if ([type isEqualToString:NSFileTypeDirectory]) {
|
|
||||||
[array addObject:@{
|
|
||||||
@"path": [[relativePath stringByAppendingPathComponent:item] stringByAppendingString:@"/"],
|
|
||||||
@"name": item
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [GCDWebServerDataResponse responseWithJSONObject:array];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (GCDWebServerResponse*)downloadFile:(GCDWebServerRequest*)request {
|
|
||||||
NSString* relativePath = [[request query] objectForKey:@"path"];
|
|
||||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
|
||||||
BOOL isDirectory = NO;
|
|
||||||
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
|
||||||
}
|
|
||||||
if (isDirectory) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"\"%@\" is a directory", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* fileName = [absolutePath lastPathComponent];
|
|
||||||
if (([fileName hasPrefix:@"."] && !_allowHidden) || ![self _checkFileExtension:fileName]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Downlading file name \"%@\" is not allowed", fileName];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([self.delegate respondsToSelector:@selector(webUploader:didDownloadFileAtPath: )]) {
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
[self.delegate webUploader:self didDownloadFileAtPath:absolutePath];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return [GCDWebServerFileResponse responseWithFile:absolutePath isAttachment:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (GCDWebServerResponse*)uploadFile:(GCDWebServerMultiPartFormRequest*)request {
|
|
||||||
NSRange range = [[request.headers objectForKey:@"Accept"] rangeOfString:@"application/json" options:NSCaseInsensitiveSearch];
|
|
||||||
NSString* contentType = (range.location != NSNotFound ? @"application/json" : @"text/plain; charset=utf-8"); // Required when using iFrame transport (see https://github.com/blueimp/jQuery-File-Upload/wiki/Setup)
|
|
||||||
|
|
||||||
GCDWebServerMultiPartFile* file = [request firstFileForControlName:@"files[]"];
|
|
||||||
if ((!_allowHidden && [file.fileName hasPrefix:@"."]) || ![self _checkFileExtension:file.fileName]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploaded file name \"%@\" is not allowed", file.fileName];
|
|
||||||
}
|
|
||||||
NSString* relativePath = [[request firstArgumentForControlName:@"path"] string];
|
|
||||||
NSString* absolutePath = [self _uniquePathForPath:[[_uploadDirectory stringByAppendingPathComponent:relativePath] stringByAppendingPathComponent:file.fileName]];
|
|
||||||
if (![self _checkSandboxedPath:absolutePath]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (![self shouldUploadFileAtPath:absolutePath withTemporaryFile:file.temporaryPath]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file \"%@\" to \"%@\" is not permitted", file.fileName, relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSError* error = nil;
|
|
||||||
if (![[NSFileManager defaultManager] moveItemAtPath:file.temporaryPath toPath:absolutePath error:&error]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving uploaded file to \"%@\"", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([self.delegate respondsToSelector:@selector(webUploader:didUploadFileAtPath:)]) {
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
[self.delegate webUploader:self didUploadFileAtPath:absolutePath];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return [GCDWebServerDataResponse responseWithJSONObject:@{} contentType:contentType];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (GCDWebServerResponse*)moveItem:(GCDWebServerURLEncodedFormRequest*)request {
|
|
||||||
NSString* oldRelativePath = [request.arguments objectForKey:@"oldPath"];
|
|
||||||
NSString* oldAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:oldRelativePath];
|
|
||||||
BOOL isDirectory = NO;
|
|
||||||
if (![self _checkSandboxedPath:oldAbsolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:oldAbsolutePath isDirectory:&isDirectory]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", oldRelativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* newRelativePath = [request.arguments objectForKey:@"newPath"];
|
|
||||||
NSString* newAbsolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:newRelativePath]];
|
|
||||||
if (![self _checkSandboxedPath:newAbsolutePath]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", newRelativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* itemName = [newAbsolutePath lastPathComponent];
|
|
||||||
if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving to item name \"%@\" is not allowed", itemName];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (![self shouldMoveItemFromPath:oldAbsolutePath toPath:newAbsolutePath]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving \"%@\" to \"%@\" is not permitted", oldRelativePath, newRelativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSError* error = nil;
|
|
||||||
if (![[NSFileManager defaultManager] moveItemAtPath:oldAbsolutePath toPath:newAbsolutePath error:&error]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving \"%@\" to \"%@\"", oldRelativePath, newRelativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([self.delegate respondsToSelector:@selector(webUploader:didMoveItemFromPath:toPath:)]) {
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
[self.delegate webUploader:self didMoveItemFromPath:oldAbsolutePath toPath:newAbsolutePath];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (GCDWebServerResponse*)deleteItem:(GCDWebServerURLEncodedFormRequest*)request {
|
|
||||||
NSString* relativePath = [request.arguments objectForKey:@"path"];
|
|
||||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
|
||||||
BOOL isDirectory = NO;
|
|
||||||
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* itemName = [absolutePath lastPathComponent];
|
|
||||||
if (([itemName hasPrefix:@"."] && !_allowHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting item name \"%@\" is not allowed", itemName];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (![self shouldDeleteItemAtPath:absolutePath]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting \"%@\" is not permitted", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSError* error = nil;
|
|
||||||
if (![[NSFileManager defaultManager] removeItemAtPath:absolutePath error:&error]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed deleting \"%@\"", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([self.delegate respondsToSelector:@selector(webUploader:didDeleteItemAtPath:)]) {
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
[self.delegate webUploader:self didDeleteItemAtPath:absolutePath];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (GCDWebServerResponse*)createDirectory:(GCDWebServerURLEncodedFormRequest*)request {
|
|
||||||
NSString* relativePath = [request.arguments objectForKey:@"path"];
|
|
||||||
NSString* absolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:relativePath]];
|
|
||||||
if (![self _checkSandboxedPath:absolutePath]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* directoryName = [absolutePath lastPathComponent];
|
|
||||||
if (!_allowHidden && [directoryName hasPrefix:@"."]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory name \"%@\" is not allowed", directoryName];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (![self shouldCreateDirectoryAtPath:absolutePath]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory \"%@\" is not permitted", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSError* error = nil;
|
|
||||||
if (![[NSFileManager defaultManager] createDirectoryAtPath:absolutePath withIntermediateDirectories:NO attributes:nil error:&error]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed creating directory \"%@\"", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([self.delegate respondsToSelector:@selector(webUploader:didCreateDirectoryAtPath:)]) {
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
[self.delegate webUploader:self didCreateDirectoryAtPath:absolutePath];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebUploader
|
@implementation GCDWebUploader
|
||||||
|
|
||||||
@synthesize uploadDirectory=_uploadDirectory, allowedFileExtensions=_allowedExtensions, allowHiddenItems=_allowHidden,
|
@dynamic delegate;
|
||||||
title=_title, header=_header, prologue=_prologue, epilogue=_epilogue, footer=_footer;
|
|
||||||
|
|
||||||
- (instancetype)initWithUploadDirectory:(NSString*)path {
|
- (instancetype)initWithUploadDirectory:(NSString*)path {
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
NSBundle* siteBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"GCDWebUploader" ofType:@"bundle"]];
|
NSString* bundlePath = [[NSBundle bundleForClass:[GCDWebUploader class]] pathForResource:@"GCDWebUploader" ofType:@"bundle"];
|
||||||
|
if (bundlePath == nil) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
NSBundle* siteBundle = [NSBundle bundleWithPath:bundlePath];
|
||||||
if (siteBundle == nil) {
|
if (siteBundle == nil) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
_uploadDirectory = [[path stringByStandardizingPath] copy];
|
_uploadDirectory = [path copy];
|
||||||
GCDWebUploader* __unsafe_unretained server = self;
|
GCDWebUploader* __unsafe_unretained server = self;
|
||||||
|
|
||||||
// Resource files
|
// Resource files
|
||||||
[self addGETHandlerForBasePath:@"/" directoryPath:[siteBundle resourcePath] indexFilename:nil cacheAge:3600 allowRangeRequests:NO];
|
[self addGETHandlerForBasePath:@"/" directoryPath:(NSString*)[siteBundle resourcePath] indexFilename:nil cacheAge:3600 allowRangeRequests:NO];
|
||||||
|
|
||||||
// Web page
|
// Web page
|
||||||
[self addHandlerForMethod:@"GET" path:@"/" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
[self addHandlerForMethod:@"GET"
|
||||||
|
path:@"/"
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
NSString* device = [[UIDevice currentDevice] name];
|
NSString* device = [[UIDevice currentDevice] name];
|
||||||
@@ -339,6 +118,9 @@
|
|||||||
NSString* footer = server.footer;
|
NSString* footer = server.footer;
|
||||||
if (footer == nil) {
|
if (footer == nil) {
|
||||||
NSString* name = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
|
NSString* name = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
|
||||||
|
if (name == nil) {
|
||||||
|
name = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
|
||||||
|
}
|
||||||
NSString* version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
|
NSString* version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
|
||||||
#if !TARGET_OS_IPHONE
|
#if !TARGET_OS_IPHONE
|
||||||
if (!name && !version) {
|
if (!name && !version) {
|
||||||
@@ -348,7 +130,7 @@
|
|||||||
#endif
|
#endif
|
||||||
footer = [NSString stringWithFormat:[siteBundle localizedStringForKey:@"FOOTER_FORMAT" value:@"" table:nil], name, version];
|
footer = [NSString stringWithFormat:[siteBundle localizedStringForKey:@"FOOTER_FORMAT" value:@"" table:nil], name, version];
|
||||||
}
|
}
|
||||||
return [GCDWebServerDataResponse responseWithHTMLTemplate:[siteBundle pathForResource:@"index" ofType:@"html"]
|
return [GCDWebServerDataResponse responseWithHTMLTemplate:(NSString*)[siteBundle pathForResource:@"index" ofType:@"html"]
|
||||||
variables:@{
|
variables:@{
|
||||||
@"device" : device,
|
@"device" : device,
|
||||||
@"title" : title,
|
@"title" : title,
|
||||||
@@ -357,45 +139,280 @@
|
|||||||
@"epilogue" : epilogue,
|
@"epilogue" : epilogue,
|
||||||
@"footer" : footer
|
@"footer" : footer
|
||||||
}];
|
}];
|
||||||
|
|
||||||
}];
|
}];
|
||||||
|
|
||||||
// File listing
|
// File listing
|
||||||
[self addHandlerForMethod:@"GET" path:@"/list" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
[self addHandlerForMethod:@"GET"
|
||||||
|
path:@"/list"
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
return [server listDirectory:request];
|
return [server listDirectory:request];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
// File download
|
// File download
|
||||||
[self addHandlerForMethod:@"GET" path:@"/download" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
[self addHandlerForMethod:@"GET"
|
||||||
|
path:@"/download"
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
return [server downloadFile:request];
|
return [server downloadFile:request];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
// File upload
|
// File upload
|
||||||
[self addHandlerForMethod:@"POST" path:@"/upload" requestClass:[GCDWebServerMultiPartFormRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
[self addHandlerForMethod:@"POST"
|
||||||
|
path:@"/upload"
|
||||||
|
requestClass:[GCDWebServerMultiPartFormRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
return [server uploadFile:(GCDWebServerMultiPartFormRequest*)request];
|
return [server uploadFile:(GCDWebServerMultiPartFormRequest*)request];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
// File and folder moving
|
// File and folder moving
|
||||||
[self addHandlerForMethod:@"POST" path:@"/move" requestClass:[GCDWebServerURLEncodedFormRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
[self addHandlerForMethod:@"POST"
|
||||||
|
path:@"/move"
|
||||||
|
requestClass:[GCDWebServerURLEncodedFormRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
return [server moveItem:(GCDWebServerURLEncodedFormRequest*)request];
|
return [server moveItem:(GCDWebServerURLEncodedFormRequest*)request];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
// File and folder deletion
|
// File and folder deletion
|
||||||
[self addHandlerForMethod:@"POST" path:@"/delete" requestClass:[GCDWebServerURLEncodedFormRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
[self addHandlerForMethod:@"POST"
|
||||||
|
path:@"/delete"
|
||||||
|
requestClass:[GCDWebServerURLEncodedFormRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
return [server deleteItem:(GCDWebServerURLEncodedFormRequest*)request];
|
return [server deleteItem:(GCDWebServerURLEncodedFormRequest*)request];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
// Directory creation
|
// Directory creation
|
||||||
[self addHandlerForMethod:@"POST" path:@"/create" requestClass:[GCDWebServerURLEncodedFormRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
[self addHandlerForMethod:@"POST"
|
||||||
|
path:@"/create"
|
||||||
|
requestClass:[GCDWebServerURLEncodedFormRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
return [server createDirectory:(GCDWebServerURLEncodedFormRequest*)request];
|
return [server createDirectory:(GCDWebServerURLEncodedFormRequest*)request];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@implementation GCDWebUploader (Methods)
|
||||||
|
|
||||||
|
- (BOOL)_checkFileExtension:(NSString*)fileName {
|
||||||
|
if (_allowedFileExtensions && ![_allowedFileExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString*)_uniquePathForPath:(NSString*)path {
|
||||||
|
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
||||||
|
NSString* directory = [path stringByDeletingLastPathComponent];
|
||||||
|
NSString* file = [path lastPathComponent];
|
||||||
|
NSString* base = [file stringByDeletingPathExtension];
|
||||||
|
NSString* extension = [file pathExtension];
|
||||||
|
int retries = 0;
|
||||||
|
do {
|
||||||
|
if (extension.length) {
|
||||||
|
path = [directory stringByAppendingPathComponent:(NSString*)[[base stringByAppendingFormat:@" (%i)", ++retries] stringByAppendingPathExtension:extension]];
|
||||||
|
} else {
|
||||||
|
path = [directory stringByAppendingPathComponent:[base stringByAppendingFormat:@" (%i)", ++retries]];
|
||||||
|
}
|
||||||
|
} while ([[NSFileManager defaultManager] fileExistsAtPath:path]);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (GCDWebServerResponse*)listDirectory:(GCDWebServerRequest*)request {
|
||||||
|
NSString* relativePath = [[request query] objectForKey:@"path"];
|
||||||
|
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
|
||||||
|
BOOL isDirectory = NO;
|
||||||
|
if (!absolutePath || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||||
|
}
|
||||||
|
if (!isDirectory) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"\"%@\" is not a directory", relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString* directoryName = [absolutePath lastPathComponent];
|
||||||
|
if (!_allowHiddenItems && [directoryName hasPrefix:@"."]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Listing directory name \"%@\" is not allowed", directoryName];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSError* error = nil;
|
||||||
|
NSArray* contents = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:absolutePath error:&error] sortedArrayUsingSelector:@selector(localizedStandardCompare:)];
|
||||||
|
if (contents == nil) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed listing directory \"%@\"", relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSMutableArray* array = [NSMutableArray array];
|
||||||
|
for (NSString* item in [contents sortedArrayUsingSelector:@selector(localizedStandardCompare:)]) {
|
||||||
|
if (_allowHiddenItems || ![item hasPrefix:@"."]) {
|
||||||
|
NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[absolutePath stringByAppendingPathComponent:item] error:NULL];
|
||||||
|
NSString* type = [attributes objectForKey:NSFileType];
|
||||||
|
if ([type isEqualToString:NSFileTypeRegular] && [self _checkFileExtension:item]) {
|
||||||
|
[array addObject:@{
|
||||||
|
@"path" : [relativePath stringByAppendingPathComponent:item],
|
||||||
|
@"name" : item,
|
||||||
|
@"size" : (NSNumber*)[attributes objectForKey:NSFileSize]
|
||||||
|
}];
|
||||||
|
} else if ([type isEqualToString:NSFileTypeDirectory]) {
|
||||||
|
[array addObject:@{
|
||||||
|
@"path" : [[relativePath stringByAppendingPathComponent:item] stringByAppendingString:@"/"],
|
||||||
|
@"name" : item
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [GCDWebServerDataResponse responseWithJSONObject:array];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (GCDWebServerResponse*)downloadFile:(GCDWebServerRequest*)request {
|
||||||
|
NSString* relativePath = [[request query] objectForKey:@"path"];
|
||||||
|
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
|
||||||
|
BOOL isDirectory = NO;
|
||||||
|
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||||
|
}
|
||||||
|
if (isDirectory) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"\"%@\" is a directory", relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString* fileName = [absolutePath lastPathComponent];
|
||||||
|
if (([fileName hasPrefix:@"."] && !_allowHiddenItems) || ![self _checkFileExtension:fileName]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Downlading file name \"%@\" is not allowed", fileName];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([self.delegate respondsToSelector:@selector(webUploader:didDownloadFileAtPath:)]) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self.delegate webUploader:self didDownloadFileAtPath:absolutePath];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [GCDWebServerFileResponse responseWithFile:absolutePath isAttachment:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (GCDWebServerResponse*)uploadFile:(GCDWebServerMultiPartFormRequest*)request {
|
||||||
|
NSRange range = [[request.headers objectForKey:@"Accept"] rangeOfString:@"application/json" options:NSCaseInsensitiveSearch];
|
||||||
|
NSString* contentType = (range.location != NSNotFound ? @"application/json" : @"text/plain; charset=utf-8"); // Required when using iFrame transport (see https://github.com/blueimp/jQuery-File-Upload/wiki/Setup)
|
||||||
|
|
||||||
|
GCDWebServerMultiPartFile* file = [request firstFileForControlName:@"files[]"];
|
||||||
|
if ((!_allowHiddenItems && [file.fileName hasPrefix:@"."]) || ![self _checkFileExtension:file.fileName]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploaded file name \"%@\" is not allowed", file.fileName];
|
||||||
|
}
|
||||||
|
NSString* relativePath = [[request firstArgumentForControlName:@"path"] string];
|
||||||
|
NSString* absolutePath = [self _uniquePathForPath:[[_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)] stringByAppendingPathComponent:file.fileName]];
|
||||||
|
|
||||||
|
if (![self shouldUploadFileAtPath:absolutePath withTemporaryFile:file.temporaryPath]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file \"%@\" to \"%@\" is not permitted", file.fileName, relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSError* error = nil;
|
||||||
|
if (![[NSFileManager defaultManager] moveItemAtPath:file.temporaryPath toPath:absolutePath error:&error]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving uploaded file to \"%@\"", relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([self.delegate respondsToSelector:@selector(webUploader:didUploadFileAtPath:)]) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self.delegate webUploader:self didUploadFileAtPath:absolutePath];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [GCDWebServerDataResponse responseWithJSONObject:@{} contentType:contentType];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (GCDWebServerResponse*)moveItem:(GCDWebServerURLEncodedFormRequest*)request {
|
||||||
|
NSString* oldRelativePath = [request.arguments objectForKey:@"oldPath"];
|
||||||
|
NSString* oldAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(oldRelativePath)];
|
||||||
|
BOOL isDirectory = NO;
|
||||||
|
if (![[NSFileManager defaultManager] fileExistsAtPath:oldAbsolutePath isDirectory:&isDirectory]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", oldRelativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString* oldItemName = [oldAbsolutePath lastPathComponent];
|
||||||
|
if ((!_allowHiddenItems && [oldItemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:oldItemName])) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving from item name \"%@\" is not allowed", oldItemName];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString* newRelativePath = [request.arguments objectForKey:@"newPath"];
|
||||||
|
NSString* newAbsolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(newRelativePath)]];
|
||||||
|
|
||||||
|
NSString* newItemName = [newAbsolutePath lastPathComponent];
|
||||||
|
if ((!_allowHiddenItems && [newItemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:newItemName])) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving to item name \"%@\" is not allowed", newItemName];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (![self shouldMoveItemFromPath:oldAbsolutePath toPath:newAbsolutePath]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving \"%@\" to \"%@\" is not permitted", oldRelativePath, newRelativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSError* error = nil;
|
||||||
|
if (![[NSFileManager defaultManager] moveItemAtPath:oldAbsolutePath toPath:newAbsolutePath error:&error]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving \"%@\" to \"%@\"", oldRelativePath, newRelativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([self.delegate respondsToSelector:@selector(webUploader:didMoveItemFromPath:toPath:)]) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self.delegate webUploader:self didMoveItemFromPath:oldAbsolutePath toPath:newAbsolutePath];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (GCDWebServerResponse*)deleteItem:(GCDWebServerURLEncodedFormRequest*)request {
|
||||||
|
NSString* relativePath = [request.arguments objectForKey:@"path"];
|
||||||
|
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
|
||||||
|
BOOL isDirectory = NO;
|
||||||
|
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString* itemName = [absolutePath lastPathComponent];
|
||||||
|
if (([itemName hasPrefix:@"."] && !_allowHiddenItems) || (!isDirectory && ![self _checkFileExtension:itemName])) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting item name \"%@\" is not allowed", itemName];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (![self shouldDeleteItemAtPath:absolutePath]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting \"%@\" is not permitted", relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSError* error = nil;
|
||||||
|
if (![[NSFileManager defaultManager] removeItemAtPath:absolutePath error:&error]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed deleting \"%@\"", relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([self.delegate respondsToSelector:@selector(webUploader:didDeleteItemAtPath:)]) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self.delegate webUploader:self didDeleteItemAtPath:absolutePath];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (GCDWebServerResponse*)createDirectory:(GCDWebServerURLEncodedFormRequest*)request {
|
||||||
|
NSString* relativePath = [request.arguments objectForKey:@"path"];
|
||||||
|
NSString* absolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)]];
|
||||||
|
|
||||||
|
NSString* directoryName = [absolutePath lastPathComponent];
|
||||||
|
if (!_allowHiddenItems && [directoryName hasPrefix:@"."]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory name \"%@\" is not allowed", directoryName];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (![self shouldCreateDirectoryAtPath:absolutePath]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory \"%@\" is not permitted", relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSError* error = nil;
|
||||||
|
if (![[NSFileManager defaultManager] createDirectoryAtPath:absolutePath withIntermediateDirectories:NO attributes:nil error:&error]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed creating directory \"%@\"", relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([self.delegate respondsToSelector:@selector(webUploader:didCreateDirectoryAtPath:)]) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self.delegate webUploader:self didCreateDirectoryAtPath:absolutePath];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation GCDWebUploader (Subclassing)
|
@implementation GCDWebUploader (Subclassing)
|
||||||
|
|
||||||
- (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath {
|
- (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath {
|
||||||
|
|||||||
73
Mac/main.m
73
Mac/main.m
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
Copyright (c) 2012-2019, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -72,6 +72,10 @@ typedef enum {
|
|||||||
[self _logDelegateCall:_cmd];
|
[self _logDelegateCall:_cmd];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)webServerDidUpdateNATPortMapping:(GCDWebServer*)server {
|
||||||
|
[self _logDelegateCall:_cmd];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)webServerDidConnect:(GCDWebServer*)server {
|
- (void)webServerDidConnect:(GCDWebServer*)server {
|
||||||
[self _logDelegateCall:_cmd];
|
[self _logDelegateCall:_cmd];
|
||||||
}
|
}
|
||||||
@@ -142,6 +146,7 @@ int main(int argc, const char* argv[]) {
|
|||||||
NSString* authenticationUser = nil;
|
NSString* authenticationUser = nil;
|
||||||
NSString* authenticationPassword = nil;
|
NSString* authenticationPassword = nil;
|
||||||
BOOL bindToLocalhost = NO;
|
BOOL bindToLocalhost = NO;
|
||||||
|
BOOL requestNATPortMapping = NO;
|
||||||
|
|
||||||
if (argc == 1) {
|
if (argc == 1) {
|
||||||
fprintf(stdout, "Usage: %s [-mode webServer | htmlPage | htmlForm | htmlFileUpload | webDAV | webUploader | streamingResponse | asyncResponse] [-record] [-root directory] [-tests directory] [-authenticationMethod Basic | Digest] [-authenticationRealm realm] [-authenticationUser user] [-authenticationPassword password] [--localhost]\n\n", basename((char*)argv[0]));
|
fprintf(stdout, "Usage: %s [-mode webServer | htmlPage | htmlForm | htmlFileUpload | webDAV | webUploader | streamingResponse | asyncResponse] [-record] [-root directory] [-tests directory] [-authenticationMethod Basic | Digest] [-authenticationRealm realm] [-authenticationUser user] [-authenticationPassword password] [--localhost]\n\n", basename((char*)argv[0]));
|
||||||
@@ -173,10 +178,10 @@ int main(int argc, const char* argv[]) {
|
|||||||
recording = YES;
|
recording = YES;
|
||||||
} else if (!strcmp(argv[i], "-root") && (i + 1 < argc)) {
|
} else if (!strcmp(argv[i], "-root") && (i + 1 < argc)) {
|
||||||
++i;
|
++i;
|
||||||
rootDirectory = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:argv[i] length:strlen(argv[i])] stringByStandardizingPath];
|
rootDirectory = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:argv[i] length:strlen(argv[i])];
|
||||||
} else if (!strcmp(argv[i], "-tests") && (i + 1 < argc)) {
|
} else if (!strcmp(argv[i], "-tests") && (i + 1 < argc)) {
|
||||||
++i;
|
++i;
|
||||||
testDirectory = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:argv[i] length:strlen(argv[i])] stringByStandardizingPath];
|
testDirectory = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:argv[i] length:strlen(argv[i])];
|
||||||
} else if (!strcmp(argv[i], "-authenticationMethod") && (i + 1 < argc)) {
|
} else if (!strcmp(argv[i], "-authenticationMethod") && (i + 1 < argc)) {
|
||||||
++i;
|
++i;
|
||||||
authenticationMethod = [NSString stringWithUTF8String:argv[i]];
|
authenticationMethod = [NSString stringWithUTF8String:argv[i]];
|
||||||
@@ -191,16 +196,17 @@ int main(int argc, const char* argv[]) {
|
|||||||
authenticationPassword = [NSString stringWithUTF8String:argv[i]];
|
authenticationPassword = [NSString stringWithUTF8String:argv[i]];
|
||||||
} else if (!strcmp(argv[i], "--localhost")) {
|
} else if (!strcmp(argv[i], "--localhost")) {
|
||||||
bindToLocalhost = YES;
|
bindToLocalhost = YES;
|
||||||
|
} else if (!strcmp(argv[i], "--nat")) {
|
||||||
|
requestNATPortMapping = YES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GCDWebServer* webServer = nil;
|
GCDWebServer* webServer = nil;
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
|
|
||||||
// Simply serve contents of home directory
|
// Simply serve contents of home directory
|
||||||
case kMode_WebServer: {
|
case kMode_WebServer: {
|
||||||
fprintf(stdout, "Running in Web Server mode from \"%s\"", [rootDirectory UTF8String]);
|
fprintf(stdout, "Running in Web Server mode from \"%s\"\n", [rootDirectory UTF8String]);
|
||||||
webServer = [[GCDWebServer alloc] init];
|
webServer = [[GCDWebServer alloc] init];
|
||||||
[webServer addGETHandlerForBasePath:@"/" directoryPath:rootDirectory indexFilename:nil cacheAge:0 allowRangeRequests:YES];
|
[webServer addGETHandlerForBasePath:@"/" directoryPath:rootDirectory indexFilename:nil cacheAge:0 allowRangeRequests:YES];
|
||||||
break;
|
break;
|
||||||
@@ -208,27 +214,24 @@ int main(int argc, const char* argv[]) {
|
|||||||
|
|
||||||
// Renders a HTML page
|
// Renders a HTML page
|
||||||
case kMode_HTMLPage: {
|
case kMode_HTMLPage: {
|
||||||
fprintf(stdout, "Running in HTML Page mode");
|
fprintf(stdout, "Running in HTML Page mode\n");
|
||||||
webServer = [[GCDWebServer alloc] init];
|
webServer = [[GCDWebServer alloc] init];
|
||||||
[webServer addDefaultHandlerForMethod:@"GET"
|
[webServer addDefaultHandlerForMethod:@"GET"
|
||||||
requestClass:[GCDWebServerRequest class]
|
requestClass:[GCDWebServerRequest class]
|
||||||
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
|
||||||
return [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
|
return [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
|
||||||
|
|
||||||
}];
|
}];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements an HTML form
|
// Implements an HTML form
|
||||||
case kMode_HTMLForm: {
|
case kMode_HTMLForm: {
|
||||||
fprintf(stdout, "Running in HTML Form mode");
|
fprintf(stdout, "Running in HTML Form mode\n");
|
||||||
webServer = [[GCDWebServer alloc] init];
|
webServer = [[GCDWebServer alloc] init];
|
||||||
[webServer addHandlerForMethod:@"GET"
|
[webServer addHandlerForMethod:@"GET"
|
||||||
path:@"/"
|
path:@"/"
|
||||||
requestClass:[GCDWebServerRequest class]
|
requestClass:[GCDWebServerRequest class]
|
||||||
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
|
||||||
NSString* html = @" \
|
NSString* html = @" \
|
||||||
<html><body> \
|
<html><body> \
|
||||||
<form name=\"input\" action=\"/\" method=\"post\" enctype=\"application/x-www-form-urlencoded\"> \
|
<form name=\"input\" action=\"/\" method=\"post\" enctype=\"application/x-www-form-urlencoded\"> \
|
||||||
@@ -238,24 +241,21 @@ int main(int argc, const char* argv[]) {
|
|||||||
</body></html> \
|
</body></html> \
|
||||||
";
|
";
|
||||||
return [GCDWebServerDataResponse responseWithHTML:html];
|
return [GCDWebServerDataResponse responseWithHTML:html];
|
||||||
|
|
||||||
}];
|
}];
|
||||||
[webServer addHandlerForMethod:@"POST"
|
[webServer addHandlerForMethod:@"POST"
|
||||||
path:@"/"
|
path:@"/"
|
||||||
requestClass:[GCDWebServerURLEncodedFormRequest class]
|
requestClass:[GCDWebServerURLEncodedFormRequest class]
|
||||||
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
|
||||||
NSString* value = [[(GCDWebServerURLEncodedFormRequest*)request arguments] objectForKey:@"value"];
|
NSString* value = [[(GCDWebServerURLEncodedFormRequest*)request arguments] objectForKey:@"value"];
|
||||||
NSString* html = [NSString stringWithFormat:@"<html><body><p>%@</p></body></html>", value];
|
NSString* html = [NSString stringWithFormat:@"<html><body><p>%@</p></body></html>", value];
|
||||||
return [GCDWebServerDataResponse responseWithHTML:html];
|
return [GCDWebServerDataResponse responseWithHTML:html];
|
||||||
|
|
||||||
}];
|
}];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements HTML file upload
|
// Implements HTML file upload
|
||||||
case kMode_HTMLFileUpload: {
|
case kMode_HTMLFileUpload: {
|
||||||
fprintf(stdout, "Running in HTML File Upload mode");
|
fprintf(stdout, "Running in HTML File Upload mode\n");
|
||||||
webServer = [[GCDWebServer alloc] init];
|
webServer = [[GCDWebServer alloc] init];
|
||||||
NSString* formHTML = @" \
|
NSString* formHTML = @" \
|
||||||
<form name=\"input\" action=\"/\" method=\"post\" enctype=\"multipart/form-data\"> \
|
<form name=\"input\" action=\"/\" method=\"post\" enctype=\"multipart/form-data\"> \
|
||||||
@@ -268,16 +268,13 @@ int main(int argc, const char* argv[]) {
|
|||||||
path:@"/"
|
path:@"/"
|
||||||
requestClass:[GCDWebServerRequest class]
|
requestClass:[GCDWebServerRequest class]
|
||||||
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
|
||||||
NSString* html = [NSString stringWithFormat:@"<html><body>%@</body></html>", formHTML];
|
NSString* html = [NSString stringWithFormat:@"<html><body>%@</body></html>", formHTML];
|
||||||
return [GCDWebServerDataResponse responseWithHTML:html];
|
return [GCDWebServerDataResponse responseWithHTML:html];
|
||||||
|
|
||||||
}];
|
}];
|
||||||
[webServer addHandlerForMethod:@"POST"
|
[webServer addHandlerForMethod:@"POST"
|
||||||
path:@"/"
|
path:@"/"
|
||||||
requestClass:[GCDWebServerMultiPartFormRequest class]
|
requestClass:[GCDWebServerMultiPartFormRequest class]
|
||||||
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
|
||||||
NSMutableString* string = [NSMutableString string];
|
NSMutableString* string = [NSMutableString string];
|
||||||
for (GCDWebServerMultiPartArgument* argument in [(GCDWebServerMultiPartFormRequest*)request arguments]) {
|
for (GCDWebServerMultiPartArgument* argument in [(GCDWebServerMultiPartFormRequest*)request arguments]) {
|
||||||
[string appendFormat:@"%@ = %@<br>", argument.controlName, argument.string];
|
[string appendFormat:@"%@ = %@<br>", argument.controlName, argument.string];
|
||||||
@@ -290,109 +287,90 @@ int main(int argc, const char* argv[]) {
|
|||||||
};
|
};
|
||||||
NSString* html = [NSString stringWithFormat:@"<html><body><p>%@</p><hr>%@</body></html>", string, formHTML];
|
NSString* html = [NSString stringWithFormat:@"<html><body><p>%@</p><hr>%@</body></html>", string, formHTML];
|
||||||
return [GCDWebServerDataResponse responseWithHTML:html];
|
return [GCDWebServerDataResponse responseWithHTML:html];
|
||||||
|
|
||||||
}];
|
}];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve home directory through WebDAV
|
// Serve home directory through WebDAV
|
||||||
case kMode_WebDAV: {
|
case kMode_WebDAV: {
|
||||||
fprintf(stdout, "Running in WebDAV mode from \"%s\"", [rootDirectory UTF8String]);
|
fprintf(stdout, "Running in WebDAV mode from \"%s\"\n", [rootDirectory UTF8String]);
|
||||||
webServer = [[GCDWebDAVServer alloc] initWithUploadDirectory:rootDirectory];
|
webServer = [[GCDWebDAVServer alloc] initWithUploadDirectory:rootDirectory];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve home directory through web uploader
|
// Serve home directory through web uploader
|
||||||
case kMode_WebUploader: {
|
case kMode_WebUploader: {
|
||||||
fprintf(stdout, "Running in Web Uploader mode from \"%s\"", [rootDirectory UTF8String]);
|
fprintf(stdout, "Running in Web Uploader mode from \"%s\"\n", [rootDirectory UTF8String]);
|
||||||
webServer = [[GCDWebUploader alloc] initWithUploadDirectory:rootDirectory];
|
webServer = [[GCDWebUploader alloc] initWithUploadDirectory:rootDirectory];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test streaming responses
|
// Test streaming responses
|
||||||
case kMode_StreamingResponse: {
|
case kMode_StreamingResponse: {
|
||||||
fprintf(stdout, "Running in Streaming Response mode");
|
fprintf(stdout, "Running in Streaming Response mode\n");
|
||||||
webServer = [[GCDWebServer alloc] init];
|
webServer = [[GCDWebServer alloc] init];
|
||||||
[webServer addHandlerForMethod:@"GET"
|
[webServer addHandlerForMethod:@"GET"
|
||||||
path:@"/sync"
|
path:@"/sync"
|
||||||
requestClass:[GCDWebServerRequest class]
|
requestClass:[GCDWebServerRequest class]
|
||||||
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
|
||||||
__block int countDown = 10;
|
__block int countDown = 10;
|
||||||
return [GCDWebServerStreamedResponse responseWithContentType:@"text/plain" streamBlock:^NSData *(NSError** error) {
|
return [GCDWebServerStreamedResponse responseWithContentType:@"text/plain"
|
||||||
|
streamBlock:^NSData*(NSError** error) {
|
||||||
usleep(100 * 1000);
|
usleep(100 * 1000);
|
||||||
if (countDown) {
|
if (countDown) {
|
||||||
return [[NSString stringWithFormat:@"%i\n", countDown--] dataUsingEncoding:NSUTF8StringEncoding];
|
return [[NSString stringWithFormat:@"%i\n", countDown--] dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
} else {
|
} else {
|
||||||
return [NSData data];
|
return [NSData data];
|
||||||
}
|
}
|
||||||
|
|
||||||
}];
|
}];
|
||||||
|
|
||||||
}];
|
}];
|
||||||
[webServer addHandlerForMethod:@"GET"
|
[webServer addHandlerForMethod:@"GET"
|
||||||
path:@"/async"
|
path:@"/async"
|
||||||
requestClass:[GCDWebServerRequest class]
|
requestClass:[GCDWebServerRequest class]
|
||||||
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
|
||||||
__block int countDown = 10;
|
__block int countDown = 10;
|
||||||
return [GCDWebServerStreamedResponse responseWithContentType:@"text/plain" asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) {
|
return [GCDWebServerStreamedResponse responseWithContentType:@"text/plain"
|
||||||
|
asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) {
|
||||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||||
|
|
||||||
NSData* data = countDown ? [[NSString stringWithFormat:@"%i\n", countDown--] dataUsingEncoding:NSUTF8StringEncoding] : [NSData data];
|
NSData* data = countDown ? [[NSString stringWithFormat:@"%i\n", countDown--] dataUsingEncoding:NSUTF8StringEncoding] : [NSData data];
|
||||||
completionBlock(data, nil);
|
completionBlock(data, nil);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}];
|
}];
|
||||||
|
|
||||||
}];
|
}];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test async responses
|
// Test async responses
|
||||||
case kMode_AsyncResponse: {
|
case kMode_AsyncResponse: {
|
||||||
fprintf(stdout, "Running in Async Response mode");
|
fprintf(stdout, "Running in Async Response mode\n");
|
||||||
webServer = [[GCDWebServer alloc] init];
|
webServer = [[GCDWebServer alloc] init];
|
||||||
[webServer addHandlerForMethod:@"GET"
|
[webServer addHandlerForMethod:@"GET"
|
||||||
path:@"/async"
|
path:@"/async"
|
||||||
requestClass:[GCDWebServerRequest class]
|
requestClass:[GCDWebServerRequest class]
|
||||||
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
|
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
|
||||||
|
|
||||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||||
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:[@"Hello World!" dataUsingEncoding:NSUTF8StringEncoding] contentType:@"text/plain"];
|
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:(NSData*)[@"Hello World!" dataUsingEncoding:NSUTF8StringEncoding] contentType:@"text/plain"];
|
||||||
completionBlock(response);
|
completionBlock(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
}];
|
}];
|
||||||
[webServer addHandlerForMethod:@"GET"
|
[webServer addHandlerForMethod:@"GET"
|
||||||
path:@"/async2"
|
path:@"/async2"
|
||||||
requestClass:[GCDWebServerRequest class]
|
requestClass:[GCDWebServerRequest class]
|
||||||
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock handlerCompletionBlock) {
|
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock handlerCompletionBlock) {
|
||||||
|
|
||||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||||
|
|
||||||
__block int countDown = 10;
|
__block int countDown = 10;
|
||||||
GCDWebServerStreamedResponse* response = [GCDWebServerStreamedResponse responseWithContentType:@"text/plain" asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock readerCompletionBlock) {
|
GCDWebServerStreamedResponse* response = [GCDWebServerStreamedResponse responseWithContentType:@"text/plain"
|
||||||
|
asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock readerCompletionBlock) {
|
||||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||||
|
|
||||||
NSData* data = countDown ? [[NSString stringWithFormat:@"%i\n", countDown--] dataUsingEncoding:NSUTF8StringEncoding] : [NSData data];
|
NSData* data = countDown ? [[NSString stringWithFormat:@"%i\n", countDown--] dataUsingEncoding:NSUTF8StringEncoding] : [NSData data];
|
||||||
readerCompletionBlock(data, nil);
|
readerCompletionBlock(data, nil);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}];
|
}];
|
||||||
handlerCompletionBlock(response);
|
handlerCompletionBlock(response);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}];
|
}];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (webServer) {
|
if (webServer) {
|
||||||
@@ -412,6 +390,7 @@ int main(int argc, const char* argv[]) {
|
|||||||
fprintf(stdout, "\n");
|
fprintf(stdout, "\n");
|
||||||
NSMutableDictionary* options = [NSMutableDictionary dictionary];
|
NSMutableDictionary* options = [NSMutableDictionary dictionary];
|
||||||
[options setObject:@8080 forKey:GCDWebServerOption_Port];
|
[options setObject:@8080 forKey:GCDWebServerOption_Port];
|
||||||
|
[options setObject:@(requestNATPortMapping) forKey:GCDWebServerOption_RequestNATPortMapping];
|
||||||
[options setObject:@(bindToLocalhost) forKey:GCDWebServerOption_BindToLocalhost];
|
[options setObject:@(bindToLocalhost) forKey:GCDWebServerOption_BindToLocalhost];
|
||||||
[options setObject:@"" forKey:GCDWebServerOption_BonjourName];
|
[options setObject:@"" forKey:GCDWebServerOption_BonjourName];
|
||||||
if (authenticationUser && authenticationPassword) {
|
if (authenticationUser && authenticationPassword) {
|
||||||
|
|||||||
284
README.md
284
README.md
@@ -2,11 +2,11 @@ Overview
|
|||||||
========
|
========
|
||||||
|
|
||||||
[](https://travis-ci.org/swisspol/GCDWebServer)
|
[](https://travis-ci.org/swisspol/GCDWebServer)
|
||||||
[](http://cocoadocs.org/docsets/GCDWebServer)
|
[](https://cocoapods.org/pods/GCDWebServer)
|
||||||
[](https://github.com/swisspol/GCDWebServer)
|
[](https://github.com/swisspol/GCDWebServer)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
|
|
||||||
GCDWebServer is a modern and lightweight GCD based HTTP 1.1 server designed to be embedded in OS X & iOS apps. It was written from scratch with the following goals in mind:
|
GCDWebServer is a modern and lightweight GCD based HTTP 1.1 server designed to be embedded in iOS, macOS & tvOS apps. It was written from scratch with the following goals in mind:
|
||||||
* Elegant and easy to use architecture with only 4 core classes: server, connection, request and response (see "Understanding GCDWebServer's Architecture" below)
|
* Elegant and easy to use architecture with only 4 core classes: server, connection, request and response (see "Understanding GCDWebServer's Architecture" below)
|
||||||
* Well designed API with fully documented headers for easy integration and customization
|
* Well designed API with fully documented headers for easy integration and customization
|
||||||
* Entirely built with an event-driven design using [Grand Central Dispatch](http://en.wikipedia.org/wiki/Grand_Central_Dispatch) for best performance and concurrency
|
* Entirely built with an event-driven design using [Grand Central Dispatch](http://en.wikipedia.org/wiki/Grand_Central_Dispatch) for best performance and concurrency
|
||||||
@@ -24,26 +24,28 @@ Extra built-in features:
|
|||||||
* [Basic](https://en.wikipedia.org/wiki/Basic_access_authentication) and [Digest Access](https://en.wikipedia.org/wiki/Digest_access_authentication) authentications for password protection
|
* [Basic](https://en.wikipedia.org/wiki/Basic_access_authentication) and [Digest Access](https://en.wikipedia.org/wiki/Digest_access_authentication) authentications for password protection
|
||||||
* Automatically handle transitions between foreground, background and suspended modes in iOS apps
|
* Automatically handle transitions between foreground, background and suspended modes in iOS apps
|
||||||
* Full support for both IPv4 and IPv6
|
* Full support for both IPv4 and IPv6
|
||||||
|
* NAT port mapping (IPv4 only)
|
||||||
|
|
||||||
Included extensions:
|
Included extensions:
|
||||||
* [GCDWebUploader](GCDWebUploader/GCDWebUploader.h): subclass of ```GCDWebServer``` that implements an interface for uploading and downloading files using a web browser
|
* [GCDWebUploader](GCDWebUploader/GCDWebUploader.h): subclass of ```GCDWebServer``` that implements an interface for uploading and downloading files using a web browser
|
||||||
* [GCDWebDAVServer](GCDWebDAVServer/GCDWebDAVServer.h): subclass of ```GCDWebServer``` that implements a class 1 [WebDAV](https://en.wikipedia.org/wiki/WebDAV) server (with partial class 2 support for OS X Finder)
|
* [GCDWebDAVServer](GCDWebDAVServer/GCDWebDAVServer.h): subclass of ```GCDWebServer``` that implements a class 1 [WebDAV](https://en.wikipedia.org/wiki/WebDAV) server (with partial class 2 support for macOS Finder)
|
||||||
|
|
||||||
What's not supported (but not really required from an embedded HTTP server):
|
What's not supported (but not really required from an embedded HTTP server):
|
||||||
* Keep-alive connections
|
* Keep-alive connections
|
||||||
* HTTPS
|
* HTTPS
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
* OS X 10.7 or later (x86_64)
|
* macOS 10.7 or later (x86_64)
|
||||||
* iOS 5.0 or later (armv7, armv7s or arm64)
|
* iOS 8.0 or later (armv7, armv7s or arm64)
|
||||||
* ARC memory management only (if you need MRC support use GCDWebServer 3.1 and earlier)
|
* tvOS 9.0 or later (arm64)
|
||||||
|
* ARC memory management only (if you need MRC support use GCDWebServer 3.1 or earlier)
|
||||||
|
|
||||||
Getting Started
|
Getting Started
|
||||||
===============
|
===============
|
||||||
|
|
||||||
Download or check out the [latest release](https://github.com/swisspol/GCDWebServer/releases) of GCDWebServer then add the entire "GCDWebServer" subfolder to your Xcode project. If you intend to use one of the extensions like GCDWebDAVServer or GCDWebUploader, add these subfolders as well.
|
Download or check out the [latest release](https://github.com/swisspol/GCDWebServer/releases) of GCDWebServer then add the entire "GCDWebServer" subfolder to your Xcode project. If you intend to use one of the extensions like GCDWebDAVServer or GCDWebUploader, add these subfolders as well. Finally link to `libz` (via Target > Build Phases > Link Binary With Libraries) and add `$(SDKROOT)/usr/include/libxml2` to your header search paths (via Target > Build Settings > HEADER_SEARCH_PATHS).
|
||||||
|
|
||||||
Alternatively, you can install GCDWebServer using [CocoaPods](http://cocoapods.org/) by simply adding this line to your Xcode project's Podfile:
|
Alternatively, you can install GCDWebServer using [CocoaPods](http://cocoapods.org/) by simply adding this line to your Podfile:
|
||||||
```
|
```
|
||||||
pod "GCDWebServer", "~> 3.0"
|
pod "GCDWebServer", "~> 3.0"
|
||||||
```
|
```
|
||||||
@@ -56,12 +58,30 @@ Or this line for GCDWebDAVServer:
|
|||||||
pod "GCDWebServer/WebDAV", "~> 3.0"
|
pod "GCDWebServer/WebDAV", "~> 3.0"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
And finally run `$ pod install`.
|
||||||
|
|
||||||
|
You can also use [Carthage](https://github.com/Carthage/Carthage) by adding this line to your Cartfile (3.2.5 is the first release with Carthage support):
|
||||||
|
```
|
||||||
|
github "swisspol/GCDWebServer" ~> 3.2.5
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run `$ carthage update` and add the generated frameworks to your Xcode projects (see [Carthage instructions](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application)).
|
||||||
|
|
||||||
|
Help & Support
|
||||||
|
==============
|
||||||
|
|
||||||
|
For help with using GCDWebServer, it's best to ask your question on Stack Overflow with the [`gcdwebserver`](http://stackoverflow.com/questions/tagged/gcdwebserver) tag. For bug reports and enhancement requests you can use [issues](https://github.com/swisspol/GCDWebServer/issues) in this project.
|
||||||
|
|
||||||
|
Be sure to read this entire README first though!
|
||||||
|
|
||||||
Hello World
|
Hello World
|
||||||
===========
|
===========
|
||||||
|
|
||||||
These code snippets show how to implement a custom HTTP server that runs on port 8080 and returns a "Hello World" HTML page to any request. Since GCDWebServer uses GCD blocks to handle requests, no subclassing or delegates are needed, which results in very clean code.
|
These code snippets show how to implement a custom HTTP server that runs on port 8080 and returns a "Hello World" HTML page to any request. Since GCDWebServer uses GCD blocks to handle requests, no subclassing or delegates are needed, which results in very clean code.
|
||||||
|
|
||||||
**OS X version (command line tool):**
|
**IMPORTANT:** If not using CocoaPods, be sure to add the `libz` shared system library to the Xcode target for your app.
|
||||||
|
|
||||||
|
**macOS version (command line tool):**
|
||||||
```objectivec
|
```objectivec
|
||||||
#import "GCDWebServer.h"
|
#import "GCDWebServer.h"
|
||||||
#import "GCDWebServerDataResponse.h"
|
#import "GCDWebServerDataResponse.h"
|
||||||
@@ -127,33 +147,146 @@ int main(int argc, const char* argv[]) {
|
|||||||
@end
|
@end
|
||||||
```
|
```
|
||||||
|
|
||||||
**OS X Swift version (command line tool):**
|
**macOS Swift version (command line tool):**
|
||||||
|
|
||||||
***webServer.swift***
|
***webServer.swift***
|
||||||
```swift
|
```swift
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import GCDWebServer
|
||||||
|
|
||||||
|
func initWebServer() {
|
||||||
|
|
||||||
let webServer = GCDWebServer()
|
let webServer = GCDWebServer()
|
||||||
|
|
||||||
webServer.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self) { request in
|
webServer.addDefaultHandler(forMethod: "GET", request: GCDWebServerRequest.self, processBlock: {request in
|
||||||
return GCDWebServerDataResponse(HTML:"<html><body><p>Hello World</p></body></html>")
|
return GCDWebServerDataResponse(html:"<html><body><p>Hello World</p></body></html>")
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
webServer.start(withPort: 8080, bonjourName: "GCD Web Server")
|
||||||
|
|
||||||
|
print("Visit \(webServer.serverURL) in your web browser")
|
||||||
}
|
}
|
||||||
|
|
||||||
webServer.runWithPort(8080, bonjourName: nil)
|
|
||||||
|
|
||||||
println("Visit \(webServer.serverURL) in your web browser")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
***WebServer-Bridging-Header.h***
|
***WebServer-Bridging-Header.h***
|
||||||
```objectivec
|
```objectivec
|
||||||
#import "GCDWebServer.h"
|
#import <GCDWebServer/GCDWebServer.h>
|
||||||
#import "GCDWebServerDataResponse.h"
|
#import <GCDWebServer/GCDWebServerDataResponse.h>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Web Based Uploads in iOS Apps
|
||||||
|
=============================
|
||||||
|
|
||||||
|
GCDWebUploader is a subclass of ```GCDWebServer``` that provides a ready-to-use HTML 5 file uploader & downloader. This lets users upload, download, delete files and create directories from a directory inside your iOS app's sandbox using a clean user interface in their web browser.
|
||||||
|
|
||||||
|
Simply instantiate and run a ```GCDWebUploader``` instance then visit ```http://{YOUR-IOS-DEVICE-IP-ADDRESS}/``` from your web browser:
|
||||||
|
|
||||||
|
```objectivec
|
||||||
|
#import "GCDWebUploader.h"
|
||||||
|
|
||||||
|
@interface AppDelegate : NSObject <UIApplicationDelegate> {
|
||||||
|
GCDWebUploader* _webUploader;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation AppDelegate
|
||||||
|
|
||||||
|
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
||||||
|
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
|
||||||
|
_webUploader = [[GCDWebUploader alloc] initWithUploadDirectory:documentsPath];
|
||||||
|
[_webUploader start];
|
||||||
|
NSLog(@"Visit %@ in your web browser", _webUploader.serverURL);
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
```
|
||||||
|
|
||||||
|
WebDAV Server in iOS Apps
|
||||||
|
=========================
|
||||||
|
|
||||||
|
GCDWebDAVServer is a subclass of ```GCDWebServer``` that provides a class 1 compliant [WebDAV](https://en.wikipedia.org/wiki/WebDAV) server. This lets users upload, download, delete files and create directories from a directory inside your iOS app's sandbox using any WebDAV client like [Transmit](https://panic.com/transmit/) (Mac), [ForkLift](http://binarynights.com/forklift/) (Mac) or [CyberDuck](http://cyberduck.io/) (Mac / Windows).
|
||||||
|
|
||||||
|
GCDWebDAVServer should also work with the [macOS Finder](http://support.apple.com/kb/PH13859) as it is partially class 2 compliant (but only when the client is the macOS WebDAV implementation).
|
||||||
|
|
||||||
|
Simply instantiate and run a ```GCDWebDAVServer``` instance then connect to ```http://{YOUR-IOS-DEVICE-IP-ADDRESS}/``` using a WebDAV client:
|
||||||
|
|
||||||
|
```objectivec
|
||||||
|
#import "GCDWebDAVServer.h"
|
||||||
|
|
||||||
|
@interface AppDelegate : NSObject <UIApplicationDelegate> {
|
||||||
|
GCDWebDAVServer* _davServer;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation AppDelegate
|
||||||
|
|
||||||
|
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
||||||
|
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
|
||||||
|
_davServer = [[GCDWebDAVServer alloc] initWithUploadDirectory:documentsPath];
|
||||||
|
[_davServer start];
|
||||||
|
NSLog(@"Visit %@ in your WebDAV client", _davServer.serverURL);
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
```
|
||||||
|
|
||||||
|
Serving a Static Website
|
||||||
|
========================
|
||||||
|
|
||||||
|
GCDWebServer includes a built-in handler that can recursively serve a directory (it also lets you control how the ["Cache-Control"](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9) header should be set):
|
||||||
|
|
||||||
|
**macOS version (command line tool):**
|
||||||
|
```objectivec
|
||||||
|
#import "GCDWebServer.h"
|
||||||
|
|
||||||
|
int main(int argc, const char* argv[]) {
|
||||||
|
@autoreleasepool {
|
||||||
|
|
||||||
|
GCDWebServer* webServer = [[GCDWebServer alloc] init];
|
||||||
|
[webServer addGETHandlerForBasePath:@"/" directoryPath:NSHomeDirectory() indexFilename:nil cacheAge:3600 allowRangeRequests:YES];
|
||||||
|
[webServer runWithPort:8080];
|
||||||
|
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Using GCDWebServer
|
||||||
|
==================
|
||||||
|
|
||||||
|
You start by creating an instance of the ```GCDWebServer``` class. Note that you can have multiple web servers running in the same app as long as they listen on different ports.
|
||||||
|
|
||||||
|
Then you add one or more "handlers" to the server: each handler gets a chance to handle an incoming web request and provide a response. Handlers are called in a LIFO queue, so the latest added handler overrides any previously added ones.
|
||||||
|
|
||||||
|
Finally you start the server on a given port.
|
||||||
|
|
||||||
|
Understanding GCDWebServer's Architecture
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
GCDWebServer's architecture consists of only 4 core classes:
|
||||||
|
* [GCDWebServer](GCDWebServer/Core/GCDWebServer.h) manages the socket that listens for new HTTP connections and the list of handlers used by the server.
|
||||||
|
* [GCDWebServerConnection](GCDWebServer/Core/GCDWebServerConnection.h) is instantiated by ```GCDWebServer``` to handle each new HTTP connection. Each instance stays alive until the connection is closed. You cannot use this class directly, but it is exposed so you can subclass it to override some hooks.
|
||||||
|
* [GCDWebServerRequest](GCDWebServer/Core/GCDWebServerRequest.h) is created by the ```GCDWebServerConnection``` instance after HTTP headers have been received. It wraps the request and handles the HTTP body if any. GCDWebServer comes with [several subclasses](GCDWebServer/Requests) of ```GCDWebServerRequest``` to handle common cases like storing the body in memory or stream it to a file on disk.
|
||||||
|
* [GCDWebServerResponse](GCDWebServer/Core/GCDWebServerResponse.h) is created by the request handler and wraps the response HTTP headers and optional body. GCDWebServer comes with [several subclasses](GCDWebServer/Responses) of ```GCDWebServerResponse``` to handle common cases like HTML text in memory or streaming a file from disk.
|
||||||
|
|
||||||
|
Implementing Handlers
|
||||||
|
=====================
|
||||||
|
|
||||||
|
GCDWebServer relies on "handlers" to process incoming web requests and generating responses. Handlers are implemented with GCD blocks which makes it very easy to provide your own. However, they are executed on arbitrary threads within GCD so __special attention must be paid to thread-safety and re-entrancy__.
|
||||||
|
|
||||||
|
Handlers require 2 GCD blocks:
|
||||||
|
* The ```GCDWebServerMatchBlock``` is called on every handler added to the ```GCDWebServer``` instance whenever a web request has started (i.e. HTTP headers have been received). It is passed the basic info for the web request (HTTP method, URL, headers...) and must decide if it wants to handle it or not. If yes, it must return a new ```GCDWebServerRequest``` instance (see above) created with this info. Otherwise, it simply returns nil.
|
||||||
|
* The ```GCDWebServerProcessBlock``` or ```GCDWebServerAsyncProcessBlock``` is called after the web request has been fully received and is passed the ```GCDWebServerRequest``` instance created at the previous step. It must return synchronously (if using ```GCDWebServerProcessBlock```) or asynchronously (if using ```GCDWebServerAsyncProcessBlock```) a ```GCDWebServerResponse``` instance (see above) or nil on error, which will result in a 500 HTTP status code returned to the client. It's however recommended to return an instance of [GCDWebServerErrorResponse](GCDWebServer/Responses/GCDWebServerErrorResponse.h) on error so more useful information can be returned to the client.
|
||||||
|
|
||||||
|
Note that most methods on ```GCDWebServer``` to add handlers only require the ```GCDWebServerProcessBlock``` or ```GCDWebServerAsyncProcessBlock``` as they already provide a built-in ```GCDWebServerMatchBlock``` e.g. to match a URL path with a Regex.
|
||||||
|
|
||||||
Asynchronous HTTP Responses
|
Asynchronous HTTP Responses
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
New in GCDWebServer 3.0 is the ability to process HTTP requests aysnchronously i.e. add handlers to the server which generate their ```GCDWebServerResponse``` asynchronously. This is achieved by adding handlers that use a ```GCDWebServerAsyncProcessBlock``` instead of a ```GCDWebServerProcessBlock```. Here's an example:
|
New in GCDWebServer 3.0 is the ability to process HTTP requests asynchronously i.e. add handlers to the server which generate their ```GCDWebServerResponse``` asynchronously. This is achieved by adding handlers that use a ```GCDWebServerAsyncProcessBlock``` instead of a ```GCDWebServerProcessBlock```. Here's an example:
|
||||||
|
|
||||||
**(Synchronous version)** The handler blocks while generating the HTTP response:
|
**(Synchronous version)** The handler blocks while generating the HTTP response:
|
||||||
```objectivec
|
```objectivec
|
||||||
@@ -210,121 +343,14 @@ New in GCDWebServer 3.0 is the ability to process HTTP requests aysnchronously i
|
|||||||
|
|
||||||
*Note that you can even combine both the asynchronous and advanced asynchronous versions to return asynchronously an asynchronous HTTP response!*
|
*Note that you can even combine both the asynchronous and advanced asynchronous versions to return asynchronously an asynchronous HTTP response!*
|
||||||
|
|
||||||
Web Based Uploads in iOS Apps
|
|
||||||
=============================
|
|
||||||
|
|
||||||
GCDWebUploader is a subclass of ```GCDWebServer``` that provides a ready-to-use HTML 5 file uploader & downloader. This lets users upload, download, delete files and create directories from a directory inside your iOS app's sandbox using a clean user interface in their web browser.
|
|
||||||
|
|
||||||
Simply instantiate and run a ```GCDWebUploader``` instance then visit ```http://{YOUR-IOS-DEVICE-IP-ADDRESS}/``` from your web browser:
|
|
||||||
|
|
||||||
```objectivec
|
|
||||||
#import "GCDWebUploader.h"
|
|
||||||
|
|
||||||
@interface AppDelegate : NSObject <UIApplicationDelegate> {
|
|
||||||
GCDWebUploader* _webUploader;
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation AppDelegate
|
|
||||||
|
|
||||||
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
|
||||||
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
|
|
||||||
_webUploader = [[GCDWebUploader alloc] initWithUploadDirectory:documentsPath];
|
|
||||||
[_webUploader start];
|
|
||||||
NSLog(@"Visit %@ in your web browser", _webUploader.serverURL);
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
```
|
|
||||||
|
|
||||||
WebDAV Server in iOS Apps
|
|
||||||
=========================
|
|
||||||
|
|
||||||
GCDWebDAVServer is a subclass of ```GCDWebServer``` that provides a class 1 compliant [WebDAV](https://en.wikipedia.org/wiki/WebDAV) server. This lets users upload, download, delete files and create directories from a directory inside your iOS app's sandbox using any WebDAV client like [Transmit](https://panic.com/transmit/) (Mac), [ForkLift](http://binarynights.com/forklift/) (Mac) or [CyberDuck](http://cyberduck.io/) (Mac / Windows).
|
|
||||||
|
|
||||||
GCDWebDAVServer should also work with the [OS X Finder](http://support.apple.com/kb/PH13859) as it is partially class 2 compliant (but only when the client is the OS X WebDAV implementation).
|
|
||||||
|
|
||||||
Simply instantiate and run a ```GCDWebDAVServer``` instance then connect to ```http://{YOUR-IOS-DEVICE-IP-ADDRESS}/``` using a WebDAV client:
|
|
||||||
|
|
||||||
```objectivec
|
|
||||||
#import "GCDWebDAVServer.h"
|
|
||||||
|
|
||||||
@interface AppDelegate : NSObject <UIApplicationDelegate> {
|
|
||||||
GCDWebDAVServer* _davServer;
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation AppDelegate
|
|
||||||
|
|
||||||
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
|
||||||
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
|
|
||||||
_davServer = [[GCDWebDAVServer alloc] initWithUploadDirectory:documentsPath];
|
|
||||||
[_davServer start];
|
|
||||||
NSLog(@"Visit %@ in your WebDAV client", _davServer.serverURL);
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
```
|
|
||||||
|
|
||||||
Serving a Static Website
|
|
||||||
========================
|
|
||||||
|
|
||||||
GCDWebServer includes a built-in handler that can recursively serve a directory (it also lets you control how the ["Cache-Control"](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9) header should be set):
|
|
||||||
|
|
||||||
```objectivec
|
|
||||||
#import "GCDWebServer.h"
|
|
||||||
|
|
||||||
int main(int argc, const char* argv[]) {
|
|
||||||
@autoreleasepool {
|
|
||||||
|
|
||||||
GCDWebServer* webServer = [[GCDWebServer alloc] init];
|
|
||||||
[webServer addGETHandlerForBasePath:@"/" directoryPath:NSHomeDirectory() indexFilename:nil cacheAge:3600 allowRangeRequests:YES];
|
|
||||||
[webServer runWithPort:8080];
|
|
||||||
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Using GCDWebServer
|
|
||||||
==================
|
|
||||||
|
|
||||||
You start by creating an instance of the ```GCDWebServer``` class. Note that you can have multiple web servers running in the same app as long as they listen on different ports.
|
|
||||||
|
|
||||||
Then you add one or more "handlers" to the server: each handler gets a chance to handle an incoming web request and provide a response. Handlers are called in a LIFO queue, so the latest added handler overrides any previously added ones.
|
|
||||||
|
|
||||||
Finally you start the server on a given port.
|
|
||||||
|
|
||||||
Understanding GCDWebServer's Architecture
|
|
||||||
=========================================
|
|
||||||
|
|
||||||
GCDWebServer's architecture consists of only 4 core classes:
|
|
||||||
* [GCDWebServer](GCDWebServer/Core/GCDWebServer.h) manages the socket that listens for new HTTP connections and the list of handlers used by the server.
|
|
||||||
* [GCDWebServerConnection](GCDWebServer/Core/GCDWebServerConnection.h) is instantiated by ```GCDWebServer``` to handle each new HTTP connection. Each instance stays alive until the connection is closed. You cannot use this class directly, but it is exposed so you can subclass it to override some hooks.
|
|
||||||
* [GCDWebServerRequest](GCDWebServer/Core/GCDWebServerRequest.h) is created by the ```GCDWebServerConnection``` instance after HTTP headers have been received. It wraps the request and handles the HTTP body if any. GCDWebServer comes with [several subclasses](GCDWebServer/Requests) of ```GCDWebServerRequest``` to handle common cases like storing the body in memory or stream it to a file on disk.
|
|
||||||
* [GCDWebServerResponse](GCDWebServer/Core/GCDWebServerResponse.h) is created by the request handler and wraps the response HTTP headers and optional body. GCDWebServer comes with [several subclasses](GCDWebServer/Responses) of ```GCDWebServerResponse``` to handle common cases like HTML text in memory or streaming a file from disk.
|
|
||||||
|
|
||||||
Implementing Handlers
|
|
||||||
=====================
|
|
||||||
|
|
||||||
GCDWebServer relies on "handlers" to process incoming web requests and generating responses. Handlers are implemented with GCD blocks which makes it very easy to provide your owns. However, they are executed on arbitrary threads within GCD so __special attention must be paid to thread-safety and re-entrancy__.
|
|
||||||
|
|
||||||
Handlers require 2 GCD blocks:
|
|
||||||
* The ```GCDWebServerMatchBlock``` is called on every handler added to the ```GCDWebServer``` instance whenever a web request has started (i.e. HTTP headers have been received). It is passed the basic info for the web request (HTTP method, URL, headers...) and must decide if it wants to handle it or not. If yes, it must return a new ```GCDWebServerRequest``` instance (see above) created with this info. Otherwise, it simply returns nil.
|
|
||||||
* The ```GCDWebServerProcessBlock``` or ```GCDWebServerAsyncProcessBlock``` is called after the web request has been fully received and is passed the ```GCDWebServerRequest``` instance created at the previous step. It must return synchronously (if using ```GCDWebServerProcessBlock```) or asynchronously (if using ```GCDWebServerAsyncProcessBlock```) a ```GCDWebServerResponse``` instance (see above) or nil on error, which will result in a 500 HTTP status code returned to the client. It's however recommended to return an instance of [GCDWebServerErrorResponse](GCDWebServer/Responses/GCDWebServerErrorResponse.h) on error so more useful information can be returned to the client.
|
|
||||||
|
|
||||||
Note that most methods on ```GCDWebServer``` to add handlers only require the ```GCDWebServerProcessBlock``` or ```GCDWebServerAsyncProcessBlock``` as they already provide a built-in ```GCDWebServerMatchBlock``` e.g. to match a URL path with a Regex.
|
|
||||||
|
|
||||||
GCDWebServer & Background Mode for iOS Apps
|
GCDWebServer & Background Mode for iOS Apps
|
||||||
===========================================
|
===========================================
|
||||||
|
|
||||||
When doing networking operations in iOS apps, you must handle carefully [what happens when iOS puts the app in the background](https://developer.apple.com/library/ios/technotes/tn2277/_index.html). Typically you must stop any network servers while the app is in the background and restart them when the app comes back to the foreground. This can become quite complex considering servers might have ongoing connections when they need to be stopped.
|
When doing networking operations in iOS apps, you must handle carefully [what happens when iOS puts the app in the background](https://developer.apple.com/library/ios/technotes/tn2277/_index.html). Typically you must stop any network servers while the app is in the background and restart them when the app comes back to the foreground. This can become quite complex considering servers might have ongoing connections when they need to be stopped.
|
||||||
|
|
||||||
Fortunately, GCDWebServer does all of this automatically for you:
|
Fortunately, GCDWebServer does all of this automatically for you:
|
||||||
- GCDWebServer begins a [background task](https://developer.apple.com/library/ios/documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html) whenever the first HTTP connection is opened and ends it only when the last one is closed. This prevents iOS from suspending the app after it goes in the background, which would immediately kill HTTP connections to the client.
|
- GCDWebServer begins a [background task](https://developer.apple.com/library/archive/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html) whenever the first HTTP connection is opened and ends it only when the last one is closed. This prevents iOS from suspending the app after it goes in the background, which would immediately kill HTTP connections to the client.
|
||||||
- While the app is in the background, as long as new HTTP connections keep being initiated, the background task will continue to exist and iOS will not suspend the app (unless under sudden and unexpected memory pressure).
|
- While the app is in the background, as long as new HTTP connections keep being initiated, the background task will continue to exist and iOS will not suspend the app **for up to 10 minutes** (unless under sudden and unexpected memory pressure).
|
||||||
- If the app is still in the background when the last HTTP connection is closed, GCDWebServer will suspend itself and stop accepting new connections as if you had called ```-stop``` (this behavior can be disabled with the ```GCDWebServerOption_AutomaticallySuspendInBackground``` option).
|
- If the app is still in the background when the last HTTP connection is closed, GCDWebServer will suspend itself and stop accepting new connections as if you had called ```-stop``` (this behavior can be disabled with the ```GCDWebServerOption_AutomaticallySuspendInBackground``` option).
|
||||||
- If the app goes in the background while no HTTP connections are opened, GCDWebServer will immediately suspend itself and stop accepting new connections as if you had called ```-stop``` (this behavior can be disabled with the ```GCDWebServerOption_AutomaticallySuspendInBackground``` option).
|
- If the app goes in the background while no HTTP connections are opened, GCDWebServer will immediately suspend itself and stop accepting new connections as if you had called ```-stop``` (this behavior can be disabled with the ```GCDWebServerOption_AutomaticallySuspendInBackground``` option).
|
||||||
- If the app comes back to the foreground and GCDWebServer had been suspended, it will automatically resume itself and start accepting again new HTTP connections as if you had called ```-start```.
|
- If the app comes back to the foreground and GCDWebServer had been suspended, it will automatically resume itself and start accepting again new HTTP connections as if you had called ```-start```.
|
||||||
@@ -338,7 +364,7 @@ Both for debugging and informational purpose, GCDWebServer logs messages extensi
|
|||||||
|
|
||||||
By default, all messages logged by GCDWebServer are sent to its built-in logging facility, which simply outputs to ```stderr``` (assuming a terminal type device is connected). In order to better integrate with the rest of your app or because of the amount of information logged, you might want to use another logging facility.
|
By default, all messages logged by GCDWebServer are sent to its built-in logging facility, which simply outputs to ```stderr``` (assuming a terminal type device is connected). In order to better integrate with the rest of your app or because of the amount of information logged, you might want to use another logging facility.
|
||||||
|
|
||||||
GCDWebServer has automatic support for [XLFacility](https://github.com/swisspol/XLFacility) (by the same author as GCDWebServer and also open-source) and [CocoaLumberjack](https://github.com/CocoaLumberjack/CocoaLumberjack). If either of them is in the same Xcode project, GCDWebServer should use it automatically instead of the built-in logging facility (see [GCDWebServerPrivate.h](GCDWebServer/Core/GCDWebServerPrivate.h) for the implementation details).
|
GCDWebServer has automatic support for [XLFacility](https://github.com/swisspol/XLFacility) (by the same author as GCDWebServer and also open-source): if it is in the same Xcode project, GCDWebServer should use it automatically instead of the built-in logging facility (see [GCDWebServerPrivate.h](GCDWebServer/Core/GCDWebServerPrivate.h) for the implementation details).
|
||||||
|
|
||||||
It's also possible to use a custom logging facility - see [GCDWebServer.h](GCDWebServer/Core/GCDWebServer.h) for more information.
|
It's also possible to use a custom logging facility - see [GCDWebServer.h](GCDWebServer/Core/GCDWebServer.h) for more information.
|
||||||
|
|
||||||
|
|||||||
67
Run-Tests.sh
67
Run-Tests.sh
@@ -1,50 +1,57 @@
|
|||||||
#!/bin/bash -ex
|
#!/bin/bash -exu -o pipefail
|
||||||
|
|
||||||
|
if [[ -f "/usr/local/bin/xcpretty" ]]; then
|
||||||
|
PRETTYFIER="xcpretty"
|
||||||
|
else
|
||||||
|
PRETTYFIER="tee" # Passthrough stdout
|
||||||
|
fi
|
||||||
|
|
||||||
OSX_SDK="macosx"
|
OSX_SDK="macosx"
|
||||||
if [ -z "$TRAVIS" ]; then
|
|
||||||
IOS_SDK="iphoneos"
|
|
||||||
else
|
|
||||||
IOS_SDK="iphonesimulator"
|
IOS_SDK="iphonesimulator"
|
||||||
fi
|
TVOS_SDK="appletvsimulator"
|
||||||
|
|
||||||
|
OSX_SDK_VERSION=`xcodebuild -version -sdk | grep -A 1 '^MacOSX' | tail -n 1 | awk '{ print $2 }'`
|
||||||
|
IOS_SDK_VERSION=`xcodebuild -version -sdk | grep -A 1 '^iPhoneOS' | tail -n 1 | awk '{ print $2 }'`
|
||||||
|
TVOS_SDK_VERSION=`xcodebuild -version -sdk | grep -A 1 '^AppleTVOS' | tail -n 1 | awk '{ print $2 }'`
|
||||||
|
|
||||||
OSX_TARGET="GCDWebServer (Mac)"
|
OSX_TARGET="GCDWebServer (Mac)"
|
||||||
IOS_TARGET="GCDWebServer (iOS)"
|
IOS_TARGET="GCDWebServer (iOS)"
|
||||||
|
TVOS_TARGET="GCDWebServer (tvOS)"
|
||||||
CONFIGURATION="Release"
|
CONFIGURATION="Release"
|
||||||
|
|
||||||
BUILD_DIR="/tmp/GCDWebServer-Build"
|
OSX_TEST_SCHEME="GCDWebServers (Mac)"
|
||||||
|
|
||||||
|
BUILD_DIR="`pwd`/build"
|
||||||
PRODUCT="$BUILD_DIR/$CONFIGURATION/GCDWebServer"
|
PRODUCT="$BUILD_DIR/$CONFIGURATION/GCDWebServer"
|
||||||
|
|
||||||
PAYLOAD_ZIP="Tests/Payload.zip"
|
PAYLOAD_ZIP="Tests/Payload.zip"
|
||||||
PAYLOAD_DIR="/tmp/GCDWebServer-Payload"
|
PAYLOAD_DIR="`pwd`/build/Payload"
|
||||||
|
|
||||||
function runTests {
|
function runTests {
|
||||||
|
EXECUTABLE="$1"
|
||||||
|
MODE="$2"
|
||||||
|
TESTS="$3"
|
||||||
|
FILE="${4:-}"
|
||||||
|
|
||||||
rm -rf "$PAYLOAD_DIR"
|
rm -rf "$PAYLOAD_DIR"
|
||||||
ditto -x -k "$PAYLOAD_ZIP" "$PAYLOAD_DIR"
|
ditto -x -k "$PAYLOAD_ZIP" "$PAYLOAD_DIR"
|
||||||
TZ=GMT find "$PAYLOAD_DIR" -type d -exec SetFile -d "1/1/2014 00:00:00" -m "1/1/2014 00:00:00" '{}' \; # ZIP archives do not preserve directories dates
|
TZ=GMT find "$PAYLOAD_DIR" -type d -exec SetFile -d "1/1/2014 00:00:00" -m "1/1/2014 00:00:00" '{}' \; # ZIP archives do not preserve directories dates
|
||||||
if [ "$4" != "" ]; then
|
if [ "$FILE" != "" ]; then
|
||||||
cp -f "$4" "$PAYLOAD_DIR/Payload"
|
cp -f "$4" "$PAYLOAD_DIR/Payload"
|
||||||
pushd "$PAYLOAD_DIR/Payload"
|
pushd "$PAYLOAD_DIR/Payload"
|
||||||
TZ=GMT SetFile -d "1/1/2014 00:00:00" -m "1/1/2014 00:00:00" `basename "$4"`
|
TZ=GMT SetFile -d "1/1/2014 00:00:00" -m "1/1/2014 00:00:00" `basename "$FILE"`
|
||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
logLevel=2 $1 -mode "$2" -root "$PAYLOAD_DIR/Payload" -tests "$3"
|
logLevel=2 $EXECUTABLE -mode "$MODE" -root "$PAYLOAD_DIR/Payload" -tests "$TESTS"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Build for iOS for oldest deployment target (TODO: run tests on iOS)
|
# Run built-in OS X tests
|
||||||
rm -rf "$BUILD_DIR"
|
rm -rf "$BUILD_DIR"
|
||||||
xcodebuild -sdk "$IOS_SDK" -target "$IOS_TARGET" -configuration "$CONFIGURATION" build "SYMROOT=$BUILD_DIR" "IPHONEOS_DEPLOYMENT_TARGET=5.1.1" > /dev/null
|
xcodebuild test -scheme "$OSX_TEST_SCHEME" "SYMROOT=$BUILD_DIR"
|
||||||
|
|
||||||
# Build for iOS for default deployment target (TODO: run tests on iOS)
|
# Build for OS X for oldest supported deployment target
|
||||||
rm -rf "$BUILD_DIR"
|
rm -rf "$BUILD_DIR"
|
||||||
xcodebuild -sdk "$IOS_SDK" -target "$IOS_TARGET" -configuration "$CONFIGURATION" build "SYMROOT=$BUILD_DIR" > /dev/null
|
xcodebuild build -sdk "$OSX_SDK" -target "$OSX_TARGET" -configuration "$CONFIGURATION" "SYMROOT=$BUILD_DIR" "MACOSX_DEPLOYMENT_TARGET=10.7" | $PRETTYFIER
|
||||||
|
|
||||||
# Build for OS X for oldest deployment target
|
|
||||||
rm -rf "$BUILD_DIR"
|
|
||||||
xcodebuild -sdk "$OSX_SDK" -target "$OSX_TARGET" -configuration "$CONFIGURATION" build "SYMROOT=$BUILD_DIR" "MACOSX_DEPLOYMENT_TARGET=10.7" > /dev/null
|
|
||||||
|
|
||||||
# Build for OS X for default deployment target
|
|
||||||
rm -rf "$BUILD_DIR"
|
|
||||||
xcodebuild -sdk "$OSX_SDK" -target "$OSX_TARGET" -configuration "$CONFIGURATION" build "SYMROOT=$BUILD_DIR" > /dev/null
|
|
||||||
|
|
||||||
# Run tests
|
# Run tests
|
||||||
runTests $PRODUCT "htmlForm" "Tests/HTMLForm"
|
runTests $PRODUCT "htmlForm" "Tests/HTMLForm"
|
||||||
@@ -56,5 +63,21 @@ runTests $PRODUCT "webDAV" "Tests/WebDAV-Finder"
|
|||||||
runTests $PRODUCT "webUploader" "Tests/WebUploader"
|
runTests $PRODUCT "webUploader" "Tests/WebUploader"
|
||||||
runTests $PRODUCT "webServer" "Tests/WebServer-Sample-Movie" "Tests/Sample-Movie.mp4"
|
runTests $PRODUCT "webServer" "Tests/WebServer-Sample-Movie" "Tests/Sample-Movie.mp4"
|
||||||
|
|
||||||
|
# Build for OS X for current deployment target
|
||||||
|
rm -rf "$BUILD_DIR"
|
||||||
|
xcodebuild build -sdk "$OSX_SDK" -target "$OSX_TARGET" -configuration "$CONFIGURATION" "SYMROOT=$BUILD_DIR" "MACOSX_DEPLOYMENT_TARGET=$OSX_SDK_VERSION" | $PRETTYFIER
|
||||||
|
|
||||||
|
# Build for iOS for oldest supported deployment target
|
||||||
|
rm -rf "$BUILD_DIR"
|
||||||
|
xcodebuild build -sdk "$IOS_SDK" -target "$IOS_TARGET" -configuration "$CONFIGURATION" "SYMROOT=$BUILD_DIR" "IPHONEOS_DEPLOYMENT_TARGET=8.0" | $PRETTYFIER
|
||||||
|
|
||||||
|
# Build for iOS for current deployment target
|
||||||
|
rm -rf "$BUILD_DIR"
|
||||||
|
xcodebuild build -sdk "$IOS_SDK" -target "$IOS_TARGET" -configuration "$CONFIGURATION" "SYMROOT=$BUILD_DIR" "IPHONEOS_DEPLOYMENT_TARGET=$IOS_SDK_VERSION" | $PRETTYFIER
|
||||||
|
|
||||||
|
# Build for tvOS for current deployment target
|
||||||
|
rm -rf "$BUILD_DIR"
|
||||||
|
xcodebuild build -sdk "$TVOS_SDK" -target "$TVOS_TARGET" -configuration "$CONFIGURATION" "SYMROOT=$BUILD_DIR" "TVOS_DEPLOYMENT_TARGET=$TVOS_SDK_VERSION" | $PRETTYFIER
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
echo "\nAll tests completed successfully!"
|
echo "\nAll tests completed successfully!"
|
||||||
|
|||||||
0
Tests/WebDAV-Cyberduck/001-200.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/001-200.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/001-HEAD.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/001-HEAD.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/002-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/002-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/002-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/002-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/003-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/003-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/003-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/003-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/004-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/004-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/004-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/004-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/005-200.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/005-200.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/005-HEAD.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/005-HEAD.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/006-404.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/006-404.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/006-HEAD.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/006-HEAD.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/007-201.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/007-201.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/007-COPY.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/007-COPY.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/008-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/008-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/008-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/008-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/009-200.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/009-200.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/009-HEAD.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/009-HEAD.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/010-200.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/010-200.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/010-GET.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/010-GET.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/011-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/011-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/011-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/011-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/012-204.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/012-204.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/012-DELETE.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/012-DELETE.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/013-204.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/013-204.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/013-DELETE.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/013-DELETE.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/014-204.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/014-204.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/014-DELETE.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/014-DELETE.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/015-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/015-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/015-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/015-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/016-201.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/016-201.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/016-MOVE.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/016-MOVE.request
Executable file → Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user