178 Commits
3.2.3 ... 3.5.3

Author SHA1 Message Date
Pierre-Olivier Latour
acdb5c9262 Bumped version 2019-08-09 06:26:10 -07:00
Pierre-Olivier Latour
f54cc20bd6 Formatted source 2019-08-09 06:26:10 -07:00
Pierre-Olivier Latour
02738433bf Enforce hidden and extensions restrictions when moving and copying files in uploaders
Fixes #433
2019-08-09 06:25:37 -07:00
Pierre-Olivier Latour
c9563db0a6 Fixed GCDWebServerBodyReaderCompletionBlock not allowing null data
Fixed #434
2019-08-09 06:16:08 -07:00
Pierre-Olivier Latour
cd1eea5612 Updated to Swift 5 2019-08-09 06:08:56 -07:00
Pierre-Olivier Latour
f7c1c4eff5 Updated to Xcode 10.3 2019-08-09 06:05:19 -07:00
Pierre-Olivier Latour
fdc0feddf0 Merge pull request #426 from beazlr02/patch-1
Update swebServer.swift hello world
2019-07-03 11:46:28 +02:00
Ross Beazley
25dbee032d Update swebServer.swift hello world
looks like the example code hasn't been updated when the method signatures have changed
2019-07-03 10:43:14 +01:00
Pierre-Olivier Latour
c3c7aaad00 Revert "Merge pull request #416 from melle/app-extensions"
This reverts commit 8d0a3599ee, reversing
changes made to 7e4dd53c98.
2019-03-13 13:40:16 -07:00
Pierre-Olivier Latour
2c53064f5d Merge pull request #379 from kayak/runtime-logger
Added support to override built-in logger at runtime
2019-03-13 23:45:25 +09:00
Pierre-Olivier Latour
8d0a3599ee Merge pull request #416 from melle/app-extensions
Allow to build and run GDWebServer in app extensions.
2019-03-13 23:32:50 +09:00
Thomas Mellenthin
653dfb727b Allow to build and run GDWebServer in app extensions. 2019-03-13 15:26:52 +01:00
Pierre-Olivier Latour
7e4dd53c98 Update README.md 2019-01-06 12:25:18 -08:00
Pierre-Olivier Latour
4739d208c0 Bumped version 2019-01-06 12:10:28 -08:00
Pierre-Olivier Latour
f57c307e7d Update README.md 2019-01-06 12:07:45 -08:00
Pierre-Olivier Latour
466b1f8444 Updated copyright years 2019-01-06 12:03:46 -08:00
Pierre-Olivier Latour
fd565421dc Updated README 2019-01-06 12:03:08 -08:00
Pierre-Olivier Latour
8811d2233e Use module in test iOS and tvOS apps 2019-01-06 11:59:40 -08:00
Pierre-Olivier Latour
e561389d33 Use explicit modulemap for frameworks 2019-01-06 11:58:43 -08:00
Pierre-Olivier Latour
32cf20a1d8 Changed iOS framework target family to "Universal" 2019-01-06 11:39:05 -08:00
Pierre-Olivier Latour
3c6c2a2b5d Switch tvOS targets to manual signing 2019-01-06 11:38:30 -08:00
Pierre-Olivier Latour
2bf2dc72c5 Use GCDWebServer framework for iOS and tvOS test apps 2019-01-06 11:32:36 -08:00
Pierre-Olivier Latour
0001648879 Fall back to "CFBundleName" in GCDWebUploader footer if "CFBundleDisplayName" is not defined 2019-01-06 11:19:27 -08:00
Pierre-Olivier Latour
cbbf5483e8 Converted tvOS test app to Swift 2019-01-06 11:16:27 -08:00
Pierre-Olivier Latour
edd1f2850b Converted iOS test app to Swift 2019-01-06 11:16:27 -08:00
Pierre-Olivier Latour
5eb5e14b70 Removed unused build setting 2019-01-06 10:31:16 -08:00
Pierre-Olivier Latour
3e46e12648 Bumped version number for framework 2019-01-06 10:30:45 -08:00
Pierre-Olivier Latour
ee4395e67d Fixed bug in _CreateHTTPMessageFromPerformingRequest() 2019-01-06 09:40:43 -08:00
Pierre-Olivier Latour
34884f273a Bumped version 2019-01-05 12:00:06 -08:00
Pierre-Olivier Latour
87745c0fde Use GCDWebServerNormalizePath() on all relative paths passed to GCDWebServer 2019-01-04 18:59:57 -08:00
Pierre-Olivier Latour
ec800b43d5 Added GCDWebServerNormalizePath() API 2019-01-04 18:58:43 -08:00
Pierre-Olivier Latour
79d9fb389c Removed -stringByStandardizingPath from path arguments to Mac CLT 2019-01-04 18:58:27 -08:00
Pierre-Olivier Latour
33d14f22e0 Use xcpretty in Run-Tests.sh if available 2019-01-03 17:35:46 -08:00
Pierre-Olivier Latour
b060305d6d Only fallback to -[NSData base64Encoding] on macOS prior to 10.9 2019-01-03 17:35:35 -08:00
Pierre-Olivier Latour
561f56e7fb Only fallback to -[NSData base64Encoding] on macOS 2019-01-03 17:30:21 -08:00
Pierre-Olivier Latour
e9fdd19830 Use @available() to check for API availability instead of -respondsToSelector: 2019-01-03 17:24:31 -08:00
Pierre-Olivier Latour
03fae468d1 Fixed implicit-retain-self warnings 2019-01-03 17:15:02 -08:00
Pierre-Olivier Latour
0a7d185417 Fixed warning 2019-01-03 17:15:02 -08:00
Pierre-Olivier Latour
1e29a0195b Fixed strict-prototypes warning 2019-01-03 17:15:02 -08:00
Pierre-Olivier Latour
4e29da53a2 Bumped version 2019-01-03 16:26:01 -08:00
Pierre-Olivier Latour
acc54ceac3 Handle CFHTTPMessageCopyBody() now returning NULL for valid messages without a body 2019-01-03 16:12:30 -08:00
Pierre-Olivier Latour
c46a2ddb22 Ensure directories are always listed in deterministic order
macOS APIs have recently changed and do not return sorted entries for directories.
2019-01-03 16:12:30 -08:00
Pierre-Olivier Latour
71e972084c Adding missing newlines to fprintf() 2019-01-03 15:58:54 -08:00
Pierre-Olivier Latour
e8c872b286 Use local "build" directory for Run-Tests.sh 2019-01-03 15:58:43 -08:00
Pierre-Olivier Latour
fc928d0e2b Improved errors detection in Run-Tests.sh 2019-01-03 15:58:06 -08:00
Pierre-Olivier Latour
e65f0eeaf1 Updated Travis to use Xcode 10.1 2019-01-03 09:20:43 -08:00
Pierre-Olivier Latour
79ae63a4c0 Fixed build warnings 2018-12-14 07:37:35 -08:00
Pierre-Olivier Latour
21cc6bfb35 Added types to collections 2018-12-14 07:37:28 -08:00
Pierre-Olivier Latour
faf28fe0f9 Updated to Xcode 10.10 2018-12-14 07:34:21 -08:00
Pierre-Olivier Latour
bac5b680df Updated to clang-format 7.0.0 2018-12-14 07:10:03 -08:00
Pierre-Olivier Latour
7df465336e Update README.md
Fixes #392
2018-10-09 06:00:52 -07:00
Duncan Cunningham
11254331d1 Make changes based on PR comments 2018-07-13 16:17:23 +02:00
Duncan Cunningham
9f345c6858 Added support to override built-in logger at runtime 2018-05-29 13:21:40 +02:00
Pierre-Olivier Latour
a554893844 Bumped version 2017-11-02 14:01:02 -07:00
Pierre-Olivier Latour
06569fe2cc Fixed NS_ASSUME_NONNULL_BEGIN not located at the right place in GCDWebServerPrivate.h 2017-11-02 14:00:10 -07:00
Pierre-Olivier Latour
e812d3f43c Updated to Xcode 9.1 2017-11-02 13:59:09 -07:00
Johnny Wu
d30c88729d Simplify podspec (#334) 2017-08-23 11:20:54 +02:00
Pierre-Olivier Latour
1a7dc913f1 Update README.md 2017-08-21 11:39:17 +02:00
Pierre-Olivier Latour
47ed2cc593 Remove support for CocoaLumberJack
Fix #328
2017-08-21 11:37:21 +02:00
Pierre-Olivier Latour
033b0d3e56 Bumped version 2017-08-21 08:46:16 +02:00
Pierre-Olivier Latour
6ed51907da Return 501 Non Implemented error for requests without matching handlers 2017-08-21 08:44:35 +02:00
Pierre-Olivier Latour
8ed23992d8 Formatted source 2017-08-21 08:39:47 +02:00
Pierre-Olivier Latour
b4927bdd75 Fixed CFURLCopyPath() returning NULL for "//" path
Fix #297
2017-08-21 08:37:49 +02:00
Pierre-Olivier Latour
44ffec1cf6 Fixed static analyzer issues 2017-08-21 08:31:20 +02:00
Pierre-Olivier Latour
257ed94b65 Fixed build warnings 2017-08-21 08:28:19 +02:00
Pierre-Olivier Latour
4ea0a12317 Updated projet to Xcode 9 2017-08-21 08:27:31 +02:00
Pierre-Olivier Latour
ac788ca906 Increased CocoaLumberjack dependency to 3.x 2017-07-03 16:43:22 -05:00
Pierre-Olivier Latour
9f9509a05e Bumped version
[ci skip]
2017-07-03 16:27:16 -05:00
Pierre-Olivier Latour
48777cc151 Change minimal iOS version to 8.0
[ci skip]
2017-07-03 16:27:09 -05:00
Pierre-Olivier Latour
c35d514b08 Allow to customiz mime types used by GCDWebServerFileResponse 2017-06-24 09:54:09 -07:00
Pierre-Olivier Latour
a013f9cebb Fixed data race issue inside GCDWebServerGetMimeTypeForExtension()
Fix #312
2017-06-24 09:30:39 -07:00
Pierre-Olivier Latour
1e28aef262 Enable address sanitizer by default for Mac unit tests 2017-06-24 09:25:52 -07:00
Pierre-Olivier Latour
09851dd71b Modernize ObjC 2017-06-24 09:25:04 -07:00
Pierre-Olivier Latour
23f77b5765 Set default Xcode indentation to 2 spaces 2017-06-22 08:27:18 -07:00
Pierre-Olivier Latour
7de8b0c643 Updated to Xcode 8.3
[ci skip]
2017-06-22 08:11:02 -07:00
Pierre-Olivier Latour
2685819b61 Updated to latest clang-format
[ci skip]
2017-06-22 08:10:16 -07:00
Pierre-Olivier Latour
fbcf3fa96c Fix build warning 2017-01-01 20:55:36 +09:00
Pierre-Olivier Latour
0f8e4f57e0 Increased minimal iOS deployment target to 8.0 2017-01-01 20:51:42 +09:00
Pierre-Olivier Latour
750214b5d5 Updated Travis CI to Xcode 8.2 2017-01-01 20:42:22 +09:00
Amir Abbas Mousavian
ce5310f2ce Resolves signing and building issue of dynamic frameworks
Fixes #277
2017-01-01 20:34:18 +09:00
Pierre-Olivier Latour
83f1c062b5 Use clang-formatter to format source code 2016-12-27 11:02:32 +09:00
Pierre-Olivier Latour
0c51a9b071 Updated Xcode project format to 8.0 2016-12-27 10:57:00 +09:00
Pierre-Olivier Latour
3b5bc40e5e Updated to Xcode 8.2 2016-12-27 10:56:43 +09:00
Pierre-Olivier Latour
3178baa86e Update README.md 2016-11-16 08:08:28 -08:00
Pierre-Olivier Latour
9a97e44d97 Made "GCDWebUploader.bundle" non-flat to improve code-signing on OS X 2016-09-19 18:12:01 -07:00
Pierre-Olivier Latour
6c2b11e7b6 Updated for Xcode 8 2016-09-19 18:00:46 -07:00
Pierre-Olivier Latour
56b8e1befe Removed executable bit from test files 2016-09-19 17:42:17 -07:00
Pierre-Olivier Latour
4231465c58 Removed executable bit from GCDWebUploader.bundle files 2016-09-19 17:42:17 -07:00
Pierre-Olivier Latour
4958eb8c72 Bumped version 2016-07-15 12:05:17 -07:00
Pierre-Olivier Latour
43555c6662 Merge pull request #272 from bizz84/master
Removed cs.default_subspec from subspecs as this is now disallowed
2016-07-15 11:46:05 -07:00
Andrea Bizzotto
7c08c54156 Removed cs.default_subspec from subspecs as this is now disallowed in Cocoapods 2016-07-15 19:23:27 +01:00
Pierre-Olivier Latour
5834770e08 Removed exception handling which was only partial anyway 2016-06-29 21:38:57 -07:00
Pierre-Olivier Latour
52406a12c2 Merge pull request #267 from hsin919/master
Fix the typo in swift samples
2016-06-29 09:15:10 -07:00
Pierre-Olivier Latour
e83814b0fa Merge pull request #269 from opsGavin/patch-1
Typo in GCDWebServerGZipDecoder:open
2016-06-29 09:11:43 -07:00
Gavin MacLean
fba593a602 Typo in GCDWebServerGZipDecoder:open
I haven't tested or dug into this, as I just happened to spot it while looking through a colleagues code.

It looks like `inflateInit2` is called and then `deflateEnd` on error. I'm wondering if this is a copy and paste issue from `GCDWebServerGZipEncoder` and really it should be `inflateEnd` here instead?
2016-06-29 15:36:40 +01:00
hsin
eec17d5c7b Fix the typo in swift samples 2016-06-08 09:25:04 +08:00
Pierre-Olivier Latour
32bc72113a Merge pull request #266 from DD-P/read-me-typo-fix
Minor documentation fix: "owns" -> "own"
2016-05-08 08:00:48 -07:00
David Davies-Payne
200d21c3fa Minor documentation fix: "owns" -> "own" 2016-05-08 19:32:34 +12:00
Pierre-Olivier Latour
3042b853bc Fix 2016-04-17 20:07:54 -07:00
Pierre-Olivier Latour
50eb0437c1 Updated for Xcode 7.3 2016-04-17 20:07:49 -07:00
Pierre-Olivier Latour
00cc560b8e Merge pull request #264 from AmpMe/refacor/add-queue-priorty-as-an-option
Add an option to set the priority of the dispatch queue
2016-04-03 17:53:13 -07:00
Martin Gagnon
fa41f26b30 Add an option to set the priority of the dispatch queue 2016-04-03 20:11:35 -04:00
Pierre-Olivier Latour
845969ec0d Merge pull request #262 from pra85/patch-1
Fix a typo in Readme
2016-03-24 06:36:57 -07:00
Prayag Verma
629df7895d Fix a typo in Readme
`asynchronously` → `asynchronously`
2016-02-18 13:32:09 +05:30
Pierre-Olivier Latour
55104e5b1e Update README.md 2016-02-08 01:00:25 -08:00
Pierre-Olivier Latour
fcc95fdc11 Merge pull request #260 from jjrscott/master
Added __kindof keyword where appropriate
2016-02-07 17:18:39 -08:00
John Scott
10a94e36fd Removed guards around __kindof usage as the project is Xcode 7 only. 2016-02-06 12:18:49 +00:00
John Scott
010ef9b8bc Added __kindof keyword where appropriate to avoid errors of the form "incompatible block pointer types sending 'GCDWebServerResponse *(^)(GCDWebServerDataRequest *__strong)' to parameter of type 'GCDWebServerProcessBlock' (aka 'GCDWebServerResponse *(^)(GCDWebServerRequest *__strong)')" 2016-02-05 20:45:46 +00:00
Pierre-Olivier Latour
5ca7c27a07 Bumped version 2016-01-20 07:24:59 -08:00
Pierre-Olivier Latour
c6632633f8 Disabled address sanitizer 2016-01-20 07:21:37 -08:00
Pierre-Olivier Latour
5b6eebbb9e Fixed build warning 2016-01-20 07:21:18 -08:00
Pierre-Olivier Latour
a4c61bd071 Bumped version 2016-01-16 06:29:35 -08:00
Pierre-Olivier Latour
2543279a6d Merge pull request #256 from anton-matosov/master
Fixed CocoaLumberjack deps
2016-01-15 10:19:28 -08:00
Anton Matosov
95231b1a66 Fixed CocoaLumberjack deps 2016-01-15 07:54:14 -08:00
Pierre-Olivier Latour
5f2877b85f Merge pull request #255 from iosphere/check-range-location
Fix NSRangeException by checking range of NSTextCheckingResult
2016-01-08 09:18:39 -08:00
Lukas Mollidor
47a51c3d42 Fix NSRangeException by checking range of NSTextCheckingResult 2016-01-08 18:06:55 +01:00
Pierre-Olivier Latour
3873dd1ad3 Merge pull request #248 from dcrawshay/master
Support WebDAV GET request byte ranges
2016-01-03 18:23:28 -08:00
David Crawshay
2ff10258e7 Fixed WebDAV test response files to correctly represent the requested byte-range response instead of the entire file. 2016-01-02 01:58:40 -06:00
David Crawshay
4360c4f7db Support WebDAV GET request byte ranges 2016-01-01 23:23:43 -06:00
Pierre-Olivier Latour
8a6a139687 Merge pull request #236 from maeldur/master
Fixed GCDWebServerGetPrimaryIPAddress() on tvOS devices
2015-12-15 17:03:52 -05:00
Pierre-Olivier Latour
4eba86f348 Merge pull request #238 from bendytree/master
Add libz instructions to readme
2015-12-14 23:02:34 -08:00
Pierre-Olivier Latour
ea973735c1 Also set CFBundleVersion in Info.plist 2015-12-14 22:38:53 -08:00
Pierre-Olivier Latour
5707076e8d Updated to Xcode 7.2 2015-12-14 22:31:53 -08:00
Pierre-Olivier Latour
e1fb807a93 Bumped version 2015-12-14 22:31:47 -08:00
Josh Wright
71575729e9 Added libz and header instructions to readme 2015-11-03 16:28:51 -06:00
Josh Wright
94e30f6442 Added libz and header instructions to readme 2015-11-03 16:23:05 -06:00
Pierre-Olivier Latour
c98941121a Use Xcode 7.1 in Travis CI 2015-11-01 13:14:42 -08:00
Pierre-Olivier Latour
4ee9c30911 Fixed build script 2015-11-01 13:12:57 -08:00
Pierre-Olivier Latour
48cf20bb55 Updated iOS app to latest best practices 2015-11-01 11:38:49 -08:00
Pierre-Olivier Latour
ac9b8a5f47 Added support for tvOS 2015-11-01 11:38:49 -08:00
Pierre-Olivier Latour
cad428ca6f Removed deprecation warnings on tvOS 2015-11-01 11:25:18 -08:00
Pierre-Olivier Latour
bb5c1a5195 No need to link explicitly to Foundation 2015-11-01 11:25:18 -08:00
Pierre-Olivier Latour
bef95231d2 Updated for Xcode 7.1 2015-11-01 11:25:18 -08:00
Pierre-Olivier Latour
0192c364b6 Fix 2015-11-01 11:25:11 -08:00
Nick Chang
062a0dcee4 allow serverURL to be assigned on tvOS with wifi connection 2015-10-30 12:31:27 -07:00
Pierre-Olivier Latour
21d9fc2f62 Merge pull request #228 from CrossWaterBridge/tvos
Enable support for tvOS
2015-10-17 12:48:07 -07:00
Hilton Campbell
614ff58be5 Enable support for tvOS. 2015-10-17 13:46:46 -06:00
Pierre-Olivier Latour
7b0477b1e0 Merge pull request #219 from macdrevx/pods-use-frameworks
Enable support for Podfiles with use_frameworks!
2015-09-28 11:40:16 -07:00
Andrew Hershberger
a674614713 Enable support for Podfiles with use_frameworks!
The previous implementation looked in the main
bundle for the GCDWebUploader resource bundle.
When using the use_frameworks! Podfile option, the
resource bundle will be in a framework bundle, not
in the main bundle.
2015-09-22 15:22:45 -04:00
Pierre-Olivier Latour
b549f1197d Merge pull request #218 from tifroz/master
Fixed async handler crash under Swift 2
2015-09-21 15:20:48 -07:00
tifroz
9d38bb4f94 Workaround for Swift2 which apparently fails to retain the completion blocks passed as parameters 2015-09-21 15:15:58 -07:00
Pierre-Olivier Latour
44c6a8adcf Update README.md
[ci skip]
2015-09-16 11:40:19 -07:00
Pierre-Olivier Latour
aaf8679308 Increased Bonjour resolution timeout to 5 seconds 2015-09-16 11:34:16 -07:00
Pierre-Olivier Latour
81d74b46b8 Fix 2015-09-16 11:26:00 -07:00
Pierre-Olivier Latour
f7de5cac09 Fix 2015-09-16 11:24:08 -07:00
Pierre-Olivier Latour
a1c68352a4 Bumped version to 3.3 2015-09-16 11:19:02 -07:00
Pierre-Olivier Latour
e70a3338a5 Added support for NAT port mapping 2015-09-16 11:16:41 -07:00
Pierre-Olivier Latour
3c33e9f056 Bumped version to 3.2.7 2015-09-10 09:02:07 -07:00
Pierre-Olivier Latour
d160e5ff91 Merge pull request #213 from 0xpablo/master
Turn 'buildForRunning' on for 'GCDWebServers' iOS and Mac Schemes
2015-09-10 09:01:04 -07:00
Pablo Carcelén
2d2343ab34 Turn 'buildForRunning' on for 'GCDWebServers' iOS and Mac Schemes
This fixes building the project using Carthage (See #212)
2015-09-10 17:14:59 +02:00
Pierre-Olivier Latour
f6783daadd Updated test script to run built-in tests 2015-09-09 09:41:00 -07:00
Pierre-Olivier Latour
99cae36644 Added minimal tests for Mac framework 2015-09-09 09:36:29 -07:00
Pierre-Olivier Latour
b292710102 Fix 2015-09-09 08:41:08 -07:00
Pierre-Olivier Latour
b8b4a35178 Add version to framework Info.plist 2015-09-09 08:37:51 -07:00
Pierre-Olivier Latour
ecc572a934 Bumped version to 3.2.6 2015-09-09 08:37:36 -07:00
Pierre-Olivier Latour
3a02341b0c Disable testing and running in shared schemes for frameworks 2015-09-09 08:31:18 -07:00
Pierre-Olivier Latour
e792fe8eb6 Fixes 2015-09-09 08:30:54 -07:00
Pierre-Olivier Latour
4c8ec1d685 Removed unnecessary files from Xcode project 2015-09-09 08:25:53 -07:00
Pierre-Olivier Latour
f7bb5babf8 Bumped copyright year 2015-09-09 08:24:47 -07:00
Pierre-Olivier Latour
ae88198f20 Update README.md 2015-08-13 20:29:26 -07:00
Pierre-Olivier Latour
d71c0d493f Update README.md 2015-08-03 08:46:44 -07:00
Pierre-Olivier Latour
d611ae0cbe Merge pull request #187 from lfaoro/patch-2
Update README.md
2015-07-06 16:07:02 -07:00
Leonard
93287edfd5 Update README.md
The previous code won't compile in Swift, it had a missing ')' and the 'println' statement is deprecated, replaced by 'print'.
The bridging-headers syntax was wrong, couldn't compile.
Swift still requires an 'import GCDWebServers' statement to use the API
2015-07-07 00:17:31 +02:00
Pierre-Olivier Latour
dc287906d6 Merge pull request #186 from lfaoro/patch-1
Update README.md
2015-07-06 10:44:57 -07:00
Leonard
ab9459a67a Update README.md
the version number should not have quotes, with the quotes carthage errors: 
Parse error: unexpected trailing characters in line: github "swisspol/GCDWebServer" ~> "3.2.5"
2015-07-06 19:26:07 +02:00
Pierre-Olivier Latour
aa8fc97b9b Fixed buffer overflow when retrieving socket addresses 2015-07-03 10:17:33 -07:00
Pierre-Olivier Latour
863febed62 Updated for Xcode 7 2015-07-03 09:55:46 -07:00
Pierre-Olivier Latour
2ff117dbf3 Merge pull request #183 from guidomb/patch-1
Fixes error in Carthage documentation
2015-07-03 09:44:00 -07:00
Guido Marucci Blas
4838d0def9 Fixes error in Carthage documentation
~> is like an operator and does not go inside the version string. https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#cartfile
2015-07-03 13:43:03 -03:00
Pierre-Olivier Latour
c394ae8bf5 Update README.md 2015-07-03 08:51:42 -07:00
Pierre-Olivier Latour
bdfe6728ae Bumped version 2015-07-03 08:49:02 -07:00
Pierre-Olivier Latour
b1ab7479b3 Update README.md 2015-06-15 10:27:51 -07:00
Seba Gamboa
03a0ac32ee Added Carthage support 2015-06-15 00:28:11 -07:00
Seba Gamboa
bd2c292cb6 Generating Frameworks 2015-06-12 10:30:02 -07:00
Pierre-Olivier Latour
e8b67264ab Bumped version 2015-06-12 09:28:46 -07:00
Pierre-Olivier Latour
3d5fd0b828 Update README.md 2015-05-12 17:46:55 -07:00
Pierre-Olivier Latour
9524d31b1b Allow harmless 'Content-Type' headers on requests 2015-05-04 09:59:03 -07:00
Pierre-Olivier Latour
a3606d6027 Don't start a background task while the app is already in background 2015-04-30 14:53:45 -07:00
860 changed files with 3988 additions and 1919 deletions

16
.clang-format Normal file
View 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
View File

@@ -1,5 +1,5 @@
.DS_Store
xcuserdata
project.xcworkspace
Tests/Payload
/build
/Carthage/Build

View File

@@ -1,2 +1,3 @@
language: objective-c
script: ./Run-Tests.sh
osx_image: xcode10.1

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
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.
*/
#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"
@interface AppDelegate () <GCDWebUploaderDelegate> {
@private
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
// GCDWebDAVServer
#import "GCDWebDAVServer.h"

22
Frameworks/Info.plist Normal file
View 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
View 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

View File

@@ -0,0 +1,3 @@
framework module GCDWebServers {
umbrella header "GCDWebServers.h"
}

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -27,6 +27,8 @@
#import "GCDWebServer.h"
NS_ASSUME_NONNULL_BEGIN
@class GCDWebDAVServer;
/**
@@ -86,14 +88,14 @@
/**
* 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.
*
* 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
@@ -154,3 +156,5 @@
- (BOOL)shouldCreateDirectoryAtPath:(NSString*)path;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
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
};
@interface GCDWebDAVServer () {
@private
NSString* _uploadDirectory;
NSArray* _allowedExtensions;
BOOL _allowHidden;
NS_ASSUME_NONNULL_BEGIN
@interface GCDWebDAVServer (Methods)
- (nullable GCDWebServerResponse*)performOPTIONS:(GCDWebServerRequest*)request;
- (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
@implementation GCDWebDAVServer (Methods)
// Must match implementation in GCDWebUploader
- (BOOL)_checkSandboxedPath:(NSString*)path {
return [[path stringByStandardizingPath] hasPrefix:_uploadDirectory];
}
- (BOOL)_checkFileExtension:(NSString*)fileName {
if (_allowedExtensions && ![_allowedExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
if (_allowedFileExtensions && ![_allowedFileExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
return NO;
}
return YES;
@@ -94,27 +181,32 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
- (GCDWebServerResponse*)performGET:(GCDWebServerRequest*)request {
NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
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];
}
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];
}
// Because HEAD requests are mapped to GET ones, we need to handle directories but it's OK to return nothing per http://webdav.org/specs/rfc4918.html#rfc.section.9.4
if (isDirectory) {
return [GCDWebServerResponse response];
}
if ([self.delegate respondsToSelector:@selector(davServer:didDownloadFileAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate davServer:self didDownloadFileAtPath:absolutePath];
});
}
if ([request hasByteRange]) {
return [GCDWebServerFileResponse responseWithFile:absolutePath byteRange:request.byteRange];
}
return [GCDWebServerFileResponse responseWithFile:absolutePath];
}
@@ -122,37 +214,34 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
if ([request hasByteRange]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Range uploads not supported"];
}
NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
if (![self _checkSandboxedPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
BOOL isDirectory;
if (![[NSFileManager defaultManager] fileExistsAtPath:[absolutePath stringByDeletingLastPathComponent] isDirectory:&isDirectory] || !isDirectory) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Conflict message:@"Missing intermediate collection(s) for \"%@\"", relativePath];
}
BOOL existing = [[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory];
if (existing && isDirectory) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_MethodNotAllowed message:@"PUT not allowed on existing collection \"%@\"", relativePath];
}
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];
}
if (![self shouldUploadFileAtPath:absolutePath withTemporaryFile:request.temporaryPath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file to \"%@\" is not permitted", relativePath];
}
[[NSFileManager defaultManager] removeItemAtPath:absolutePath error:NULL];
NSError* error = nil;
if (![[NSFileManager defaultManager] moveItemAtPath:request.temporaryPath toPath:absolutePath error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving uploaded file to \"%@\"", relativePath];
}
if ([self.delegate respondsToSelector:@selector(davServer:didUploadFileAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate davServer:self didUploadFileAtPath:absolutePath];
@@ -166,28 +255,28 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
if (depthHeader && ![depthHeader isEqualToString:@"infinity"]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Unsupported 'Depth' header: %@", depthHeader];
}
NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
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];
}
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];
}
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(davServer:didDeleteItemAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate davServer:self didDeleteItemAtPath:absolutePath];
@@ -200,26 +289,23 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
if ([request hasBody] && (request.contentLength > 0)) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_UnsupportedMediaType message:@"Unexpected request body for MKCOL method"];
}
NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
if (![self _checkSandboxedPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
BOOL isDirectory;
if (![[NSFileManager defaultManager] fileExistsAtPath:[absolutePath stringByDeletingLastPathComponent] isDirectory:&isDirectory] || !isDirectory) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Conflict message:@"Missing intermediate collection(s) for \"%@\"", relativePath];
}
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];
}
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];
@@ -228,12 +314,12 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
NSString* creationDateHeader = [request.headers objectForKey:@"X-GCDWebServer-CreationDate"];
if (creationDateHeader) {
NSDate* date = GCDWebServerParseISO8601(creationDateHeader);
if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileCreationDate: date} ofItemAtPath:absolutePath error:&error]) {
if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileCreationDate : date} ofItemAtPath:absolutePath error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed setting creation date for directory \"%@\"", relativePath];
}
}
#endif
if ([self.delegate respondsToSelector:@selector(davServer:didCreateDirectoryAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate davServer:self didCreateDirectoryAtPath:absolutePath];
@@ -249,40 +335,45 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Unsupported 'Depth' header: %@", depthHeader];
}
}
NSString* srcRelativePath = request.path;
NSString* srcAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:srcRelativePath];
if (![self _checkSandboxedPath:srcAbsolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", srcRelativePath];
}
NSString* srcAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(srcRelativePath)];
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)) {
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];
NSString* dstAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:dstRelativePath];
if (![self _checkSandboxedPath:dstAbsolutePath]) {
#pragma clang diagnostic pop
NSString* dstAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(dstRelativePath)];
if (!dstAbsolutePath) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", srcRelativePath];
}
BOOL isDirectory;
if (![[NSFileManager defaultManager] fileExistsAtPath:[dstAbsolutePath stringByDeletingLastPathComponent] isDirectory:&isDirectory] || !isDirectory) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Conflict message:@"Invalid destination \"%@\"", dstRelativePath];
}
NSString* itemName = [dstAbsolutePath lastPathComponent];
if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"%@ to item name \"%@\" is not allowed", isMove ? @"Moving" : @"Copying", itemName];
NSString* srcName = [srcAbsolutePath lastPathComponent];
if ((!_allowHiddenItems && [srcName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:srcName])) {
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"];
BOOL existing = [[NSFileManager defaultManager] fileExistsAtPath:dstAbsolutePath];
if (existing && ((isMove && ![overwriteHeader isEqualToString:@"T"]) || (!isMove && [overwriteHeader isEqualToString:@"F"]))) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_PreconditionFailed message:@"Destination \"%@\" already exists", dstRelativePath];
}
if (isMove) {
if (![self shouldMoveItemFromPath:srcAbsolutePath toPath:dstAbsolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving \"%@\" to \"%@\" is not permitted", srcRelativePath, dstRelativePath];
@@ -292,7 +383,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Copying \"%@\" to \"%@\" is not permitted", srcRelativePath, dstRelativePath];
}
}
NSError* error = nil;
if (isMove) {
[[NSFileManager defaultManager] removeItemAtPath:dstAbsolutePath error:NULL];
@@ -304,7 +395,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden underlyingError:error message:@"Failed copying \"%@\" to \"%@\"", srcRelativePath, dstRelativePath];
}
}
if (isMove) {
if ([self.delegate respondsToSelector:@selector(davServer:didMoveItemFromPath:toPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
@@ -318,7 +409,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
});
}
}
return [GCDWebServerResponse responseWithStatusCode:(existing ? kGCDWebServerHTTPStatusCode_NoContent : kGCDWebServerHTTPStatusCode_Created)];
}
@@ -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 {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
CFStringRef escapedPath = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)resourcePath, NULL, CFSTR("<&>?+"), kCFStringEncodingUTF8);
#pragma clang diagnostic pop
if (escapedPath) {
NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:itemPath error:NULL];
NSString* type = [attributes objectForKey:NSFileType];
@@ -344,7 +438,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
[xmlString appendFormat:@"<D:href>%@</D:href>", escapedPath];
[xmlString appendString:@"<D:propstat>"];
[xmlString appendString:@"<D:prop>"];
if (properties & kDAVProperty_ResourceType) {
if (isDirectory) {
[xmlString appendString:@"<D:resourcetype><D:collection/></D:resourcetype>"];
@@ -352,19 +446,19 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
[xmlString appendString:@"<D:resourcetype/>"];
}
}
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
[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]) {
[xmlString appendFormat:@"<D:getcontentlength>%llu</D:getcontentlength>", [attributes fileSize]];
}
[xmlString appendString:@"</D:prop>"];
[xmlString appendString:@"<D:status>HTTP/1.1 200 OK</D:status>"];
[xmlString appendString:@"</D:propstat>"];
@@ -386,7 +480,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
} else {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Unsupported 'Depth' header: %@", depthHeader]; // TODO: Return 403 / propfind-finite-depth for "infinity" depth
}
DAVProperties properties = 0;
if (request.data.length) {
BOOL success = YES;
@@ -427,28 +521,28 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
} else {
properties = kDAVAllProperties;
}
NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
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];
}
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];
}
NSArray* items = nil;
if (isDirectory) {
NSError* error = nil;
items = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:absolutePath error:&error];
items = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:absolutePath error:&error] sortedArrayUsingSelector:@selector(localizedStandardCompare:)];
if (items == nil) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed listing directory \"%@\"", relativePath];
}
}
NSMutableString* xmlString = [NSMutableString stringWithString:@"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"];
[xmlString appendString:@"<D:multistatus xmlns:D=\"DAV:\">\n"];
if (![relativePath hasPrefix:@"/"]) {
@@ -460,14 +554,14 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
relativePath = [relativePath stringByAppendingString:@"/"];
}
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];
}
}
}
[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\""];
response.statusCode = kGCDWebServerHTTPStatusCode_MultiStatus;
return response;
@@ -477,14 +571,14 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
if (!_IsMacFinder(request)) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_MethodNotAllowed message:@"LOCK method only allowed for Mac Finder"];
}
NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
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];
}
NSString* depthHeader = [request.headers objectForKey:@"Depth"];
NSString* timeoutHeader = [request.headers objectForKey:@"Timeout"];
NSString* scope = nil;
@@ -522,16 +616,16 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
NSString* string = [[NSString alloc] initWithData:request.data encoding:NSUTF8StringEncoding];
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Invalid DAV properties:\n%@", string];
}
if (![scope isEqualToString:@"exclusive"] || ![type isEqualToString:@"write"] || ![depthHeader isEqualToString:@"0"]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Locking request \"%@/%@/%@\" for \"%@\" is not allowed", scope, type, depthHeader, relativePath];
}
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];
}
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
NSString* lockTokenHeader = [request.headers objectForKey:@"X-GCDWebServer-LockToken"];
if (lockTokenHeader) {
@@ -545,7 +639,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
CFRelease(string);
CFRelease(uuid);
}
NSMutableString* xmlString = [NSMutableString stringWithString:@"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"];
[xmlString appendString:@"<D:prop xmlns:D=\"DAV:\">\n"];
[xmlString appendString:@"<D:lockdiscovery>\n<D:activelock>\n"];
@@ -559,13 +653,13 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
[xmlString appendFormat:@"<D:timeout>%@</D:timeout>\n", timeoutHeader];
}
[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 appendString:@"</D:activelock>\n</D:lockdiscovery>\n"];
[xmlString appendString:@"</D:prop>"];
[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\""];
return response;
}
@@ -574,97 +668,30 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
if (!_IsMacFinder(request)) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_MethodNotAllowed message:@"UNLOCK method only allowed for Mac Finder"];
}
NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
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];
}
NSString* tokenHeader = [request.headers objectForKey:@"Lock-Token"];
if (!tokenHeader.length) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Missing 'Lock-Token' header"];
}
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];
}
[self logVerbose:@"WebDAV pretending to unlock \"%@\"", relativePath];
return [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_NoContent];
}
@end
@implementation GCDWebDAVServer
@synthesize uploadDirectory=_uploadDirectory, allowedFileExtensions=_allowedExtensions, allowHiddenItems=_allowHidden;
@dynamic delegate;
- (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)
- (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath {

View File

@@ -2,19 +2,20 @@
# http://guides.cocoapods.org/making/getting-setup-with-trunk.html
# $ sudo gem update cocoapods
# (optional) $ pod trunk register {email} {name} --description={computer}
# $ pod trunk push
# $ pod trunk --verbose push
# DELETE THIS SECTION BEFORE PROCEEDING!
Pod::Spec.new do |s|
s.name = 'GCDWebServer'
s.version = '3.2.3'
s.version = '3.5.3'
s.author = { 'Pierre-Olivier Latour' => 'info@pol-online.net' }
s.license = { :type => 'BSD', :file => 'LICENSE' }
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.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.requires_arc = true
@@ -26,6 +27,8 @@ Pod::Spec.new do |s|
cs.requires_arc = true
cs.ios.library = 'z'
cs.ios.frameworks = 'MobileCoreServices', 'CFNetwork'
cs.tvos.library = 'z'
cs.tvos.frameworks = 'MobileCoreServices', 'CFNetwork'
cs.osx.library = 'z'
cs.osx.framework = 'SystemConfiguration'
end
@@ -35,6 +38,7 @@ Pod::Spec.new do |s|
cs.source_files = 'GCDWebDAVServer/*.{h,m}'
cs.requires_arc = true
cs.ios.library = 'xml2'
cs.tvos.library = 'xml2'
cs.osx.library = 'xml2'
cs.compiler_flags = '-I$(SDKROOT)/usr/include/libxml2'
end
@@ -44,6 +48,5 @@ Pod::Spec.new do |s|
cs.source_files = 'GCDWebUploader/*.{h,m}'
cs.requires_arc = true
cs.resource = "GCDWebUploader/GCDWebUploader.bundle"
end
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1030"
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"
enableAddressSanitizer = "YES"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E24039241BA09207000B7089"
BuildableName = "Tests.xctest"
BlueprintName = "Tests (Mac)"
ReferencedContainer = "container:GCDWebServer.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</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>
<AdditionalOptions>
</AdditionalOptions>
</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>

View File

@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1030"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "NO"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CEE28CEE1AE0051F00F4023C"
BuildableName = "GCDWebServers.framework"
BlueprintName = "GCDWebServers (iOS)"
ReferencedContainer = "container:GCDWebServer.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CEE28CEE1AE0051F00F4023C"
BuildableName = "GCDWebServers.framework"
BlueprintName = "GCDWebServers (iOS)"
ReferencedContainer = "container:GCDWebServer.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</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 = "CEE28CEE1AE0051F00F4023C"
BuildableName = "GCDWebServers.framework"
BlueprintName = "GCDWebServers (iOS)"
ReferencedContainer = "container:GCDWebServer.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CEE28CEE1AE0051F00F4023C"
BuildableName = "GCDWebServers.framework"
BlueprintName = "GCDWebServers (iOS)"
ReferencedContainer = "container:GCDWebServer.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1030"
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">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E2DDD18A1BE69404002CE867"
BuildableName = "GCDWebServers.framework"
BlueprintName = "GCDWebServers (tvOS)"
ReferencedContainer = "container:GCDWebServer.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</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>
<AdditionalOptions>
</AdditionalOptions>
</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>

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -30,6 +30,8 @@
#import "GCDWebServerRequest.h"
#import "GCDWebServerResponse.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerMatchBlock is called for every handler added to the
* 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.
* 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
@@ -52,7 +54,7 @@ typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod,
* recommended to return a GCDWebServerErrorResponse on error so more useful
* 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
@@ -64,8 +66,15 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* r
* It's however recommended to return a GCDWebServerErrorResponse on error so more
* useful information can be returned to the client.
*/
typedef void (^GCDWebServerCompletionBlock)(GCDWebServerResponse* response);
typedef void (^GCDWebServerAsyncProcessBlock)(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock);
typedef void (^GCDWebServerCompletionBlock)(GCDWebServerResponse* _Nullable response);
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).
@@ -90,14 +99,26 @@ extern NSString* const GCDWebServerOption_BonjourName;
*/
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
* network (NSNumber / BOOL).
*
* The default value is NO.
*
* @warning Bonjour should be disabled if using this option since the server
* will not be reachable from the outside network anyway.
* @warning Bonjour and NAT port mapping should be disabled if using this option
* since the server will not be reachable from the outside network anyway.
*/
extern NSString* const GCDWebServerOption_BindToLocalhost;
@@ -164,6 +185,15 @@ extern NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET;
*/
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
/**
@@ -213,9 +243,21 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
/**
* This method is called after the Bonjour registration for the server has
* successfully completed.
*
* Use the "bonjourServerURL" property to retrieve the Bonjour address of the
* 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
* server to serve a series of HTTP requests.
@@ -262,7 +304,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
/**
* 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.
@@ -282,7 +324,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* @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.
*/
@property(nonatomic, readonly) NSString* bonjourName;
@property(nonatomic, readonly, nullable) NSString* bonjourName;
/**
* Returns the Bonjour service type used by the server.
@@ -290,7 +332,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* @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.
*/
@property(nonatomic, readonly) NSString* bonjourType;
@property(nonatomic, readonly, nullable) NSString* bonjourType;
/**
* This method is the designated initializer for the class.
@@ -330,7 +372,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
*
* 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.
@@ -350,7 +392,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
*
* @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.
@@ -360,7 +402,15 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* 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).
*/
@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)
@@ -377,7 +427,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
*
* 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
@@ -390,7 +440,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
*
* @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
@@ -401,7 +451,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
*
* @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
@@ -457,7 +507,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* Adds a handler to the server to respond to incoming "GET" HTTP requests
* 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
@@ -474,7 +524,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* The "indexFilename" argument allows to specify an "index" file name to use
* 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
@@ -489,11 +539,10 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
*
* Currently supported third-party logging facilities are:
* - 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
* logging level is INFO (or DEBUG if the preprocessor constant "DEBUG"
* evaluates to non-zero at compile time).
* For the built-in logging facility, the default logging level is INFO
* (or DEBUG if the preprocessor constant "DEBUG" evaluates to non-zero at
* compile time).
*
* It's possible to have GCDWebServer use a custom logging facility by defining
* the "__GCDWEBSERVER_LOGGING_HEADER__" preprocessor constant in Xcode build
@@ -505,12 +554,10 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* GWS_LOG_INFO(...)
* GWS_LOG_WARNING(...)
* GWS_LOG_ERROR(...)
* GWS_LOG_EXCEPTION(__EXCEPTION__)
*
* IMPORTANT: Except for GWS_LOG_EXCEPTION() which gets passed an NSException,
* these macros must behave like NSLog(). Furthermore the GWS_LOG_DEBUG() macro
* should not do anything unless the preprocessor constant "DEBUG" evaluates to
* non-zero.
* IMPORTANT: These macros must behave like NSLog(). Furthermore the GWS_LOG_DEBUG()
* macro should not do anything unless the preprocessor constant "DEBUG" evaluates
* to non-zero.
*
* The logging methods below send log messages to the same logging facility
* used by GCDWebServer. They can be used for consistency wherever you interact
@@ -530,34 +577,36 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* INFO = 2
* WARNING = 3
* ERROR = 4
* EXCEPTION = 5
*/
+ (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.
*/
- (void)logVerbose:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
- (void)logVerbose:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2);
/**
* Logs a message to the logging facility at the INFO level.
*/
- (void)logInfo:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
- (void)logInfo:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2);
/**
* Logs a message to the logging facility at the WARNING level.
*/
- (void)logWarning:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
- (void)logWarning:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2);
/**
* Logs a message to the logging facility at the ERROR level.
*/
- (void)logError:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
/**
* Logs an exception to the logging facility at the EXCEPTION level.
*/
- (void)logException:(NSException*)exception;
- (void)logError:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2);
@end
@@ -579,8 +628,10 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
*
* 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
#endif
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -27,6 +27,8 @@
#import "GCDWebServer.h"
NS_ASSUME_NONNULL_BEGIN
@class GCDWebServerHandler;
/**
@@ -128,7 +130,7 @@
*
* 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
@@ -139,7 +141,7 @@
* The default implementation checks for HTTP authentication if applicable
* 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,
@@ -169,7 +171,7 @@
* @warning If the request was invalid (e.g. the HTTP headers were malformed),
* 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.
@@ -177,3 +179,5 @@
- (void)close;
@end
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -27,6 +27,8 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#ifdef __cplusplus
extern "C" {
#endif
@@ -34,27 +36,31 @@ extern "C" {
/**
* Converts a file extension to the corresponding MIME type.
* 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.
* The legal characters ":@/?&=+" are also escaped to ensure compatibility
* with URL encoded forms and URL queries.
*/
NSString* GCDWebServerEscapeURLString(NSString* string);
NSString* _Nullable GCDWebServerEscapeURLString(NSString* string);
/**
* Unescapes a URL percent-encoded string.
*/
NSString* GCDWebServerUnescapeURLString(NSString* string);
NSString* _Nullable GCDWebServerUnescapeURLString(NSString* string);
/**
* Extracts the unescaped names and values from an
* "application/x-www-form-urlencoded" form.
* 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
@@ -63,7 +69,7 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
* On iOS, returns the IPv4 or IPv6 address as a string of the WiFi
* interface if connected or nil otherwise.
*/
NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6);
NSString* _Nullable GCDWebServerGetPrimaryIPAddress(BOOL useIPv6);
/**
* 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.
*/
NSDate* GCDWebServerParseRFC822(NSString* string);
NSDate* _Nullable GCDWebServerParseRFC822(NSString* string);
/**
* 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
* 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
}
#endif
NS_ASSUME_NONNULL_END

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -83,21 +83,28 @@ NSString* GCDWebServerNormalizeHeaderValue(NSString* value) {
}
NSString* GCDWebServerTruncateHeaderValue(NSString* value) {
NSRange range = [value rangeOfString:@";"];
return range.location != NSNotFound ? [value substringToIndex:range.location] : value;
if (value) {
NSRange range = [value rangeOfString:@";"];
if (range.location != NSNotFound) {
return [value substringToIndex:range.location];
}
}
return value;
}
NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) {
NSString* parameter = nil;
NSScanner* scanner = [[NSScanner alloc] initWithString:value];
[scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive
NSString* string = [NSString stringWithFormat:@"%@=", name];
if ([scanner scanUpToString:string intoString:NULL]) {
[scanner scanString:string intoString:NULL];
if ([scanner scanString:@"\"" intoString:NULL]) {
[scanner scanUpToString:@"\"" intoString:&parameter];
} else {
[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&parameter];
if (value) {
NSScanner* scanner = [[NSScanner alloc] initWithString:value];
[scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive
NSString* string = [NSString stringWithFormat:@"%@=", name];
if ([scanner scanUpToString:string intoString:NULL]) {
[scanner scanString:string intoString:NULL];
if ([scanner scanString:@"\"" intoString:NULL]) {
[scanner scanUpToString:@"\"" intoString:&parameter];
} else {
[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&parameter];
}
}
}
return parameter;
@@ -159,17 +166,15 @@ NSString* GCDWebServerDescribeData(NSData* data, NSString* type) {
return [NSString stringWithFormat:@"<%lu bytes>", (unsigned long)data.length];
}
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) {
static NSDictionary* _overrides = nil;
if (_overrides == nil) {
_overrides = [[NSDictionary alloc] initWithObjectsAndKeys:
@"text/css", @"css",
nil];
}
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary<NSString*, NSString*>* overrides) {
NSDictionary* builtInOverrides = @{@"css" : @"text/css"};
NSString* mimeType = nil;
extension = [extension lowercaseString];
if (extension.length) {
mimeType = [_overrides objectForKey:extension];
mimeType = [overrides objectForKey:extension];
if (mimeType == nil) {
mimeType = [builtInOverrides objectForKey:extension];
}
if (mimeType == nil) {
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
if (uti) {
@@ -182,14 +187,20 @@ NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) {
}
NSString* GCDWebServerEscapeURLString(NSString* string) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":@/?&=+"), kCFStringEncodingUTF8));
#pragma clang diagnostic pop
}
NSString* GCDWebServerUnescapeURLString(NSString* string) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
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];
NSScanner* scanner = [[NSScanner alloc] initWithString:form];
[scanner setCharactersToBeSkipped:nil];
@@ -199,13 +210,13 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
break;
}
[scanner setScanLocation:([scanner scanLocation] + 1)];
NSString* value = nil;
[scanner scanUpToString:@"&" intoString:&value];
if (value == nil) {
value = @"";
}
key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "];
NSString* unescapedKey = key ? GCDWebServerUnescapeURLString(key) : nil;
value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
@@ -216,7 +227,7 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
GWS_LOG_WARNING(@"Failed parsing URL encoded form for key \"%@\" and value \"%@\"", key, value);
GWS_DNOT_REACHED();
}
if ([scanner isAtEnd]) {
break;
}
@@ -226,21 +237,22 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
}
NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) {
NSString* string = nil;
char hostBuffer[NI_MAXHOST];
char serviceBuffer[NI_MAXSERV];
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];
} else {
if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) != 0) {
#if DEBUG
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* address = nil;
#if TARGET_OS_IPHONE
#if !TARGET_IPHONE_SIMULATOR
#if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_TV
const char* primaryInterface = "en0"; // WiFi interface on iOS
#endif
#else
@@ -249,7 +261,10 @@ NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
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
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(store);
@@ -261,8 +276,10 @@ NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
struct ifaddrs* list;
if (getifaddrs(&list) >= 0) {
for (struct ifaddrs* ifap = list; ifap; ifap = ifap->ifa_next) {
#if TARGET_IPHONE_SIMULATOR
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
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_TV
// 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
if (strcmp(ifap->ifa_name, primaryInterface))
#endif
@@ -295,5 +312,20 @@ NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) {
buffer[2 * i + 1] = byteLo >= 10 ? 'a' + byteLo - 10 : '0' + byteLo;
}
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:@"/"];
}

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
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_WARNING(...) XLOG_WARNING(__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_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("CocoaLumberjack/CocoaLumberjack.h")
#import <CocoaLumberjack/CocoaLumberjack.h>
#define __GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__
#undef LOG_LEVEL_DEF
#define LOG_LEVEL_DEF GCDWebServerLogLevel
extern DDLogLevel 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
* logging facility.
@@ -118,23 +95,36 @@ typedef NS_ENUM(int, GCDWebServerLoggingLevel) {
kGCDWebServerLoggingLevel_Verbose,
kGCDWebServerLoggingLevel_Info,
kGCDWebServerLoggingLevel_Warning,
kGCDWebServerLoggingLevel_Error,
kGCDWebServerLoggingLevel_Exception
kGCDWebServerLoggingLevel_Error
};
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
#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
#define GWS_LOG_DEBUG(...)
#endif
#define GWS_LOG_VERBOSE(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Verbose) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Verbose, __VA_ARGS__); } 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)
#define GWS_LOG_EXCEPTION(__EXCEPTION__) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Exception) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Exception, @"%@", __EXCEPTION__); } while (0)
#define GWS_LOG_VERBOSE(...) \
do { \
if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Verbose) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Verbose, __VA_ARGS__); \
} 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
@@ -147,10 +137,10 @@ extern void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* for
#if DEBUG
#define GWS_DCHECK(__CONDITION__) \
do { \
if (!(__CONDITION__)) { \
abort(); \
} \
do { \
if (!(__CONDITION__)) { \
abort(); \
} \
} while (0)
#define GWS_DNOT_REACHED() abort()
@@ -163,12 +153,13 @@ extern void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* for
#endif
NS_ASSUME_NONNULL_BEGIN
/**
* GCDWebServer internal constants and APIs.
*/
#define kGCDWebServerDefaultMimeType @"application/octet-stream"
#define kGCDWebServerGCDQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
#define kGCDWebServerErrorDomain @"GCDWebServerErrorDomain"
static inline BOOL GCDWebServerIsValidByteRange(NSRange range) {
@@ -176,30 +167,31 @@ static inline BOOL GCDWebServerIsValidByteRange(NSRange range) {
}
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 NSString* GCDWebServerNormalizeHeaderValue(NSString* value);
extern NSString* GCDWebServerTruncateHeaderValue(NSString* value);
extern NSString* GCDWebServerExtractHeaderValueParameter(NSString* header, NSString* attribute);
extern void GCDWebServerInitializeFunctions(void);
extern NSString* _Nullable GCDWebServerNormalizeHeaderValue(NSString* _Nullable value);
extern NSString* _Nullable GCDWebServerTruncateHeaderValue(NSString* _Nullable value);
extern NSString* _Nullable GCDWebServerExtractHeaderValueParameter(NSString* _Nullable value, NSString* attribute);
extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset);
extern BOOL GCDWebServerIsTextContentType(NSString* type);
extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType);
extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_FUNCTION(1,2);
extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_FUNCTION(1, 2);
extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService);
@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
@interface GCDWebServer ()
@property(nonatomic, readonly) NSArray* handlers;
@property(nonatomic, readonly) NSString* serverName;
@property(nonatomic, readonly) NSString* authenticationRealm;
@property(nonatomic, readonly) NSDictionary* authenticationBasicAccounts;
@property(nonatomic, readonly) NSDictionary* authenticationDigestAccounts;
@property(nonatomic, readonly) NSMutableArray<GCDWebServerHandler*>* handlers;
@property(nonatomic, readonly, nullable) NSString* serverName;
@property(nonatomic, readonly, nullable) NSString* authenticationRealm;
@property(nonatomic, readonly, nullable) NSMutableDictionary<NSString*, NSString*>* authenticationBasicAccounts;
@property(nonatomic, readonly, nullable) NSMutableDictionary<NSString*, NSString*>* authenticationDigestAccounts;
@property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET;
@property(nonatomic, readonly) dispatch_queue_priority_t dispatchQueuePriority;
- (void)willStartConnection:(GCDWebServerConnection*)connection;
- (void)didEndConnection:(GCDWebServerConnection*)connection;
@end
@@ -211,20 +203,22 @@ extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOO
@interface GCDWebServerRequest ()
@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
@property(nonatomic, readwrite) NSData* localAddressData;
@property(nonatomic, readwrite) NSData* remoteAddressData;
@property(nonatomic) NSData* localAddressData;
@property(nonatomic) NSData* remoteAddressData;
- (void)prepareForWriting;
- (BOOL)performOpen:(NSError**)error;
- (BOOL)performWriteData:(NSData*)data error:(NSError**)error;
- (BOOL)performClose:(NSError**)error;
- (void)setAttribute:(id)attribute forKey:(NSString*)key;
- (void)setAttribute:(nullable id)attribute forKey:(NSString*)key;
@end
@interface GCDWebServerResponse ()
@property(nonatomic, readonly) NSDictionary* additionalHeaders;
@property(nonatomic, readonly) NSDictionary<NSString*, NSString*>* additionalHeaders;
@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
- (void)prepareForReading;
- (BOOL)performOpen:(NSError**)error;
- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block;
- (void)performClose;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -27,6 +27,8 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Attribute key to retrieve an NSArray containing NSStrings from a GCDWebServerRequest
* 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.
*/
@property(nonatomic, readonly) NSDictionary* headers;
@property(nonatomic, readonly) NSDictionary<NSString*, NSString*>* headers;
/**
* 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.
*/
@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
@@ -122,7 +124,7 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
* "application/octet-stream" if a body is present but there was no
* "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
@@ -137,12 +139,12 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
/**
* 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.
*/
@property(nonatomic, readonly) NSString* ifNoneMatch;
@property(nonatomic, readonly, nullable) NSString* ifNoneMatch;
/**
* Returns the parsed "Range" header or (NSUIntegerMax, 0) if absent or malformed.
@@ -184,7 +186,7 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
/**
* 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.
@@ -201,6 +203,8 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
*
* @return The attribute value for the key.
*/
- (id)attributeForKey:(NSString*)key;
- (nullable id)attributeForKey:(NSString*)key;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -39,22 +39,17 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
#define kGZipInitialBufferSize (256 * 1024)
@interface GCDWebServerBodyDecoder : NSObject <GCDWebServerBodyWriter>
- (id)initWithRequest:(GCDWebServerRequest*)request writer:(id<GCDWebServerBodyWriter>)writer;
@end
@interface GCDWebServerGZipDecoder : GCDWebServerBodyDecoder
@end
@interface GCDWebServerBodyDecoder () {
@private
@implementation GCDWebServerBodyDecoder {
GCDWebServerRequest* __unsafe_unretained _request;
id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
}
@end
@implementation GCDWebServerBodyDecoder
- (id)initWithRequest:(GCDWebServerRequest*)request writer:(id<GCDWebServerBodyWriter>)writer {
- (instancetype)initWithRequest:(GCDWebServerRequest* _Nonnull)request writer:(id<GCDWebServerBodyWriter> _Nonnull)writer {
if ((self = [super init])) {
_request = request;
_writer = writer;
@@ -76,14 +71,10 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
@end
@interface GCDWebServerGZipDecoder () {
@private
@implementation GCDWebServerGZipDecoder {
z_stream _stream;
BOOL _finished;
}
@end
@implementation GCDWebServerGZipDecoder
- (BOOL)open:(NSError**)error {
int result = inflateInit2(&_stream, 15 + 16);
@@ -94,7 +85,7 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
return NO;
}
if (![super open:error]) {
deflateEnd(&_stream);
inflateEnd(&_stream);
return NO;
}
return YES;
@@ -143,109 +134,88 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
@end
@interface 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;
NSData* _localAddress;
NSData* _remoteAddress;
@implementation GCDWebServerRequest {
BOOL _opened;
NSMutableArray* _decoders;
NSMutableDictionary* _attributes;
NSMutableArray<GCDWebServerBodyDecoder*>* _decoders;
id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
NSMutableDictionary<NSString*, id>* _attributes;
}
@end
@implementation GCDWebServerRequest : NSObject
@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, localAddressData=_localAddress, remoteAddressData=_remoteAddress;
- (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 init])) {
_method = [method copy];
_url = url;
_URL = url;
_headers = headers;
_path = [path copy];
_query = query;
_type = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Content-Type"]);
_chunked = [GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Transfer-Encoding"]) isEqualToString:@"chunked"];
_contentType = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Content-Type"]);
_usesChunkedTransferEncoding = [GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Transfer-Encoding"]) isEqualToString:@"chunked"];
NSString* lengthHeader = [_headers objectForKey:@"Content-Length"];
if (lengthHeader) {
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();
return nil;
}
_length = length;
if (_type == nil) {
_type = kGCDWebServerDefaultMimeType;
_contentLength = length;
if (_contentType == nil) {
_contentType = kGCDWebServerDefaultMimeType;
}
} else if (_chunked) {
if (_type == nil) {
_type = kGCDWebServerDefaultMimeType;
} else if (_usesChunkedTransferEncoding) {
if (_contentType == nil) {
_contentType = kGCDWebServerDefaultMimeType;
}
_length = NSUIntegerMax;
_contentLength = NSUIntegerMax;
} else {
if (_type) {
GWS_DNOT_REACHED();
return nil;
if (_contentType) {
GWS_LOG_WARNING(@"Ignoring 'Content-Type' header for '%@' request on \"%@\"", _method, _URL);
_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"];
if (modifiedHeader) {
_modifiedSince = [GCDWebServerParseRFC822(modifiedHeader) copy];
_ifModifiedSince = [GCDWebServerParseRFC822(modifiedHeader) copy];
}
_noneMatch = [_headers objectForKey:@"If-None-Match"];
_range = NSMakeRange(NSUIntegerMax, 0);
_ifNoneMatch = [_headers objectForKey:@"If-None-Match"];
_byteRange = NSMakeRange(NSUIntegerMax, 0);
NSString* rangeHeader = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Range"]);
if (rangeHeader) {
if ([rangeHeader hasPrefix:@"bytes="]) {
NSArray* components = [[rangeHeader substringFromIndex:6] componentsSeparatedByString:@","];
if (components.count == 1) {
components = [[components firstObject] componentsSeparatedByString:@"-"];
components = [(NSString*)[components firstObject] componentsSeparatedByString:@"-"];
if (components.count == 2) {
NSString* startString = [components objectAtIndex:0];
NSInteger startValue = [startString integerValue];
NSString* endString = [components objectAtIndex:1];
NSInteger endValue = [endString integerValue];
if (startString.length && (startValue >= 0) && endString.length && (endValue >= startValue)) { // The second 500 bytes: "500-999"
_range.location = startValue;
_range.length = endValue - startValue + 1;
_byteRange.location = startValue;
_byteRange.length = endValue - startValue + 1;
} else if (startString.length && (startValue >= 0)) { // The bytes after 9500 bytes: "9500-"
_range.location = startValue;
_range.length = NSUIntegerMax;
_byteRange.location = startValue;
_byteRange.length = NSUIntegerMax;
} else if (endString.length && (endValue > 0)) { // The final 500 bytes: "-500"
_range.location = NSUIntegerMax;
_range.length = endValue;
_byteRange.location = NSUIntegerMax;
_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);
}
}
if ([[_headers objectForKey:@"Accept-Encoding"] rangeOfString:@"gzip"].location != NSNotFound) {
_gzipAccepted = YES;
_acceptsGzipContentEncoding = YES;
}
_decoders = [[NSMutableArray alloc] init];
_attributes = [[NSMutableDictionary alloc] init];
}
@@ -253,11 +223,11 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
}
- (BOOL)hasBody {
return _type ? YES : NO;
return _contentType ? YES : NO;
}
- (BOOL)hasByteRange {
return GCDWebServerIsValidByteRange(_range);
return GCDWebServerIsValidByteRange(_byteRange);
}
- (id)attributeForKey:(NSString*)key {
@@ -286,7 +256,7 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
}
- (BOOL)performOpen:(NSError**)error {
GWS_DCHECK(_type);
GWS_DCHECK(_contentType);
GWS_DCHECK(_writer);
if (_opened) {
GWS_DNOT_REACHED();
@@ -311,11 +281,11 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
}
- (NSString*)localAddressString {
return GCDWebServerStringFromSockAddr(_localAddress.bytes, YES);
return GCDWebServerStringFromSockAddr(_localAddressData.bytes, YES);
}
- (NSString*)remoteAddressString {
return GCDWebServerStringFromSockAddr(_remoteAddress.bytes, YES);
return GCDWebServerStringFromSockAddr(_remoteAddressData.bytes, YES);
}
- (NSString*)description {

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -27,11 +27,13 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerBodyReaderCompletionBlock is passed by GCDWebServer to the
* 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
@@ -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
* 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.
@@ -102,7 +104,7 @@ typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* err
*
* @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
@@ -136,14 +138,14 @@ typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* err
*
* 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.
*
* The default value is nil.
*/
@property(nonatomic, copy) NSString* eTag;
@property(nonatomic, copy, nullable) NSString* eTag;
/**
* 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
* 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.
@@ -206,3 +208,5 @@ typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* err
- (instancetype)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -37,22 +37,17 @@
#define kGZipInitialBufferSize (256 * 1024)
@interface GCDWebServerBodyEncoder : NSObject <GCDWebServerBodyReader>
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader;
@end
@interface GCDWebServerGZipEncoder : GCDWebServerBodyEncoder
@end
@interface GCDWebServerBodyEncoder () {
@private
@implementation GCDWebServerBodyEncoder {
GCDWebServerResponse* __unsafe_unretained _response;
id<GCDWebServerBodyReader> __unsafe_unretained _reader;
}
@end
@implementation GCDWebServerBodyEncoder
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader {
- (instancetype)initWithResponse:(GCDWebServerResponse* _Nonnull)response reader:(id<GCDWebServerBodyReader> _Nonnull)reader {
if ((self = [super init])) {
_response = response;
_reader = reader;
@@ -74,16 +69,12 @@
@end
@interface GCDWebServerGZipEncoder () {
@private
@implementation GCDWebServerGZipEncoder {
z_stream _stream;
BOOL _finished;
}
@end
@implementation GCDWebServerGZipEncoder
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader {
- (instancetype)initWithResponse:(GCDWebServerResponse* _Nonnull)response reader:(id<GCDWebServerBodyReader> _Nonnull)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 setValue:@"gzip" forAdditionalHeader:@"Content-Encoding"];
@@ -157,55 +148,38 @@
@end
@interface GCDWebServerResponse () {
@private
NSString* _type;
NSUInteger _length;
NSInteger _status;
NSUInteger _maxAge;
NSDate* _lastModified;
NSString* _eTag;
NSMutableDictionary* _headers;
BOOL _chunked;
BOOL _gzipped;
@implementation GCDWebServerResponse {
BOOL _opened;
NSMutableArray* _encoders;
NSMutableArray<GCDWebServerBodyEncoder*>* _encoders;
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 {
return [[[self class] alloc] init];
return [(GCDWebServerResponse*)[[self class] alloc] init];
}
- (instancetype)init {
if ((self = [super init])) {
_type = nil;
_length = NSUIntegerMax;
_status = kGCDWebServerHTTPStatusCode_OK;
_maxAge = 0;
_headers = [[NSMutableDictionary alloc] init];
_contentType = nil;
_contentLength = NSUIntegerMax;
_statusCode = kGCDWebServerHTTPStatusCode_OK;
_cacheControlMaxAge = 0;
_additionalHeaders = [[NSMutableDictionary alloc] init];
_encoders = [[NSMutableArray alloc] init];
}
return self;
}
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header {
[_headers setValue:value forKey:header];
[_additionalHeaders setValue:value forKey:header];
}
- (BOOL)hasBody {
return _type ? YES : NO;
return _contentType ? YES : NO;
}
- (BOOL)usesChunkedTransferEncoding {
return (_type != nil) && (_length == NSUIntegerMax);
return (_contentType != nil) && (_contentLength == NSUIntegerMax);
}
- (BOOL)open:(NSError**)error {
@@ -222,7 +196,7 @@
- (void)prepareForReading {
_reader = self;
if (_gzipped) {
if (_gzipContentEncodingEnabled) {
GCDWebServerGZipEncoder* encoder = [[GCDWebServerGZipEncoder alloc] initWithResponse:self reader:_reader];
[_encoders addObject:encoder];
_reader = encoder;
@@ -230,7 +204,7 @@
}
- (BOOL)performOpen:(NSError**)error {
GWS_DCHECK(_type);
GWS_DCHECK(_contentType);
GWS_DCHECK(_reader);
if (_opened) {
GWS_DNOT_REACHED();
@@ -241,8 +215,9 @@
}
- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block {
GWS_DCHECK(_opened);
if ([_reader respondsToSelector:@selector(asyncReadDataWithCompletion:)]) {
[_reader asyncReadDataWithCompletion:block];
[_reader asyncReadDataWithCompletion:[block copy]];
} else {
NSError* error = nil;
NSData* data = [_reader readData:&error];
@@ -256,24 +231,24 @@
}
- (NSString*)description {
NSMutableString* description = [NSMutableString stringWithFormat:@"Status Code = %i", (int)_status];
if (_type) {
[description appendFormat:@"\nContent Type = %@", _type];
NSMutableString* description = [NSMutableString stringWithFormat:@"Status Code = %i", (int)_statusCode];
if (_contentType) {
[description appendFormat:@"\nContent Type = %@", _contentType];
}
if (_length != NSUIntegerMax) {
[description appendFormat:@"\nContent Length = %lu", (unsigned long)_length];
if (_contentLength != NSUIntegerMax) {
[description appendFormat:@"\nContent Length = %lu", (unsigned long)_contentLength];
}
[description appendFormat:@"\nCache Control Max Age = %lu", (unsigned long)_maxAge];
if (_lastModified) {
[description appendFormat:@"\nLast Modified Date = %@", _lastModified];
[description appendFormat:@"\nCache Control Max Age = %lu", (unsigned long)_cacheControlMaxAge];
if (_lastModifiedDate) {
[description appendFormat:@"\nLast Modified Date = %@", _lastModifiedDate];
}
if (_eTag) {
[description appendFormat:@"\nETag = %@", _eTag];
}
if (_headers.count) {
if (_additionalHeaders.count) {
[description appendString:@"\n"];
for (NSString* header in [[_headers allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
[description appendFormat:@"\n%@: %@", header, [_headers objectForKey:header]];
for (NSString* header in [[_additionalHeaders allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
[description appendFormat:@"\n%@: %@", header, [_additionalHeaders objectForKey:header]];
}
}
return description;
@@ -284,11 +259,11 @@
@implementation GCDWebServerResponse (Extensions)
+ (instancetype)responseWithStatusCode:(NSInteger)statusCode {
return [[self alloc] initWithStatusCode:statusCode];
return [(GCDWebServerResponse*)[self alloc] initWithStatusCode:statusCode];
}
+ (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 {

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -27,6 +27,8 @@
#import "GCDWebServerRequest.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerDataRequest subclass of GCDWebServerRequest stores the body
* of the HTTP request in memory.
@@ -49,12 +51,14 @@
* The text encoding used to interpret the data is extracted from the
* "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
* 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
NS_ASSUME_NONNULL_END

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -31,18 +31,14 @@
#import "GCDWebServerPrivate.h"
@interface GCDWebServerDataRequest () {
@private
NSMutableData* _data;
@interface GCDWebServerDataRequest ()
@property(nonatomic) NSMutableData* data;
@end
@implementation GCDWebServerDataRequest {
NSString* _text;
id _jsonObject;
}
@end
@implementation GCDWebServerDataRequest
@synthesize data=_data;
- (BOOL)open:(NSError**)error {
if (self.contentLength != NSUIntegerMax) {
@@ -52,7 +48,7 @@
}
if (_data == nil) {
if (error) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed allocating memory"}];
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed allocating memory"}];
}
return NO;
}
@@ -72,7 +68,7 @@
NSMutableString* description = [NSMutableString stringWithString:[super description]];
if (_data) {
[description appendString:@"\n\n"];
[description appendString:GCDWebServerDescribeData(_data, self.contentType)];
[description appendString:GCDWebServerDescribeData(_data, (NSString*)self.contentType)];
}
return description;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -27,6 +27,8 @@
#import "GCDWebServerRequest.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerFileRequest subclass of GCDWebServerRequest stores the body
* of the HTTP request to a file on disk.
@@ -43,3 +45,5 @@
@property(nonatomic, readonly) NSString* temporaryPath;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -31,18 +31,11 @@
#import "GCDWebServerPrivate.h"
@interface GCDWebServerFileRequest () {
@private
NSString* _temporaryPath;
@implementation GCDWebServerFileRequest {
int _file;
}
@end
@implementation GCDWebServerFileRequest
@synthesize temporaryPath=_temporaryPath;
- (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])) {
_temporaryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
}
@@ -85,14 +78,14 @@
NSString* creationDateHeader = [self.headers objectForKey:@"X-GCDWebServer-CreationDate"];
if (creationDateHeader) {
NSDate* date = GCDWebServerParseISO8601(creationDateHeader);
if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileCreationDate: date} ofItemAtPath:_temporaryPath error:error]) {
if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileCreationDate : date} ofItemAtPath:_temporaryPath error:error]) {
return NO;
}
}
NSString* modifiedDateHeader = [self.headers objectForKey:@"X-GCDWebServer-ModifiedDate"];
if (modifiedDateHeader) {
NSDate* date = GCDWebServerParseRFC822(modifiedDateHeader);
if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileModificationDate: date} ofItemAtPath:_temporaryPath error:error]) {
if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileModificationDate : date} ofItemAtPath:_temporaryPath error:error]) {
return NO;
}
}

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -27,6 +27,8 @@
#import "GCDWebServerRequest.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerMultiPart class is an abstract class that wraps the content
* of a part.
@@ -69,7 +71,7 @@
* The text encoding used to interpret the data is extracted from the
* "Content-Type" header or defaults to UTF-8.
*/
@property(nonatomic, readonly) NSString* string;
@property(nonatomic, readonly, nullable) NSString* string;
@end
@@ -105,13 +107,13 @@
* Returns the argument parts from the multipart encoded form as
* name / GCDWebServerMultiPartArgument pairs.
*/
@property(nonatomic, readonly) NSArray* arguments;
@property(nonatomic, readonly) NSArray<GCDWebServerMultiPartArgument*>* arguments;
/**
* Returns the files parts from the multipart encoded form as
* name / GCDWebServerMultiPartFile pairs.
*/
@property(nonatomic, readonly) NSArray* files;
@property(nonatomic, readonly) NSArray<GCDWebServerMultiPartFile*>* files;
/**
* 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.
*/
- (GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name;
- (nullable GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name;
/**
* Returns the first file for a given control name or nil if not found.
*/
- (GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name;
- (nullable GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -42,53 +42,31 @@ typedef enum {
} ParserState;
@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
static NSData* _newlineData = nil;
static NSData* _newlinesData = nil;
static NSData* _dashNewlineData = nil;
@interface GCDWebServerMultiPart () {
@private
NSString* _controlName;
NSString* _contentType;
NSString* _mimeType;
}
@end
@implementation GCDWebServerMultiPart
@synthesize controlName=_controlName, contentType=_contentType, mimeType=_mimeType;
- (id)initWithControlName:(NSString*)name contentType:(NSString*)type {
- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type {
if ((self = [super init])) {
_controlName = [name copy];
_contentType = [type copy];
_mimeType = GCDWebServerTruncateHeaderValue(_contentType);
_mimeType = (NSString*)GCDWebServerTruncateHeaderValue(_contentType);
}
return self;
}
@end
@interface GCDWebServerMultiPartArgument () {
@private
NSData* _data;
NSString* _string;
}
@end
@implementation GCDWebServerMultiPartArgument
@synthesize data=_data, string=_string;
- (id)initWithControlName:(NSString*)name contentType:(NSString*)type data:(NSData*)data {
- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type data:(NSData* _Nonnull)data {
if ((self = [super initWithControlName:name contentType:type])) {
_data = data;
if ([self.contentType hasPrefix:@"text/"]) {
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
_string = [[NSString alloc] initWithData:_data encoding:GCDWebServerStringEncodingFromCharset(charset)];
@@ -103,18 +81,9 @@ static NSData* _dashNewlineData = nil;
@end
@interface GCDWebServerMultiPartFile () {
@private
NSString* _fileName;
NSString* _temporaryPath;
}
@end
@implementation GCDWebServerMultiPartFile
@synthesize fileName=_fileName, temporaryPath=_temporaryPath;
- (id)initWithControlName:(NSString*)name contentType:(NSString*)type fileName:(NSString*)fileName temporaryPath:(NSString*)temporaryPath {
- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type fileName:(NSString* _Nonnull)fileName temporaryPath:(NSString* _Nonnull)temporaryPath {
if ((self = [super initWithControlName:name contentType:type])) {
_fileName = [fileName copy];
_temporaryPath = [temporaryPath copy];
@@ -132,15 +101,14 @@ static NSData* _dashNewlineData = nil;
@end
@interface GCDWebServerMIMEStreamParser () {
@private
@implementation GCDWebServerMIMEStreamParser {
NSData* _boundary;
NSString* _defaultcontrolName;
ParserState _state;
NSMutableData* _data;
NSMutableArray* _arguments;
NSMutableArray* _files;
NSMutableArray<GCDWebServerMultiPartArgument*>* _arguments;
NSMutableArray<GCDWebServerMultiPartFile*>* _files;
NSString* _controlName;
NSString* _fileName;
NSString* _contentType;
@@ -148,9 +116,6 @@ static NSData* _dashNewlineData = nil;
int _tmpFile;
GCDWebServerMIMEStreamParser* _subParser;
}
@end
@implementation GCDWebServerMIMEStreamParser
+ (void)initialize {
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;
if (data == nil) {
GWS_DNOT_REACHED();
@@ -194,11 +159,10 @@ static NSData* _dashNewlineData = nil;
// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2
- (BOOL)_parseData {
BOOL success = YES;
if (_state == kParserState_Headers) {
NSRange range = [_data rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _data.length)];
if (range.location != NSNotFound) {
_controlName = nil;
_fileName = nil;
_contentType = nil;
@@ -256,12 +220,12 @@ static NSData* _dashNewlineData = nil;
GWS_DNOT_REACHED();
success = NO;
}
[_data replaceBytesInRange:NSMakeRange(0, range.location + range.length) withBytes:NULL length:0];
_state = kParserState_Content;
}
}
if ((_state == kParserState_Start) || (_state == kParserState_Content)) {
NSRange range = [_data rangeOfData:_boundary options:0 range:NSMakeRange(0, _data.length)];
if (range.location != NSNotFound) {
@@ -269,7 +233,6 @@ static NSData* _dashNewlineData = nil;
NSRange subRange1 = [_data rangeOfData:_newlineData options:NSDataSearchAnchored range:subRange];
NSRange subRange2 = [_data rangeOfData:_dashNewlineData options:NSDataSearchAnchored range:subRange];
if ((subRange1.location != NSNotFound) || (subRange2.location != NSNotFound)) {
if (_state == kParserState_Content) {
const void* dataBytes = _data.bytes;
NSUInteger dataLength = range.location - 2;
@@ -301,7 +264,7 @@ static NSData* _dashNewlineData = nil;
[_arguments addObject:argument];
}
}
if (subRange1.location != NSNotFound) {
[_data replaceBytesInRange:NSMakeRange(0, subRange1.location + subRange1.length) withBytes:NULL length:0];
_state = kParserState_Headers;
@@ -333,7 +296,7 @@ static NSData* _dashNewlineData = nil;
}
}
}
return success;
}
@@ -348,23 +311,20 @@ static NSData* _dashNewlineData = nil;
@end
@interface GCDWebServerMultiPartFormRequest () {
@private
GCDWebServerMIMEStreamParser* _parser;
NSMutableArray* _arguments;
NSMutableArray* _files;
}
@interface GCDWebServerMultiPartFormRequest ()
@property(nonatomic) NSMutableArray<GCDWebServerMultiPartArgument*>* arguments;
@property(nonatomic) NSMutableArray<GCDWebServerMultiPartFile*>* files;
@end
@implementation GCDWebServerMultiPartFormRequest
@synthesize arguments=_arguments, files=_files;
@implementation GCDWebServerMultiPartFormRequest {
GCDWebServerMIMEStreamParser* _parser;
}
+ (NSString*)mimeType {
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])) {
_arguments = [[NSMutableArray alloc] init];
_files = [[NSMutableArray alloc] init];
@@ -377,7 +337,7 @@ static NSData* _dashNewlineData = nil;
_parser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:nil arguments:_arguments files:_files];
if (_parser == nil) {
if (error) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed starting to parse multipart form data"}];
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed starting to parse multipart form data"}];
}
return NO;
}
@@ -387,7 +347,7 @@ static NSData* _dashNewlineData = nil;
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
if (![_parser appendBytes:data.bytes length:data.length]) {
if (error) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed continuing to parse multipart form data"}];
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed continuing to parse multipart form data"}];
}
return NO;
}
@@ -399,7 +359,7 @@ static NSData* _dashNewlineData = nil;
_parser = nil;
if (!atEnd) {
if (error) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed finishing to parse multipart form data"}];
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed finishing to parse multipart form data"}];
}
return NO;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -27,6 +27,8 @@
#import "GCDWebServerDataRequest.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerURLEncodedFormRequest subclass of GCDWebServerRequest
* 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
* "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
@@ -49,3 +51,5 @@
+ (NSString*)mimeType;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -31,16 +31,8 @@
#import "GCDWebServerPrivate.h"
@interface GCDWebServerURLEncodedFormRequest () {
@private
NSDictionary* _arguments;
}
@end
@implementation GCDWebServerURLEncodedFormRequest
@synthesize arguments=_arguments;
+ (NSString*)mimeType {
return @"application/x-www-form-urlencoded";
}
@@ -49,12 +41,10 @@
if (![super close:error]) {
return NO;
}
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
NSString* string = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)];
_arguments = GCDWebServerParseURLEncodedForm(string);
GWS_DCHECK(_arguments);
return YES;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -27,11 +27,14 @@
#import "GCDWebServerResponse.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerDataResponse subclass of GCDWebServerResponse reads the body
* of the HTTP response from memory.
*/
@interface GCDWebServerDataResponse : GCDWebServerResponse
@property(nonatomic, copy) NSString* contentType; // Redeclare as non-null
/**
* 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.
*/
+ (instancetype)responseWithText:(NSString*)text;
+ (nullable instancetype)responseWithText:(NSString*)text;
/**
* 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.
* 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
* "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
* 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.
*/
- (instancetype)initWithText:(NSString*)text;
- (nullable instancetype)initWithText:(NSString*)text;
/**
* 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.
@@ -91,18 +94,20 @@
* All occurences of "%variable%" within the HTML template are replaced with
* 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
* "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
* content type.
*/
- (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type;
- (nullable instancetype)initWithJSONObject:(id)object contentType:(NSString*)type;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -31,28 +31,21 @@
#import "GCDWebServerPrivate.h"
@interface GCDWebServerDataResponse () {
@private
@implementation GCDWebServerDataResponse {
NSData* _data;
BOOL _done;
}
@end
@implementation GCDWebServerDataResponse
@dynamic contentType;
+ (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 {
if (data == nil) {
GWS_DNOT_REACHED();
return nil;
}
if ((self = [super init])) {
_data = data;
self.contentType = type;
self.contentLength = data.length;
}
@@ -82,23 +75,23 @@
@implementation GCDWebServerDataResponse (Extensions)
+ (instancetype)responseWithText:(NSString*)text {
return [[self alloc] initWithText:text];
return [(GCDWebServerDataResponse*)[self alloc] initWithText:text];
}
+ (instancetype)responseWithHTML:(NSString*)html {
return [[self alloc] initWithHTML:html];
return [(GCDWebServerDataResponse*)[self alloc] initWithHTML:html];
}
+ (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables {
return [[self alloc] initWithHTMLTemplate:path variables:variables];
+ (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary<NSString*, NSString*>*)variables {
return [(GCDWebServerDataResponse*)[self alloc] initWithHTMLTemplate:path variables:variables];
}
+ (instancetype)responseWithJSONObject:(id)object {
return [[self alloc] initWithJSONObject:object];
return [(GCDWebServerDataResponse*)[self alloc] initWithJSONObject:object];
}
+ (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 {
@@ -119,13 +112,12 @@
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];
[variables enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL* stop) {
[html replaceOccurrencesOfString:[NSString stringWithFormat:@"%%%@%%", key] withString:value options:0 range:NSMakeRange(0, html.length)];
}];
id response = [self initWithHTML:html];
return response;
return [self initWithHTML:html];
}
- (instancetype)initWithJSONObject:(id)object {
@@ -135,6 +127,7 @@
- (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type {
NSData* data = [NSJSONSerialization dataWithJSONObject:object options:0 error:NULL];
if (data == nil) {
GWS_DNOT_REACHED();
return nil;
}
return [self initWithData:data contentType:type];

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -28,6 +28,8 @@
#import "GCDWebServerDataResponse.h"
#import "GCDWebServerHTTPStatusCodes.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerDataResponse subclass of GCDWebServerDataResponse generates
* an HTML body from an HTTP status code and an error message.
@@ -37,45 +39,47 @@
/**
* Creates a client error response with the corresponding HTTP status code.
*/
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2,3);
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3);
/**
* Creates a server error response with the corresponding HTTP status code.
*/
+ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2,3);
+ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3);
/**
* Creates a client error response with the corresponding HTTP status code
* 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
* 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.
*/
- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2,3);
- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3);
/**
* Initializes a server error response with the corresponding HTTP status code.
*/
- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2,3);
- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3);
/**
* Initializes a client error response with the corresponding HTTP status code
* 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
* 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
NS_ASSUME_NONNULL_END

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -31,17 +31,13 @@
#import "GCDWebServerPrivate.h"
@interface GCDWebServerErrorResponse ()
- (instancetype)initWithStatusCode:(NSInteger)statusCode underlyingError:(NSError*)underlyingError messageFormat:(NSString*)format arguments:(va_list)arguments;
@end
@implementation GCDWebServerErrorResponse
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
va_list arguments;
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);
return response;
}
@@ -50,7 +46,7 @@
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
va_list arguments;
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);
return response;
}
@@ -59,7 +55,7 @@
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
va_list arguments;
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);
return response;
}
@@ -68,7 +64,7 @@
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
va_list arguments;
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);
return response;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -27,6 +27,8 @@
#import "GCDWebServerResponse.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerFileResponse subclass of GCDWebServerResponse reads the body
* of the HTTP response from a file on disk.
@@ -36,17 +38,20 @@
* metadata.
*/
@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.
*/
+ (instancetype)responseWithFile:(NSString*)path;
+ (nullable instancetype)responseWithFile:(NSString*)path;
/**
* Creates a response like +responseWithFile: and sets the "Content-Disposition"
* 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
@@ -54,26 +59,26 @@
*
* 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
* "Content-Disposition" HTTP header for a download if the "attachment"
* 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.
*/
- (instancetype)initWithFile:(NSString*)path;
- (nullable instancetype)initWithFile:(NSString*)path;
/**
* Initializes a response like +responseWithFile: and sets the
* "Content-Disposition" HTTP header for a download if the "attachment"
* 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
@@ -86,11 +91,18 @@
* This argument would typically be set to the value of the byteRange property
* 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.
*
* 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
NS_ASSUME_NONNULL_END

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -35,50 +35,48 @@
#define kFileReadBufferSize (32 * 1024)
@interface GCDWebServerFileResponse () {
@private
@implementation GCDWebServerFileResponse {
NSString* _path;
NSUInteger _offset;
NSUInteger _size;
int _file;
}
@end
@implementation GCDWebServerFileResponse
@dynamic contentType, lastModifiedDate, eTag;
+ (instancetype)responseWithFile:(NSString*)path {
return [[[self class] alloc] initWithFile:path];
return [(GCDWebServerFileResponse*)[[self class] alloc] initWithFile:path];
}
+ (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 {
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 {
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 {
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 {
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 {
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) {
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;
if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) {
GWS_DNOT_REACHED();
@@ -91,7 +89,7 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
}
#endif
NSUInteger fileSize = (NSUInteger)info.st_size;
BOOL hasByteRange = GCDWebServerIsValidByteRange(range);
if (hasByteRange) {
if (range.location != NSUIntegerMax) {
@@ -108,7 +106,7 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
range.location = 0;
range.length = fileSize;
}
if ((self = [super init])) {
_path = [path copy];
_offset = range.location;
@@ -118,7 +116,7 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
[self setValue:[NSString stringWithFormat:@"bytes %lu-%lu/%lu", (unsigned long)_offset, (unsigned long)(_offset + _size - 1), (unsigned long)fileSize] forAdditionalHeader:@"Content-Range"];
GWS_LOG_DEBUG(@"Using content bytes range [%lu-%lu] for file \"%@\"", (unsigned long)_offset, (unsigned long)(_offset + _size - 1), path);
}
if (attachment) {
NSString* fileName = [path lastPathComponent];
NSData* data = [[fileName stringByReplacingOccurrencesOfString:@"\"" withString:@""] dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:YES];
@@ -130,8 +128,8 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
GWS_DNOT_REACHED();
}
}
self.contentType = GCDWebServerGetMimeTypeForExtension([_path pathExtension]);
self.contentType = GCDWebServerGetMimeTypeForExtension([_path pathExtension], overrides);
self.contentLength = _size;
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];

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -27,12 +27,14 @@
#import "GCDWebServerResponse.h"
NS_ASSUME_NONNULL_BEGIN
/**
* 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
* 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
@@ -51,6 +53,7 @@ typedef void (^GCDWebServerAsyncStreamBlock)(GCDWebServerBodyReaderCompletionBlo
* the body of the HTTP response using a GCD block.
*/
@interface GCDWebServerStreamedResponse : GCDWebServerResponse
@property(nonatomic, copy) NSString* contentType; // Redeclare as non-null
/**
* 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;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -31,36 +31,33 @@
#import "GCDWebServerPrivate.h"
@interface GCDWebServerStreamedResponse () {
@private
@implementation GCDWebServerStreamedResponse {
GCDWebServerAsyncStreamBlock _block;
}
@end
@implementation GCDWebServerStreamedResponse
@dynamic contentType;
+ (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 {
return [[[self class] alloc] initWithContentType:type asyncStreamBlock:block];
return [(GCDWebServerStreamedResponse*)[[self class] alloc] initWithContentType:type asyncStreamBlock:block];
}
- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
return [self initWithContentType:type asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) {
NSError* error = nil;
NSData* data = block(&error);
completionBlock(data, error);
}];
return [self initWithContentType:type
asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) {
NSError* error = nil;
NSData* data = block(&error);
completionBlock(data, error);
}];
}
- (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block {
if ((self = [super init])) {
_block = [block copy];
self.contentType = type;
}
return self;

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@@ -1,5 +1,5 @@
<!--
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -27,6 +27,8 @@
#import "GCDWebServer.h"
NS_ASSUME_NONNULL_BEGIN
@class GCDWebUploader;
/**
@@ -84,14 +86,14 @@
/**
* 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.
*
* 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
@@ -195,3 +197,5 @@
- (BOOL)shouldCreateDirectoryAtPath:(NSString*)path;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -37,6 +37,7 @@
#endif
#import "GCDWebUploader.h"
#import "GCDWebServerFunctions.h"
#import "GCDWebServerDataRequest.h"
#import "GCDWebServerMultiPartFormRequest.h"
@@ -46,34 +47,163 @@
#import "GCDWebServerErrorResponse.h"
#import "GCDWebServerFileResponse.h"
@interface GCDWebUploader () {
@private
NSString* _uploadDirectory;
NSArray* _allowedExtensions;
BOOL _allowHidden;
NSString* _title;
NSString* _header;
NSString* _prologue;
NSString* _epilogue;
NSString* _footer;
NS_ASSUME_NONNULL_BEGIN
@interface GCDWebUploader (Methods)
- (nullable GCDWebServerResponse*)listDirectory:(GCDWebServerRequest*)request;
- (nullable GCDWebServerResponse*)downloadFile:(GCDWebServerRequest*)request;
- (nullable GCDWebServerResponse*)uploadFile:(GCDWebServerMultiPartFormRequest*)request;
- (nullable GCDWebServerResponse*)moveItem:(GCDWebServerURLEncodedFormRequest*)request;
- (nullable GCDWebServerResponse*)deleteItem:(GCDWebServerURLEncodedFormRequest*)request;
- (nullable GCDWebServerResponse*)createDirectory:(GCDWebServerURLEncodedFormRequest*)request;
@end
NS_ASSUME_NONNULL_END
@implementation GCDWebUploader
@dynamic delegate;
- (instancetype)initWithUploadDirectory:(NSString*)path {
if ((self = [super init])) {
NSString* bundlePath = [[NSBundle bundleForClass:[GCDWebUploader class]] pathForResource:@"GCDWebUploader" ofType:@"bundle"];
if (bundlePath == nil) {
return nil;
}
NSBundle* siteBundle = [NSBundle bundleWithPath:bundlePath];
if (siteBundle == nil) {
return nil;
}
_uploadDirectory = [path copy];
GCDWebUploader* __unsafe_unretained server = self;
// Resource files
[self addGETHandlerForBasePath:@"/" directoryPath:(NSString*)[siteBundle resourcePath] indexFilename:nil cacheAge:3600 allowRangeRequests:NO];
// Web page
[self addHandlerForMethod:@"GET"
path:@"/"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
#if TARGET_OS_IPHONE
NSString* device = [[UIDevice currentDevice] name];
#else
NSString* device = CFBridgingRelease(SCDynamicStoreCopyComputerName(NULL, NULL));
#endif
NSString* title = server.title;
if (title == nil) {
title = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
if (title == nil) {
title = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
}
#if !TARGET_OS_IPHONE
if (title == nil) {
title = [[NSProcessInfo processInfo] processName];
}
#endif
}
NSString* header = server.header;
if (header == nil) {
header = title;
}
NSString* prologue = server.prologue;
if (prologue == nil) {
prologue = [siteBundle localizedStringForKey:@"PROLOGUE" value:@"" table:nil];
}
NSString* epilogue = server.epilogue;
if (epilogue == nil) {
epilogue = [siteBundle localizedStringForKey:@"EPILOGUE" value:@"" table:nil];
}
NSString* footer = server.footer;
if (footer == nil) {
NSString* name = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
if (name == nil) {
name = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
}
NSString* version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
#if !TARGET_OS_IPHONE
if (!name && !version) {
name = @"OS X";
version = [[NSProcessInfo processInfo] operatingSystemVersionString];
}
#endif
footer = [NSString stringWithFormat:[siteBundle localizedStringForKey:@"FOOTER_FORMAT" value:@"" table:nil], name, version];
}
return [GCDWebServerDataResponse responseWithHTMLTemplate:(NSString*)[siteBundle pathForResource:@"index" ofType:@"html"]
variables:@{
@"device" : device,
@"title" : title,
@"header" : header,
@"prologue" : prologue,
@"epilogue" : epilogue,
@"footer" : footer
}];
}];
// File listing
[self addHandlerForMethod:@"GET"
path:@"/list"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server listDirectory:request];
}];
// File download
[self addHandlerForMethod:@"GET"
path:@"/download"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server downloadFile:request];
}];
// File upload
[self addHandlerForMethod:@"POST"
path:@"/upload"
requestClass:[GCDWebServerMultiPartFormRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server uploadFile:(GCDWebServerMultiPartFormRequest*)request];
}];
// File and folder moving
[self addHandlerForMethod:@"POST"
path:@"/move"
requestClass:[GCDWebServerURLEncodedFormRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server moveItem:(GCDWebServerURLEncodedFormRequest*)request];
}];
// File and folder deletion
[self addHandlerForMethod:@"POST"
path:@"/delete"
requestClass:[GCDWebServerURLEncodedFormRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server deleteItem:(GCDWebServerURLEncodedFormRequest*)request];
}];
// Directory creation
[self addHandlerForMethod:@"POST"
path:@"/create"
requestClass:[GCDWebServerURLEncodedFormRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server createDirectory:(GCDWebServerURLEncodedFormRequest*)request];
}];
}
return self;
}
@end
@implementation GCDWebUploader (Methods)
// 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]]) {
if (_allowedFileExtensions && ![_allowedFileExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
return NO;
}
return YES;
}
- (NSString*) _uniquePathForPath:(NSString*)path {
- (NSString*)_uniquePathForPath:(NSString*)path {
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSString* directory = [path stringByDeletingLastPathComponent];
NSString* file = [path lastPathComponent];
@@ -82,7 +212,7 @@
int retries = 0;
do {
if (extension.length) {
path = [directory stringByAppendingPathComponent:[[base stringByAppendingFormat:@" (%i)", ++retries] stringByAppendingPathExtension:extension]];
path = [directory stringByAppendingPathComponent:(NSString*)[[base stringByAppendingFormat:@" (%i)", ++retries] stringByAppendingPathExtension:extension]];
} else {
path = [directory stringByAppendingPathComponent:[base stringByAppendingFormat:@" (%i)", ++retries]];
}
@@ -93,42 +223,42 @@
- (GCDWebServerResponse*)listDirectory:(GCDWebServerRequest*)request {
NSString* relativePath = [[request query] objectForKey:@"path"];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
BOOL isDirectory = NO;
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
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 (!_allowHidden && [directoryName hasPrefix:@"."]) {
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];
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 (_allowHidden || ![item hasPrefix:@"."]) {
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": [attributes objectForKey:NSFileSize]
}];
@"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
}];
@"path" : [[relativePath stringByAppendingPathComponent:item] stringByAppendingString:@"/"],
@"name" : item
}];
}
}
}
@@ -137,21 +267,21 @@
- (GCDWebServerResponse*)downloadFile:(GCDWebServerRequest*)request {
NSString* relativePath = [[request query] objectForKey:@"path"];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
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];
}
if (isDirectory) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"\"%@\" is a directory", relativePath];
}
NSString* fileName = [absolutePath lastPathComponent];
if (([fileName hasPrefix:@"."] && !_allowHidden) || ![self _checkFileExtension:fileName]) {
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: )]) {
if ([self.delegate respondsToSelector:@selector(webUploader:didDownloadFileAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didDownloadFileAtPath:absolutePath];
});
@@ -162,26 +292,23 @@
- (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]) {
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:relativePath] stringByAppendingPathComponent:file.fileName]];
if (![self _checkSandboxedPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
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];
@@ -192,32 +319,34 @@
- (GCDWebServerResponse*)moveItem:(GCDWebServerURLEncodedFormRequest*)request {
NSString* oldRelativePath = [request.arguments objectForKey:@"oldPath"];
NSString* oldAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:oldRelativePath];
NSString* oldAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(oldRelativePath)];
BOOL isDirectory = NO;
if (![self _checkSandboxedPath:oldAbsolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:oldAbsolutePath isDirectory:&isDirectory]) {
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:newRelativePath]];
if (![self _checkSandboxedPath:newAbsolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", newRelativePath];
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];
}
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];
@@ -228,26 +357,26 @@
- (GCDWebServerResponse*)deleteItem:(GCDWebServerURLEncodedFormRequest*)request {
NSString* relativePath = [request.arguments objectForKey:@"path"];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
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];
}
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];
}
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];
@@ -258,25 +387,22 @@
- (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* absolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)]];
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];
}
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];
@@ -287,117 +413,6 @@
@end
@implementation GCDWebUploader
@synthesize uploadDirectory=_uploadDirectory, allowedFileExtensions=_allowedExtensions, allowHiddenItems=_allowHidden,
title=_title, header=_header, prologue=_prologue, epilogue=_epilogue, footer=_footer;
@dynamic delegate;
- (instancetype)initWithUploadDirectory:(NSString*)path {
if ((self = [super init])) {
NSBundle* siteBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"GCDWebUploader" ofType:@"bundle"]];
if (siteBundle == nil) {
return nil;
}
_uploadDirectory = [[path stringByStandardizingPath] copy];
GCDWebUploader* __unsafe_unretained server = self;
// Resource files
[self addGETHandlerForBasePath:@"/" directoryPath:[siteBundle resourcePath] indexFilename:nil cacheAge:3600 allowRangeRequests:NO];
// Web page
[self addHandlerForMethod:@"GET" path:@"/" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
#if TARGET_OS_IPHONE
NSString* device = [[UIDevice currentDevice] name];
#else
NSString* device = CFBridgingRelease(SCDynamicStoreCopyComputerName(NULL, NULL));
#endif
NSString* title = server.title;
if (title == nil) {
title = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
if (title == nil) {
title = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
}
#if !TARGET_OS_IPHONE
if (title == nil) {
title = [[NSProcessInfo processInfo] processName];
}
#endif
}
NSString* header = server.header;
if (header == nil) {
header = title;
}
NSString* prologue = server.prologue;
if (prologue == nil) {
prologue = [siteBundle localizedStringForKey:@"PROLOGUE" value:@"" table:nil];
}
NSString* epilogue = server.epilogue;
if (epilogue == nil) {
epilogue = [siteBundle localizedStringForKey:@"EPILOGUE" value:@"" table:nil];
}
NSString* footer = server.footer;
if (footer == nil) {
NSString* name = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
NSString* version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
#if !TARGET_OS_IPHONE
if (!name && !version) {
name = @"OS X";
version = [[NSProcessInfo processInfo] operatingSystemVersionString];
}
#endif
footer = [NSString stringWithFormat:[siteBundle localizedStringForKey:@"FOOTER_FORMAT" value:@"" table:nil], name, version];
}
return [GCDWebServerDataResponse responseWithHTMLTemplate:[siteBundle pathForResource:@"index" ofType:@"html"]
variables:@{
@"device": device,
@"title": title,
@"header": header,
@"prologue": prologue,
@"epilogue": epilogue,
@"footer": footer
}];
}];
// File listing
[self addHandlerForMethod:@"GET" path:@"/list" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server listDirectory:request];
}];
// File download
[self addHandlerForMethod:@"GET" path:@"/download" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server downloadFile:request];
}];
// File upload
[self addHandlerForMethod:@"POST" path:@"/upload" requestClass:[GCDWebServerMultiPartFormRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server uploadFile:(GCDWebServerMultiPartFormRequest*)request];
}];
// File and folder moving
[self addHandlerForMethod:@"POST" path:@"/move" requestClass:[GCDWebServerURLEncodedFormRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server moveItem:(GCDWebServerURLEncodedFormRequest*)request];
}];
// File and folder deletion
[self addHandlerForMethod:@"POST" path:@"/delete" requestClass:[GCDWebServerURLEncodedFormRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server deleteItem:(GCDWebServerURLEncodedFormRequest*)request];
}];
// Directory creation
[self addHandlerForMethod:@"POST" path:@"/create" requestClass:[GCDWebServerURLEncodedFormRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server createDirectory:(GCDWebServerURLEncodedFormRequest*)request];
}];
}
return self;
}
@end
@implementation GCDWebUploader (Subclassing)
- (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath {

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2014, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -72,6 +72,10 @@ typedef enum {
[self _logDelegateCall:_cmd];
}
- (void)webServerDidUpdateNATPortMapping:(GCDWebServer*)server {
[self _logDelegateCall:_cmd];
}
- (void)webServerDidConnect:(GCDWebServer*)server {
[self _logDelegateCall:_cmd];
}
@@ -142,7 +146,8 @@ int main(int argc, const char* argv[]) {
NSString* authenticationUser = nil;
NSString* authenticationPassword = nil;
BOOL bindToLocalhost = NO;
BOOL requestNATPortMapping = NO;
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]));
} else {
@@ -173,10 +178,10 @@ int main(int argc, const char* argv[]) {
recording = YES;
} else if (!strcmp(argv[i], "-root") && (i + 1 < argc)) {
++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)) {
++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)) {
++i;
authenticationMethod = [NSString stringWithUTF8String:argv[i]];
@@ -191,45 +196,43 @@ int main(int argc, const char* argv[]) {
authenticationPassword = [NSString stringWithUTF8String:argv[i]];
} else if (!strcmp(argv[i], "--localhost")) {
bindToLocalhost = YES;
} else if (!strcmp(argv[i], "--nat")) {
requestNATPortMapping = YES;
}
}
}
GCDWebServer* webServer = nil;
switch (mode) {
// Simply serve contents of home directory
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 addGETHandlerForBasePath:@"/" directoryPath:rootDirectory indexFilename:nil cacheAge:0 allowRangeRequests:YES];
break;
}
// Renders a HTML page
case kMode_HTMLPage: {
fprintf(stdout, "Running in HTML Page mode");
fprintf(stdout, "Running in HTML Page mode\n");
webServer = [[GCDWebServer alloc] init];
[webServer addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
}];
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
}];
break;
}
// Implements an HTML form
case kMode_HTMLForm: {
fprintf(stdout, "Running in HTML Form mode");
fprintf(stdout, "Running in HTML Form mode\n");
webServer = [[GCDWebServer alloc] init];
[webServer addHandlerForMethod:@"GET"
path:@"/"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
NSString* html = @" \
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
NSString* html = @" \
<html><body> \
<form name=\"input\" action=\"/\" method=\"post\" enctype=\"application/x-www-form-urlencoded\"> \
Value: <input type=\"text\" name=\"value\"> \
@@ -237,25 +240,22 @@ int main(int argc, const char* argv[]) {
</form> \
</body></html> \
";
return [GCDWebServerDataResponse responseWithHTML:html];
}];
return [GCDWebServerDataResponse responseWithHTML:html];
}];
[webServer addHandlerForMethod:@"POST"
path:@"/"
requestClass:[GCDWebServerURLEncodedFormRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
NSString* value = [[(GCDWebServerURLEncodedFormRequest*)request arguments] objectForKey:@"value"];
NSString* html = [NSString stringWithFormat:@"<html><body><p>%@</p></body></html>", value];
return [GCDWebServerDataResponse responseWithHTML:html];
}];
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
NSString* value = [[(GCDWebServerURLEncodedFormRequest*)request arguments] objectForKey:@"value"];
NSString* html = [NSString stringWithFormat:@"<html><body><p>%@</p></body></html>", value];
return [GCDWebServerDataResponse responseWithHTML:html];
}];
break;
}
// Implements HTML file upload
case kMode_HTMLFileUpload: {
fprintf(stdout, "Running in HTML File Upload mode");
fprintf(stdout, "Running in HTML File Upload mode\n");
webServer = [[GCDWebServer alloc] init];
NSString* formHTML = @" \
<form name=\"input\" action=\"/\" method=\"post\" enctype=\"multipart/form-data\"> \
@@ -267,134 +267,112 @@ int main(int argc, const char* argv[]) {
[webServer addHandlerForMethod:@"GET"
path:@"/"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
NSString* html = [NSString stringWithFormat:@"<html><body>%@</body></html>", formHTML];
return [GCDWebServerDataResponse responseWithHTML:html];
}];
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
NSString* html = [NSString stringWithFormat:@"<html><body>%@</body></html>", formHTML];
return [GCDWebServerDataResponse responseWithHTML:html];
}];
[webServer addHandlerForMethod:@"POST"
path:@"/"
requestClass:[GCDWebServerMultiPartFormRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
NSMutableString* string = [NSMutableString string];
for (GCDWebServerMultiPartArgument* argument in [(GCDWebServerMultiPartFormRequest*)request arguments]) {
[string appendFormat:@"%@ = %@<br>", argument.controlName, argument.string];
}
for (GCDWebServerMultiPartFile* file in [(GCDWebServerMultiPartFormRequest*)request files]) {
NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:file.temporaryPath error:NULL];
[string appendFormat:@"%@ = &quot;%@&quot; (%@ | %llu %@)<br>", file.controlName, file.fileName, file.mimeType,
attributes.fileSize >= 1000 ? attributes.fileSize / 1000 : attributes.fileSize,
attributes.fileSize >= 1000 ? @"KB" : @"Bytes"];
};
NSString* html = [NSString stringWithFormat:@"<html><body><p>%@</p><hr>%@</body></html>", string, formHTML];
return [GCDWebServerDataResponse responseWithHTML:html];
}];
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
NSMutableString* string = [NSMutableString string];
for (GCDWebServerMultiPartArgument* argument in [(GCDWebServerMultiPartFormRequest*)request arguments]) {
[string appendFormat:@"%@ = %@<br>", argument.controlName, argument.string];
}
for (GCDWebServerMultiPartFile* file in [(GCDWebServerMultiPartFormRequest*)request files]) {
NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:file.temporaryPath error:NULL];
[string appendFormat:@"%@ = &quot;%@&quot; (%@ | %llu %@)<br>", file.controlName, file.fileName, file.mimeType,
attributes.fileSize >= 1000 ? attributes.fileSize / 1000 : attributes.fileSize,
attributes.fileSize >= 1000 ? @"KB" : @"Bytes"];
};
NSString* html = [NSString stringWithFormat:@"<html><body><p>%@</p><hr>%@</body></html>", string, formHTML];
return [GCDWebServerDataResponse responseWithHTML:html];
}];
break;
}
// Serve home directory through 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];
break;
}
// Serve home directory through web uploader
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];
break;
}
// Test streaming responses
case kMode_StreamingResponse: {
fprintf(stdout, "Running in Streaming Response mode");
fprintf(stdout, "Running in Streaming Response mode\n");
webServer = [[GCDWebServer alloc] init];
[webServer addHandlerForMethod:@"GET"
path:@"/sync"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
__block int countDown = 10;
return [GCDWebServerStreamedResponse responseWithContentType:@"text/plain" streamBlock:^NSData *(NSError** error) {
usleep(100 * 1000);
if (countDown) {
return [[NSString stringWithFormat:@"%i\n", countDown--] dataUsingEncoding:NSUTF8StringEncoding];
} else {
return [NSData data];
}
}];
}];
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
__block int countDown = 10;
return [GCDWebServerStreamedResponse responseWithContentType:@"text/plain"
streamBlock:^NSData*(NSError** error) {
usleep(100 * 1000);
if (countDown) {
return [[NSString stringWithFormat:@"%i\n", countDown--] dataUsingEncoding:NSUTF8StringEncoding];
} else {
return [NSData data];
}
}];
}];
[webServer addHandlerForMethod:@"GET"
path:@"/async"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
__block int countDown = 10;
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(), ^{
NSData* data = countDown ? [[NSString stringWithFormat:@"%i\n", countDown--] dataUsingEncoding:NSUTF8StringEncoding] : [NSData data];
completionBlock(data, nil);
});
}];
}];
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
__block int countDown = 10;
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(), ^{
NSData* data = countDown ? [[NSString stringWithFormat:@"%i\n", countDown--] dataUsingEncoding:NSUTF8StringEncoding] : [NSData data];
completionBlock(data, nil);
});
}];
}];
break;
}
// Test async responses
case kMode_AsyncResponse: {
fprintf(stdout, "Running in Async Response mode");
fprintf(stdout, "Running in Async Response mode\n");
webServer = [[GCDWebServer alloc] init];
[webServer addHandlerForMethod:@"GET"
path:@"/async"
requestClass:[GCDWebServerRequest class]
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), ^{
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:[@"Hello World!" dataUsingEncoding:NSUTF8StringEncoding] contentType:@"text/plain"];
completionBlock(response);
});
}];
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:(NSData*)[@"Hello World!" dataUsingEncoding:NSUTF8StringEncoding] contentType:@"text/plain"];
completionBlock(response);
});
}];
[webServer addHandlerForMethod:@"GET"
path:@"/async2"
requestClass:[GCDWebServerRequest class]
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), ^{
__block int countDown = 10;
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(), ^{
NSData* data = countDown ? [[NSString stringWithFormat:@"%i\n", countDown--] dataUsingEncoding:NSUTF8StringEncoding] : [NSData data];
readerCompletionBlock(data, nil);
});
}];
handlerCompletionBlock(response);
});
}];
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;
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(), ^{
NSData* data = countDown ? [[NSString stringWithFormat:@"%i\n", countDown--] dataUsingEncoding:NSUTF8StringEncoding] : [NSData data];
readerCompletionBlock(data, nil);
});
}];
handlerCompletionBlock(response);
});
}];
break;
}
}
if (webServer) {
Delegate* delegate = [[Delegate alloc] init];
if (testDirectory) {
@@ -402,7 +380,7 @@ int main(int argc, const char* argv[]) {
webServer.delegate = delegate;
#endif
fprintf(stdout, "<RUNNING TESTS FROM \"%s\">\n\n", [testDirectory UTF8String]);
result = (int)[webServer runTestsWithOptions:@{GCDWebServerOption_Port: @8080} inDirectory:testDirectory];
result = (int)[webServer runTestsWithOptions:@{ GCDWebServerOption_Port : @8080 } inDirectory:testDirectory];
} else {
webServer.delegate = delegate;
if (recording) {
@@ -412,11 +390,12 @@ int main(int argc, const char* argv[]) {
fprintf(stdout, "\n");
NSMutableDictionary* options = [NSMutableDictionary dictionary];
[options setObject:@8080 forKey:GCDWebServerOption_Port];
[options setObject:@(requestNATPortMapping) forKey:GCDWebServerOption_RequestNATPortMapping];
[options setObject:@(bindToLocalhost) forKey:GCDWebServerOption_BindToLocalhost];
[options setObject:@"" forKey:GCDWebServerOption_BonjourName];
if (authenticationUser && authenticationPassword) {
[options setValue:authenticationRealm forKey:GCDWebServerOption_AuthenticationRealm];
[options setObject:@{authenticationUser: authenticationPassword} forKey:GCDWebServerOption_AuthenticationAccounts];
[options setObject:@{authenticationUser : authenticationPassword} forKey:GCDWebServerOption_AuthenticationAccounts];
if ([authenticationMethod isEqualToString:@"Basic"]) {
[options setObject:GCDWebServerAuthenticationMethod_Basic forKey:GCDWebServerOption_AuthenticationMethod];
} else if ([authenticationMethod isEqualToString:@"Digest"]) {

View File

@@ -6,7 +6,7 @@ Overview
[![Platform](http://cocoapod-badges.herokuapp.com/p/GCDWebServer/badge.png)](https://github.com/swisspol/GCDWebServer)
[![License](http://img.shields.io/cocoapods/l/GCDWebServer.svg)](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)
* 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
@@ -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
* Automatically handle transitions between foreground, background and suspended modes in iOS apps
* Full support for both IPv4 and IPv6
* NAT port mapping (IPv4 only)
Included extensions:
* [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):
* Keep-alive connections
* HTTPS
Requirements:
* OS X 10.7 or later (x86_64)
* iOS 5.0 or later (armv7, armv7s or arm64)
* ARC memory management only (if you need MRC support use GCDWebServer 3.1 and earlier)
* macOS 10.7 or later (x86_64)
* iOS 8.0 or later (armv7, armv7s or arm64)
* tvOS 9.0 or later (arm64)
* ARC memory management only (if you need MRC support use GCDWebServer 3.1 or earlier)
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"
```
@@ -56,12 +58,30 @@ Or this line for GCDWebDAVServer:
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
===========
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
#import "GCDWebServer.h"
#import "GCDWebServerDataResponse.h"
@@ -127,27 +147,32 @@ int main(int argc, const char* argv[]) {
@end
```
**OS X Swift version (command line tool):**
**macOS Swift version (command line tool):**
***webServer.swift***
```swift
import Foundation
import GCDWebServer
let webServer = GCDWebServer()
func initWebServer() {
webServer.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self, processBlock: {request in
return GCDWebServerDataResponse(HTML:"<html><body><p>Hello World</p></body></html>")
let webServer = GCDWebServer()
webServer.addDefaultHandler(forMethod: "GET", request: GCDWebServerRequest.self, processBlock: {request in
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***
```objectivec
#import "GCDWebServer.h"
#import "GCDWebServerDataResponse.h"
#import <GCDWebServer/GCDWebServer.h>
#import <GCDWebServer/GCDWebServerDataResponse.h>
```
Web Based Uploads in iOS Apps
@@ -183,7 +208,7 @@ 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).
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:
@@ -213,6 +238,7 @@ 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"
@@ -249,7 +275,7 @@ GCDWebServer's architecture consists of only 4 core classes:
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__.
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.
@@ -260,7 +286,7 @@ Note that most methods on ```GCDWebServer``` to add handlers only require the ``
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:
```objectivec
@@ -323,8 +349,8 @@ 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.
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.
- 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).
- 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 **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 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```.
@@ -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.
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.

View File

@@ -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"
if [ -z "$TRAVIS" ]; then
IOS_SDK="iphoneos"
else
IOS_SDK="iphonesimulator"
fi
IOS_SDK="iphonesimulator"
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)"
IOS_TARGET="GCDWebServer (iOS)"
TVOS_TARGET="GCDWebServer (tvOS)"
CONFIGURATION="Release"
BUILD_DIR="/tmp/GCDWebServer-Build"
OSX_TEST_SCHEME="GCDWebServers (Mac)"
BUILD_DIR="`pwd`/build"
PRODUCT="$BUILD_DIR/$CONFIGURATION/GCDWebServer"
PAYLOAD_ZIP="Tests/Payload.zip"
PAYLOAD_DIR="/tmp/GCDWebServer-Payload"
PAYLOAD_DIR="`pwd`/build/Payload"
function runTests {
EXECUTABLE="$1"
MODE="$2"
TESTS="$3"
FILE="${4:-}"
rm -rf "$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
if [ "$4" != "" ]; then
if [ "$FILE" != "" ]; then
cp -f "$4" "$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
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"
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"
xcodebuild -sdk "$IOS_SDK" -target "$IOS_TARGET" -configuration "$CONFIGURATION" build "SYMROOT=$BUILD_DIR" > /dev/null
# 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
xcodebuild build -sdk "$OSX_SDK" -target "$OSX_TARGET" -configuration "$CONFIGURATION" "SYMROOT=$BUILD_DIR" "MACOSX_DEPLOYMENT_TARGET=10.7" | $PRETTYFIER
# Run tests
runTests $PRODUCT "htmlForm" "Tests/HTMLForm"
@@ -56,5 +63,21 @@ runTests $PRODUCT "webDAV" "Tests/WebDAV-Finder"
runTests $PRODUCT "webUploader" "Tests/WebUploader"
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
echo "\nAll tests completed successfully!"

0
Tests/WebDAV-Cyberduck/001-200.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/001-HEAD.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/002-207.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/002-PROPFIND.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/003-207.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/003-PROPFIND.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/004-207.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/004-PROPFIND.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/005-200.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/005-HEAD.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/006-404.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/006-HEAD.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/007-201.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/007-COPY.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/008-207.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/008-PROPFIND.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/009-200.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/009-HEAD.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/010-200.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/010-GET.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/011-207.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/011-PROPFIND.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/012-204.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/012-DELETE.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/013-204.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/013-DELETE.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/014-204.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/014-DELETE.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/015-207.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/015-PROPFIND.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/016-201.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/016-MOVE.request Executable file → Normal file
View File

Some files were not shown because too many files have changed in this diff Show More