550 Commits
1.1 ... 3.5.2

Author SHA1 Message Date
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
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
Pierre-Olivier Latour
00b2c38109 Bumped version 2015-04-13 13:04:37 -07:00
Pierre-Olivier Latour
0a9d3105fc Fixed -serverURL not taking into account GCDWebServerOption_BindToLocalhost 2015-04-13 13:02:52 -07:00
Pierre-Olivier Latour
0f0a9840e4 Fixed Xcode 6.3 warnings 2015-04-13 12:54:41 -07:00
Pierre-Olivier Latour
047fdddb0e Merge pull request #155 from sergiou87/master
Add remote and local addresses to GCDWebServerRequest
2015-03-21 16:21:48 -07:00
Sergio Padrino
79d6075a84 Add remote and local addresses to GCDWebServerRequest 2015-03-22 00:17:26 +01:00
Pierre-Olivier Latour
594497d234 Updated for CocoaLumberJack 2.0 2015-03-21 11:10:10 -07:00
Pierre-Olivier Latour
1f7c0366f0 Removed Bot scheme 2015-03-13 00:23:33 -07:00
Pierre-Olivier Latour
fe472cdd54 Update README.md 2015-03-05 16:53:10 -08:00
Pierre-Olivier Latour
9c33c83351 Handle starting the server with nil options 2015-03-05 16:48:11 -08:00
Pierre-Olivier Latour
9719406303 Made _CompareResources() easier to read 2015-02-23 16:02:14 -08:00
Pierre-Olivier Latour
0b8f7ff6ad Merge remote-tracking branch 'origin/master' 2015-02-23 16:00:39 -08:00
Pierre-Olivier Latour
71c08cff73 Update README.md 2015-01-28 10:35:43 -08:00
Pierre-Olivier Latour
1a6786488a Bumped version 2015-01-15 09:34:49 -08:00
Pierre-Olivier Latour
472c7855a7 Only wipe GCDWebUploader.bundle on Debug to avoid issues on Xcode bot 2015-01-05 23:54:23 -08:00
Pierre-Olivier Latour
2fdeb9581c Added Xcode bot scheme 2015-01-05 23:47:58 -08:00
Pierre-Olivier Latour
c4310fcdf4 Addressed static analyzer warnings 2015-01-05 23:47:49 -08:00
Pierre-Olivier Latour
33645d3c6b Fixed incorrect documentation for GCDWebServerAsyncStreamBlock 2015-01-02 18:55:30 -08:00
Pierre-Olivier Latour
3618dcac7e Added asyncResponse2 mode 2015-01-02 09:41:45 -08:00
Pierre-Olivier Latour
432e3826c9 Update README.md 2014-12-08 07:49:07 -08:00
Pierre-Olivier Latour
4e31508195 Removed invalid check 2014-12-02 08:39:29 -08:00
Pierre-Olivier Latour
628f6673b0 Bumped version 2014-12-02 06:52:31 -08:00
Pierre-Olivier Latour
1944cd8a6e Fixed tests 2014-11-28 16:40:55 +09:00
Pierre-Olivier Latour
d2001e38ca Fixes 2014-11-28 16:21:01 +09:00
Pierre-Olivier Latour
18889793b7 Fixed behavior of GCDWebServerOption_BonjourName option 2014-11-19 09:01:57 +09:00
Pierre-Olivier Latour
14d538b0fb Added GCDWebServerOption_BindToLocalhost option 2014-11-19 08:56:43 +09:00
Pierre-Olivier Latour
3b7198b4cc Merge pull request #105 from nickgravelyn/fix-crash-in-ios-unit-test
Prevent GWS_DNOT_REACHED when there is no application
2014-11-19 08:31:56 +09:00
Nick Gravelyn
abb891334a Adding check to _endBackgroundTask to verify the application exists before calling GWS_DNOT_REACHED.
This enables what is admittedly a rare scenario which is running these servers inside application-less unit tests where there is no UIApplication.
2014-11-18 14:13:00 -08:00
Pierre-Olivier Latour
059f5c8d01 Update README.md 2014-11-17 23:05:18 +09:00
Pierre-Olivier Latour
9d9546bb6d Bumped version 2014-11-07 11:44:11 +09:00
Pierre-Olivier Latour
2ff05b1aa0 Bumped version 2014-11-07 11:43:10 +09:00
Pierre-Olivier Latour
bf2c9a170d Workaround Firefox and IE not showing file selection dialog 2014-11-07 11:42:36 +09:00
Pierre-Olivier Latour
15caa9cd20 Fix 2014-10-18 12:42:38 -07:00
Pierre-Olivier Latour
32ba49ae34 Removed MRC support entirely 2014-10-18 09:32:24 -07:00
Pierre-Olivier Latour
8b87924776 Bumped version 2014-10-16 08:27:05 -07:00
Pierre-Olivier Latour
5bda05c1f9 Update README.md 2014-10-16 08:19:28 -07:00
Pierre-Olivier Latour
a8481af765 Update README.md 2014-10-16 08:18:42 -07:00
Pierre-Olivier Latour
b5ad507a57 Update README.md 2014-10-16 08:17:36 -07:00
Pierre-Olivier Latour
8c8e4847a5 Fix 2014-10-16 08:13:31 -07:00
Pierre-Olivier Latour
514c09dc39 Added truly asynchronous support to GCDWebServerStreamedResponse 2014-10-14 12:40:51 -07:00
Pierre-Olivier Latour
c4bf7b11e2 Added support for asynchronous reading in GCDWebServerBodyReader 2014-10-14 12:40:19 -07:00
Pierre-Olivier Latour
a933b2126e Fix 2014-10-14 12:10:10 -07:00
Pierre-Olivier Latour
001df4ea39 Fix 2014-10-14 01:09:17 -07:00
Pierre-Olivier Latour
75d018a375 #85 Added support for IPv6 2014-10-14 00:45:08 -07:00
Pierre-Olivier Latour
4449e42601 Update README.md 2014-10-13 22:48:09 -07:00
Pierre-Olivier Latour
c45053bc11 Added support for third-party logging facilities 2014-10-13 22:45:07 -07:00
Pierre-Olivier Latour
5070e4fc33 Update README.md 2014-10-13 12:29:06 -07:00
Pierre-Olivier Latour
7c1e70a538 Added XLFacilityLogging.h 2014-10-13 12:23:42 -07:00
Pierre-Olivier Latour
7102c7922e Fix 2014-10-13 12:23:23 -07:00
Pierre-Olivier Latour
2de9418307 Enabled ENABLE_STRICT_OBJC_MSGSEND 2014-10-13 12:04:01 -07:00
Pierre-Olivier Latour
e59cf4b6df Bumped version 2014-10-12 23:31:42 -07:00
Pierre-Olivier Latour
9e8f0e00f3 Updated iOS app for iOS 8 SDK 2014-10-12 00:47:12 -07:00
Pierre-Olivier Latour
d7650a71e0 Lowered deployment targets 2014-10-11 01:02:01 -07:00
Pierre-Olivier Latour
420ddc3eac Replaced preprocessor constant "NDEBUG" by the more standard "DEBUG" one and flipped behavior accordingly 2014-10-10 10:27:07 -07:00
Pierre-Olivier Latour
143e38c968 Added README and podspec files to Xcode project for convenience 2014-10-10 10:18:20 -07:00
Pierre-Olivier Latour
8e9fe4c4c1 Update README.md 2014-10-09 12:31:05 -07:00
Pierre-Olivier Latour
95bccff2f7 Fix 2014-10-09 10:16:13 -07:00
Pierre-Olivier Latour
780a608d6c Update README.md 2014-10-09 09:57:08 -07:00
Pierre-Olivier Latour
18d93bbf47 Bumped version 2014-10-09 09:54:08 -07:00
Pierre-Olivier Latour
b35ebd7d58 #92 Added support for async handlers 2014-10-09 09:53:03 -07:00
Pierre-Olivier Latour
a11b047233 Renamed GCDWebServerStreamingBlock to GCDWebServerStreamBlock 2014-10-09 09:43:59 -07:00
Pierre-Olivier Latour
4eac9d4f8e Upgrade to Xcode 6.1 2014-10-09 09:09:34 -07:00
Pierre-Olivier Latour
d1e2a1a12f Bumped version 2014-10-07 13:27:04 -07:00
Pierre-Olivier Latour
54d5abd3a8 Fixed rare race-condition with disconnection timer 2014-10-07 13:26:55 -07:00
Pierre-Olivier Latour
a9fee8d7e2 Fixed rare exception in GCDWebServerParseURLEncodedForm() 2014-10-07 12:16:14 -07:00
Pierre-Olivier Latour
6b15bdaa4e Merge pull request #89 from nickgravelyn/capture-regex-parameters-in-request
Added attributes to GCDWebServerRequest with support to retrieve regex captured variables
2014-09-30 10:52:31 -07:00
Nick Gravelyn
3771cf4e92 Adding an attribute collection to GCDWebServerRequest and adding regular expression captures as an attribute. 2014-09-30 10:45:45 -07:00
Pierre-Olivier Latour
a5d83abdd0 Bumped version to 2.5.2 2014-09-21 10:35:46 -07:00
Pierre-Olivier Latour
f1e9f1a37c Improved handling of symbolic links in -addGETHandlerForBasePath:directoryPath:indexFilename:cacheAge:allowRangeRequests: 2014-09-19 08:19:49 -07:00
Pierre-Olivier Latour
00b5ec87ba #76 Fixed -bonjourServerURL to correctly return hostname instead of service name 2014-09-15 09:00:39 -07:00
Pierre-Olivier Latour
cf94e70a42 Fall back to CFBundleName if CFBundleDisplayName is not available 2014-09-15 08:05:07 -07:00
Pierre-Olivier Latour
d47409c776 Updated for Xcode6 2014-09-15 08:05:00 -07:00
Pierre-Olivier Latour
a9db13475b Bumped version 2014-08-24 12:28:51 -07:00
Pierre-Olivier Latour
17fad0f1b9 Improved automatic detection of when to use dispatch_retain() and dispatch_release() depending on compiler settings 2014-08-24 12:24:16 -07:00
Pierre-Olivier Latour
5493d9e803 Run test against default and oldest supported deployment targets 2014-08-24 12:20:55 -07:00
Pierre-Olivier Latour
12b1edb958 #70 Ensure -isRunning works as expected even if GCDWebServerOption_AutomaticallySuspendInBackground is enabled 2014-07-23 07:40:37 -07:00
Pierre-Olivier Latour
7544a6dc4e Update 2014-07-12 17:11:26 -07:00
Pierre-Olivier Latour
f9621c8aac Bumped version 2014-07-12 16:55:31 -07:00
Pierre-Olivier Latour
7a93b27478 Update README.md 2014-07-03 19:54:25 -07:00
Pierre-Olivier Latour
9d48f9ec12 #57 Validate paths passed to GCDWebDAVServer and GCDWebUploader to ensure they are within the upload directory 2014-06-22 15:53:03 -07:00
Pierre-Olivier Latour
7c6e85cf9a Merge pull request #60 from pvblivs/master
Adding instructions for Swift command line tool
2014-06-22 15:36:21 -07:00
pvblivs
24fbd161d8 Update README.md 2014-06-10 08:28:59 +02:00
pvblivs
0ae0d4175a Adding instructions for Swift command line tool 2014-06-06 16:34:26 +02:00
Pierre-Olivier Latour
6d550a02b7 Merge pull request #55 from mstarinteractive/fix-json-content-type
Fix content-types like "application/json; charset=utf-8"
2014-05-16 12:17:19 -07:00
Braden MacDonald
a7f46b762f Fix content-types like "application/json; charset=utf-8" 2014-05-16 11:55:20 -07:00
Pierre-Olivier Latour
d1c7f9a323 Merge pull request #56 from jaanus/customBonjourType
Can specify a custom Bonjour service type for the server.
2014-05-16 08:31:21 -07:00
jaanus
93bfe65211 Removed unneeded API. If custom Bonjour type is needed, startWithOptions should be used to specify an options dictionary with the custom type. 2014-05-16 17:12:52 +02:00
jaanus
8ab53f74d5 Can specify a custom Bonjour service type for the server. 2014-05-16 11:20:28 +02:00
Pierre-Olivier Latour
04a69787bf Update README.md 2014-05-05 13:43:38 -07:00
Pierre-Olivier Latour
dfd019de7d Update README.md 2014-05-05 13:18:04 -07:00
Pierre-Olivier Latour
4db631fa27 Fixed errno being corrupted by LOG_ERROR() 2014-05-02 22:17:25 -07:00
Pierre-Olivier Latour
ba03d756c6 Merge pull request #52 from tipbit/fix-empty-query-param
Fix GCDWebServerParseURLEncodedForm to allow empty values.
2014-04-30 17:22:56 -07:00
Ewan Mellor
04f59a9214 Fix GCDWebServerParseURLEncodedForm to allow empty values.
If the input string is something like foo=&bar= then both foo and bar
should be in the result, with the empty string as their corresponding value.

The [scanner scanUpToString:@"&" ...] call was returning failure, because
it was already positioned at the &.  In this situation, just set value to the
empty string.
2014-04-30 17:08:42 -07:00
Pierre-Olivier Latour
40ea252ad6 Ensure connected state is updated immediately after calling -stop 2014-04-30 14:04:46 -07:00
Pierre-Olivier Latour
c193860468 Fix 2014-04-30 13:59:26 -07:00
Pierre-Olivier Latour
94ad8c745e No need to call -stop from -dealloc 2014-04-30 13:36:06 -07:00
Pierre-Olivier Latour
56c096996f Fix 2014-04-30 13:06:02 -07:00
Pierre-Olivier Latour
8cbaf0f867 Added video streaming unit test 2014-04-30 11:31:20 -07:00
Pierre-Olivier Latour
295901c0b3 Reject files greater than 4 GiB in 32 bit mode 2014-04-30 09:57:12 -07:00
Pierre-Olivier Latour
2dda0c98ce #50 Use NSUIntegerMax instead of NSNotFound to indicate undefined length 2014-04-30 09:56:18 -07:00
Pierre-Olivier Latour
70a38c8b01 Fix 2014-04-30 09:15:30 -07:00
Pierre-Olivier Latour
1b12a7bd14 Ensure pending scheduled callbacks have executed in "run" APIs 2014-04-29 22:12:28 -07:00
Pierre-Olivier Latour
75e6332500 Don't delay disconnected state update if already stopped 2014-04-29 22:06:13 -07:00
Pierre-Olivier Latour
420ed719e8 Add SIGTERM support to -runWithOptions:error: 2014-04-29 16:20:03 -07:00
Pierre-Olivier Latour
3b75f9dd20 Added -rewriteRequestURL:withMethod:headers: hook 2014-04-28 14:09:15 -07:00
Pierre-Olivier Latour
f01307b2a7 Bumped version 2014-04-27 19:25:51 -07:00
Pierre-Olivier Latour
1f5e650423 Fix 2014-04-27 19:20:38 -07:00
Pierre-Olivier Latour
d404112a88 #46 Added "error" argument to -startWithOptions: 2014-04-27 19:19:55 -07:00
Pierre-Olivier Latour
dd3f539f74 #47 Ensure listening socket is closed when -stop returns 2014-04-27 19:05:34 -07:00
Pierre-Olivier Latour
0c51d09b69 Update README.md 2014-04-27 18:46:45 -07:00
Pierre-Olivier Latour
0c53c52dd4 Fix 2014-04-27 12:50:36 -07:00
Pierre-Olivier Latour
a687b52563 #39 Added support for "multipart/mixed" parts inside "multipart/form-data" 2014-04-26 19:31:11 -07:00
Pierre-Olivier Latour
c8c34aa61f Fix 2014-04-26 19:30:18 -07:00
Pierre-Olivier Latour
ed709d1476 Added HTMLFileUpload unit tests 2014-04-25 07:17:34 -07:00
Pierre-Olivier Latour
3dc7cb0ec4 Added HTMLForm unit tests 2014-04-25 07:14:40 -07:00
Pierre-Olivier Latour
142f007e58 Added "htmlFileUpload" mode 2014-04-24 19:18:13 -07:00
Pierre-Olivier Latour
c8d2b225ba Modified GCDWebServerMultiPart to allow duplicate control names 2014-04-24 19:01:46 -07:00
Pierre-Olivier Latour
f7d6da55cd Added GCDWebServerMIMEStreamParser class 2014-04-24 18:45:34 -07:00
Pierre-Olivier Latour
5a0c274807 Fix 2014-04-23 10:40:43 -07:00
Pierre-Olivier Latour
591da12aa3 Exclude GCDWebServerPrivate.h from Podspec 2014-04-23 10:39:42 -07:00
Pierre-Olivier Latour
72475429e4 Bumped version 2014-04-23 10:33:50 -07:00
Pierre-Olivier Latour
01da5969e4 Update README.md 2014-04-22 22:47:07 -07:00
Pierre-Olivier Latour
46890a0642 Merge pull request #43 from iosphere/pullrequest/unusedVar
Fixes a static analyzer warning about unused variables for disabled logging
2014-04-21 21:15:35 -03:00
Pierre-Olivier Latour
143ca5b99f Fix 2014-04-20 10:34:45 -03:00
Pierre-Olivier Latour
519866bc03 Fix 2014-04-19 23:05:13 -03:00
Pierre-Olivier Latour
a93cac5ea6 Fix 2014-04-19 22:56:45 -03:00
Pierre-Olivier Latour
43b578677f Fix 2014-04-19 22:19:47 -03:00
Pierre-Olivier Latour
10cbe27e50 Fixed open() calls 2014-04-19 22:17:17 -03:00
Pierre-Olivier Latour
b1169ce7d1 Bumped version 2014-04-19 17:48:53 -03:00
Pierre-Olivier Latour
766072eb89 Renamed GCDWebServerStreamingResponse to GCDWebServerStreamedResponse.h 2014-04-19 17:43:36 -03:00
Pierre-Olivier Latour
0807cf5fe6 #33 Documented Requests/ and Responses/ 2014-04-19 17:39:09 -03:00
Pierre-Olivier Latour
c6701cd474 Renamed "showHiddenFiles" property to "allowHiddenItems" 2014-04-19 16:31:00 -03:00
Pierre-Olivier Latour
b6866bee8e #33 Documented GCDWebDAVServer/ and GCDWebUploader/ 2014-04-19 16:27:49 -03:00
Pierre-Olivier Latour
075774b6c0 Added -webServerDidCompleteBonjourRegistration: 2014-04-19 12:57:01 -03:00
Pierre-Olivier Latour
7743d18006 Added -logException: 2014-04-19 12:52:34 -03:00
Pierre-Olivier Latour
633d40f155 Don't cache GCDWebServerDataResponse 2014-04-19 12:51:36 -03:00
Pierre-Olivier Latour
5d82a80a34 #33 Documented Core/ 2014-04-19 12:51:00 -03:00
Felix Lamouroux
3e5fe3f956 Fixes logging for non-arc builds 2014-04-19 14:09:55 +02:00
Pierre-Olivier Latour
5a26a09d8e Update README.md 2014-04-19 00:17:45 -03:00
Pierre-Olivier Latour
51e8dbe475 #41 Fixed linking issues with Podspec 2014-04-18 18:38:17 -03:00
Pierre-Olivier Latour
4a3d4537a3 Update README.md 2014-04-18 18:25:35 -03:00
Felix Lamouroux
a5208bd60f Fixes a static analyzer warning about unused variables when logging is disabled 2014-04-18 21:26:18 +02:00
Pierre-Olivier Latour
2597d6da00 Bumped version 2014-04-18 12:59:16 -03:00
Pierre-Olivier Latour
1ca5a56952 Allow multiple user accounts for authentication 2014-04-18 12:50:11 -03:00
Pierre-Olivier Latour
80cff01154 Update README.md 2014-04-18 12:33:59 -03:00
Pierre-Olivier Latour
1e17d5c455 #38 Added support for digest authentication 2014-04-18 12:32:55 -03:00
Pierre-Olivier Latour
ce1eb3c29a Fix 2014-04-18 12:31:41 -03:00
Pierre-Olivier Latour
f11647ee7f Fixed unit tests 2014-04-18 10:59:29 -03:00
Pierre-Olivier Latour
60f281fd30 #40 Allow non ISO Latin 1 file names when downloading files 2014-04-18 10:46:50 -03:00
Pierre-Olivier Latour
181984ba6d Update README.md 2014-04-17 23:10:10 -03:00
Pierre-Olivier Latour
4685fae29c Cleaned up authentication options 2014-04-17 23:08:16 -03:00
Pierre-Olivier Latour
8619799e62 Added -preflightRequest: and -overrideResponse:forRequest: APIs 2014-04-17 22:51:55 -03:00
Pierre-Olivier Latour
b1061378fb Fix 2014-04-17 22:16:25 -03:00
Pierre-Olivier Latour
9bc2bfa506 Update README.md 2014-04-17 18:06:15 -03:00
Pierre-Olivier Latour
078c5e7bc3 Bumped version 2014-04-17 11:50:17 -03:00
Pierre-Olivier Latour
3f304b3c14 Fixed parsing of 'multipart/form-data' with non-ASCII headers 2014-04-17 11:50:09 -03:00
Pierre-Olivier Latour
80348079a6 Added support for Basic Authentication 2014-04-17 11:14:17 -03:00
Pierre-Olivier Latour
099b2a03e6 Updated run APIs to use options 2014-04-17 11:12:18 -03:00
Pierre-Olivier Latour
f0c63f4776 Fixes 2014-04-17 10:41:33 -03:00
Pierre-Olivier Latour
4e3aa3bc5c Update README.md 2014-04-17 01:57:30 -03:00
Pierre-Olivier Latour
c5d82b85ed Update README.md 2014-04-17 01:55:15 -03:00
Pierre-Olivier Latour
b1181d2e40 Update README.md 2014-04-17 01:54:21 -03:00
Pierre-Olivier Latour
d931e04d47 Update README.md 2014-04-17 01:52:19 -03:00
Pierre-Olivier Latour
016c4da6d2 Update README.md 2014-04-17 01:50:07 -03:00
Pierre-Olivier Latour
38dd39a789 Update README.md 2014-04-17 01:49:03 -03:00
Pierre-Olivier Latour
748f6a8bc9 Update README.md 2014-04-17 01:48:22 -03:00
Pierre-Olivier Latour
0b3f825f1a Bumped version 2014-04-17 01:04:55 -03:00
Pierre-Olivier Latour
6b9142642e Update README.md 2014-04-17 00:47:34 -03:00
Pierre-Olivier Latour
0a21059d25 Added support for background mode on iOS 2014-04-16 23:49:35 -03:00
Pierre-Olivier Latour
14acb7b323 Fix 2014-04-16 23:30:22 -03:00
Pierre-Olivier Latour
2f7f7b7b50 Fix 2014-04-16 01:50:17 -03:00
Pierre-Olivier Latour
998a47b099 Replaced GCDWebServer subclassing with explicit options 2014-04-16 01:29:52 -03:00
Pierre-Olivier Latour
2d8996b91e Added connected state to GCDWebServer 2014-04-16 00:57:41 -03:00
Pierre-Olivier Latour
c5ca0f7cee Added GCDWebServerDelegate 2014-04-16 00:57:41 -03:00
Pierre-Olivier Latour
05a704bcef Fix 2014-04-16 00:57:41 -03:00
Pierre-Olivier Latour
c170fb4fd3 Use strerror() consistently 2014-04-16 00:57:41 -03:00
Pierre-Olivier Latour
55a9abd506 Fix 2014-04-16 00:57:39 -03:00
Pierre-Olivier Latour
489c8c6236 Added +[GCDWebServer maxPendingConnections] 2014-04-16 00:57:14 -03:00
Pierre-Olivier Latour
9c73736225 Fix 2014-04-15 21:53:02 -03:00
Pierre-Olivier Latour
1f503aa3b6 Fix 2014-04-15 18:02:38 -03:00
Pierre-Olivier Latour
4eeeab7dc2 Build iOS app using iOS Simulator SDK on Travis CI 2014-04-15 17:57:42 -03:00
Pierre-Olivier Latour
813f124f6a Fix 2014-04-15 17:57:15 -03:00
Pierre-Olivier Latour
8fe66444ae Cleaned up connections read / write implementation 2014-04-15 17:46:43 -03:00
Pierre-Olivier Latour
e3efc065df Build iOS app as part of unit tests 2014-04-15 16:50:57 -03:00
Pierre-Olivier Latour
016153f900 Isolated all testing code with __GCDWEBSERVER_ENABLE_TESTING__ 2014-04-15 16:50:57 -03:00
Pierre-Olivier Latour
a55781e2c1 Changed -[GCDWebServerConnection open] to return a BOOL 2014-04-15 16:50:57 -03:00
Pierre-Olivier Latour
4fb5d67e9b Fixed broken iOS build with __GCDWEBSERVER_ENABLE_TESTING__ 2014-04-15 16:50:57 -03:00
Pierre-Olivier Latour
14e04b445f Fixes 2014-04-15 16:50:57 -03:00
Pierre-Olivier Latour
ad01c15dcd Update README.md 2014-04-15 01:28:46 -03:00
Pierre-Olivier Latour
bb0f62416e Update README.md 2014-04-15 01:24:31 -03:00
Pierre-Olivier Latour
14228834d6 Update README.md 2014-04-15 01:09:10 -03:00
Pierre-Olivier Latour
eb16566605 Update README.md 2014-04-15 01:08:19 -03:00
Pierre-Olivier Latour
06e0fc531d Fixes 2014-04-15 01:01:55 -03:00
Pierre-Olivier Latour
ea777865f3 Bumped version 2014-04-15 00:33:17 -03:00
Pierre-Olivier Latour
5842149d4b Fixed unit tests to work in any time zone 2014-04-15 00:29:56 -03:00
Pierre-Olivier Latour
46ee1a48d6 Fixed source folder name typo! 2014-04-15 00:13:31 -03:00
Pierre-Olivier Latour
c1b2b79b06 Update README.md 2014-04-15 00:04:56 -03:00
Pierre-Olivier Latour
41c56334d0 Update README.md 2014-04-15 00:02:42 -03:00
Pierre-Olivier Latour
2810086816 Update README.md 2014-04-14 23:59:07 -03:00
Pierre-Olivier Latour
c8cd771697 Make header parsing more robust 2014-04-14 19:08:08 -03:00
Pierre-Olivier Latour
252c38c42a Force preserve scrolling position 2014-04-14 18:45:31 -03:00
Pierre-Olivier Latour
cedeb88cb6 Bumped version 2014-04-14 18:37:35 -03:00
Pierre-Olivier Latour
854bbdc6b2 Disable table reloads while renaming 2014-04-14 18:34:26 -03:00
Pierre-Olivier Latour
b949187770 Use jquery data() instead of element attributes 2014-04-14 18:28:22 -03:00
Pierre-Olivier Latour
dafcb0d895 Enable Enter key for dialogs 2014-04-14 10:34:43 -03:00
Pierre-Olivier Latour
7bceb8132a Force overwrite of entire GCDWebUploader.bundle on build 2014-04-14 10:34:14 -03:00
Pierre-Olivier Latour
4c2ac12f7b Fix 2014-04-13 19:20:55 -03:00
Pierre-Olivier Latour
1d2efbbbc7 Update README.md 2014-04-12 09:12:48 -07:00
Pierre-Olivier Latour
91b832715a Organized source code in subfolders 2014-04-12 09:11:24 -07:00
Pierre-Olivier Latour
0852bf2d05 Moved functions to GCDWebServerFunctions.[h/m] 2014-04-12 09:07:09 -07:00
Pierre-Olivier Latour
eb29232842 #34 Added Travis CI integration 2014-04-11 23:37:45 -07:00
Pierre-Olivier Latour
3b1fa05046 Update .travis.yml 2014-04-11 23:31:28 -07:00
Pierre-Olivier Latour
4535c5d61a Update Run-Tests.sh 2014-04-11 23:29:40 -07:00
Pierre-Olivier Latour
ccd1eaa880 Create .travis.yml 2014-04-11 23:27:35 -07:00
Pierre-Olivier Latour
1b805c3951 Update GCDWebServerRequest.h 2014-04-11 23:19:09 -07:00
Pierre-Olivier Latour
2172872787 Update README.md 2014-04-11 23:17:57 -07:00
Pierre-Olivier Latour
894eacd517 Bumped version 2014-04-11 22:39:51 -07:00
Pierre-Olivier Latour
7a54bcbae5 #35 Finalized unit tests 2014-04-11 22:39:50 -07:00
Pierre-Olivier Latour
a28ac82ba2 #35 More work on unit tests 2014-04-11 22:39:50 -07:00
Pierre-Olivier Latour
c062d9d6d3 Use internal functions for date formatting in WebDAV 2014-04-11 22:39:50 -07:00
Pierre-Olivier Latour
bb32a721b6 Update README.md 2014-04-11 08:22:42 -07:00
Pierre-Olivier Latour
1b6e4f6491 #35 First pass at unit tests 2014-04-10 20:22:44 -07:00
Pierre-Olivier Latour
7b51023373 Fixed memory corruption under non-ARC 2014-04-10 20:19:37 -07:00
Pierre-Olivier Latour
f21c6ab667 Fix 2014-04-10 19:49:24 -07:00
Pierre-Olivier Latour
0dd6d8c5fc Fix memory leak 2014-04-10 15:28:40 -07:00
Pierre-Olivier Latour
d58b2122ed Improved CocoaPods integration 2014-04-10 14:37:55 -07:00
Pierre-Olivier Latour
4fa91f6802 Don't use SO_REUSEPORT 2014-04-10 14:27:39 -07:00
Pierre-Olivier Latour
dfd37078ae Fix 2014-04-10 14:27:20 -07:00
Pierre-Olivier Latour
582c6da74f More cleaning of logging system 2014-04-10 14:27:07 -07:00
Pierre-Olivier Latour
1e1fd24b5d Optimized logging 2014-04-09 19:15:03 -07:00
Pierre-Olivier Latour
a3996f3fbf Merge branch 'v2' 2014-04-09 14:05:21 -07:00
Pierre-Olivier Latour
2ecbfea72f Fix 2014-04-09 14:02:58 -07:00
Pierre-Olivier Latour
d78aa3baae Allow HEAD requests on collections 2014-04-09 13:59:20 -07:00
Pierre-Olivier Latour
fcea9cad44 Moved +shouldAutomaticallyMapHEADToGET to GCDWebServer class 2014-04-09 13:53:46 -07:00
Pierre-Olivier Latour
d383845fcc Cleaned up file servers error handling 2014-04-09 13:47:26 -07:00
Pierre-Olivier Latour
97929f7d89 Added -description methods 2014-04-09 13:47:26 -07:00
Pierre-Olivier Latour
4008b5b476 Only set "Cache-Control" on successful responses 2014-04-09 13:47:26 -07:00
Pierre-Olivier Latour
e49b9219ea Renamed "filePath" to "temporaryPath" 2014-04-09 13:46:43 -07:00
Pierre-Olivier Latour
30eb01ca6f Fixed memory corruption 2014-04-09 13:38:30 -07:00
Pierre-Olivier Latour
6f90a3e6ce Log real request method 2014-04-09 12:20:37 -07:00
Pierre-Olivier Latour
efad06f506 __unsafe_unretained does not prevent self retain-cycles when not under ARC 2014-04-09 11:10:45 -07:00
Pierre-Olivier Latour
811e45ab26 Properly handle casing of header values 2014-04-09 10:34:33 -07:00
Pierre-Olivier Latour
f14dda522c Update README.md 2014-04-09 01:56:15 -07:00
Pierre-Olivier Latour
e5550bf290 Fix non-ARC build failure 2014-04-09 01:42:52 -07:00
Pierre-Olivier Latour
9f0544b449 Removed Mac Finder mode 2014-04-09 01:41:56 -07:00
Pierre-Olivier Latour
d2c0d6da2b Update README.md 2014-04-09 01:40:16 -07:00
Pierre-Olivier Latour
d5811fe6df Added compatibility with OS X Finder for WebDAV 2014-04-09 01:38:14 -07:00
Pierre-Olivier Latour
b494e40442 Fix 2014-04-09 01:38:14 -07:00
Pierre-Olivier Latour
157b683082 Automatically map HEAD requests to GET ones 2014-04-09 01:38:14 -07:00
Pierre-Olivier Latour
62ee560d51 Added -replaceResponse:forRequest: hook 2014-04-09 01:38:14 -07:00
Pierre-Olivier Latour
bda3d917ca Automatically handle ETag and Last-Modified-Date caching 2014-04-09 01:38:14 -07:00
Pierre-Olivier Latour
6210564bfc Added support for "ETag" and "If-None-Match" headers 2014-04-09 01:38:14 -07:00
Pierre-Olivier Latour
c454dc4e8e Simplified internal checks for requests and responses 2014-04-09 01:38:14 -07:00
Pierre-Olivier Latour
289059c875 Added support for "If-Modified-Since" and "Accept-Encoding" headers 2014-04-09 01:38:14 -07:00
Pierre-Olivier Latour
f1a79ffd11 Added support for "Last-Modified" response header 2014-04-09 01:38:13 -07:00
Pierre-Olivier Latour
7339a7a2a6 Factored out HTTP date parsing and formatting 2014-04-09 01:38:13 -07:00
Pierre-Olivier Latour
1be1966252 Fix 2014-04-09 01:37:05 -07:00
Pierre-Olivier Latour
30756fc8f9 Update README.md 2014-04-08 19:59:41 -07:00
Pierre-Olivier Latour
4c993ebfac Update README.md 2014-04-08 19:51:01 -07:00
Pierre-Olivier Latour
60f9ee975e Fix 2014-04-08 19:47:04 -07:00
Pierre-Olivier Latour
b3a700d38a Ensure Content-Type header is lowercased 2014-04-08 19:47:03 -07:00
Pierre-Olivier Latour
881cc3b00c Added JSON and text extensions to GCDWebServerDataRequest 2014-04-08 19:47:03 -07:00
Pierre-Olivier Latour
e26c9b76ea Updated to "instancetype" type 2014-04-08 19:47:03 -07:00
Pierre-Olivier Latour
3401206279 Fix 2014-04-08 19:47:03 -07:00
Pierre-Olivier Latour
715d985475 Update README.md 2014-04-08 19:47:03 -07:00
Pierre-Olivier Latour
efb1f9f4eb First pass at implementing class 1 WebDAV server 2014-04-08 19:47:03 -07:00
Pierre-Olivier Latour
93c6a9bece More refactoring of GCDWebUploader 2014-04-08 19:47:03 -07:00
Pierre-Olivier Latour
1d381c9fc6 Refactor GCDWebUploader 2014-04-08 19:47:03 -07:00
Pierre-Olivier Latour
31d51cf5c0 Updated GCDWebUploader to latest APIs 2014-04-08 19:47:03 -07:00
Pierre-Olivier Latour
35ce178323 Added GCDWebServerErrorResponse 2014-04-08 19:47:03 -07:00
Pierre-Olivier Latour
5ece52fa1b Fix 2014-04-08 19:47:03 -07:00
Pierre-Olivier Latour
131810229f Improved logging APIs 2014-04-08 19:47:03 -07:00
Pierre-Olivier Latour
b942a9d2b8 Fix 2014-04-08 19:47:02 -07:00
Pierre-Olivier Latour
047a0604bf Make default implementation for GCDWebServerRequest and GCDWebServerResponse ignore bodies 2014-04-08 19:47:02 -07:00
Pierre-Olivier Latour
4b46c95a78 Allow requests with body but no Content-Type header 2014-04-08 19:47:02 -07:00
Pierre-Olivier Latour
794ab5f293 Fixes 2014-04-08 19:47:02 -07:00
Pierre-Olivier Latour
fb08e77c0c Added -hasByteRange API 2014-04-08 19:47:02 -07:00
Pierre-Olivier Latour
c51f9ad7d9 Added GCDWebServerHTTPStatusCodes.h 2014-04-08 19:47:02 -07:00
Pierre-Olivier Latour
7ec8d5247a Added logging APIs 2014-04-08 19:47:02 -07:00
Pierre-Olivier Latour
dcbc0f96c5 Fixed addDefaultHandlerForMethod:requestClass:processBlock: ignoring method 2014-04-08 19:47:02 -07:00
Pierre-Olivier Latour
f61ff832ea Added -abortRequest:withStatusCode: API 2014-04-08 19:47:02 -07:00
Pierre-Olivier Latour
e85a0c9a61 Update README.md 2014-04-08 19:47:02 -07:00
Pierre-Olivier Latour
8f9c03991d Renamed GCDWebServerStreamResponse to GCDWebServerStreamingResponse 2014-04-08 19:47:02 -07:00
Pierre-Olivier Latour
c5d3764913 Moved response body chunked transfer encoding to GCDWebServerConnection 2014-04-08 19:47:02 -07:00
Pierre-Olivier Latour
7af258eb6b #17 Added support for chunked transfer encoding in request bodies 2014-04-08 19:47:02 -07:00
Pierre-Olivier Latour
c213e167b4 Fix 2014-04-08 19:47:02 -07:00
Pierre-Olivier Latour
06630d3245 Added support for gzip body encoding 2014-04-08 19:47:01 -07:00
Pierre-Olivier Latour
63a66ff331 Added GCDWebServerBodyWriter protocol 2014-04-08 19:47:01 -07:00
Pierre-Olivier Latour
1f9a0d38d0 Split class files 2014-04-08 19:47:01 -07:00
Pierre-Olivier Latour
81638ad086 First pass at adding body encoders 2014-04-08 19:47:01 -07:00
Pierre-Olivier Latour
7506f9c9a2 Fix 2014-04-06 10:10:25 -07:00
Pierre-Olivier Latour
1315c0e965 Enable -Wshadow 2014-04-04 10:48:12 -07:00
Pierre-Olivier Latour
2c98a5931d Merge pull request #32 from iosphere/pullrequest/shadowLocalVar
Fixes warnings regarding shadowing local variables
2014-04-04 10:46:16 -07:00
felixLam
9a4ec5b7e8 Fixes warning regarding shadowing local variables 2014-04-04 12:05:32 +02:00
Pierre-Olivier Latour
8bebee94ec Enforce Content-Type and Content-Length consistency on requests 2014-04-03 18:30:28 -07:00
Pierre-Olivier Latour
7cd68b3d96 Fix 2014-04-03 15:27:44 -07:00
Pierre-Olivier Latour
6a4f74c2e4 Added GCDWebServerChunkedResponse 2014-04-03 15:25:34 -07:00
Pierre-Olivier Latour
8116d88ec4 Make contentType and contentLength properties 2014-04-03 15:23:33 -07:00
Pierre-Olivier Latour
467830e4de Fix 2014-04-02 10:12:27 -07:00
Pierre-Olivier Latour
030c28eb93 Fix 2014-04-02 10:09:07 -07:00
Pierre-Olivier Latour
e4702059c8 #30 Add support for moving files 2014-04-02 10:05:38 -07:00
Pierre-Olivier Latour
877813ed0f Fix 2014-04-02 09:44:18 -07:00
Pierre-Olivier Latour
ccfb4f0b54 Use modal panel instead of prompt() when creating folders 2014-04-02 09:41:07 -07:00
Pierre-Olivier Latour
e27fd601a8 Fix 2014-04-02 09:40:43 -07:00
Pierre-Olivier Latour
7c7f561f90 Fixed default footer for CLTs 2014-04-02 09:02:43 -07:00
Pierre-Olivier Latour
689cb5f26a Fix 2014-04-02 08:50:18 -07:00
Pierre-Olivier Latour
1e4c508737 Update README.md 2014-04-02 08:49:51 -07:00
Pierre-Olivier Latour
ef08ab5516 Update README.md 2014-04-02 08:49:11 -07:00
Pierre-Olivier Latour
18ad23f503 Update README.md 2014-04-02 08:47:32 -07:00
Pierre-Olivier Latour
d68687af62 Fix 2014-04-02 08:34:19 -07:00
Pierre-Olivier Latour
0cdf441830 Update README.md 2014-04-01 16:59:43 -07:00
Pierre-Olivier Latour
33efd2a24c Fixed retain cycle 2014-04-01 16:19:26 -07:00
Pierre-Olivier Latour
df95ecb1c2 Updated web interface 2014-04-01 16:06:44 -07:00
Pierre-Olivier Latour
0bad724f08 Updated API 2014-04-01 15:45:51 -07:00
Pierre-Olivier Latour
74f99167da Fix 2014-04-01 15:43:35 -07:00
Pierre-Olivier Latour
f505e3a493 Fix 2014-04-01 15:43:22 -07:00
Pierre-Olivier Latour
a8725319e1 Fixed crash under non-ARC 2014-04-01 14:07:42 -07:00
Pierre-Olivier Latour
4a3135db81 Handle recursive reloads 2014-04-01 13:49:28 -07:00
Pierre-Olivier Latour
25c486cafa Fix 2014-04-01 13:30:11 -07:00
Pierre-Olivier Latour
73ff754de4 Removed global progress bar 2014-04-01 13:19:03 -07:00
Pierre-Olivier Latour
22d12406c8 Fix 2014-04-01 13:16:58 -07:00
Pierre-Olivier Latour
148735ba8f Fix 2014-04-01 13:13:53 -07:00
Pierre-Olivier Latour
a92da4ffae First pass at new Web Uploader UX 2014-04-01 13:13:22 -07:00
Pierre-Olivier Latour
e463655aab Fix 2014-04-01 12:34:43 -07:00
Pierre-Olivier Latour
7f5ef92b36 Fix 2014-03-30 17:04:35 -07:00
Pierre-Olivier Latour
768d9fe8e1 Fix 2014-03-30 17:04:03 -07:00
Pierre-Olivier Latour
c0854f2bec Fix 2014-03-30 17:04:03 -07:00
Pierre-Olivier Latour
9b79f2e0d4 Update README.md 2014-03-29 21:58:31 -07:00
Pierre-Olivier Latour
e630482bb1 Fix 2014-03-29 21:40:22 -07:00
Pierre-Olivier Latour
8bfefdec4c Added GCDWebServerGetPrimaryIPv4Address() and server URL properties 2014-03-29 21:29:09 -07:00
Pierre-Olivier Latour
d32ea02b97 Expose local and remote address on GCDWebServerConnection 2014-03-29 21:29:08 -07:00
Pierre-Olivier Latour
4a93b19385 Update README.md 2014-03-29 19:43:43 -07:00
Pierre-Olivier Latour
3f9aef4dd6 Update README.md 2014-03-29 19:42:40 -07:00
Pierre-Olivier Latour
35bf846ee7 Added GCDWebUploader 2014-03-29 18:19:45 -07:00
Pierre-Olivier Latour
4446c1198f Fixes 2014-03-29 17:41:36 -07:00
Pierre-Olivier Latour
223bc4ba16 Allow customizing content type for JSON responses 2014-03-29 13:55:43 -07:00
Pierre-Olivier Latour
6d43485039 Added GCDWebServerEscapeURLString() 2014-03-29 13:55:22 -07:00
Pierre-Olivier Latour
aa5dd8fca0 GCDWebServerGetMimeTypeForExtension() always returns a MIME type 2014-03-29 11:45:51 -07:00
Pierre-Olivier Latour
eac83a4d0d Added JSON support to GCDWebServerDataResponse 2014-03-29 09:21:23 -07:00
Pierre-Olivier Latour
120f6fc864 Fixed more build warnings 2014-03-27 11:37:53 -07:00
Pierre-Olivier Latour
e911472f28 Switched to standard architectures on iOS 2014-03-26 09:29:42 -07:00
Pierre-Olivier Latour
fb247dc703 Enabled -Wshorten-64-to-32 2014-03-26 09:28:32 -07:00
Pierre-Olivier Latour
4d53e18b0b Moved logging message function to GCDWebServer.m 2014-03-26 09:25:10 -07:00
Pierre-Olivier Latour
ea1924994e Changed default port to 80 on iOS but still 8080 on Mac & iOS Simulator 2014-03-22 20:16:26 -07:00
Pierre-Olivier Latour
a3894fbf9b Disable -runWithPort: on iOS 2014-03-22 20:11:52 -07:00
Pierre-Olivier Latour
47b8ea5f7c Added Drag & Drop browser file upload demo based on filedropjs.org 2014-03-20 17:55:20 -07:00
Pierre-Olivier Latour
dc7fe87878 Exposed internal utility functions 2014-03-20 17:28:54 -07:00
Pierre-Olivier Latour
cedec20673 Updated handlers convenience API 2014-03-20 12:55:09 -07:00
Pierre-Olivier Latour
ed0f3ac68e Enable -Weverything for Debug builds 2014-03-20 08:56:54 -07:00
Pierre-Olivier Latour
0a48f42ccb Exposed mode to CLI 2014-03-20 08:53:29 -07:00
Pierre-Olivier Latour
6c1439405d Moved more ivars to class extensions 2014-03-20 08:52:32 -07:00
Pierre-Olivier Latour
79e041eae5 Merge branch 'Range-Requests' 2014-03-20 08:38:47 -07:00
Pierre-Olivier Latour
fb02b9f9d1 Fix 2014-03-19 21:17:21 -07:00
Pierre-Olivier Latour
1b163b1b8b #27 Updated API to expose range requests support 2014-03-19 21:15:25 -07:00
Pierre-Olivier Latour
eb6589e27a Fix 2014-03-19 21:10:02 -07:00
Pierre-Olivier Latour
0a34a0b205 #27 Only set "Accept-Ranges" header on files 2014-03-19 21:08:20 -07:00
Pierre-Olivier Latour
1e99e91407 #27 Initial pass at HTTP range requests support 2014-03-19 20:57:35 -07:00
Pierre-Olivier Latour
096b07a201 #22 Exposed hooks to monitor bytes read and written 2014-03-19 09:44:15 -07:00
Pierre-Olivier Latour
e65b569ddc Move ivars to class extensions 2014-03-19 09:19:59 -07:00
Pierre-Olivier Latour
08e58e4a5a Rename "class" method arguments to "aClass" for C++ compatibility 2014-03-19 09:10:47 -07:00
Pierre-Olivier Latour
111027f413 Updated to Xcode 5.1 2014-03-19 09:07:14 -07:00
Pierre-Olivier Latour
d7e9386272 Fixed rare exception 2014-03-03 22:11:34 -08:00
Pierre-Olivier Latour
040515aff4 Update README.md 2014-02-10 10:44:36 -08:00
Pierre-Olivier Latour
bb56e1e808 Fix 2014-02-06 18:44:59 -08:00
Pierre-Olivier Latour
ff7a5c8e0a Updated copyright year 2014-02-06 18:44:04 -08:00
Pierre-Olivier Latour
7c24996be3 Removed podspec file 2014-01-29 09:24:31 -08:00
Pierre-Olivier Latour
feacf7601e Updated podspec 2014-01-29 09:16:00 -08:00
Pierre-Olivier Latour
36658278f8 Fixed more build warnings 2014-01-29 09:14:23 -08:00
Pierre-Olivier Latour
0f2f22a1b0 Updated for arm64 2014-01-29 08:38:25 -08:00
Pierre-Olivier Latour
628cf6833c Make sure @bonjourName is not an empty string 2014-01-23 14:26:31 -08:00
Pierre-Olivier Latour
9392ddadb6 Added bonjourName property 2014-01-23 14:17:44 -08:00
Pierre-Olivier Latour
5dee044caa Check for main thread only during first +initialize call 2014-01-23 11:44:43 -08:00
Pierre-Olivier Latour
7c8205caa0 Don't use dispatch_release() under ARC in OS X 10.8 or iOS 6.0 and later 2014-01-23 11:33:33 -08:00
Pierre-Olivier Latour
965e111280 Don't use deprecated dispatch_get_current_queue() 2014-01-23 11:20:43 -08:00
Pierre-Olivier Latour
8a69050cca Merge pull request #14 from chrisdevereux/master
Log to stderr, not stdout
2014-01-13 10:03:33 -08:00
Chris Devereux
a15a49240a Log to stderr, not stdout 2014-01-13 12:58:54 +00:00
Pierre-Olivier Latour
404b46537e Merge pull request #13 from djmadcat/master
Add podspec for version 1.2
2014-01-12 17:06:46 -08:00
Alexey Aleshkov
811e18e2fa Add podspec for version 1.2 2014-01-13 01:44:54 +04:00
Pierre-Olivier Latour
78480e004a Added ARC support 2014-01-08 22:27:15 -08:00
Pierre-Olivier Latour
2e587919ca Cleaned up .gitignore 2013-12-29 20:56:58 -08:00
Pierre-Olivier Latour
544e236144 Merge branch 'master' of https://github.com/swisspol/GCDWebServer 2013-12-29 19:02:06 -08:00
Pierre-Olivier Latour
1a1ee2869e Fixed copyrights 2013-12-29 19:02:01 -08:00
Pierre-Olivier Latour
e665d0a778 Update README.md 2013-12-28 21:40:38 -08:00
Pierre-Olivier Latour
43cc98ad47 Fixed placeholders 2013-12-26 23:14:42 -08:00
Pierre-Olivier Latour
1d08a8fcc3 Update README.md 2013-12-26 23:04:54 -08:00
Pierre-Olivier Latour
2cddc8c939 Update README.md 2013-12-26 23:03:38 -08:00
Pierre-Olivier Latour
1344ad9e04 Fixed links 2013-12-26 23:03:01 -08:00
Pierre-Olivier Latour
3eb27a4d7b Updated to Xcode 5.0 2013-10-10 16:45:21 -07:00
912 changed files with 30640 additions and 2442 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
...

19
.gitignore vendored
View File

@@ -1,18 +1,5 @@
# Xcode
.DS_Store
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
*.xcworkspace
!default.xcworkspace
xcuserdata
profile
*.moved-aside
DerivedData
.idea/
project.xcworkspace
/build
/Carthage/Build

3
.travis.yml Normal file
View File

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

View File

@@ -1,66 +0,0 @@
/*
Copyright (c) 2012-2013, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "GCDWebServerRequest.h"
#import "GCDWebServerResponse.h"
typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery);
typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* request);
@interface GCDWebServer : NSObject {
@private
NSMutableArray* _handlers;
NSUInteger _port;
dispatch_source_t _source;
CFNetServiceRef _service;
}
@property(nonatomic, readonly, getter=isRunning) BOOL running;
@property(nonatomic, readonly) NSUInteger port;
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;
- (void)removeAllHandlers;
- (BOOL)start; // Default is 8080 port and computer name
- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name; // Pass nil name to disable Bonjour or empty string to use computer name
- (void)stop;
@end
@interface GCDWebServer (Subclassing)
+ (Class)connectionClass;
+ (NSString*)serverName; // Default is class name
@end
@interface GCDWebServer (Extensions)
- (BOOL)runWithPort:(NSUInteger)port; // Starts then automatically stops on SIGINT i.e. Ctrl-C (use on main thread only)
@end
@interface GCDWebServer (Handlers)
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)class processBlock:(GCDWebServerProcessBlock)block;
- (void)addHandlerForBasePath:(NSString*)basePath localPath:(NSString*)localPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge; // Base path is recursive and case-sensitive
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)class processBlock:(GCDWebServerProcessBlock)block; // Path is case-insensitive
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)class processBlock:(GCDWebServerProcessBlock)block; // Regular expression is case-insensitive
@end

View File

@@ -1,444 +0,0 @@
/*
Copyright (c) 2012-2013, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <TargetConditionals.h>
#if TARGET_OS_IPHONE
#import <MobileCoreServices/MobileCoreServices.h>
#endif
#import <netinet/in.h>
#import "GCDWebServerPrivate.h"
#define kMaxPendingConnections 16
static BOOL _run;
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) {
static NSDictionary* _overrides = nil;
if (_overrides == nil) {
_overrides = [[NSDictionary alloc] initWithObjectsAndKeys:
@"text/css", @"css",
nil];
}
NSString* mimeType = nil;
extension = [extension lowercaseString];
if (extension.length) {
mimeType = [_overrides objectForKey:extension];
if (mimeType == nil) {
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)extension, NULL);
if (uti) {
mimeType = [(id)UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType) autorelease];
CFRelease(uti);
}
}
}
return mimeType;
}
NSString* GCDWebServerUnescapeURLString(NSString* string) {
return [(id)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""),
kCFStringEncodingUTF8) autorelease];
}
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
NSScanner* scanner = [[NSScanner alloc] initWithString:form];
[scanner setCharactersToBeSkipped:nil];
while (1) {
NSString* key = nil;
if (![scanner scanUpToString:@"=" intoString:&key] || [scanner isAtEnd]) {
break;
}
[scanner setScanLocation:([scanner scanLocation] + 1)];
NSString* value = nil;
if (![scanner scanUpToString:@"&" intoString:&value]) {
break;
}
key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "];
value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
[parameters setObject:GCDWebServerUnescapeURLString(value) forKey:GCDWebServerUnescapeURLString(key)];
if ([scanner isAtEnd]) {
break;
}
[scanner setScanLocation:([scanner scanLocation] + 1)];
}
[scanner release];
return parameters;
}
static void _SignalHandler(int signal) {
_run = NO;
printf("\n");
}
@implementation GCDWebServerHandler
@synthesize matchBlock=_matchBlock, processBlock=_processBlock;
- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock {
if ((self = [super init])) {
_matchBlock = Block_copy(matchBlock);
_processBlock = Block_copy(processBlock);
}
return self;
}
- (void)dealloc {
Block_release(_matchBlock);
Block_release(_processBlock);
[super dealloc];
}
@end
@implementation GCDWebServer
@synthesize handlers=_handlers, port=_port;
+ (void)initialize {
[GCDWebServerConnection class]; // Initialize class immediately to make sure it happens on the main thread
}
- (id)init {
if ((self = [super init])) {
_handlers = [[NSMutableArray alloc] init];
}
return self;
}
- (void)dealloc {
if (_source) {
[self stop];
}
[_handlers release];
[super dealloc];
}
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)handlerBlock {
DCHECK(_source == NULL);
GCDWebServerHandler* handler = [[GCDWebServerHandler alloc] initWithMatchBlock:matchBlock processBlock:handlerBlock];
[_handlers insertObject:handler atIndex:0];
[handler release];
}
- (void)removeAllHandlers {
DCHECK(_source == NULL);
[_handlers removeAllObjects];
}
- (BOOL)start {
return [self startWithPort:8080 bonjourName:@""];
}
static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* error, void* info) {
@autoreleasepool {
if (error->error) {
LOG_ERROR(@"Bonjour error %i (domain %i)", error->error, (int)error->domain);
} else {
LOG_VERBOSE(@"Registered Bonjour service \"%@\" with type '%@' on port %i", CFNetServiceGetName(service), CFNetServiceGetType(service), CFNetServiceGetPortNumber(service));
}
}
}
- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name {
DCHECK(_source == NULL);
int listeningSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listeningSocket > 0) {
int yes = 1;
setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes));
struct sockaddr_in addr4;
bzero(&addr4, sizeof(addr4));
addr4.sin_len = sizeof(addr4);
addr4.sin_family = AF_INET;
addr4.sin_port = htons(port);
addr4.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listeningSocket, (void*)&addr4, sizeof(addr4)) == 0) {
if (listen(listeningSocket, kMaxPendingConnections) == 0) {
_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, kGCDWebServerGCDQueue);
dispatch_source_set_cancel_handler(_source, ^{
@autoreleasepool {
int result = close(listeningSocket);
if (result != 0) {
LOG_ERROR(@"Failed closing socket (%i): %s", errno, strerror(errno));
} else {
LOG_DEBUG(@"Closed listening socket");
}
}
});
dispatch_source_set_event_handler(_source, ^{
@autoreleasepool {
struct sockaddr addr;
socklen_t addrlen = sizeof(addr);
int socket = accept(listeningSocket, &addr, &addrlen);
if (socket > 0) {
int yes = 1;
setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &yes, sizeof(yes)); // Make sure this socket cannot generate SIG_PIPE
NSData* data = [NSData dataWithBytes:&addr length:addrlen];
Class connectionClass = [[self class] connectionClass];
GCDWebServerConnection* connection = [[connectionClass alloc] initWithServer:self address:data socket:socket];
[connection release]; // Connection will automatically retain itself while opened
} else {
LOG_ERROR(@"Failed accepting socket (%i): %s", errno, strerror(errno));
}
}
});
if (port == 0) { // Determine the actual port we are listening on
struct sockaddr addr;
socklen_t addrlen = sizeof(addr);
if (getsockname(listeningSocket, &addr, &addrlen) == 0) {
struct sockaddr_in* sockaddr = (struct sockaddr_in*)&addr;
_port = ntohs(sockaddr->sin_port);
} else {
LOG_ERROR(@"Failed retrieving socket address (%i): %s", errno, strerror(errno));
}
} else {
_port = port;
}
if (name) {
_service = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), CFSTR("_http._tcp"), (CFStringRef)name, _port);
if (_service) {
CFNetServiceClientContext context = {0, self, NULL, NULL, NULL};
CFNetServiceSetClient(_service, _NetServiceClientCallBack, &context);
CFNetServiceScheduleWithRunLoop(_service, CFRunLoopGetMain(), kCFRunLoopCommonModes);
CFStreamError error = {0};
CFNetServiceRegisterWithOptions(_service, 0, &error);
} else {
LOG_ERROR(@"Failed creating CFNetService");
}
}
dispatch_resume(_source);
LOG_VERBOSE(@"%@ started on port %i", [self class], (int)_port);
} else {
LOG_ERROR(@"Failed listening on socket (%i): %s", errno, strerror(errno));
close(listeningSocket);
}
} else {
LOG_ERROR(@"Failed binding socket (%i): %s", errno, strerror(errno));
close(listeningSocket);
}
} else {
LOG_ERROR(@"Failed creating socket (%i): %s", errno, strerror(errno));
}
return (_source ? YES : NO);
}
- (BOOL)isRunning {
return (_source ? YES : NO);
}
- (void)stop {
DCHECK(_source != NULL);
if (_source) {
if (_service) {
CFNetServiceUnscheduleFromRunLoop(_service, CFRunLoopGetMain(), kCFRunLoopCommonModes);
CFNetServiceSetClient(_service, NULL, NULL);
CFRelease(_service);
_service = NULL;
}
dispatch_source_cancel(_source); // This will close the socket
dispatch_release(_source);
_source = NULL;
LOG_VERBOSE(@"%@ stopped", [self class]);
}
_port = 0;
}
@end
@implementation GCDWebServer (Subclassing)
+ (Class)connectionClass {
return [GCDWebServerConnection class];
}
+ (NSString*)serverName {
return NSStringFromClass(self);
}
@end
@implementation GCDWebServer (Extensions)
- (BOOL)runWithPort:(NSUInteger)port {
BOOL success = NO;
_run = YES;
void* handler = signal(SIGINT, _SignalHandler);
if (handler != SIG_ERR) {
if ([self startWithPort:port bonjourName:@""]) {
while (_run) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, true);
}
[self stop];
success = YES;
}
signal(SIGINT, handler);
}
return success;
}
@end
@implementation GCDWebServer (Handlers)
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)class processBlock:(GCDWebServerProcessBlock)block {
[self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
return [[[class alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery] autorelease];
} processBlock:block];
}
- (GCDWebServerResponse*)_responseWithContentsOfFile:(NSString*)path {
return [GCDWebServerFileResponse responseWithFile:path];
}
- (GCDWebServerResponse*)_responseWithContentsOfDirectory:(NSString*)path {
NSDirectoryEnumerator* enumerator = [[NSFileManager defaultManager] enumeratorAtPath:path];
if (enumerator == nil) {
return nil;
}
NSMutableString* html = [NSMutableString string];
[html appendString:@"<html><body>\n"];
[html appendString:@"<ul>\n"];
for (NSString* file in enumerator) {
if (![file hasPrefix:@"."]) {
NSString* type = [[enumerator fileAttributes] objectForKey:NSFileType];
NSString* escapedFile = [file stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
DCHECK(escapedFile);
if ([type isEqualToString:NSFileTypeRegular]) {
[html appendFormat:@"<li><a href=\"%@\">%@</a></li>\n", escapedFile, file];
} else if ([type isEqualToString:NSFileTypeDirectory]) {
[html appendFormat:@"<li><a href=\"%@/\">%@/</a></li>\n", escapedFile, file];
}
}
[enumerator skipDescendents];
}
[html appendString:@"</ul>\n"];
[html appendString:@"</body></html>\n"];
return [GCDWebServerDataResponse responseWithHTML:html];
}
- (void)addHandlerForBasePath:(NSString*)basePath localPath:(NSString*)localPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge {
if ([basePath hasPrefix:@"/"] && [basePath hasSuffix:@"/"]) {
[self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
if (![requestMethod isEqualToString:@"GET"]) {
return nil;
}
if (![urlPath hasPrefix:basePath]) {
return nil;
}
return [[[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery] autorelease];
} processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
GCDWebServerResponse* response = nil;
NSString* filePath = [localPath stringByAppendingPathComponent:[request.path substringFromIndex:basePath.length]];
BOOL isDirectory;
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory]) {
if (isDirectory) {
if (indexFilename) {
NSString* indexPath = [filePath stringByAppendingPathComponent:indexFilename];
if ([[NSFileManager defaultManager] fileExistsAtPath:indexPath isDirectory:&isDirectory] && !isDirectory) {
return [self _responseWithContentsOfFile:indexPath];
}
}
response = [self _responseWithContentsOfDirectory:filePath];
} else {
response = [self _responseWithContentsOfFile:filePath];
}
}
if (response) {
response.cacheControlMaxAge = cacheAge;
} else {
response = [GCDWebServerResponse responseWithStatusCode:404];
}
return response;
}];
} else {
DNOT_REACHED();
}
}
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)class processBlock:(GCDWebServerProcessBlock)block {
if ([path hasPrefix:@"/"] && [class isSubclassOfClass:[GCDWebServerRequest class]]) {
[self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
if (![requestMethod isEqualToString:method]) {
return nil;
}
if ([urlPath caseInsensitiveCompare:path] != NSOrderedSame) {
return nil;
}
return [[[class alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery] autorelease];
} processBlock:block];
} else {
DNOT_REACHED();
}
}
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)class processBlock:(GCDWebServerProcessBlock)block {
NSRegularExpression* expression = [NSRegularExpression regularExpressionWithPattern:regex options:NSRegularExpressionCaseInsensitive error:NULL];
if (expression && [class isSubclassOfClass:[GCDWebServerRequest class]]) {
[self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
if (![requestMethod isEqualToString:method]) {
return nil;
}
if ([expression firstMatchInString:urlPath options:0 range:NSMakeRange(0, urlPath.length)] == nil) {
return nil;
}
return [[[class alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery] autorelease];
} processBlock:block];
} else {
DNOT_REACHED();
}
}
@end

View File

@@ -1,56 +0,0 @@
/*
Copyright (c) 2012-2013, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "GCDWebServer.h"
@class GCDWebServerHandler;
@interface GCDWebServerConnection : NSObject {
@private
GCDWebServer* _server;
NSData* _address;
CFSocketNativeHandle _socket;
NSUInteger _bytesRead;
NSUInteger _bytesWritten;
CFHTTPMessageRef _requestMessage;
GCDWebServerRequest* _request;
GCDWebServerHandler* _handler;
CFHTTPMessageRef _responseMessage;
GCDWebServerResponse* _response;
}
@property(nonatomic, readonly) GCDWebServer* server;
@property(nonatomic, readonly) NSData* address; // struct sockaddr
@property(nonatomic, readonly) NSUInteger totalBytesRead;
@property(nonatomic, readonly) NSUInteger totalBytesWritten;
@end
@interface GCDWebServerConnection (Subclassing)
- (void)open;
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block;
- (void)close;
@end

View File

@@ -1,492 +0,0 @@
/*
Copyright (c) 2012-2013, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "GCDWebServerPrivate.h"
#define kHeadersReadBuffer 1024
#define kBodyWriteBufferSize (32 * 1024)
typedef void (^ReadBufferCompletionBlock)(dispatch_data_t buffer);
typedef void (^ReadDataCompletionBlock)(NSData* data);
typedef void (^ReadHeadersCompletionBlock)(NSData* extraData);
typedef void (^ReadBodyCompletionBlock)(BOOL success);
typedef void (^WriteBufferCompletionBlock)(BOOL success);
typedef void (^WriteDataCompletionBlock)(BOOL success);
typedef void (^WriteHeadersCompletionBlock)(BOOL success);
typedef void (^WriteBodyCompletionBlock)(BOOL success);
static NSData* _separatorData = nil;
static NSData* _continueData = nil;
static NSDateFormatter* _dateFormatter = nil;
static dispatch_queue_t _formatterQueue = NULL;
@implementation GCDWebServerConnection (Read)
- (void)_readBufferWithLength:(NSUInteger)length completionBlock:(ReadBufferCompletionBlock)block {
dispatch_read(_socket, length, kGCDWebServerGCDQueue, ^(dispatch_data_t buffer, int error) {
@autoreleasepool {
if (error == 0) {
size_t size = dispatch_data_get_size(buffer);
if (size > 0) {
LOG_DEBUG(@"Connection received %i bytes on socket %i", size, _socket);
_bytesRead += size;
block(buffer);
} else {
if (_bytesRead > 0) {
LOG_ERROR(@"No more data available on socket %i", _socket);
} else {
LOG_WARNING(@"No data received from socket %i", _socket);
}
block(NULL);
}
} else {
LOG_ERROR(@"Error while reading from socket %i: %s (%i)", _socket, strerror(error), error);
block(NULL);
}
}
});
}
- (void)_readDataWithCompletionBlock:(ReadDataCompletionBlock)block {
[self _readBufferWithLength:SIZE_T_MAX completionBlock:^(dispatch_data_t buffer) {
if (buffer) {
NSMutableData* data = [[NSMutableData alloc] initWithCapacity:dispatch_data_get_size(buffer)];
dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t offset, const void* buffer, size_t size) {
[data appendBytes:buffer length:size];
return true;
});
block(data);
[data release];
} else {
block(nil);
}
}];
}
- (void)_readHeadersWithCompletionBlock:(ReadHeadersCompletionBlock)block {
DCHECK(_requestMessage);
[self _readBufferWithLength:SIZE_T_MAX completionBlock:^(dispatch_data_t buffer) {
if (buffer) {
NSMutableData* data = [NSMutableData dataWithCapacity:kHeadersReadBuffer];
dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t offset, const void* buffer, size_t size) {
[data appendBytes:buffer length:size];
return true;
});
NSRange range = [data rangeOfData:_separatorData options:0 range:NSMakeRange(0, data.length)];
if (range.location == NSNotFound) {
if (CFHTTPMessageAppendBytes(_requestMessage, data.bytes, data.length)) {
[self _readHeadersWithCompletionBlock:block];
} else {
LOG_ERROR(@"Failed appending request headers data from socket %i", _socket);
block(nil);
}
} else {
NSUInteger length = range.location + range.length;
if (CFHTTPMessageAppendBytes(_requestMessage, data.bytes, length)) {
if (CFHTTPMessageIsHeaderComplete(_requestMessage)) {
block([data subdataWithRange:NSMakeRange(length, data.length - length)]);
} else {
LOG_ERROR(@"Failed parsing request headers from socket %i", _socket);
block(nil);
}
} else {
LOG_ERROR(@"Failed appending request headers data from socket %i", _socket);
block(nil);
}
}
} else {
block(nil);
}
}];
}
- (void)_readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block {
DCHECK([_request hasBody]);
[self _readBufferWithLength:length completionBlock:^(dispatch_data_t buffer) {
if (buffer) {
NSInteger remainingLength = length - dispatch_data_get_size(buffer);
if (remainingLength >= 0) {
bool success = dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t offset, const void* buffer, size_t size) {
NSInteger result = [_request write:buffer maxLength:size];
if (result != size) {
LOG_ERROR(@"Failed writing request body on socket %i (error %i)", _socket, (int)result);
return false;
}
return true;
});
if (success) {
if (remainingLength > 0) {
[self _readBodyWithRemainingLength:remainingLength completionBlock:block];
} else {
block(YES);
}
} else {
block(NO);
}
} else {
DNOT_REACHED();
block(NO);
}
} else {
block(NO);
}
}];
}
@end
@implementation GCDWebServerConnection (Write)
- (void)_writeBuffer:(dispatch_data_t)buffer withCompletionBlock:(WriteBufferCompletionBlock)block {
size_t size = dispatch_data_get_size(buffer);
dispatch_write(_socket, buffer, kGCDWebServerGCDQueue, ^(dispatch_data_t data, int error) {
@autoreleasepool {
if (error == 0) {
DCHECK(data == NULL);
LOG_DEBUG(@"Connection sent %i bytes on socket %i", size, _socket);
_bytesWritten += size;
block(YES);
} else {
LOG_ERROR(@"Error while writing to socket %i: %s (%i)", _socket, strerror(error), error);
block(NO);
}
}
});
}
- (void)_writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block {
[data retain];
dispatch_data_t buffer = dispatch_data_create(data.bytes, data.length, dispatch_get_current_queue(), ^{
[data release];
});
[self _writeBuffer:buffer withCompletionBlock:block];
dispatch_release(buffer);
}
- (void)_writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block {
DCHECK(_responseMessage);
CFDataRef message = CFHTTPMessageCopySerializedMessage(_responseMessage);
[self _writeData:(NSData*)message withCompletionBlock:block];
CFRelease(message);
}
- (void)_writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block {
DCHECK([_response hasBody]);
void* buffer = malloc(kBodyWriteBufferSize);
NSInteger result = [_response read:buffer maxLength:kBodyWriteBufferSize];
if (result > 0) {
dispatch_data_t wrapper = dispatch_data_create(buffer, result, NULL, DISPATCH_DATA_DESTRUCTOR_FREE);
[self _writeBuffer:wrapper withCompletionBlock:^(BOOL success) {
if (success) {
[self _writeBodyWithCompletionBlock:block];
} else {
block(NO);
}
}];
dispatch_release(wrapper);
} else if (result < 0) {
LOG_ERROR(@"Failed reading response body on socket %i (error %i)", _socket, (int)result);
block(NO);
free(buffer);
} else {
block(YES);
free(buffer);
}
}
@end
@implementation GCDWebServerConnection
@synthesize server=_server, address=_address, totalBytesRead=_bytesRead, totalBytesWritten=_bytesWritten;
+ (void)initialize {
DCHECK([NSThread isMainThread]); // NSDateFormatter should be initialized on main thread
if (_separatorData == nil) {
_separatorData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4];
DCHECK(_separatorData);
}
if (_continueData == nil) {
CFHTTPMessageRef message = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 100, NULL, kCFHTTPVersion1_1);
_continueData = (NSData*)CFHTTPMessageCopySerializedMessage(message);
CFRelease(message);
DCHECK(_continueData);
}
if (_dateFormatter == nil) {
_dateFormatter = [[NSDateFormatter alloc] init];
_dateFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
_dateFormatter.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
_dateFormatter.locale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease];
DCHECK(_dateFormatter);
}
if (_formatterQueue == NULL) {
_formatterQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
DCHECK(_formatterQueue);
}
}
- (void)_initializeResponseHeadersWithStatusCode:(NSInteger)statusCode {
_responseMessage = CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_1);
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Connection"), CFSTR("Close"));
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Server"), (CFStringRef)[[_server class] serverName]);
dispatch_sync(_formatterQueue, ^{
NSString* date = [_dateFormatter stringFromDate:[NSDate date]];
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Date"), (CFStringRef)date);
});
}
- (void)_abortWithStatusCode:(NSUInteger)statusCode {
DCHECK(_responseMessage == NULL);
DCHECK((statusCode >= 400) && (statusCode < 600));
[self _initializeResponseHeadersWithStatusCode:statusCode];
[self _writeHeadersWithCompletionBlock:^(BOOL success) {
; // Nothing more to do
}];
LOG_DEBUG(@"Connection aborted with status code %i on socket %i", statusCode, _socket);
}
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
- (void)_processRequest {
DCHECK(_responseMessage == NULL);
GCDWebServerResponse* response = [self processRequest:_request withBlock:_handler.processBlock];
if (![response hasBody] || [response open]) {
_response = [response retain];
}
if (_response) {
[self _initializeResponseHeadersWithStatusCode:_response.statusCode];
NSUInteger maxAge = _response.cacheControlMaxAge;
if (maxAge > 0) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), (CFStringRef)[NSString stringWithFormat:@"max-age=%i, public", (int)maxAge]);
} else {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), CFSTR("no-cache"));
}
[_response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, (CFStringRef)key, (CFStringRef)obj);
}];
if ([_response hasBody]) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Type"), (CFStringRef)_response.contentType);
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Length"), (CFStringRef)[NSString stringWithFormat:@"%i", (int)_response.contentLength]);
}
[self _writeHeadersWithCompletionBlock:^(BOOL success) {
if (success) {
if ([_response hasBody]) {
[self _writeBodyWithCompletionBlock:^(BOOL success) {
[_response close]; // Can't do anything with result anyway
}];
}
} else if ([_response hasBody]) {
[_response close]; // Can't do anything with result anyway
}
}];
} else {
[self _abortWithStatusCode:500];
}
}
- (void)_readRequestBody:(NSData*)initialData {
if ([_request open]) {
NSInteger length = _request.contentLength;
if (initialData.length) {
NSInteger result = [_request write:initialData.bytes maxLength:initialData.length];
if (result == initialData.length) {
length -= initialData.length;
DCHECK(length >= 0);
} else {
LOG_ERROR(@"Failed writing request body on socket %i (error %i)", _socket, (int)result);
length = -1;
}
}
if (length > 0) {
[self _readBodyWithRemainingLength:length completionBlock:^(BOOL success) {
if (![_request close]) {
success = NO;
}
if (success) {
[self _processRequest];
} else {
[self _abortWithStatusCode:500];
}
}];
} else if (length == 0) {
if ([_request close]) {
[self _processRequest];
} else {
[self _abortWithStatusCode:500];
}
} else {
[_request close]; // Can't do anything with result anyway
[self _abortWithStatusCode:500];
}
} else {
[self _abortWithStatusCode:500];
}
}
- (void)_readRequestHeaders {
_requestMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true);
[self _readHeadersWithCompletionBlock:^(NSData* extraData) {
if (extraData) {
NSString* requestMethod = [[(id)CFHTTPMessageCopyRequestMethod(_requestMessage) autorelease] uppercaseString];
DCHECK(requestMethod);
NSURL* requestURL = [(id)CFHTTPMessageCopyRequestURL(_requestMessage) autorelease];
DCHECK(requestURL);
NSString* requestPath = GCDWebServerUnescapeURLString([(id)CFURLCopyPath((CFURLRef)requestURL) autorelease]); // Don't use -[NSURL path] which strips the ending slash
DCHECK(requestPath);
NSDictionary* requestQuery = nil;
NSString* queryString = [(id)CFURLCopyQueryString((CFURLRef)requestURL, NULL) autorelease]; // Don't use -[NSURL query] to make sure query is not unescaped;
if (queryString.length) {
requestQuery = GCDWebServerParseURLEncodedForm(queryString);
DCHECK(requestQuery);
}
NSDictionary* requestHeaders = [(id)CFHTTPMessageCopyAllHeaderFields(_requestMessage) autorelease];
DCHECK(requestHeaders);
for (_handler in _server.handlers) {
_request = [_handler.matchBlock(requestMethod, requestURL, requestHeaders, requestPath, requestQuery) retain];
if (_request) {
break;
}
}
if (_request) {
if (_request.hasBody) {
if (extraData.length <= _request.contentLength) {
NSString* expectHeader = [(id)CFHTTPMessageCopyHeaderFieldValue(_requestMessage, CFSTR("Expect")) autorelease];
if (expectHeader) {
if ([expectHeader caseInsensitiveCompare:@"100-continue"] == NSOrderedSame) {
[self _writeData:_continueData withCompletionBlock:^(BOOL success) {
if (success) {
[self _readRequestBody:extraData];
}
}];
} else {
LOG_ERROR(@"Unsupported 'Expect' / 'Content-Length' header combination on socket %i", _socket);
[self _abortWithStatusCode:417];
}
} else {
[self _readRequestBody:extraData];
}
} else {
LOG_ERROR(@"Unexpected 'Content-Length' header value on socket %i", _socket);
[self _abortWithStatusCode:400];
}
} else {
[self _processRequest];
}
} else {
[self _abortWithStatusCode:405];
}
} else {
[self _abortWithStatusCode:500];
}
}];
}
- (id)initWithServer:(GCDWebServer*)server address:(NSData*)address socket:(CFSocketNativeHandle)socket {
if ((self = [super init])) {
_server = [server retain];
_address = [address retain];
_socket = socket;
[self open];
}
return self;
}
- (void)dealloc {
[self close];
[_server release];
[_address release];
if (_requestMessage) {
CFRelease(_requestMessage);
}
[_request release];
if (_responseMessage) {
CFRelease(_responseMessage);
}
[_response release];
[super dealloc];
}
@end
@implementation GCDWebServerConnection (Subclassing)
- (void)open {
LOG_DEBUG(@"Did open connection on socket %i", _socket);
[self _readRequestHeaders];
}
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block {
LOG_DEBUG(@"Connection on socket %i processing %@ request for \"%@\" (%i bytes body)", _socket, _request.method, _request.path, _request.contentLength);
GCDWebServerResponse* response = nil;
@try {
response = block(request);
}
@catch (NSException* exception) {
LOG_EXCEPTION(exception);
}
return response;
}
- (void)close {
int result = close(_socket);
if (result != 0) {
LOG_ERROR(@"Failed closing socket %i for connection (%i): %s", _socket, errno, strerror(errno));
}
LOG_DEBUG(@"Did close connection on socket %i", _socket);
}
@end

View File

@@ -1,112 +0,0 @@
/*
Copyright (c) 2012-2013, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "GCDWebServerConnection.h"
#ifdef __GCDWEBSERVER_LOGGING_HEADER__
// Define __GCDWEBSERVER_LOGGING_HEADER__ as a preprocessor constant to redirect GCDWebServer logging to your own system
#import __GCDWEBSERVER_LOGGING_HEADER__
#else
static inline void __LogMessage(long level, NSString* format, ...) {
static const char* levelNames[] = {"DEBUG", "VERBOSE", "INFO", "WARNING", "ERROR", "EXCEPTION"};
static long minLevel = -1;
if (minLevel < 0) {
const char* logLevel = getenv("logLevel");
minLevel = logLevel ? atoi(logLevel) : 0;
}
if (level >= minLevel) {
va_list arguments;
va_start(arguments, format);
NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments];
va_end(arguments);
printf("[%s] %s\n", levelNames[level], [message UTF8String]);
[message release];
}
}
#define LOG_VERBOSE(...) __LogMessage(1, __VA_ARGS__)
#define LOG_INFO(...) __LogMessage(2, __VA_ARGS__)
#define LOG_WARNING(...) __LogMessage(3, __VA_ARGS__)
#define LOG_ERROR(...) __LogMessage(4, __VA_ARGS__)
#define LOG_EXCEPTION(__EXCEPTION__) __LogMessage(5, @"%@", __EXCEPTION__)
#ifdef NDEBUG
#define DCHECK(__CONDITION__)
#define DNOT_REACHED()
#define LOG_DEBUG(...)
#else
#define DCHECK(__CONDITION__) \
do { \
if (!(__CONDITION__)) { \
abort(); \
} \
} while (0)
#define DNOT_REACHED() abort()
#define LOG_DEBUG(...) __LogMessage(0, __VA_ARGS__)
#endif
#endif
#define kGCDWebServerDefaultMimeType @"application/octet-stream"
#define kGCDWebServerGCDQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
#ifdef __cplusplus
extern "C" {
#endif
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension);
NSString* GCDWebServerUnescapeURLString(NSString* string);
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
#ifdef __cplusplus
}
#endif
@interface GCDWebServerConnection ()
- (id)initWithServer:(GCDWebServer*)server address:(NSData*)address socket:(CFSocketNativeHandle)socket;
@end
@interface GCDWebServer ()
@property(nonatomic, readonly) NSArray* handlers;
@end
@interface GCDWebServerHandler : NSObject {
@private
GCDWebServerMatchBlock _matchBlock;
GCDWebServerProcessBlock _processBlock;
}
@property(nonatomic, readonly) GCDWebServerMatchBlock matchBlock;
@property(nonatomic, readonly) GCDWebServerProcessBlock processBlock;
- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;
@end

View File

@@ -1,125 +0,0 @@
/*
Copyright (c) 2012-2013, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Foundation/Foundation.h>
@interface GCDWebServerRequest : NSObject {
@private
NSString* _method;
NSURL* _url;
NSDictionary* _headers;
NSString* _path;
NSDictionary* _query;
NSString* _type;
NSUInteger _length;
}
@property(nonatomic, readonly) NSString* method;
@property(nonatomic, readonly) NSURL* URL;
@property(nonatomic, readonly) NSDictionary* headers;
@property(nonatomic, readonly) NSString* path;
@property(nonatomic, readonly) NSDictionary* query; // May be nil
@property(nonatomic, readonly) NSString* contentType; // Automatically parsed from headers (nil if request has no body)
@property(nonatomic, readonly) NSUInteger contentLength; // Automatically parsed from headers
- (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query;
- (BOOL)hasBody; // Convenience method
@end
@interface GCDWebServerRequest (Subclassing)
- (BOOL)open; // Implementation required
- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length; // Implementation required
- (BOOL)close; // Implementation required
@end
@interface GCDWebServerDataRequest : GCDWebServerRequest {
@private
NSMutableData* _data;
}
@property(nonatomic, readonly) NSData* data; // Only valid after open / write / close sequence
@end
@interface GCDWebServerFileRequest : GCDWebServerRequest {
@private
NSString* _filePath;
int _file;
}
@property(nonatomic, readonly) NSString* filePath; // Only valid after open / write / close sequence
@end
@interface GCDWebServerURLEncodedFormRequest : GCDWebServerDataRequest {
@private
NSDictionary* _arguments;
}
@property(nonatomic, readonly) NSDictionary* arguments; // Only valid after open / write / close sequence
+ (NSString*)mimeType;
@end
@interface GCDWebServerMultiPart : NSObject {
@private
NSString* _contentType;
NSString* _mimeType;
}
@property(nonatomic, readonly) NSString* contentType; // May be nil
@property(nonatomic, readonly) NSString* mimeType; // Defaults to "text/plain" per specifications if undefined
@end
@interface GCDWebServerMultiPartArgument : GCDWebServerMultiPart {
@private
NSData* _data;
NSString* _string;
}
@property(nonatomic, readonly) NSData* data;
@property(nonatomic, readonly) NSString* string; // May be nil (only valid for text mime types
@end
@interface GCDWebServerMultiPartFile : GCDWebServerMultiPart {
@private
NSString* _fileName;
NSString* _temporaryPath;
}
@property(nonatomic, readonly) NSString* fileName; // May be nil
@property(nonatomic, readonly) NSString* temporaryPath;
@end
@interface GCDWebServerMultiPartFormRequest : GCDWebServerRequest {
@private
NSData* _boundary;
NSUInteger _parserState;
NSMutableData* _parserData;
NSString* _controlName;
NSString* _fileName;
NSString* _contentType;
NSString* _tmpPath;
int _tmpFile;
NSMutableDictionary* _arguments;
NSMutableDictionary* _files;
}
@property(nonatomic, readonly) NSDictionary* arguments; // Only valid after open / write / close sequence
@property(nonatomic, readonly) NSDictionary* files; // Only valid after open / write / close sequence
+ (NSString*)mimeType;
@end

View File

@@ -1,512 +0,0 @@
/*
Copyright (c) 2012-2013, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "GCDWebServerPrivate.h"
#define kMultiPartBufferSize (256 * 1024)
enum {
kParserState_Undefined = 0,
kParserState_Start,
kParserState_Headers,
kParserState_Content,
kParserState_End
};
static NSData* _newlineData = nil;
static NSData* _newlinesData = nil;
static NSData* _dashNewlineData = nil;
static NSString* _ExtractHeaderParameter(NSString* header, NSString* attribute) {
NSString* value = nil;
if (header) {
NSScanner* scanner = [[NSScanner alloc] initWithString:header];
NSString* string = [NSString stringWithFormat:@"%@=", attribute];
if ([scanner scanUpToString:string intoString:NULL]) {
[scanner scanString:string intoString:NULL];
if ([scanner scanString:@"\"" intoString:NULL]) {
[scanner scanUpToString:@"\"" intoString:&value];
} else {
[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&value];
}
}
[scanner release];
}
return value;
}
// http://www.w3schools.com/tags/ref_charactersets.asp
static NSStringEncoding _StringEncodingFromCharset(NSString* charset) {
NSStringEncoding encoding = kCFStringEncodingInvalidId;
if (charset) {
encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)charset));
}
return (encoding != kCFStringEncodingInvalidId ? encoding : NSUTF8StringEncoding);
}
@implementation GCDWebServerRequest : NSObject
@synthesize method=_method, URL=_url, headers=_headers, path=_path, query=_query, contentType=_type, contentLength=_length;
- (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
if ((self = [super init])) {
_method = [method copy];
_url = [url retain];
_headers = [headers retain];
_path = [path copy];
_query = [query retain];
_type = [[_headers objectForKey:@"Content-Type"] retain];
NSInteger length = [[_headers objectForKey:@"Content-Length"] integerValue];
if (length < 0) {
DNOT_REACHED();
[self release];
return nil;
}
_length = length;
if ((_length > 0) && (_type == nil)) {
_type = [kGCDWebServerDefaultMimeType copy];
}
}
return self;
}
- (void)dealloc {
[_method release];
[_url release];
[_headers release];
[_path release];
[_query release];
[_type release];
[super dealloc];
}
- (BOOL)hasBody {
return _type ? YES : NO;
}
@end
@implementation GCDWebServerRequest (Subclassing)
- (BOOL)open {
[self doesNotRecognizeSelector:_cmd];
return NO;
}
- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
[self doesNotRecognizeSelector:_cmd];
return -1;
}
- (BOOL)close {
[self doesNotRecognizeSelector:_cmd];
return NO;
}
@end
@implementation GCDWebServerDataRequest
@synthesize data=_data;
- (void)dealloc {
DCHECK(_data != nil);
[_data release];
[super dealloc];
}
- (BOOL)open {
DCHECK(_data == nil);
_data = [[NSMutableData alloc] initWithCapacity:self.contentLength];
return _data ? YES : NO;
}
- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
DCHECK(_data != nil);
[_data appendBytes:buffer length:length];
return length;
}
- (BOOL)close {
DCHECK(_data != nil);
return YES;
}
@end
@implementation GCDWebServerFileRequest
@synthesize filePath=_filePath;
- (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) {
_filePath = [[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]] retain];
}
return self;
}
- (void)dealloc {
DCHECK(_file < 0);
unlink([_filePath fileSystemRepresentation]);
[_filePath release];
[super dealloc];
}
- (BOOL)open {
DCHECK(_file == 0);
_file = open([_filePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
return (_file > 0 ? YES : NO);
}
- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
DCHECK(_file > 0);
return write(_file, buffer, length);
}
- (BOOL)close {
DCHECK(_file > 0);
int result = close(_file);
_file = -1;
return (result == 0 ? YES : NO);
}
@end
@implementation GCDWebServerURLEncodedFormRequest
@synthesize arguments=_arguments;
+ (NSString*)mimeType {
return @"application/x-www-form-urlencoded";
}
- (void)dealloc {
[_arguments release];
[super dealloc];
}
- (BOOL)close {
if (![super close]) {
return NO;
}
NSString* charset = _ExtractHeaderParameter(self.contentType, @"charset");
NSString* string = [[NSString alloc] initWithData:self.data encoding:_StringEncodingFromCharset(charset)];
_arguments = [GCDWebServerParseURLEncodedForm(string) retain];
[string release];
return (_arguments ? YES : NO);
}
@end
@implementation GCDWebServerMultiPart
@synthesize contentType=_contentType, mimeType=_mimeType;
- (id)initWithContentType:(NSString*)contentType {
if ((self = [super init])) {
_contentType = [contentType copy];
NSArray* components = [_contentType componentsSeparatedByString:@";"];
if (components.count) {
_mimeType = [[[components objectAtIndex:0] lowercaseString] retain];
}
if (_mimeType == nil) {
_mimeType = @"text/plain";
}
}
return self;
}
- (void)dealloc {
[_contentType release];
[_mimeType release];
[super dealloc];
}
@end
@implementation GCDWebServerMultiPartArgument
@synthesize data=_data, string=_string;
- (id)initWithContentType:(NSString*)contentType data:(NSData*)data {
if ((self = [super initWithContentType:contentType])) {
_data = [data retain];
if ([self.mimeType hasPrefix:@"text/"]) {
NSString* charset = _ExtractHeaderParameter(self.contentType, @"charset");
_string = [[NSString alloc] initWithData:_data encoding:_StringEncodingFromCharset(charset)];
}
}
return self;
}
- (void)dealloc {
[_data release];
[_string release];
[super dealloc];
}
- (NSString*)description {
return [NSString stringWithFormat:@"<%@ | '%@' | %i bytes>", [self class], self.mimeType, (int)_data.length];
}
@end
@implementation GCDWebServerMultiPartFile
@synthesize fileName=_fileName, temporaryPath=_temporaryPath;
- (id)initWithContentType:(NSString*)contentType fileName:(NSString*)fileName temporaryPath:(NSString*)temporaryPath {
if ((self = [super initWithContentType:contentType])) {
_fileName = [fileName copy];
_temporaryPath = [temporaryPath copy];
}
return self;
}
- (void)dealloc {
unlink([_temporaryPath fileSystemRepresentation]);
[_fileName release];
[_temporaryPath release];
[super dealloc];
}
- (NSString*)description {
return [NSString stringWithFormat:@"<%@ | '%@' | '%@>'", [self class], self.mimeType, _fileName];
}
@end
@implementation GCDWebServerMultiPartFormRequest
@synthesize arguments=_arguments, files=_files;
+ (void)initialize {
if (_newlineData == nil) {
_newlineData = [[NSData alloc] initWithBytes:"\r\n" length:2];
DCHECK(_newlineData);
}
if (_newlinesData == nil) {
_newlinesData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4];
DCHECK(_newlinesData);
}
if (_dashNewlineData == nil) {
_dashNewlineData = [[NSData alloc] initWithBytes:"--\r\n" length:4];
DCHECK(_dashNewlineData);
}
}
+ (NSString*)mimeType {
return @"multipart/form-data";
}
- (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) {
NSString* boundary = _ExtractHeaderParameter(self.contentType, @"boundary");
if (boundary) {
_boundary = [[[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding] retain];
}
if (_boundary == nil) {
DNOT_REACHED();
[self release];
return nil;
}
_arguments = [[NSMutableDictionary alloc] init];
_files = [[NSMutableDictionary alloc] init];
}
return self;
}
- (BOOL)open {
DCHECK(_parserData == nil);
_parserData = [[NSMutableData alloc] initWithCapacity:kMultiPartBufferSize];
_parserState = kParserState_Start;
return YES;
}
// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4
- (BOOL)_parseData {
BOOL success = YES;
if (_parserState == kParserState_Headers) {
NSRange range = [_parserData rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _parserData.length)];
if (range.location != NSNotFound) {
[_controlName release];
_controlName = nil;
[_fileName release];
_fileName = nil;
[_contentType release];
_contentType = nil;
[_tmpPath release];
_tmpPath = nil;
CFHTTPMessageRef message = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true);
const char* temp = "GET / HTTP/1.0\r\n";
CFHTTPMessageAppendBytes(message, (const UInt8*)temp, strlen(temp));
CFHTTPMessageAppendBytes(message, _parserData.bytes, range.location + range.length);
if (CFHTTPMessageIsHeaderComplete(message)) {
NSString* controlName = nil;
NSString* fileName = nil;
NSDictionary* headers = [(id)CFHTTPMessageCopyAllHeaderFields(message) autorelease];
NSString* contentDisposition = [headers objectForKey:@"Content-Disposition"];
if ([[contentDisposition lowercaseString] hasPrefix:@"form-data;"]) {
controlName = _ExtractHeaderParameter(contentDisposition, @"name");
fileName = _ExtractHeaderParameter(contentDisposition, @"filename");
}
_controlName = [controlName copy];
_fileName = [fileName copy];
_contentType = [[headers objectForKey:@"Content-Type"] retain];
}
CFRelease(message);
if (_controlName) {
if (_fileName) {
NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
_tmpFile = open([path fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (_tmpFile > 0) {
_tmpPath = [path copy];
} else {
DNOT_REACHED();
success = NO;
}
}
} else {
DNOT_REACHED();
success = NO;
}
[_parserData replaceBytesInRange:NSMakeRange(0, range.location + range.length) withBytes:NULL length:0];
_parserState = kParserState_Content;
}
}
if ((_parserState == kParserState_Start) || (_parserState == kParserState_Content)) {
NSRange range = [_parserData rangeOfData:_boundary options:0 range:NSMakeRange(0, _parserData.length)];
if (range.location != NSNotFound) {
NSRange subRange = NSMakeRange(range.location + range.length, _parserData.length - range.location - range.length);
NSRange subRange1 = [_parserData rangeOfData:_newlineData options:NSDataSearchAnchored range:subRange];
NSRange subRange2 = [_parserData rangeOfData:_dashNewlineData options:NSDataSearchAnchored range:subRange];
if ((subRange1.location != NSNotFound) || (subRange2.location != NSNotFound)) {
if (_parserState == kParserState_Content) {
const void* dataBytes = _parserData.bytes;
NSUInteger dataLength = range.location - 2;
if (_tmpPath) {
int result = write(_tmpFile, dataBytes, dataLength);
if (result == dataLength) {
if (close(_tmpFile) == 0) {
_tmpFile = 0;
GCDWebServerMultiPartFile* file = [[GCDWebServerMultiPartFile alloc] initWithContentType:_contentType fileName:_fileName temporaryPath:_tmpPath];
[_files setObject:file forKey:_controlName];
[file release];
} else {
DNOT_REACHED();
success = NO;
}
} else {
DNOT_REACHED();
success = NO;
}
[_tmpPath release];
_tmpPath = nil;
} else {
NSData* data = [[NSData alloc] initWithBytesNoCopy:(void*)dataBytes length:dataLength freeWhenDone:NO];
GCDWebServerMultiPartArgument* argument = [[GCDWebServerMultiPartArgument alloc] initWithContentType:_contentType data:data];
[_arguments setObject:argument forKey:_controlName];
[argument release];
[data release];
}
}
if (subRange1.location != NSNotFound) {
[_parserData replaceBytesInRange:NSMakeRange(0, subRange1.location + subRange1.length) withBytes:NULL length:0];
_parserState = kParserState_Headers;
success = [self _parseData];
} else {
_parserState = kParserState_End;
}
}
} else {
NSUInteger margin = 2 * _boundary.length;
if (_tmpPath && (_parserData.length > margin)) {
NSUInteger length = _parserData.length - margin;
int result = write(_tmpFile, _parserData.bytes, length);
if (result == length) {
[_parserData replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
} else {
DNOT_REACHED();
success = NO;
}
}
}
}
return success;
}
- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
DCHECK(_parserData != nil);
[_parserData appendBytes:buffer length:length];
return ([self _parseData] ? length : -1);
}
- (BOOL)close {
DCHECK(_parserData != nil);
[_parserData release];
_parserData = nil;
[_controlName release];
[_fileName release];
[_contentType release];
if (_tmpFile > 0) {
close(_tmpFile);
unlink([_tmpPath fileSystemRepresentation]);
}
[_tmpPath release];
return (_parserState == kParserState_End ? YES : NO);
}
- (void)dealloc {
DCHECK(_parserData == nil);
[_arguments release];
[_files release];
[_boundary release];
[super dealloc];
}
@end

View File

@@ -1,90 +0,0 @@
/*
Copyright (c) 2012-2013, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Foundation/Foundation.h>
@interface GCDWebServerResponse : NSObject {
@private
NSString* _type;
NSUInteger _length;
NSInteger _status;
NSUInteger _maxAge;
NSMutableDictionary* _headers;
}
@property(nonatomic, readonly) NSString* contentType;
@property(nonatomic, readonly) NSUInteger contentLength;
@property(nonatomic) NSInteger statusCode; // Default is 200
@property(nonatomic) NSUInteger cacheControlMaxAge; // Default is 0 seconds i.e. "no-cache"
@property(nonatomic, readonly) NSDictionary* additionalHeaders;
+ (GCDWebServerResponse*) response;
- (id)init;
- (id)initWithContentType:(NSString*)type contentLength:(NSUInteger)length; // Pass nil contentType to indicate empty body
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header;
- (BOOL)hasBody; // Convenience method
@end
@interface GCDWebServerResponse (Subclassing)
- (BOOL)open; // Implementation required
- (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length; // Implementation required
- (BOOL)close; // Implementation required
@end
@interface GCDWebServerResponse (Extensions)
+ (GCDWebServerResponse*)responseWithStatusCode:(NSInteger)statusCode;
+ (GCDWebServerResponse*)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent;
- (id)initWithStatusCode:(NSInteger)statusCode;
- (id)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent;
@end
@interface GCDWebServerDataResponse : GCDWebServerResponse {
@private
NSData* _data;
NSInteger _offset;
}
+ (GCDWebServerDataResponse*)responseWithData:(NSData*)data contentType:(NSString*)type;
- (id)initWithData:(NSData*)data contentType:(NSString*)type;
@end
@interface GCDWebServerDataResponse (Extensions)
+ (GCDWebServerDataResponse*)responseWithText:(NSString*)text;
+ (GCDWebServerDataResponse*)responseWithHTML:(NSString*)html;
+ (GCDWebServerDataResponse*)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables;
- (id)initWithText:(NSString*)text; // Encodes using UTF-8
- (id)initWithHTML:(NSString*)html; // Encodes using UTF-8
- (id)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; // Simple template system that replaces all occurences of "%variable%" with corresponding value (encodes using UTF-8)
@end
@interface GCDWebServerFileResponse : GCDWebServerResponse {
@private
NSString* _path;
int _file;
}
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path;
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment;
- (id)initWithFile:(NSString*)path;
- (id)initWithFile:(NSString*)path isAttachment:(BOOL)attachment;
@end

View File

@@ -1,287 +0,0 @@
/*
Copyright (c) 2012-2013, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <sys/stat.h>
#import "GCDWebServerPrivate.h"
@implementation GCDWebServerResponse
@synthesize contentType=_type, contentLength=_length, statusCode=_status, cacheControlMaxAge=_maxAge, additionalHeaders=_headers;
+(GCDWebServerResponse*) response {
return [[[[self class] alloc] init] autorelease];
}
- (id)init {
return [self initWithContentType:nil contentLength:0];
}
- (id)initWithContentType:(NSString*)type contentLength:(NSUInteger)length {
if ((self = [super init])) {
_type = [type copy];
_length = length;
_status = 200;
_maxAge = 0;
_headers = [[NSMutableDictionary alloc] init];
if ((_length > 0) && (_type == nil)) {
_type = [kGCDWebServerDefaultMimeType copy];
}
}
return self;
}
- (void)dealloc {
[_type release];
[_headers release];
[super dealloc];
}
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header {
[_headers setValue:value forKey:header];
}
- (BOOL)hasBody {
return _type ? YES : NO;
}
@end
@implementation GCDWebServerResponse (Subclassing)
- (BOOL)open {
[self doesNotRecognizeSelector:_cmd];
return NO;
}
- (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length {
[self doesNotRecognizeSelector:_cmd];
return -1;
}
- (BOOL)close {
[self doesNotRecognizeSelector:_cmd];
return NO;
}
@end
@implementation GCDWebServerResponse (Extensions)
+ (GCDWebServerResponse*)responseWithStatusCode:(NSInteger)statusCode {
return [[[self alloc] initWithStatusCode:statusCode] autorelease];
}
+ (GCDWebServerResponse*)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent {
return [[[self alloc] initWithRedirect:location permanent:permanent] autorelease];
}
- (id)initWithStatusCode:(NSInteger)statusCode {
if ((self = [self initWithContentType:nil contentLength:0])) {
self.statusCode = statusCode;
}
return self;
}
- (id)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent {
if ((self = [self initWithContentType:nil contentLength:0])) {
self.statusCode = permanent ? 301 : 307;
[self setValue:[location absoluteString] forAdditionalHeader:@"Location"];
}
return self;
}
@end
@implementation GCDWebServerDataResponse
+ (GCDWebServerDataResponse*)responseWithData:(NSData*)data contentType:(NSString*)type {
return [[[[self class] alloc] initWithData:data contentType:type] autorelease];
}
- (id)initWithData:(NSData*)data contentType:(NSString*)type {
if (data == nil) {
DNOT_REACHED();
[self release];
return nil;
}
if ((self = [super initWithContentType:type contentLength:data.length])) {
_data = [data retain];
_offset = -1;
}
return self;
}
- (void)dealloc {
DCHECK(_offset < 0);
[_data release];
[super dealloc];
}
- (BOOL)open {
DCHECK(_offset < 0);
_offset = 0;
return YES;
}
- (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length {
DCHECK(_offset >= 0);
NSInteger size = 0;
if (_offset < _data.length) {
size = MIN(_data.length - _offset, length);
bcopy((char*)_data.bytes + _offset, buffer, size);
_offset += size;
}
return size;
}
- (BOOL)close {
DCHECK(_offset >= 0);
_offset = -1;
return YES;
}
@end
@implementation GCDWebServerDataResponse (Extensions)
+ (GCDWebServerDataResponse*)responseWithText:(NSString*)text {
return [[[self alloc] initWithText:text] autorelease];
}
+ (GCDWebServerDataResponse*)responseWithHTML:(NSString*)html {
return [[[self alloc] initWithHTML:html] autorelease];
}
+ (GCDWebServerDataResponse*)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables {
return [[[self alloc] initWithHTMLTemplate:path variables:variables] autorelease];
}
- (id)initWithText:(NSString*)text {
NSData* data = [text dataUsingEncoding:NSUTF8StringEncoding];
if (data == nil) {
DNOT_REACHED();
[self release];
return nil;
}
return [self initWithData:data contentType:@"text/plain; charset=utf-8"];
}
- (id)initWithHTML:(NSString*)html {
NSData* data = [html dataUsingEncoding:NSUTF8StringEncoding];
if (data == nil) {
DNOT_REACHED();
[self release];
return nil;
}
return [self initWithData:data contentType:@"text/html; charset=utf-8"];
}
- (id)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)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];
[html release];
return response;
}
@end
@implementation GCDWebServerFileResponse
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path {
return [[[[self class] alloc] initWithFile:path] autorelease];
}
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment {
return [[[[self class] alloc] initWithFile:path isAttachment:attachment] autorelease];
}
- (id)initWithFile:(NSString*)path {
return [self initWithFile:path isAttachment:NO];
}
- (id)initWithFile:(NSString*)path isAttachment:(BOOL)attachment {
struct stat info;
if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) {
DNOT_REACHED();
[self release];
return nil;
}
NSString* type = GCDWebServerGetMimeTypeForExtension([path pathExtension]);
if (type == nil) {
type = kGCDWebServerDefaultMimeType;
}
if ((self = [super initWithContentType:type contentLength:info.st_size])) {
_path = [path copy];
if (attachment) { // TODO: Use http://tools.ietf.org/html/rfc5987 to encode file names with special characters instead of using lossy conversion to ISO 8859-1
NSData* data = [[path lastPathComponent] dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:YES];
NSString* fileName = data ? [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding] : nil;
if (fileName) {
[self setValue:[NSString stringWithFormat:@"attachment; filename=\"%@\"", fileName] forAdditionalHeader:@"Content-Disposition"];
[fileName release];
} else {
DNOT_REACHED();
}
}
}
return self;
}
- (void)dealloc {
DCHECK(_file <= 0);
[_path release];
[super dealloc];
}
- (BOOL)open {
DCHECK(_file <= 0);
_file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY);
return (_file > 0 ? YES : NO);
}
- (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length {
DCHECK(_file > 0);
return read(_file, buffer, length);
}
- (BOOL)close {
DCHECK(_file > 0);
int result = close(_file);
_file = 0;
return (result == 0 ? YES : NO);
}
@end

View File

@@ -0,0 +1,52 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// 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"
// 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

@@ -0,0 +1,160 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "GCDWebServer.h"
NS_ASSUME_NONNULL_BEGIN
@class GCDWebDAVServer;
/**
* Delegate methods for GCDWebDAVServer.
*
* @warning These methods are always called on the main thread in a serialized way.
*/
@protocol GCDWebDAVServerDelegate <GCDWebServerDelegate>
@optional
/**
* This method is called whenever a file has been downloaded.
*/
- (void)davServer:(GCDWebDAVServer*)server didDownloadFileAtPath:(NSString*)path;
/**
* This method is called whenever a file has been uploaded.
*/
- (void)davServer:(GCDWebDAVServer*)server didUploadFileAtPath:(NSString*)path;
/**
* This method is called whenever a file or directory has been moved.
*/
- (void)davServer:(GCDWebDAVServer*)server didMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath;
/**
* This method is called whenever a file or directory has been copied.
*/
- (void)davServer:(GCDWebDAVServer*)server didCopyItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath;
/**
* This method is called whenever a file or directory has been deleted.
*/
- (void)davServer:(GCDWebDAVServer*)server didDeleteItemAtPath:(NSString*)path;
/**
* This method is called whenever a directory has been created.
*/
- (void)davServer:(GCDWebDAVServer*)server didCreateDirectoryAtPath:(NSString*)path;
@end
/**
* The GCDWebDAVServer subclass of GCDWebServer implements a class 1 compliant
* WebDAV server. It is also partially class 2 compliant but only when the
* client is the OS X WebDAV implementation (so it can work with the OS X Finder).
*
* See the README.md file for more information about the features of GCDWebDAVServer.
*/
@interface GCDWebDAVServer : GCDWebServer
/**
* Returns the upload directory as specified when the server was initialized.
*/
@property(nonatomic, readonly) NSString* uploadDirectory;
/**
* Sets the delegate for the server.
*/
@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<NSString*>* allowedFileExtensions;
/**
* Sets if files and directories whose name start with a period are allowed to
* be operated on.
*
* The default value is NO.
*/
@property(nonatomic) BOOL allowHiddenItems;
/**
* This method is the designated initializer for the class.
*/
- (instancetype)initWithUploadDirectory:(NSString*)path;
@end
/**
* Hooks to customize the behavior of GCDWebDAVServer.
*
* @warning These methods can be called on any GCD thread.
*/
@interface GCDWebDAVServer (Subclassing)
/**
* This method is called to check if a file upload is allowed to complete.
* The uploaded file is available for inspection at "tempPath".
*
* The default implementation returns YES.
*/
- (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath;
/**
* This method is called to check if a file or directory is allowed to be moved.
*
* The default implementation returns YES.
*/
- (BOOL)shouldMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath;
/**
* This method is called to check if a file or directory is allowed to be copied.
*
* The default implementation returns YES.
*/
- (BOOL)shouldCopyItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath;
/**
* This method is called to check if a file or directory is allowed to be deleted.
*
* The default implementation returns YES.
*/
- (BOOL)shouldDeleteItemAtPath:(NSString*)path;
/**
* This method is called to check if a directory is allowed to be created.
*
* The default implementation returns YES.
*/
- (BOOL)shouldCreateDirectoryAtPath:(NSString*)path;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,712 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebDAVServer requires ARC
#endif
// WebDAV specifications: http://webdav.org/specs/rfc4918.html
// Requires "HEADER_SEARCH_PATHS = $(SDKROOT)/usr/include/libxml2" in Xcode build settings
#import <libxml/parser.h>
#import "GCDWebDAVServer.h"
#import "GCDWebServerFunctions.h"
#import "GCDWebServerDataRequest.h"
#import "GCDWebServerFileRequest.h"
#import "GCDWebServerDataResponse.h"
#import "GCDWebServerErrorResponse.h"
#import "GCDWebServerFileResponse.h"
#define kXMLParseOptions (XML_PARSE_NONET | XML_PARSE_RECOVER | XML_PARSE_NOBLANKS | XML_PARSE_COMPACT | XML_PARSE_NOWARNING | XML_PARSE_NOERROR)
typedef NS_ENUM(NSInteger, DAVProperties) {
kDAVProperty_ResourceType = (1 << 0),
kDAVProperty_CreationDate = (1 << 1),
kDAVProperty_LastModified = (1 << 2),
kDAVProperty_ContentLength = (1 << 3),
kDAVAllProperties = kDAVProperty_ResourceType | kDAVProperty_CreationDate | kDAVProperty_LastModified | kDAVProperty_ContentLength
};
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)
- (BOOL)_checkFileExtension:(NSString*)fileName {
if (_allowedFileExtensions && ![_allowedFileExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
return NO;
}
return YES;
}
static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
NSString* userAgentHeader = [request.headers objectForKey:@"User-Agent"];
return ([userAgentHeader hasPrefix:@"WebDAVFS/"] || [userAgentHeader hasPrefix:@"WebDAVLib/"]); // OS X WebDAV client
}
- (GCDWebServerResponse*)performOPTIONS:(GCDWebServerRequest*)request {
GCDWebServerResponse* response = [GCDWebServerResponse response];
if (_IsMacFinder(request)) {
[response setValue:@"1, 2" forAdditionalHeader:@"DAV"]; // Classes 1 and 2
} else {
[response setValue:@"1" forAdditionalHeader:@"DAV"]; // Class 1
}
return response;
}
- (GCDWebServerResponse*)performGET:(GCDWebServerRequest*)request {
NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
BOOL isDirectory = NO;
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
NSString* itemName = [absolutePath lastPathComponent];
if (([itemName hasPrefix:@"."] && !_allowHiddenItems) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"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];
}
- (GCDWebServerResponse*)performPUT:(GCDWebServerFileRequest*)request {
if ([request hasByteRange]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Range uploads not supported"];
}
NSString* relativePath = request.path;
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:@"."] && !_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];
});
}
return [GCDWebServerResponse responseWithStatusCode:(existing ? kGCDWebServerHTTPStatusCode_NoContent : kGCDWebServerHTTPStatusCode_Created)];
}
- (GCDWebServerResponse*)performDELETE:(GCDWebServerRequest*)request {
NSString* depthHeader = [request.headers objectForKey:@"Depth"];
if (depthHeader && ![depthHeader isEqualToString:@"infinity"]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Unsupported 'Depth' header: %@", depthHeader];
}
NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
BOOL isDirectory = NO;
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
NSString* itemName = [absolutePath lastPathComponent];
if (([itemName hasPrefix:@"."] && !_allowHiddenItems) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting item name \"%@\" is not allowed", itemName];
}
if (![self shouldDeleteItemAtPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting \"%@\" is not permitted", relativePath];
}
NSError* error = nil;
if (![[NSFileManager defaultManager] removeItemAtPath:absolutePath error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed deleting \"%@\"", relativePath];
}
if ([self.delegate respondsToSelector:@selector(davServer:didDeleteItemAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate davServer:self didDeleteItemAtPath:absolutePath];
});
}
return [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_NoContent];
}
- (GCDWebServerResponse*)performMKCOL:(GCDWebServerDataRequest*)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: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 (!_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];
}
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
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]) {
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];
});
}
return [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_Created];
}
- (GCDWebServerResponse*)performCOPY:(GCDWebServerRequest*)request isMove:(BOOL)isMove {
if (!isMove) {
NSString* depthHeader = [request.headers objectForKey:@"Depth"]; // TODO: Support "Depth: 0"
if (depthHeader && ![depthHeader isEqualToString:@"infinity"]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Unsupported 'Depth' header: %@", depthHeader];
}
}
NSString* srcRelativePath = request.path;
NSString* srcAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(srcRelativePath)];
NSString* dstRelativePath = [request.headers objectForKey:@"Destination"];
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];
#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 ((!_allowHiddenItems && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"%@ to item name \"%@\" is not allowed", isMove ? @"Moving" : @"Copying", itemName];
}
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];
}
} else {
if (![self shouldCopyItemFromPath:srcAbsolutePath toPath:dstAbsolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Copying \"%@\" to \"%@\" is not permitted", srcRelativePath, dstRelativePath];
}
}
NSError* error = nil;
if (isMove) {
[[NSFileManager defaultManager] removeItemAtPath:dstAbsolutePath error:NULL];
if (![[NSFileManager defaultManager] moveItemAtPath:srcAbsolutePath toPath:dstAbsolutePath error:&error]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden underlyingError:error message:@"Failed copying \"%@\" to \"%@\"", srcRelativePath, dstRelativePath];
}
} else {
if (![[NSFileManager defaultManager] copyItemAtPath:srcAbsolutePath toPath:dstAbsolutePath error:&error]) {
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(), ^{
[self.delegate davServer:self didMoveItemFromPath:srcAbsolutePath toPath:dstAbsolutePath];
});
}
} else {
if ([self.delegate respondsToSelector:@selector(davServer:didCopyItemFromPath:toPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate davServer:self didCopyItemFromPath:srcAbsolutePath toPath:dstAbsolutePath];
});
}
}
return [GCDWebServerResponse responseWithStatusCode:(existing ? kGCDWebServerHTTPStatusCode_NoContent : kGCDWebServerHTTPStatusCode_Created)];
}
static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name) {
while (child) {
if ((child->type == XML_ELEMENT_NODE) && !xmlStrcmp(child->name, name)) {
return child;
}
child = child->next;
}
return NULL;
}
- (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];
BOOL isFile = [type isEqualToString:NSFileTypeRegular];
BOOL isDirectory = [type isEqualToString:NSFileTypeDirectory];
if ((isFile && [self _checkFileExtension:itemPath]) || isDirectory) {
[xmlString appendString:@"<D:response>"];
[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>"];
} else {
[xmlString appendString:@"<D:resourcetype/>"];
}
}
if ((properties & kDAVProperty_CreationDate) && [attributes objectForKey:NSFileCreationDate]) {
[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((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>"];
[xmlString appendString:@"</D:response>\n"];
}
CFRelease(escapedPath);
} else {
[self logError:@"Failed escaping path: %@", itemPath];
}
}
- (GCDWebServerResponse*)performPROPFIND:(GCDWebServerDataRequest*)request {
NSInteger depth;
NSString* depthHeader = [request.headers objectForKey:@"Depth"];
if ([depthHeader isEqualToString:@"0"]) {
depth = 0;
} else if ([depthHeader isEqualToString:@"1"]) {
depth = 1;
} 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;
xmlDocPtr document = xmlReadMemory(request.data.bytes, (int)request.data.length, NULL, NULL, kXMLParseOptions);
if (document) {
xmlNodePtr rootNode = _XMLChildWithName(document->children, (const xmlChar*)"propfind");
xmlNodePtr allNode = rootNode ? _XMLChildWithName(rootNode->children, (const xmlChar*)"allprop") : NULL;
xmlNodePtr propNode = rootNode ? _XMLChildWithName(rootNode->children, (const xmlChar*)"prop") : NULL;
if (allNode) {
properties = kDAVAllProperties;
} else if (propNode) {
xmlNodePtr node = propNode->children;
while (node) {
if (!xmlStrcmp(node->name, (const xmlChar*)"resourcetype")) {
properties |= kDAVProperty_ResourceType;
} else if (!xmlStrcmp(node->name, (const xmlChar*)"creationdate")) {
properties |= kDAVProperty_CreationDate;
} else if (!xmlStrcmp(node->name, (const xmlChar*)"getlastmodified")) {
properties |= kDAVProperty_LastModified;
} else if (!xmlStrcmp(node->name, (const xmlChar*)"getcontentlength")) {
properties |= kDAVProperty_ContentLength;
} else {
[self logWarning:@"Unknown DAV property requested \"%s\"", node->name];
}
node = node->next;
}
} else {
success = NO;
}
xmlFreeDoc(document);
} else {
success = NO;
}
if (!success) {
NSString* string = [[NSString alloc] initWithData:request.data encoding:NSUTF8StringEncoding];
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Invalid DAV properties:\n%@", string];
}
} else {
properties = kDAVAllProperties;
}
NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
BOOL isDirectory = NO;
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
NSString* itemName = [absolutePath lastPathComponent];
if (([itemName hasPrefix:@"."] && !_allowHiddenItems) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Retrieving properties for item name \"%@\" is not allowed", itemName];
}
NSArray* items = nil;
if (isDirectory) {
NSError* error = nil;
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:@"/"]) {
relativePath = [@"/" stringByAppendingString:relativePath];
}
[self _addPropertyResponseForItem:absolutePath resource:relativePath properties:properties xmlString:xmlString];
if (depth == 1) {
if (![relativePath hasSuffix:@"/"]) {
relativePath = [relativePath stringByAppendingString:@"/"];
}
for (NSString* item in items) {
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:(NSData*)[xmlString dataUsingEncoding:NSUTF8StringEncoding]
contentType:@"application/xml; charset=\"utf-8\""];
response.statusCode = kGCDWebServerHTTPStatusCode_MultiStatus;
return response;
}
- (GCDWebServerResponse*)performLOCK:(GCDWebServerDataRequest*)request {
if (!_IsMacFinder(request)) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_MethodNotAllowed message:@"LOCK method only allowed for Mac Finder"];
}
NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
BOOL isDirectory = NO;
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
NSString* depthHeader = [request.headers objectForKey:@"Depth"];
NSString* timeoutHeader = [request.headers objectForKey:@"Timeout"];
NSString* scope = nil;
NSString* type = nil;
NSString* owner = nil;
NSString* token = nil;
BOOL success = YES;
xmlDocPtr document = xmlReadMemory(request.data.bytes, (int)request.data.length, NULL, NULL, kXMLParseOptions);
if (document) {
xmlNodePtr node = _XMLChildWithName(document->children, (const xmlChar*)"lockinfo");
if (node) {
xmlNodePtr scopeNode = _XMLChildWithName(node->children, (const xmlChar*)"lockscope");
if (scopeNode && scopeNode->children && scopeNode->children->name) {
scope = [NSString stringWithUTF8String:(const char*)scopeNode->children->name];
}
xmlNodePtr typeNode = _XMLChildWithName(node->children, (const xmlChar*)"locktype");
if (typeNode && typeNode->children && typeNode->children->name) {
type = [NSString stringWithUTF8String:(const char*)typeNode->children->name];
}
xmlNodePtr ownerNode = _XMLChildWithName(node->children, (const xmlChar*)"owner");
if (ownerNode) {
ownerNode = _XMLChildWithName(ownerNode->children, (const xmlChar*)"href");
if (ownerNode && ownerNode->children && ownerNode->children->content) {
owner = [NSString stringWithUTF8String:(const char*)ownerNode->children->content];
}
}
} else {
success = NO;
}
xmlFreeDoc(document);
} else {
success = NO;
}
if (!success) {
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 ((!_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) {
token = lockTokenHeader;
}
#endif
if (!token) {
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
CFStringRef string = CFUUIDCreateString(kCFAllocatorDefault, uuid);
token = [NSString stringWithFormat:@"urn:uuid:%@", (__bridge NSString*)string];
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"];
[xmlString appendFormat:@"<D:locktype><D:%@/></D:locktype>\n", type];
[xmlString appendFormat:@"<D:lockscope><D:%@/></D:lockscope>\n", scope];
[xmlString appendFormat:@"<D:depth>%@</D:depth>\n", depthHeader];
if (owner) {
[xmlString appendFormat:@"<D:owner><D:href>%@</D:href></D:owner>\n", owner];
}
if (timeoutHeader) {
[xmlString appendFormat:@"<D:timeout>%@</D:timeout>\n", timeoutHeader];
}
[xmlString appendFormat:@"<D:locktoken><D:href>%@</D:href></D:locktoken>\n", token];
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:(NSData*)[xmlString dataUsingEncoding:NSUTF8StringEncoding]
contentType:@"application/xml; charset=\"utf-8\""];
return response;
}
- (GCDWebServerResponse*)performUNLOCK:(GCDWebServerRequest*)request {
if (!_IsMacFinder(request)) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_MethodNotAllowed message:@"UNLOCK method only allowed for Mac Finder"];
}
NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
BOOL isDirectory = NO;
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
NSString* tokenHeader = [request.headers objectForKey:@"Lock-Token"];
if (!tokenHeader.length) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Missing 'Lock-Token' header"];
}
NSString* itemName = [absolutePath lastPathComponent];
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 (Subclassing)
- (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath {
return YES;
}
- (BOOL)shouldMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath {
return YES;
}
- (BOOL)shouldCopyItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath {
return YES;
}
- (BOOL)shouldDeleteItemAtPath:(NSString*)path {
return YES;
}
- (BOOL)shouldCreateDirectoryAtPath:(NSString*)path {
return YES;
}
@end

52
GCDWebServer.podspec Normal file
View File

@@ -0,0 +1,52 @@
# http://guides.cocoapods.org/syntax/podspec.html
# http://guides.cocoapods.org/making/getting-setup-with-trunk.html
# $ sudo gem update cocoapods
# (optional) $ pod trunk register {email} {name} --description={computer}
# $ pod trunk --verbose push
# DELETE THIS SECTION BEFORE PROCEEDING!
Pod::Spec.new do |s|
s.name = 'GCDWebServer'
s.version = '3.5.2'
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 = '8.0'
s.tvos.deployment_target = '9.0'
s.osx.deployment_target = '10.7'
s.requires_arc = true
s.default_subspec = 'Core'
s.subspec 'Core' do |cs|
cs.source_files = 'GCDWebServer/**/*.{h,m}'
cs.private_header_files = "GCDWebServer/Core/GCDWebServerPrivate.h"
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
s.subspec 'WebDAV' do |cs|
cs.dependency 'GCDWebServer/Core'
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
s.subspec 'WebUploader' do |cs|
cs.dependency 'GCDWebServer/Core'
cs.source_files = 'GCDWebUploader/*.{h,m}'
cs.requires_arc = true
cs.resource = "GCDWebUploader/GCDWebUploader.bundle"
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 = "1010"
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 = "1010"
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 = "1010"
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

@@ -0,0 +1,622 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <TargetConditionals.h>
#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
* been received). The block is passed the basic info for the request (HTTP method,
* URL, headers...) and must decide if it wants to handle it or not.
*
* If the handler can handle the request, the block must return a new
* GCDWebServerRequest instance created with the same basic info.
* Otherwise, it simply returns nil.
*/
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
* received (i.e. the entire HTTP body has been read). The block is passed the
* GCDWebServerRequest created at the previous step by the GCDWebServerMatchBlock.
*
* The block must return a GCDWebServerResponse or nil on error, which will
* result in a 500 HTTP status code returned to the client. It's however
* recommended to return a GCDWebServerErrorResponse on error so more useful
* information can be returned to the client.
*/
typedef GCDWebServerResponse* _Nullable (^GCDWebServerProcessBlock)(__kindof GCDWebServerRequest* request);
/**
* The GCDWebServerAsynchronousProcessBlock works like the GCDWebServerProcessBlock
* except the GCDWebServerResponse can be returned to the server at a later time
* allowing for asynchronous generation of the response.
*
* The block must eventually call "completionBlock" passing a GCDWebServerResponse
* or nil on error, which will result in a 500 HTTP status code returned to the client.
* It's however recommended to return a GCDWebServerErrorResponse on error so more
* useful information can be returned to the client.
*/
typedef void (^GCDWebServerCompletionBlock)(GCDWebServerResponse* _Nullable response);
typedef void (^GCDWebServerAsyncProcessBlock)(__kindof GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock);
/**
* The port used by the GCDWebServer (NSNumber / NSUInteger).
*
* The default value is 0 i.e. let the OS pick a random port.
*/
extern NSString* const GCDWebServerOption_Port;
/**
* The Bonjour name used by the GCDWebServer (NSString). If set to an empty string,
* the name will automatically take the value of the GCDWebServerOption_ServerName
* option. If this option is set to nil, Bonjour will be disabled.
*
* The default value is nil.
*/
extern NSString* const GCDWebServerOption_BonjourName;
/**
* The Bonjour service type used by the GCDWebServer (NSString).
*
* The default value is "_http._tcp", the service type for HTTP web servers.
*/
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 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;
/**
* The maximum number of incoming HTTP requests that can be queued waiting to
* be handled before new ones are dropped (NSNumber / NSUInteger).
*
* The default value is 16.
*/
extern NSString* const GCDWebServerOption_MaxPendingConnections;
/**
* The value for "Server" HTTP header used by the GCDWebServer (NSString).
*
* The default value is the GCDWebServer class name.
*/
extern NSString* const GCDWebServerOption_ServerName;
/**
* The authentication method used by the GCDWebServer
* (one of "GCDWebServerAuthenticationMethod_...").
*
* The default value is nil i.e. authentication is disabled.
*/
extern NSString* const GCDWebServerOption_AuthenticationMethod;
/**
* The authentication realm used by the GCDWebServer (NSString).
*
* The default value is the same as the GCDWebServerOption_ServerName option.
*/
extern NSString* const GCDWebServerOption_AuthenticationRealm;
/**
* The authentication accounts used by the GCDWebServer
* (NSDictionary of username / password pairs).
*
* The default value is nil i.e. no accounts.
*/
extern NSString* const GCDWebServerOption_AuthenticationAccounts;
/**
* The class used by the GCDWebServer when instantiating GCDWebServerConnection
* (subclass of GCDWebServerConnection).
*
* The default value is the GCDWebServerConnection class.
*/
extern NSString* const GCDWebServerOption_ConnectionClass;
/**
* Allow the GCDWebServer to pretend "HEAD" requests are actually "GET" ones
* and automatically discard the HTTP body of the response (NSNumber / BOOL).
*
* The default value is YES.
*/
extern NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET;
/**
* The interval expressed in seconds used by the GCDWebServer to decide how to
* coalesce calls to -webServerDidConnect: and -webServerDidDisconnect:
* (NSNumber / double). Coalescing will be disabled if the interval is <= 0.0.
*
* The default value is 1.0 second.
*/
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
/**
* Enables the GCDWebServer to automatically suspend itself (as if -stop was
* called) when the iOS app goes into the background and the last
* GCDWebServerConnection is closed, then resume itself (as if -start was called)
* when the iOS app comes back to the foreground (NSNumber / BOOL).
*
* See the README.md file for more information about this option.
*
* The default value is YES.
*
* @warning The running property will be NO while the GCDWebServer is suspended.
*/
extern NSString* const GCDWebServerOption_AutomaticallySuspendInBackground;
#endif
/**
* HTTP Basic Authentication scheme (see https://tools.ietf.org/html/rfc2617).
*
* @warning Use of this authentication scheme is not recommended as the
* passwords are sent in clear.
*/
extern NSString* const GCDWebServerAuthenticationMethod_Basic;
/**
* HTTP Digest Access Authentication scheme (see https://tools.ietf.org/html/rfc2617).
*/
extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
@class GCDWebServer;
/**
* Delegate methods for GCDWebServer.
*
* @warning These methods are always called on the main thread in a serialized way.
*/
@protocol GCDWebServerDelegate <NSObject>
@optional
/**
* This method is called after the server has successfully started.
*/
- (void)webServerDidStart:(GCDWebServer*)server;
/**
* 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.
*
* A series of HTTP requests is considered ongoing as long as new HTTP requests
* keep coming (and new GCDWebServerConnection instances keep being opened),
* until before the last HTTP request has been responded to (and the
* corresponding last GCDWebServerConnection closed).
*/
- (void)webServerDidConnect:(GCDWebServer*)server;
/**
* This method is called when the last GCDWebServerConnection is closed after
* the server has served a series of HTTP requests.
*
* The GCDWebServerOption_ConnectedStateCoalescingInterval option can be used
* to have the server wait some extra delay before considering that the series
* of HTTP requests has ended (in case there some latency between consecutive
* requests). This effectively coalesces the calls to -webServerDidConnect:
* and -webServerDidDisconnect:.
*/
- (void)webServerDidDisconnect:(GCDWebServer*)server;
/**
* This method is called after the server has stopped.
*/
- (void)webServerDidStop:(GCDWebServer*)server;
@end
/**
* The GCDWebServer class listens for incoming HTTP requests on a given port,
* then passes each one to a "handler" capable of generating an HTTP response
* for it, which is then sent back to the client.
*
* GCDWebServer instances can be created and used from any thread but it's
* recommended to have the main thread's runloop be running so internal callbacks
* can be handled e.g. for Bonjour registration.
*
* See the README.md file for more information about the architecture of GCDWebServer.
*/
@interface GCDWebServer : NSObject
/**
* Sets the delegate for the server.
*/
@property(nonatomic, weak, nullable) id<GCDWebServerDelegate> delegate;
/**
* Returns YES if the server is currently running.
*/
@property(nonatomic, readonly, getter=isRunning) BOOL running;
/**
* Returns the port used by the server.
*
* @warning This property is only valid if the server is running.
*/
@property(nonatomic, readonly) NSUInteger port;
/**
* Returns the Bonjour name used by the server.
*
* @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, nullable) NSString* bonjourName;
/**
* Returns the Bonjour service type used by the server.
*
* @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, nullable) NSString* bonjourType;
/**
* This method is the designated initializer for the class.
*/
- (instancetype)init;
/**
* Adds to the server a handler that generates responses synchronously when handling incoming HTTP requests.
*
* Handlers are called in a LIFO queue, so if multiple handlers can potentially
* respond to a given request, the latest added one wins.
*
* @warning Addling handlers while the server is running is not allowed.
*/
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;
/**
* Adds to the server a handler that generates responses asynchronously when handling incoming HTTP requests.
*
* Handlers are called in a LIFO queue, so if multiple handlers can potentially
* respond to a given request, the latest added one wins.
*
* @warning Addling handlers while the server is running is not allowed.
*/
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock;
/**
* Removes all handlers previously added to the server.
*
* @warning Removing handlers while the server is running is not allowed.
*/
- (void)removeAllHandlers;
/**
* Starts the server with explicit options. This method is the designated way
* to start the server.
*
* Returns NO if the server failed to start and sets "error" argument if not NULL.
*/
- (BOOL)startWithOptions:(nullable NSDictionary<NSString*, id>*)options error:(NSError** _Nullable)error;
/**
* Stops the server and prevents it to accepts new HTTP requests.
*
* @warning Stopping the server does not abort GCDWebServerConnection instances
* currently handling already received HTTP requests. These connections will
* continue to execute normally until completion.
*/
- (void)stop;
@end
@interface GCDWebServer (Extensions)
/**
* Returns the server's URL.
*
* @warning This property is only valid if the server is running.
*/
@property(nonatomic, readonly, nullable) NSURL* serverURL;
/**
* Returns the server's Bonjour URL.
*
* @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.
* 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, 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)
* using the default Bonjour name.
*
* Returns NO if the server failed to start.
*/
- (BOOL)start;
/**
* Starts the server on a given port and with a specific Bonjour name.
* Pass a nil Bonjour name to disable Bonjour entirely or an empty string to
* use the default name.
*
* Returns NO if the server failed to start.
*/
- (BOOL)startWithPort:(NSUInteger)port bonjourName:(nullable NSString*)name;
#if !TARGET_OS_IPHONE
/**
* Runs the server synchronously using -startWithPort:bonjourName: until a
* SIGINT signal is received i.e. Ctrl-C. This method is intended to be used
* by command line tools.
*
* Returns NO if the server failed to start.
*
* @warning This method must be used from the main thread only.
*/
- (BOOL)runWithPort:(NSUInteger)port bonjourName:(nullable NSString*)name;
/**
* Runs the server synchronously using -startWithOptions: until a SIGTERM or
* SIGINT signal is received i.e. Ctrl-C in Terminal. This method is intended to
* be used by command line tools.
*
* Returns NO if the server failed to start and sets "error" argument if not NULL.
*
* @warning This method must be used from the main thread only.
*/
- (BOOL)runWithOptions:(nullable NSDictionary<NSString*, id>*)options error:(NSError** _Nullable)error;
#endif
@end
@interface GCDWebServer (Handlers)
/**
* Adds a default handler to the server to handle all incoming HTTP requests
* with a given HTTP method and generate responses synchronously.
*/
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
/**
* Adds a default handler to the server to handle all incoming HTTP requests
* with a given HTTP method and generate responses asynchronously.
*/
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block;
/**
* Adds a handler to the server to handle incoming HTTP requests with a given
* HTTP method and a specific case-insensitive path and generate responses
* synchronously.
*/
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
/**
* Adds a handler to the server to handle incoming HTTP requests with a given
* HTTP method and a specific case-insensitive path and generate responses
* asynchronously.
*/
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block;
/**
* Adds a handler to the server to handle incoming HTTP requests with a given
* HTTP method and a path matching a case-insensitive regular expression and
* generate responses synchronously.
*/
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
/**
* Adds a handler to the server to handle incoming HTTP requests with a given
* HTTP method and a path matching a case-insensitive regular expression and
* generate responses asynchronously.
*/
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block;
@end
@interface GCDWebServer (GETHandlers)
/**
* 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:(nullable NSString*)contentType cacheAge:(NSUInteger)cacheAge;
/**
* Adds a handler to the server to respond to incoming "GET" HTTP requests
* with a specific case-insensitive path with a file.
*/
- (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests;
/**
* Adds a handler to the server to respond to incoming "GET" HTTP requests
* with a case-insensitive path inside a base path with the corresponding file
* inside a local directory. If no local file matches the request path, a 401
* HTTP status code is returned to the client.
*
* 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:(nullable NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests;
@end
/**
* GCDWebServer provides its own built-in logging facility which is used by
* default. It simply sends log messages to stderr assuming it is connected
* to a terminal type device.
*
* GCDWebServer is also compatible with a limited set of third-party logging
* facilities. If one of them is available at compile time, GCDWebServer will
* automatically use it in place of the built-in one.
*
* Currently supported third-party logging facilities are:
* - XLFacility (by the same author as GCDWebServer): https://github.com/swisspol/XLFacility
*
* 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
* settings to the name of a custom header file (escaped like \"MyLogging.h\").
* This header file must define the following set of macros:
*
* GWS_LOG_DEBUG(...)
* GWS_LOG_VERBOSE(...)
* GWS_LOG_INFO(...)
* GWS_LOG_WARNING(...)
* GWS_LOG_ERROR(...)
*
* 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
* with GCDWebServer in your code (e.g. in the implementation of handlers).
*/
@interface GCDWebServer (Logging)
/**
* Sets the log level of the logging facility below which log messages are discarded.
*
* @warning The interpretation of the "level" argument depends on the logging
* facility used at compile time.
*
* If using the built-in logging facility, the log levels are as follow:
* DEBUG = 0
* VERBOSE = 1
* INFO = 2
* WARNING = 3
* ERROR = 4
*/
+ (void)setLogLevel:(int)level;
/**
* Logs a message to the logging facility at the VERBOSE level.
*/
- (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);
/**
* Logs a message to the logging facility at the WARNING level.
*/
- (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);
@end
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
@interface GCDWebServer (Testing)
/**
* Activates recording of HTTP requests and responses which create files in the
* current directory containing the raw data for all requests and responses.
*
* @warning The current directory must not contain any prior recording files.
*/
@property(nonatomic, getter=isRecordingEnabled) BOOL recordingEnabled;
/**
* Runs tests by playing back pre-recorded HTTP requests in the given directory
* and comparing the generated responses with the pre-recorded ones.
*
* Returns the number of failed tests or -1 if server failed to start.
*/
- (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

@@ -0,0 +1,183 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "GCDWebServer.h"
NS_ASSUME_NONNULL_BEGIN
@class GCDWebServerHandler;
/**
* The GCDWebServerConnection class is instantiated by GCDWebServer to handle
* each new HTTP connection. Each instance stays alive until the connection is
* closed.
*
* You cannot use this class directly, but it is made public so you can
* subclass it to override some hooks. Use the GCDWebServerOption_ConnectionClass
* option for GCDWebServer to install your custom subclass.
*
* @warning The GCDWebServerConnection retains the GCDWebServer until the
* connection is closed.
*/
@interface GCDWebServerConnection : NSObject
/**
* Returns the GCDWebServer that owns the connection.
*/
@property(nonatomic, readonly) GCDWebServer* server;
/**
* Returns YES if the connection is using IPv6.
*/
@property(nonatomic, readonly, getter=isUsingIPv6) BOOL usingIPv6;
/**
* Returns the address of the local peer (i.e. server) of the connection
* as a raw "struct sockaddr".
*/
@property(nonatomic, readonly) NSData* localAddressData;
/**
* Returns the address of the local peer (i.e. server) of the connection
* as a string.
*/
@property(nonatomic, readonly) NSString* localAddressString;
/**
* Returns the address of the remote peer (i.e. client) of the connection
* as a raw "struct sockaddr".
*/
@property(nonatomic, readonly) NSData* remoteAddressData;
/**
* Returns the address of the remote peer (i.e. client) of the connection
* as a string.
*/
@property(nonatomic, readonly) NSString* remoteAddressString;
/**
* Returns the total number of bytes received from the remote peer (i.e. client)
* so far.
*/
@property(nonatomic, readonly) NSUInteger totalBytesRead;
/**
* Returns the total number of bytes sent to the remote peer (i.e. client) so far.
*/
@property(nonatomic, readonly) NSUInteger totalBytesWritten;
@end
/**
* Hooks to customize the behavior of GCDWebServer HTTP connections.
*
* @warning These methods can be called on any GCD thread.
* Be sure to also call "super" when overriding them.
*/
@interface GCDWebServerConnection (Subclassing)
/**
* This method is called when the connection is opened.
*
* Return NO to reject the connection e.g. after validating the local
* or remote address.
*/
- (BOOL)open;
/**
* This method is called whenever data has been received
* from the remote peer (i.e. client).
*
* @warning Do not attempt to modify this data.
*/
- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length;
/**
* This method is called whenever data has been sent
* to the remote peer (i.e. client).
*
* @warning Do not attempt to modify this data.
*/
- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length;
/**
* This method is called after the HTTP headers have been received to
* allow replacing the request URL by another one.
*
* The default implementation returns the original URL.
*/
- (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary<NSString*, NSString*>*)headers;
/**
* Assuming a valid HTTP request was received, this method is called before
* the request is processed.
*
* Return a non-nil GCDWebServerResponse to bypass the request processing entirely.
*
* The default implementation checks for HTTP authentication if applicable
* and returns a barebone 401 status code response if authentication failed.
*/
- (nullable GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request;
/**
* Assuming a valid HTTP request was received and -preflightRequest: returned nil,
* this method is called to process the request by executing the handler's
* process block.
*/
- (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion;
/**
* Assuming a valid HTTP request was received and either -preflightRequest:
* or -processRequest:completion: returned a non-nil GCDWebServerResponse,
* this method is called to override the response.
*
* You can either modify the current response and return it, or return a
* completely new one.
*
* The default implementation replaces any response matching the "ETag" or
* "Last-Modified-Date" header of the request by a barebone "Not-Modified" (304)
* one.
*/
- (GCDWebServerResponse*)overrideResponse:(GCDWebServerResponse*)response forRequest:(GCDWebServerRequest*)request;
/**
* This method is called if any error happens while validing or processing
* the request or if no GCDWebServerResponse was generated during processing.
*
* @warning If the request was invalid (e.g. the HTTP headers were malformed),
* the "request" argument will be nil.
*/
- (void)abortRequest:(nullable GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode;
/**
* Called when the connection is closed.
*/
- (void)close;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,843 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif
#import <TargetConditionals.h>
#import <netdb.h>
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
#import <libkern/OSAtomic.h>
#endif
#import "GCDWebServerPrivate.h"
#define kHeadersReadCapacity (1 * 1024)
#define kBodyReadCapacity (256 * 1024)
typedef void (^ReadDataCompletionBlock)(BOOL success);
typedef void (^ReadHeadersCompletionBlock)(NSData* extraData);
typedef void (^ReadBodyCompletionBlock)(BOOL success);
typedef void (^WriteDataCompletionBlock)(BOOL success);
typedef void (^WriteHeadersCompletionBlock)(BOOL success);
typedef void (^WriteBodyCompletionBlock)(BOOL success);
static NSData* _CRLFData = nil;
static NSData* _CRLFCRLFData = nil;
static NSData* _continueData = nil;
static NSData* _lastChunkData = nil;
static NSString* _digestAuthenticationNonce = nil;
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
static int32_t _connectionCounter = 0;
#endif
NS_ASSUME_NONNULL_BEGIN
@interface GCDWebServerConnection (Read)
- (void)readData:(NSMutableData*)data withLength:(NSUInteger)length completionBlock:(ReadDataCompletionBlock)block;
- (void)readHeaders:(NSMutableData*)headersData withCompletionBlock:(ReadHeadersCompletionBlock)block;
- (void)readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block;
- (void)readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block;
@end
@interface GCDWebServerConnection (Write)
- (void)writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block;
- (void)writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block;
- (void)writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block;
@end
NS_ASSUME_NONNULL_END
@implementation GCDWebServerConnection {
CFSocketNativeHandle _socket;
BOOL _virtualHEAD;
CFHTTPMessageRef _requestMessage;
GCDWebServerRequest* _request;
GCDWebServerHandler* _handler;
CFHTTPMessageRef _responseMessage;
GCDWebServerResponse* _response;
NSInteger _statusCode;
BOOL _opened;
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
NSUInteger _connectionIndex;
NSString* _requestPath;
int _requestFD;
NSString* _responsePath;
int _responseFD;
#endif
}
+ (void)initialize {
if (_CRLFData == nil) {
_CRLFData = [[NSData alloc] initWithBytes:"\r\n" length:2];
GWS_DCHECK(_CRLFData);
}
if (_CRLFCRLFData == nil) {
_CRLFCRLFData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4];
GWS_DCHECK(_CRLFCRLFData);
}
if (_continueData == nil) {
CFHTTPMessageRef message = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 100, NULL, kCFHTTPVersion1_1);
_continueData = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(message));
CFRelease(message);
GWS_DCHECK(_continueData);
}
if (_lastChunkData == nil) {
_lastChunkData = [[NSData alloc] initWithBytes:"0\r\n\r\n" length:5];
}
if (_digestAuthenticationNonce == nil) {
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
_digestAuthenticationNonce = GCDWebServerComputeMD5Digest(@"%@", CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid)));
CFRelease(uuid);
}
}
- (BOOL)isUsingIPv6 {
const struct sockaddr* localSockAddr = _localAddressData.bytes;
return (localSockAddr->sa_family == AF_INET6);
}
- (void)_initializeResponseHeadersWithStatusCode:(NSInteger)statusCode {
_statusCode = statusCode;
_responseMessage = CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_1);
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Connection"), CFSTR("Close"));
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Server"), (__bridge CFStringRef)_server.serverName);
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Date"), (__bridge CFStringRef)GCDWebServerFormatRFC822([NSDate date]));
}
- (void)_startProcessingRequest {
GWS_DCHECK(_responseMessage == NULL);
GCDWebServerResponse* preflightResponse = [self preflightRequest:_request];
if (preflightResponse) {
[self _finishProcessingRequest:preflightResponse];
} else {
[self processRequest:_request
completion:^(GCDWebServerResponse* processResponse) {
[self _finishProcessingRequest:processResponse];
}];
}
}
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
- (void)_finishProcessingRequest:(GCDWebServerResponse*)response {
GWS_DCHECK(_responseMessage == NULL);
BOOL hasBody = NO;
if (response) {
response = [self overrideResponse:response forRequest:_request];
}
if (response) {
if ([response hasBody]) {
[response prepareForReading];
hasBody = !_virtualHEAD;
}
NSError* error = nil;
if (hasBody && ![response performOpen:&error]) {
GWS_LOG_ERROR(@"Failed opening response body for socket %i: %@", _socket, error);
} else {
_response = response;
}
}
if (_response) {
[self _initializeResponseHeadersWithStatusCode:_response.statusCode];
if (_response.lastModifiedDate) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Last-Modified"), (__bridge CFStringRef)GCDWebServerFormatRFC822((NSDate*)_response.lastModifiedDate));
}
if (_response.eTag) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("ETag"), (__bridge CFStringRef)_response.eTag);
}
if ((_response.statusCode >= 200) && (_response.statusCode < 300)) {
if (_response.cacheControlMaxAge > 0) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), (__bridge CFStringRef)[NSString stringWithFormat:@"max-age=%i, public", (int)_response.cacheControlMaxAge]);
} else {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), CFSTR("no-cache"));
}
}
if (_response.contentType != nil) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Type"), (__bridge CFStringRef)GCDWebServerNormalizeHeaderValue(_response.contentType));
}
if (_response.contentLength != NSUIntegerMax) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Length"), (__bridge CFStringRef)[NSString stringWithFormat:@"%lu", (unsigned long)_response.contentLength]);
}
if (_response.usesChunkedTransferEncoding) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Transfer-Encoding"), CFSTR("chunked"));
}
[_response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) {
CFHTTPMessageSetHeaderFieldValue(self->_responseMessage, (__bridge CFStringRef)key, (__bridge CFStringRef)obj);
}];
[self writeHeadersWithCompletionBlock:^(BOOL success) {
if (success) {
if (hasBody) {
[self writeBodyWithCompletionBlock:^(BOOL successInner) {
[self->_response performClose]; // TODO: There's nothing we can do on failure as headers have already been sent
}];
}
} else if (hasBody) {
[self->_response performClose];
}
}];
} else {
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
}
}
- (void)_readBodyWithLength:(NSUInteger)length initialData:(NSData*)initialData {
NSError* error = nil;
if (![_request performOpen:&error]) {
GWS_LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error);
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
return;
}
if (initialData.length) {
if (![_request performWriteData:initialData error:&error]) {
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
if (![_request performClose:&error]) {
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
}
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
return;
}
length -= initialData.length;
}
if (length) {
[self readBodyWithRemainingLength:length
completionBlock:^(BOOL success) {
NSError* localError = nil;
if ([self->_request performClose:&localError]) {
[self _startProcessingRequest];
} else {
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", self->_socket, error);
[self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
}
}];
} else {
if ([_request performClose:&error]) {
[self _startProcessingRequest];
} else {
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
}
}
}
- (void)_readChunkedBodyWithInitialData:(NSData*)initialData {
NSError* error = nil;
if (![_request performOpen:&error]) {
GWS_LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error);
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
return;
}
NSMutableData* chunkData = [[NSMutableData alloc] initWithData:initialData];
[self readNextBodyChunk:chunkData
completionBlock:^(BOOL success) {
NSError* localError = nil;
if ([self->_request performClose:&localError]) {
[self _startProcessingRequest];
} else {
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", self->_socket, error);
[self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
}
}];
}
- (void)_readRequestHeaders {
_requestMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true);
NSMutableData* headersData = [[NSMutableData alloc] initWithCapacity:kHeadersReadCapacity];
[self readHeaders:headersData
withCompletionBlock:^(NSData* extraData) {
if (extraData) {
NSString* requestMethod = CFBridgingRelease(CFHTTPMessageCopyRequestMethod(self->_requestMessage)); // Method verbs are case-sensitive and uppercase
if (self->_server.shouldAutomaticallyMapHEADToGET && [requestMethod isEqualToString:@"HEAD"]) {
requestMethod = @"GET";
self->_virtualHEAD = YES;
}
NSDictionary* requestHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(self->_requestMessage)); // Header names are case-insensitive but CFHTTPMessageCopyAllHeaderFields() will standardize the common ones
NSURL* requestURL = CFBridgingRelease(CFHTTPMessageCopyRequestURL(self->_requestMessage));
if (requestURL) {
requestURL = [self rewriteRequestURL:requestURL withMethod:requestMethod headers:requestHeaders];
GWS_DCHECK(requestURL);
}
NSString* urlPath = requestURL ? CFBridgingRelease(CFURLCopyPath((CFURLRef)requestURL)) : nil; // Don't use -[NSURL path] which strips the ending slash
if (urlPath == nil) {
urlPath = @"/"; // CFURLCopyPath() returns NULL for a relative URL with path "//" contrary to -[NSURL path] which returns "/"
}
NSString* requestPath = urlPath ? GCDWebServerUnescapeURLString(urlPath) : nil;
NSString* queryString = requestURL ? CFBridgingRelease(CFURLCopyQueryString((CFURLRef)requestURL, NULL)) : nil; // Don't use -[NSURL query] to make sure query is not unescaped;
NSDictionary* requestQuery = queryString ? GCDWebServerParseURLEncodedForm(queryString) : @{};
if (requestMethod && requestURL && requestHeaders && requestPath && requestQuery) {
for (self->_handler in self->_server.handlers) {
self->_request = self->_handler.matchBlock(requestMethod, requestURL, requestHeaders, requestPath, requestQuery);
if (self->_request) {
break;
}
}
if (self->_request) {
self->_request.localAddressData = self.localAddressData;
self->_request.remoteAddressData = self.remoteAddressData;
if ([self->_request hasBody]) {
[self->_request prepareForWriting];
if (self->_request.usesChunkedTransferEncoding || (extraData.length <= self->_request.contentLength)) {
NSString* expectHeader = [requestHeaders objectForKey:@"Expect"];
if (expectHeader) {
if ([expectHeader caseInsensitiveCompare:@"100-continue"] == NSOrderedSame) { // TODO: Actually validate request before continuing
[self writeData:_continueData
withCompletionBlock:^(BOOL success) {
if (success) {
if (self->_request.usesChunkedTransferEncoding) {
[self _readChunkedBodyWithInitialData:extraData];
} else {
[self _readBodyWithLength:self->_request.contentLength initialData:extraData];
}
}
}];
} else {
GWS_LOG_ERROR(@"Unsupported 'Expect' / 'Content-Length' header combination on socket %i", self->_socket);
[self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_ExpectationFailed];
}
} else {
if (self->_request.usesChunkedTransferEncoding) {
[self _readChunkedBodyWithInitialData:extraData];
} else {
[self _readBodyWithLength:self->_request.contentLength initialData:extraData];
}
}
} else {
GWS_LOG_ERROR(@"Unexpected 'Content-Length' header value on socket %i", self->_socket);
[self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_BadRequest];
}
} else {
[self _startProcessingRequest];
}
} else {
self->_request = [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:requestPath query:requestQuery];
GWS_DCHECK(self->_request);
[self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_NotImplemented];
}
} else {
[self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
GWS_DNOT_REACHED();
}
} else {
[self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
}
}];
}
- (instancetype)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket {
if ((self = [super init])) {
_server = server;
_localAddressData = localAddress;
_remoteAddressData = remoteAddress;
_socket = socket;
GWS_LOG_DEBUG(@"Did open connection on socket %i", _socket);
[_server willStartConnection:self];
if (![self open]) {
close(_socket);
return nil;
}
_opened = YES;
[self _readRequestHeaders];
}
return self;
}
- (NSString*)localAddressString {
return GCDWebServerStringFromSockAddr(_localAddressData.bytes, YES);
}
- (NSString*)remoteAddressString {
return GCDWebServerStringFromSockAddr(_remoteAddressData.bytes, YES);
}
- (void)dealloc {
int result = close(_socket);
if (result != 0) {
GWS_LOG_ERROR(@"Failed closing socket %i for connection: %s (%i)", _socket, strerror(errno), errno);
} else {
GWS_LOG_DEBUG(@"Did close connection on socket %i", _socket);
}
if (_opened) {
[self close];
}
[_server didEndConnection:self];
if (_requestMessage) {
CFRelease(_requestMessage);
}
if (_responseMessage) {
CFRelease(_responseMessage);
}
}
@end
@implementation GCDWebServerConnection (Read)
- (void)readData:(NSMutableData*)data withLength:(NSUInteger)length completionBlock:(ReadDataCompletionBlock)block {
dispatch_read(_socket, length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t buffer, int error) {
@autoreleasepool {
if (error == 0) {
size_t size = dispatch_data_get_size(buffer);
if (size > 0) {
NSUInteger originalLength = data.length;
dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t chunkOffset, const void* chunkBytes, size_t chunkSize) {
[data appendBytes:chunkBytes length:chunkSize];
return true;
});
[self didReadBytes:((char*)data.bytes + originalLength) length:(data.length - originalLength)];
block(YES);
} else {
if (self->_totalBytesRead > 0) {
GWS_LOG_ERROR(@"No more data available on socket %i", self->_socket);
} else {
GWS_LOG_WARNING(@"No data received from socket %i", self->_socket);
}
block(NO);
}
} else {
GWS_LOG_ERROR(@"Error while reading from socket %i: %s (%i)", self->_socket, strerror(error), error);
block(NO);
}
}
});
}
- (void)readHeaders:(NSMutableData*)headersData withCompletionBlock:(ReadHeadersCompletionBlock)block {
GWS_DCHECK(_requestMessage);
[self readData:headersData
withLength:NSUIntegerMax
completionBlock:^(BOOL success) {
if (success) {
NSRange range = [headersData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(0, headersData.length)];
if (range.location == NSNotFound) {
[self readHeaders:headersData withCompletionBlock:block];
} else {
NSUInteger length = range.location + range.length;
if (CFHTTPMessageAppendBytes(self->_requestMessage, headersData.bytes, length)) {
if (CFHTTPMessageIsHeaderComplete(self->_requestMessage)) {
block([headersData subdataWithRange:NSMakeRange(length, headersData.length - length)]);
} else {
GWS_LOG_ERROR(@"Failed parsing request headers from socket %i", self->_socket);
block(nil);
}
} else {
GWS_LOG_ERROR(@"Failed appending request headers data from socket %i", self->_socket);
block(nil);
}
}
} else {
block(nil);
}
}];
}
- (void)readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block {
GWS_DCHECK([_request hasBody] && ![_request usesChunkedTransferEncoding]);
NSMutableData* bodyData = [[NSMutableData alloc] initWithCapacity:kBodyReadCapacity];
[self readData:bodyData
withLength:length
completionBlock:^(BOOL success) {
if (success) {
if (bodyData.length <= length) {
NSError* error = nil;
if ([self->_request performWriteData:bodyData error:&error]) {
NSUInteger remainingLength = length - bodyData.length;
if (remainingLength) {
[self readBodyWithRemainingLength:remainingLength completionBlock:block];
} else {
block(YES);
}
} else {
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", self->_socket, error);
block(NO);
}
} else {
GWS_LOG_ERROR(@"Unexpected extra content reading request body on socket %i", self->_socket);
block(NO);
GWS_DNOT_REACHED();
}
} else {
block(NO);
}
}];
}
static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
char buffer[size + 1];
bcopy(bytes, buffer, size);
buffer[size] = 0;
char* end = NULL;
long result = strtol(buffer, &end, 16);
return ((end != NULL) && (*end == 0) && (result >= 0) ? result : NSNotFound);
}
- (void)readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block {
GWS_DCHECK([_request hasBody] && [_request usesChunkedTransferEncoding]);
while (1) {
NSRange range = [chunkData rangeOfData:_CRLFData options:0 range:NSMakeRange(0, chunkData.length)];
if (range.location == NSNotFound) {
break;
}
NSRange extensionRange = [chunkData rangeOfData:[NSData dataWithBytes:";" length:1] options:0 range:NSMakeRange(0, range.location)]; // Ignore chunk extensions
NSUInteger length = _ScanHexNumber((char*)chunkData.bytes, extensionRange.location != NSNotFound ? extensionRange.location : range.location);
if (length != NSNotFound) {
if (length) {
if (chunkData.length < range.location + range.length + length + 2) {
break;
}
const char* ptr = (char*)chunkData.bytes + range.location + range.length + length;
if ((*ptr == '\r') && (*(ptr + 1) == '\n')) {
NSError* error = nil;
if ([_request performWriteData:[chunkData subdataWithRange:NSMakeRange(range.location + range.length, length)] error:&error]) {
[chunkData replaceBytesInRange:NSMakeRange(0, range.location + range.length + length + 2) withBytes:NULL length:0];
} else {
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
block(NO);
return;
}
} else {
GWS_LOG_ERROR(@"Missing terminating CRLF sequence for chunk reading request body on socket %i", _socket);
block(NO);
return;
}
} else {
NSRange trailerRange = [chunkData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(range.location, chunkData.length - range.location)]; // Ignore trailers
if (trailerRange.location != NSNotFound) {
block(YES);
return;
}
}
} else {
GWS_LOG_ERROR(@"Invalid chunk length reading request body on socket %i", _socket);
block(NO);
return;
}
}
[self readData:chunkData
withLength:NSUIntegerMax
completionBlock:^(BOOL success) {
if (success) {
[self readNextBodyChunk:chunkData completionBlock:block];
} else {
block(NO);
}
}];
}
@end
@implementation GCDWebServerConnection (Write)
- (void)writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block {
dispatch_data_t buffer = dispatch_data_create(data.bytes, data.length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^{
[data self]; // Keeps ARC from releasing data too early
});
dispatch_write(_socket, buffer, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t remainingData, int error) {
@autoreleasepool {
if (error == 0) {
GWS_DCHECK(remainingData == NULL);
[self didWriteBytes:data.bytes length:data.length];
block(YES);
} else {
GWS_LOG_ERROR(@"Error while writing to socket %i: %s (%i)", self->_socket, strerror(error), error);
block(NO);
}
}
});
#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
dispatch_release(buffer);
#endif
}
- (void)writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block {
GWS_DCHECK(_responseMessage);
CFDataRef data = CFHTTPMessageCopySerializedMessage(_responseMessage);
[self writeData:(__bridge NSData*)data withCompletionBlock:block];
CFRelease(data);
}
- (void)writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block {
GWS_DCHECK([_response hasBody]);
[_response performReadDataWithCompletion:^(NSData* data, NSError* error) {
if (data) {
if (data.length) {
if (self->_response.usesChunkedTransferEncoding) {
const char* hexString = [[NSString stringWithFormat:@"%lx", (unsigned long)data.length] UTF8String];
size_t hexLength = strlen(hexString);
NSData* chunk = [NSMutableData dataWithLength:(hexLength + 2 + data.length + 2)];
if (chunk == nil) {
GWS_LOG_ERROR(@"Failed allocating memory for response body chunk for socket %i: %@", self->_socket, error);
block(NO);
return;
}
char* ptr = (char*)[(NSMutableData*)chunk mutableBytes];
bcopy(hexString, ptr, hexLength);
ptr += hexLength;
*ptr++ = '\r';
*ptr++ = '\n';
bcopy(data.bytes, ptr, data.length);
ptr += data.length;
*ptr++ = '\r';
*ptr = '\n';
data = chunk;
}
[self writeData:data
withCompletionBlock:^(BOOL success) {
if (success) {
[self writeBodyWithCompletionBlock:block];
} else {
block(NO);
}
}];
} else {
if (self->_response.usesChunkedTransferEncoding) {
[self writeData:_lastChunkData
withCompletionBlock:^(BOOL success) {
block(success);
}];
} else {
block(YES);
}
}
} else {
GWS_LOG_ERROR(@"Failed reading response body for socket %i: %@", self->_socket, error);
block(NO);
}
}];
}
@end
@implementation GCDWebServerConnection (Subclassing)
- (BOOL)open {
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
if (_server.recordingEnabled) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
_connectionIndex = OSAtomicIncrement32(&_connectionCounter);
#pragma clang diagnostic pop
_requestPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
_requestFD = open([_requestPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
GWS_DCHECK(_requestFD > 0);
_responsePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
_responseFD = open([_responsePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
GWS_DCHECK(_responseFD > 0);
}
#endif
return YES;
}
- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length {
GWS_LOG_DEBUG(@"Connection received %lu bytes on socket %i", (unsigned long)length, _socket);
_totalBytesRead += length;
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
if ((_requestFD > 0) && (write(_requestFD, bytes, length) != (ssize_t)length)) {
GWS_LOG_ERROR(@"Failed recording request data: %s (%i)", strerror(errno), errno);
close(_requestFD);
_requestFD = 0;
}
#endif
}
- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length {
GWS_LOG_DEBUG(@"Connection sent %lu bytes on socket %i", (unsigned long)length, _socket);
_totalBytesWritten += length;
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
if ((_responseFD > 0) && (write(_responseFD, bytes, length) != (ssize_t)length)) {
GWS_LOG_ERROR(@"Failed recording response data: %s (%i)", strerror(errno), errno);
close(_responseFD);
_responseFD = 0;
}
#endif
}
- (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary<NSString*, NSString*>*)headers {
return url;
}
// https://tools.ietf.org/html/rfc2617
- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request {
GWS_LOG_DEBUG(@"Connection on socket %i preflighting request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_totalBytesRead);
GCDWebServerResponse* response = nil;
if (_server.authenticationBasicAccounts) {
__block BOOL authenticated = NO;
NSString* authorizationHeader = [request.headers objectForKey:@"Authorization"];
if ([authorizationHeader hasPrefix:@"Basic "]) {
NSString* basicAccount = [authorizationHeader substringFromIndex:6];
[_server.authenticationBasicAccounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* digest, BOOL* stop) {
if ([basicAccount isEqualToString:digest]) {
authenticated = YES;
*stop = YES;
}
}];
}
if (!authenticated) {
response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_Unauthorized];
[response setValue:[NSString stringWithFormat:@"Basic realm=\"%@\"", _server.authenticationRealm] forAdditionalHeader:@"WWW-Authenticate"];
}
} else if (_server.authenticationDigestAccounts) {
BOOL authenticated = NO;
BOOL isStaled = NO;
NSString* authorizationHeader = [request.headers objectForKey:@"Authorization"];
if ([authorizationHeader hasPrefix:@"Digest "]) {
NSString* realm = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"realm");
if (realm && [_server.authenticationRealm isEqualToString:realm]) {
NSString* nonce = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"nonce");
if ([nonce isEqualToString:_digestAuthenticationNonce]) {
NSString* username = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"username");
NSString* uri = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"uri");
NSString* actualResponse = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"response");
NSString* ha1 = [_server.authenticationDigestAccounts objectForKey:username];
NSString* ha2 = GCDWebServerComputeMD5Digest(@"%@:%@", request.method, uri); // We cannot use "request.path" as the query string is required
NSString* expectedResponse = GCDWebServerComputeMD5Digest(@"%@:%@:%@", ha1, _digestAuthenticationNonce, ha2);
if ([actualResponse isEqualToString:expectedResponse]) {
authenticated = YES;
}
} else if (nonce.length) {
isStaled = YES;
}
}
}
if (!authenticated) {
response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_Unauthorized];
[response setValue:[NSString stringWithFormat:@"Digest realm=\"%@\", nonce=\"%@\"%@", _server.authenticationRealm, _digestAuthenticationNonce, isStaled ? @", stale=TRUE" : @""] forAdditionalHeader:@"WWW-Authenticate"]; // TODO: Support Quality of Protection ("qop")
}
}
return response;
}
- (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion {
GWS_LOG_DEBUG(@"Connection on socket %i processing request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_totalBytesRead);
_handler.asyncProcessBlock(request, [completion copy]);
}
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
static inline BOOL _CompareResources(NSString* responseETag, NSString* requestETag, NSDate* responseLastModified, NSDate* requestLastModified) {
if (requestLastModified && responseLastModified) {
if ([responseLastModified compare:requestLastModified] != NSOrderedDescending) {
return YES;
}
}
if (requestETag && responseETag) { // Per the specs "If-None-Match" must be checked after "If-Modified-Since"
if ([requestETag isEqualToString:@"*"]) {
return YES;
}
if ([responseETag isEqualToString:requestETag]) {
return YES;
}
}
return NO;
}
- (GCDWebServerResponse*)overrideResponse:(GCDWebServerResponse*)response forRequest:(GCDWebServerRequest*)request {
if ((response.statusCode >= 200) && (response.statusCode < 300) && _CompareResources(response.eTag, request.ifNoneMatch, response.lastModifiedDate, request.ifModifiedSince)) {
NSInteger code = [request.method isEqualToString:@"HEAD"] || [request.method isEqualToString:@"GET"] ? kGCDWebServerHTTPStatusCode_NotModified : kGCDWebServerHTTPStatusCode_PreconditionFailed;
GCDWebServerResponse* newResponse = [GCDWebServerResponse responseWithStatusCode:code];
newResponse.cacheControlMaxAge = response.cacheControlMaxAge;
newResponse.lastModifiedDate = response.lastModifiedDate;
newResponse.eTag = response.eTag;
GWS_DCHECK(newResponse);
return newResponse;
}
return response;
}
- (void)abortRequest:(GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode {
GWS_DCHECK(_responseMessage == NULL);
GWS_DCHECK((statusCode >= 400) && (statusCode < 600));
[self _initializeResponseHeadersWithStatusCode:statusCode];
[self writeHeadersWithCompletionBlock:^(BOOL success) {
; // Nothing more to do
}];
GWS_LOG_DEBUG(@"Connection aborted with status code %i on socket %i", (int)statusCode, _socket);
}
- (void)close {
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
if (_requestPath) {
BOOL success = NO;
NSError* error = nil;
if (_requestFD > 0) {
close(_requestFD);
NSString* name = [NSString stringWithFormat:@"%03lu-%@.request", (unsigned long)_connectionIndex, _virtualHEAD ? @"HEAD" : _request.method];
success = [[NSFileManager defaultManager] moveItemAtPath:_requestPath toPath:[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:name] error:&error];
}
if (!success) {
GWS_LOG_ERROR(@"Failed saving recorded request: %@", error);
GWS_DNOT_REACHED();
}
unlink([_requestPath fileSystemRepresentation]);
}
if (_responsePath) {
BOOL success = NO;
NSError* error = nil;
if (_responseFD > 0) {
close(_responseFD);
NSString* name = [NSString stringWithFormat:@"%03lu-%i.response", (unsigned long)_connectionIndex, (int)_statusCode];
success = [[NSFileManager defaultManager] moveItemAtPath:_responsePath toPath:[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:name] error:&error];
}
if (!success) {
GWS_LOG_ERROR(@"Failed saving recorded response: %@", error);
GWS_DNOT_REACHED();
}
unlink([_responsePath fileSystemRepresentation]);
}
#endif
if (_request) {
GWS_LOG_VERBOSE(@"[%@] %@ %i \"%@ %@\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_totalBytesRead, (unsigned long)_totalBytesWritten);
} else {
GWS_LOG_VERBOSE(@"[%@] %@ %i \"(invalid request)\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, (unsigned long)_totalBytesRead, (unsigned long)_totalBytesWritten);
}
}
@end

View File

@@ -0,0 +1,114 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#ifdef __cplusplus
extern "C" {
#endif
/**
* 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, 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* _Nullable GCDWebServerEscapeURLString(NSString* string);
/**
* Unescapes a URL percent-encoded 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<NSString*, NSString*>* GCDWebServerParseURLEncodedForm(NSString* form);
/**
* On OS X, returns the IPv4 or IPv6 address as a string of the primary
* connected service or nil if not available.
*
* On iOS, returns the IPv4 or IPv6 address as a string of the WiFi
* interface if connected or nil otherwise.
*/
NSString* _Nullable GCDWebServerGetPrimaryIPAddress(BOOL useIPv6);
/**
* Converts a date into a string using RFC822 formatting.
* https://tools.ietf.org/html/rfc822#section-5
* https://tools.ietf.org/html/rfc1123#section-5.2.14
*/
NSString* GCDWebServerFormatRFC822(NSDate* date);
/**
* Converts a RFC822 formatted string into a date.
* https://tools.ietf.org/html/rfc822#section-5
* https://tools.ietf.org/html/rfc1123#section-5.2.14
*
* @warning Timezones other than GMT are not supported by this function.
*/
NSDate* _Nullable GCDWebServerParseRFC822(NSString* string);
/**
* Converts a date into a string using IOS 8601 formatting.
* http://tools.ietf.org/html/rfc3339#section-5.6
*/
NSString* GCDWebServerFormatISO8601(NSDate* date);
/**
* Converts a ISO 8601 formatted string into a date.
* http://tools.ietf.org/html/rfc3339#section-5.6
*
* @warning Only "calendar" variant is supported at this time and timezones
* other than GMT are not supported either.
*/
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

@@ -0,0 +1,331 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif
#import <TargetConditionals.h>
#if TARGET_OS_IPHONE
#import <MobileCoreServices/MobileCoreServices.h>
#else
#import <SystemConfiguration/SystemConfiguration.h>
#endif
#import <CommonCrypto/CommonDigest.h>
#import <ifaddrs.h>
#import <net/if.h>
#import <netdb.h>
#import "GCDWebServerPrivate.h"
static NSDateFormatter* _dateFormatterRFC822 = nil;
static NSDateFormatter* _dateFormatterISO8601 = nil;
static dispatch_queue_t _dateFormatterQueue = NULL;
// TODO: Handle RFC 850 and ANSI C's asctime() format
void GCDWebServerInitializeFunctions() {
GWS_DCHECK([NSThread isMainThread]); // NSDateFormatter should be initialized on main thread
if (_dateFormatterRFC822 == nil) {
_dateFormatterRFC822 = [[NSDateFormatter alloc] init];
_dateFormatterRFC822.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
_dateFormatterRFC822.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
_dateFormatterRFC822.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
GWS_DCHECK(_dateFormatterRFC822);
}
if (_dateFormatterISO8601 == nil) {
_dateFormatterISO8601 = [[NSDateFormatter alloc] init];
_dateFormatterISO8601.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
_dateFormatterISO8601.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'+00:00'";
_dateFormatterISO8601.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
GWS_DCHECK(_dateFormatterISO8601);
}
if (_dateFormatterQueue == NULL) {
_dateFormatterQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
GWS_DCHECK(_dateFormatterQueue);
}
}
NSString* GCDWebServerNormalizeHeaderValue(NSString* value) {
if (value) {
NSRange range = [value rangeOfString:@";"]; // Assume part before ";" separator is case-insensitive
if (range.location != NSNotFound) {
value = [[[value substringToIndex:range.location] lowercaseString] stringByAppendingString:[value substringFromIndex:range.location]];
} else {
value = [value lowercaseString];
}
}
return value;
}
NSString* GCDWebServerTruncateHeaderValue(NSString* 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;
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;
}
// http://www.w3schools.com/tags/ref_charactersets.asp
NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset) {
NSStringEncoding encoding = kCFStringEncodingInvalidId;
if (charset) {
encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)charset));
}
return (encoding != kCFStringEncodingInvalidId ? encoding : NSUTF8StringEncoding);
}
NSString* GCDWebServerFormatRFC822(NSDate* date) {
__block NSString* string;
dispatch_sync(_dateFormatterQueue, ^{
string = [_dateFormatterRFC822 stringFromDate:date];
});
return string;
}
NSDate* GCDWebServerParseRFC822(NSString* string) {
__block NSDate* date;
dispatch_sync(_dateFormatterQueue, ^{
date = [_dateFormatterRFC822 dateFromString:string];
});
return date;
}
NSString* GCDWebServerFormatISO8601(NSDate* date) {
__block NSString* string;
dispatch_sync(_dateFormatterQueue, ^{
string = [_dateFormatterISO8601 stringFromDate:date];
});
return string;
}
NSDate* GCDWebServerParseISO8601(NSString* string) {
__block NSDate* date;
dispatch_sync(_dateFormatterQueue, ^{
date = [_dateFormatterISO8601 dateFromString:string];
});
return date;
}
BOOL GCDWebServerIsTextContentType(NSString* type) {
return ([type hasPrefix:@"text/"] || [type hasPrefix:@"application/json"] || [type hasPrefix:@"application/xml"]);
}
NSString* GCDWebServerDescribeData(NSData* data, NSString* type) {
if (GCDWebServerIsTextContentType(type)) {
NSString* charset = GCDWebServerExtractHeaderValueParameter(type, @"charset");
NSString* string = [[NSString alloc] initWithData:data encoding:GCDWebServerStringEncodingFromCharset(charset)];
if (string) {
return string;
}
}
return [NSString stringWithFormat:@"<%lu bytes>", (unsigned long)data.length];
}
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];
if (mimeType == nil) {
mimeType = [builtInOverrides objectForKey:extension];
}
if (mimeType == nil) {
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
if (uti) {
mimeType = CFBridgingRelease(UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType));
CFRelease(uti);
}
}
}
return mimeType ? mimeType : kGCDWebServerDefaultMimeType;
}
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<NSString*, NSString*>* GCDWebServerParseURLEncodedForm(NSString* form) {
NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
NSScanner* scanner = [[NSScanner alloc] initWithString:form];
[scanner setCharactersToBeSkipped:nil];
while (1) {
NSString* key = nil;
if (![scanner scanUpToString:@"=" intoString:&key] || [scanner isAtEnd]) {
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:@" "];
NSString* unescapedValue = value ? GCDWebServerUnescapeURLString(value) : nil;
if (unescapedKey && unescapedValue) {
[parameters setObject:unescapedValue forKey:unescapedKey];
} else {
GWS_LOG_WARNING(@"Failed parsing URL encoded form for key \"%@\" and value \"%@\"", key, value);
GWS_DNOT_REACHED();
}
if ([scanner isAtEnd]) {
break;
}
[scanner setScanLocation:([scanner scanLocation] + 1)];
}
return parameters;
}
NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) {
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) {
#if DEBUG
GWS_DNOT_REACHED();
#else
return @"";
#endif
}
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 && !TARGET_OS_TV
const char* primaryInterface = "en0"; // WiFi interface on iOS
#endif
#else
const char* primaryInterface = NULL;
SCDynamicStoreRef store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("GCDWebServer"), NULL, NULL);
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) {
NSString* interface = [(__bridge NSDictionary*)info objectForKey:@"PrimaryInterface"];
if (interface) {
primaryInterface = [[NSString stringWithString:interface] UTF8String]; // Copy string to auto-release pool
}
CFRelease(info);
}
CFRelease(store);
}
if (primaryInterface == NULL) {
primaryInterface = "lo0";
}
#endif
struct ifaddrs* list;
if (getifaddrs(&list) >= 0) {
for (struct ifaddrs* ifap = list; ifap; ifap = ifap->ifa_next) {
#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
{
continue;
}
if ((ifap->ifa_flags & IFF_UP) && ((!useIPv6 && (ifap->ifa_addr->sa_family == AF_INET)) || (useIPv6 && (ifap->ifa_addr->sa_family == AF_INET6)))) {
address = GCDWebServerStringFromSockAddr(ifap->ifa_addr, NO);
break;
}
}
freeifaddrs(list);
}
return address;
}
NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) {
va_list arguments;
va_start(arguments, format);
const char* string = [[[NSString alloc] initWithFormat:format arguments:arguments] UTF8String];
va_end(arguments);
unsigned char md5[CC_MD5_DIGEST_LENGTH];
CC_MD5(string, (CC_LONG)strlen(string), md5);
char buffer[2 * CC_MD5_DIGEST_LENGTH + 1];
for (int i = 0; i < CC_MD5_DIGEST_LENGTH; ++i) {
unsigned char byte = md5[i];
unsigned char byteHi = (byte & 0xF0) >> 4;
buffer[2 * i + 0] = byteHi >= 10 ? 'a' + byteHi - 10 : '0' + byteHi;
unsigned char byteLo = byte & 0x0F;
buffer[2 * i + 1] = byteLo >= 10 ? 'a' + byteLo - 10 : '0' + byteLo;
}
buffer[2 * CC_MD5_DIGEST_LENGTH] = 0;
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

@@ -0,0 +1,116 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
// http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
#import <Foundation/Foundation.h>
/**
* Convenience constants for "informational" HTTP status codes.
*/
typedef NS_ENUM(NSInteger, GCDWebServerInformationalHTTPStatusCode) {
kGCDWebServerHTTPStatusCode_Continue = 100,
kGCDWebServerHTTPStatusCode_SwitchingProtocols = 101,
kGCDWebServerHTTPStatusCode_Processing = 102
};
/**
* Convenience constants for "successful" HTTP status codes.
*/
typedef NS_ENUM(NSInteger, GCDWebServerSuccessfulHTTPStatusCode) {
kGCDWebServerHTTPStatusCode_OK = 200,
kGCDWebServerHTTPStatusCode_Created = 201,
kGCDWebServerHTTPStatusCode_Accepted = 202,
kGCDWebServerHTTPStatusCode_NonAuthoritativeInformation = 203,
kGCDWebServerHTTPStatusCode_NoContent = 204,
kGCDWebServerHTTPStatusCode_ResetContent = 205,
kGCDWebServerHTTPStatusCode_PartialContent = 206,
kGCDWebServerHTTPStatusCode_MultiStatus = 207,
kGCDWebServerHTTPStatusCode_AlreadyReported = 208
};
/**
* Convenience constants for "redirection" HTTP status codes.
*/
typedef NS_ENUM(NSInteger, GCDWebServerRedirectionHTTPStatusCode) {
kGCDWebServerHTTPStatusCode_MultipleChoices = 300,
kGCDWebServerHTTPStatusCode_MovedPermanently = 301,
kGCDWebServerHTTPStatusCode_Found = 302,
kGCDWebServerHTTPStatusCode_SeeOther = 303,
kGCDWebServerHTTPStatusCode_NotModified = 304,
kGCDWebServerHTTPStatusCode_UseProxy = 305,
kGCDWebServerHTTPStatusCode_TemporaryRedirect = 307,
kGCDWebServerHTTPStatusCode_PermanentRedirect = 308
};
/**
* Convenience constants for "client error" HTTP status codes.
*/
typedef NS_ENUM(NSInteger, GCDWebServerClientErrorHTTPStatusCode) {
kGCDWebServerHTTPStatusCode_BadRequest = 400,
kGCDWebServerHTTPStatusCode_Unauthorized = 401,
kGCDWebServerHTTPStatusCode_PaymentRequired = 402,
kGCDWebServerHTTPStatusCode_Forbidden = 403,
kGCDWebServerHTTPStatusCode_NotFound = 404,
kGCDWebServerHTTPStatusCode_MethodNotAllowed = 405,
kGCDWebServerHTTPStatusCode_NotAcceptable = 406,
kGCDWebServerHTTPStatusCode_ProxyAuthenticationRequired = 407,
kGCDWebServerHTTPStatusCode_RequestTimeout = 408,
kGCDWebServerHTTPStatusCode_Conflict = 409,
kGCDWebServerHTTPStatusCode_Gone = 410,
kGCDWebServerHTTPStatusCode_LengthRequired = 411,
kGCDWebServerHTTPStatusCode_PreconditionFailed = 412,
kGCDWebServerHTTPStatusCode_RequestEntityTooLarge = 413,
kGCDWebServerHTTPStatusCode_RequestURITooLong = 414,
kGCDWebServerHTTPStatusCode_UnsupportedMediaType = 415,
kGCDWebServerHTTPStatusCode_RequestedRangeNotSatisfiable = 416,
kGCDWebServerHTTPStatusCode_ExpectationFailed = 417,
kGCDWebServerHTTPStatusCode_UnprocessableEntity = 422,
kGCDWebServerHTTPStatusCode_Locked = 423,
kGCDWebServerHTTPStatusCode_FailedDependency = 424,
kGCDWebServerHTTPStatusCode_UpgradeRequired = 426,
kGCDWebServerHTTPStatusCode_PreconditionRequired = 428,
kGCDWebServerHTTPStatusCode_TooManyRequests = 429,
kGCDWebServerHTTPStatusCode_RequestHeaderFieldsTooLarge = 431
};
/**
* Convenience constants for "server error" HTTP status codes.
*/
typedef NS_ENUM(NSInteger, GCDWebServerServerErrorHTTPStatusCode) {
kGCDWebServerHTTPStatusCode_InternalServerError = 500,
kGCDWebServerHTTPStatusCode_NotImplemented = 501,
kGCDWebServerHTTPStatusCode_BadGateway = 502,
kGCDWebServerHTTPStatusCode_ServiceUnavailable = 503,
kGCDWebServerHTTPStatusCode_GatewayTimeout = 504,
kGCDWebServerHTTPStatusCode_HTTPVersionNotSupported = 505,
kGCDWebServerHTTPStatusCode_InsufficientStorage = 507,
kGCDWebServerHTTPStatusCode_LoopDetected = 508,
kGCDWebServerHTTPStatusCode_NotExtended = 510,
kGCDWebServerHTTPStatusCode_NetworkAuthenticationRequired = 511
};

View File

@@ -0,0 +1,224 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <os/object.h>
#import <sys/socket.h>
/**
* All GCDWebServer headers.
*/
#import "GCDWebServerHTTPStatusCodes.h"
#import "GCDWebServerFunctions.h"
#import "GCDWebServer.h"
#import "GCDWebServerConnection.h"
#import "GCDWebServerDataRequest.h"
#import "GCDWebServerFileRequest.h"
#import "GCDWebServerMultiPartFormRequest.h"
#import "GCDWebServerURLEncodedFormRequest.h"
#import "GCDWebServerDataResponse.h"
#import "GCDWebServerErrorResponse.h"
#import "GCDWebServerFileResponse.h"
#import "GCDWebServerStreamedResponse.h"
/**
* Check if a custom logging facility should be used instead.
*/
#if defined(__GCDWEBSERVER_LOGGING_HEADER__)
#define __GCDWEBSERVER_LOGGING_FACILITY_CUSTOM__
#import __GCDWEBSERVER_LOGGING_HEADER__
/**
* Automatically detect if XLFacility is available and if so use it as a
* logging facility.
*/
#elif defined(__has_include) && __has_include("XLFacilityMacros.h")
#define __GCDWEBSERVER_LOGGING_FACILITY_XLFACILITY__
#undef XLOG_TAG
#define XLOG_TAG @"gcdwebserver.internal"
#import "XLFacilityMacros.h"
#define GWS_LOG_DEBUG(...) XLOG_DEBUG(__VA_ARGS__)
#define GWS_LOG_VERBOSE(...) XLOG_VERBOSE(__VA_ARGS__)
#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_DCHECK(__CONDITION__) XLOG_DEBUG_CHECK(__CONDITION__)
#define GWS_DNOT_REACHED() XLOG_DEBUG_UNREACHABLE()
/**
* If all of the above fail, then use GCDWebServer built-in
* logging facility.
*/
#else
#define __GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__
typedef NS_ENUM(int, GCDWebServerLoggingLevel) {
kGCDWebServerLoggingLevel_Debug = 0,
kGCDWebServerLoggingLevel_Verbose,
kGCDWebServerLoggingLevel_Info,
kGCDWebServerLoggingLevel_Warning,
kGCDWebServerLoggingLevel_Error
};
extern GCDWebServerLoggingLevel GCDWebServerLogLevel;
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)
#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)
#endif
/**
* Consistency check macros used when building Debug only.
*/
#if !defined(GWS_DCHECK) || !defined(GWS_DNOT_REACHED)
#if DEBUG
#define GWS_DCHECK(__CONDITION__) \
do { \
if (!(__CONDITION__)) { \
abort(); \
} \
} while (0)
#define GWS_DNOT_REACHED() abort()
#else
#define GWS_DCHECK(__CONDITION__)
#define GWS_DNOT_REACHED()
#endif
#endif
NS_ASSUME_NONNULL_BEGIN
/**
* GCDWebServer internal constants and APIs.
*/
#define kGCDWebServerDefaultMimeType @"application/octet-stream"
#define kGCDWebServerErrorDomain @"GCDWebServerErrorDomain"
static inline BOOL GCDWebServerIsValidByteRange(NSRange range) {
return ((range.location != NSUIntegerMax) || (range.length > 0));
}
static inline NSError* GCDWebServerMakePosixError(int code) {
return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : (NSString*)[NSString stringWithUTF8String:strerror(code)]}];
}
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* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService);
@interface GCDWebServerConnection ()
- (instancetype)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket;
@end
@interface GCDWebServer ()
@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
@interface GCDWebServerHandler : NSObject
@property(nonatomic, readonly) GCDWebServerMatchBlock matchBlock;
@property(nonatomic, readonly) GCDWebServerAsyncProcessBlock asyncProcessBlock;
@end
@interface GCDWebServerRequest ()
@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
@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:(nullable id)attribute forKey:(NSString*)key;
@end
@interface GCDWebServerResponse ()
@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

@@ -0,0 +1,210 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#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.
*
* @warning This attribute will only be set on the request if adding a handler using
* -addHandlerForMethod:pathRegex:requestClass:processBlock:.
*/
extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
/**
* This protocol is used by the GCDWebServerConnection to communicate with
* the GCDWebServerRequest and write the received HTTP body data.
*
* Note that multiple GCDWebServerBodyWriter objects can be chained together
* internally e.g. to automatically decode gzip encoded content before
* passing it on to the GCDWebServerRequest.
*
* @warning These methods can be called on any GCD thread.
*/
@protocol GCDWebServerBodyWriter <NSObject>
/**
* This method is called before any body data is received.
*
* It should return YES on success or NO on failure and set the "error" argument
* which is guaranteed to be non-NULL.
*/
- (BOOL)open:(NSError**)error;
/**
* This method is called whenever body data has been received.
*
* It should return YES on success or NO on failure and set the "error" argument
* which is guaranteed to be non-NULL.
*/
- (BOOL)writeData:(NSData*)data error:(NSError**)error;
/**
* This method is called after all body data has been received.
*
* It should return YES on success or NO on failure and set the "error" argument
* which is guaranteed to be non-NULL.
*/
- (BOOL)close:(NSError**)error;
@end
/**
* The GCDWebServerRequest class is instantiated by the GCDWebServerConnection
* after the HTTP headers have been received. Each instance wraps a single HTTP
* request. If a body is present, the methods from the GCDWebServerBodyWriter
* protocol will be called by the GCDWebServerConnection to receive it.
*
* The default implementation of the GCDWebServerBodyWriter protocol on the class
* simply ignores the body data.
*
* @warning GCDWebServerRequest instances can be created and used on any GCD thread.
*/
@interface GCDWebServerRequest : NSObject <GCDWebServerBodyWriter>
/**
* Returns the HTTP method for the request.
*/
@property(nonatomic, readonly) NSString* method;
/**
* Returns the URL for the request.
*/
@property(nonatomic, readonly) NSURL* URL;
/**
* Returns the HTTP headers for the request.
*/
@property(nonatomic, readonly) NSDictionary<NSString*, NSString*>* headers;
/**
* Returns the path component of the URL for the request.
*/
@property(nonatomic, readonly) NSString* path;
/**
* Returns the parsed and unescaped query component of the URL for the request.
*
* @warning This property will be nil if there is no query in the URL.
*/
@property(nonatomic, readonly, nullable) NSDictionary<NSString*, NSString*>* query;
/**
* Returns the content type for the body of the request parsed from the
* "Content-Type" header.
*
* This property will be nil if the request has no body or set to
* "application/octet-stream" if a body is present but there was no
* "Content-Type" header.
*/
@property(nonatomic, readonly, nullable) NSString* contentType;
/**
* Returns the content length for the body of the request parsed from the
* "Content-Length" header.
*
* This property will be set to "NSUIntegerMax" if the request has no body or
* if there is a body but no "Content-Length" header, typically because
* chunked transfer encoding is used.
*/
@property(nonatomic, readonly) NSUInteger contentLength;
/**
* Returns the parsed "If-Modified-Since" header or nil if absent or malformed.
*/
@property(nonatomic, readonly, nullable) NSDate* ifModifiedSince;
/**
* Returns the parsed "If-None-Match" header or nil if absent or malformed.
*/
@property(nonatomic, readonly, nullable) NSString* ifNoneMatch;
/**
* Returns the parsed "Range" header or (NSUIntegerMax, 0) if absent or malformed.
* The range will be set to (offset, length) if expressed from the beginning
* of the entity body, or (NSUIntegerMax, length) if expressed from its end.
*/
@property(nonatomic, readonly) NSRange byteRange;
/**
* Returns YES if the client supports gzip content encoding according to the
* "Accept-Encoding" header.
*/
@property(nonatomic, readonly) BOOL acceptsGzipContentEncoding;
/**
* Returns the address of the local peer (i.e. server) for the request
* as a raw "struct sockaddr".
*/
@property(nonatomic, readonly) NSData* localAddressData;
/**
* Returns the address of the local peer (i.e. server) for the request
* as a string.
*/
@property(nonatomic, readonly) NSString* localAddressString;
/**
* Returns the address of the remote peer (i.e. client) for the request
* as a raw "struct sockaddr".
*/
@property(nonatomic, readonly) NSData* remoteAddressData;
/**
* Returns the address of the remote peer (i.e. client) for the request
* as a string.
*/
@property(nonatomic, readonly) NSString* remoteAddressString;
/**
* This method is the designated initializer for the class.
*/
- (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.
*/
- (BOOL)hasBody;
/**
* Convenience method that checks if the byteRange property is defined.
*/
- (BOOL)hasByteRange;
/**
* Retrieves an attribute associated with this request using the given key.
*
* @return The attribute value for the key.
*/
- (nullable id)attributeForKey:(NSString*)key;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,303 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif
#import <zlib.h>
#import "GCDWebServerPrivate.h"
NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerRequestAttribute_RegexCaptures";
#define kZlibErrorDomain @"ZlibErrorDomain"
#define kGZipInitialBufferSize (256 * 1024)
@interface GCDWebServerBodyDecoder : NSObject <GCDWebServerBodyWriter>
@end
@interface GCDWebServerGZipDecoder : GCDWebServerBodyDecoder
@end
@implementation GCDWebServerBodyDecoder {
GCDWebServerRequest* __unsafe_unretained _request;
id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
}
- (instancetype)initWithRequest:(GCDWebServerRequest* _Nonnull)request writer:(id<GCDWebServerBodyWriter> _Nonnull)writer {
if ((self = [super init])) {
_request = request;
_writer = writer;
}
return self;
}
- (BOOL)open:(NSError**)error {
return [_writer open:error];
}
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
return [_writer writeData:data error:error];
}
- (BOOL)close:(NSError**)error {
return [_writer close:error];
}
@end
@implementation GCDWebServerGZipDecoder {
z_stream _stream;
BOOL _finished;
}
- (BOOL)open:(NSError**)error {
int result = inflateInit2(&_stream, 15 + 16);
if (result != Z_OK) {
if (error) {
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
}
return NO;
}
if (![super open:error]) {
inflateEnd(&_stream);
return NO;
}
return YES;
}
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
GWS_DCHECK(!_finished);
_stream.next_in = (Bytef*)data.bytes;
_stream.avail_in = (uInt)data.length;
NSMutableData* decodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize];
if (decodedData == nil) {
GWS_DNOT_REACHED();
return NO;
}
NSUInteger length = 0;
while (1) {
NSUInteger maxLength = decodedData.length - length;
_stream.next_out = (Bytef*)((char*)decodedData.mutableBytes + length);
_stream.avail_out = (uInt)maxLength;
int result = inflate(&_stream, Z_NO_FLUSH);
if ((result != Z_OK) && (result != Z_STREAM_END)) {
if (error) {
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
}
return NO;
}
length += maxLength - _stream.avail_out;
if (_stream.avail_out > 0) {
if (result == Z_STREAM_END) {
_finished = YES;
}
break;
}
decodedData.length = 2 * decodedData.length; // zlib has used all the output buffer so resize it and try again in case more data is available
}
decodedData.length = length;
BOOL success = length ? [super writeData:decodedData error:error] : YES; // No need to call writer if we have no data yet
return success;
}
- (BOOL)close:(NSError**)error {
GWS_DCHECK(_finished);
inflateEnd(&_stream);
return [super close:error];
}
@end
@implementation GCDWebServerRequest {
BOOL _opened;
NSMutableArray<GCDWebServerBodyDecoder*>* _decoders;
id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
NSMutableDictionary<NSString*, id>* _attributes;
}
- (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;
_headers = headers;
_path = [path copy];
_query = query;
_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 (_usesChunkedTransferEncoding || (length < 0)) {
GWS_LOG_WARNING(@"Invalid 'Content-Length' header '%@' for '%@' request on \"%@\"", lengthHeader, _method, _URL);
GWS_DNOT_REACHED();
return nil;
}
_contentLength = length;
if (_contentType == nil) {
_contentType = kGCDWebServerDefaultMimeType;
}
} else if (_usesChunkedTransferEncoding) {
if (_contentType == nil) {
_contentType = kGCDWebServerDefaultMimeType;
}
_contentLength = NSUIntegerMax;
} else {
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
}
_contentLength = NSUIntegerMax;
}
NSString* modifiedHeader = [_headers objectForKey:@"If-Modified-Since"];
if (modifiedHeader) {
_ifModifiedSince = [GCDWebServerParseRFC822(modifiedHeader) copy];
}
_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 = [(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"
_byteRange.location = startValue;
_byteRange.length = endValue - startValue + 1;
} else if (startString.length && (startValue >= 0)) { // The bytes after 9500 bytes: "9500-"
_byteRange.location = startValue;
_byteRange.length = NSUIntegerMax;
} else if (endString.length && (endValue > 0)) { // The final 500 bytes: "-500"
_byteRange.location = NSUIntegerMax;
_byteRange.length = endValue;
}
}
}
}
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) {
_acceptsGzipContentEncoding = YES;
}
_decoders = [[NSMutableArray alloc] init];
_attributes = [[NSMutableDictionary alloc] init];
}
return self;
}
- (BOOL)hasBody {
return _contentType ? YES : NO;
}
- (BOOL)hasByteRange {
return GCDWebServerIsValidByteRange(_byteRange);
}
- (id)attributeForKey:(NSString*)key {
return [_attributes objectForKey:key];
}
- (BOOL)open:(NSError**)error {
return YES;
}
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
return YES;
}
- (BOOL)close:(NSError**)error {
return YES;
}
- (void)prepareForWriting {
_writer = self;
if ([GCDWebServerNormalizeHeaderValue([self.headers objectForKey:@"Content-Encoding"]) isEqualToString:@"gzip"]) {
GCDWebServerGZipDecoder* decoder = [[GCDWebServerGZipDecoder alloc] initWithRequest:self writer:_writer];
[_decoders addObject:decoder];
_writer = decoder;
}
}
- (BOOL)performOpen:(NSError**)error {
GWS_DCHECK(_contentType);
GWS_DCHECK(_writer);
if (_opened) {
GWS_DNOT_REACHED();
return NO;
}
_opened = YES;
return [_writer open:error];
}
- (BOOL)performWriteData:(NSData*)data error:(NSError**)error {
GWS_DCHECK(_opened);
return [_writer writeData:data error:error];
}
- (BOOL)performClose:(NSError**)error {
GWS_DCHECK(_opened);
return [_writer close:error];
}
- (void)setAttribute:(id)attribute forKey:(NSString*)key {
[_attributes setValue:attribute forKey:key];
}
- (NSString*)localAddressString {
return GCDWebServerStringFromSockAddr(_localAddressData.bytes, YES);
}
- (NSString*)remoteAddressString {
return GCDWebServerStringFromSockAddr(_remoteAddressData.bytes, YES);
}
- (NSString*)description {
NSMutableString* description = [NSMutableString stringWithFormat:@"%@ %@", _method, _path];
for (NSString* argument in [[_query allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
[description appendFormat:@"\n %@ = %@", argument, [_query objectForKey:argument]];
}
[description appendString:@"\n"];
for (NSString* header in [[_headers allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
[description appendFormat:@"\n%@: %@", header, [_headers objectForKey:header]];
}
return description;
}
@end

View File

@@ -0,0 +1,212 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#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* _Nullable error);
/**
* This protocol is used by the GCDWebServerConnection to communicate with
* the GCDWebServerResponse and read the HTTP body data to send.
*
* Note that multiple GCDWebServerBodyReader objects can be chained together
* internally e.g. to automatically apply gzip encoding to the content before
* passing it on to the GCDWebServerResponse.
*
* @warning These methods can be called on any GCD thread.
*/
@protocol GCDWebServerBodyReader <NSObject>
@required
/**
* This method is called before any body data is sent.
*
* It should return YES on success or NO on failure and set the "error" argument
* which is guaranteed to be non-NULL.
*/
- (BOOL)open:(NSError**)error;
/**
* This method is called whenever body data is sent.
*
* It should return a non-empty NSData if there is body data available,
* 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.
*/
- (nullable NSData*)readData:(NSError**)error;
/**
* This method is called after all body data has been sent.
*/
- (void)close;
@optional
/**
* If this method is implemented, it will be preferred over -readData:.
*
* It must call the passed block when data is available, passing a non-empty
* NSData if there is body data available, or an empty NSData there is no more
* body data, or nil on error and pass an NSError along.
*/
- (void)asyncReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block;
@end
/**
* The GCDWebServerResponse class is used to wrap a single HTTP response.
* It is instantiated by the handler of the GCDWebServer that handled the request.
* If a body is present, the methods from the GCDWebServerBodyReader protocol
* will be called by the GCDWebServerConnection to send it.
*
* The default implementation of the GCDWebServerBodyReader protocol
* on the class simply returns an empty body.
*
* @warning GCDWebServerResponse instances can be created and used on any GCD thread.
*/
@interface GCDWebServerResponse : NSObject <GCDWebServerBodyReader>
/**
* Sets the content type for the body of the response.
*
* The default value is nil i.e. the response has no body.
*
* @warning This property must be set if a body is present.
*/
@property(nonatomic, copy, nullable) NSString* contentType;
/**
* Sets the content length for the body of the response. If a body is present
* but this property is set to "NSUIntegerMax", this means the length of the body
* cannot be known ahead of time. Chunked transfer encoding will be
* automatically enabled by the GCDWebServerConnection to comply with HTTP/1.1
* specifications.
*
* The default value is "NSUIntegerMax" i.e. the response has no body or its length
* is undefined.
*/
@property(nonatomic) NSUInteger contentLength;
/**
* Sets the HTTP status code for the response.
*
* The default value is 200 i.e. "OK".
*/
@property(nonatomic) NSInteger statusCode;
/**
* Sets the caching hint for the response using the "Cache-Control" header.
* This value is expressed in seconds.
*
* The default value is 0 i.e. "no-cache".
*/
@property(nonatomic) NSUInteger cacheControlMaxAge;
/**
* Sets the last modified date for the response using the "Last-Modified" header.
*
* The default value is nil.
*/
@property(nonatomic, nullable) NSDate* lastModifiedDate;
/**
* Sets the ETag for the response using the "ETag" header.
*
* The default value is nil.
*/
@property(nonatomic, copy, nullable) NSString* eTag;
/**
* Enables gzip encoding for the response body.
*
* The default value is NO.
*
* @warning Enabling gzip encoding will remove any "Content-Length" header
* since the length of the body is not known anymore. The client will still
* be able to determine the body length when connection is closed per
* HTTP/1.1 specifications.
*/
@property(nonatomic, getter=isGZipContentEncodingEnabled) BOOL gzipContentEncodingEnabled;
/**
* Creates an empty response.
*/
+ (instancetype)response;
/**
* This method is the designated initializer for the class.
*/
- (instancetype)init;
/**
* Sets an additional HTTP header on the response.
* Pass a nil value to remove an additional header.
*
* @warning Do not attempt to override the primary headers used
* by GCDWebServerResponse like "Content-Type", "ETag", etc...
*/
- (void)setValue:(nullable NSString*)value forAdditionalHeader:(NSString*)header;
/**
* Convenience method that checks if the contentType property is defined.
*/
- (BOOL)hasBody;
@end
@interface GCDWebServerResponse (Extensions)
/**
* Creates a empty response with a specific HTTP status code.
*/
+ (instancetype)responseWithStatusCode:(NSInteger)statusCode;
/**
* Creates an HTTP redirect response to a new URL.
*/
+ (instancetype)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent;
/**
* Initializes an empty response with a specific HTTP status code.
*/
- (instancetype)initWithStatusCode:(NSInteger)statusCode;
/**
* Initializes an HTTP redirect response to a new URL.
*/
- (instancetype)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,284 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif
#import <zlib.h>
#import "GCDWebServerPrivate.h"
#define kZlibErrorDomain @"ZlibErrorDomain"
#define kGZipInitialBufferSize (256 * 1024)
@interface GCDWebServerBodyEncoder : NSObject <GCDWebServerBodyReader>
@end
@interface GCDWebServerGZipEncoder : GCDWebServerBodyEncoder
@end
@implementation GCDWebServerBodyEncoder {
GCDWebServerResponse* __unsafe_unretained _response;
id<GCDWebServerBodyReader> __unsafe_unretained _reader;
}
- (instancetype)initWithResponse:(GCDWebServerResponse* _Nonnull)response reader:(id<GCDWebServerBodyReader> _Nonnull)reader {
if ((self = [super init])) {
_response = response;
_reader = reader;
}
return self;
}
- (BOOL)open:(NSError**)error {
return [_reader open:error];
}
- (NSData*)readData:(NSError**)error {
return [_reader readData:error];
}
- (void)close {
[_reader close];
}
@end
@implementation GCDWebServerGZipEncoder {
z_stream _stream;
BOOL _finished;
}
- (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"];
}
return self;
}
- (BOOL)open:(NSError**)error {
int result = deflateInit2(&_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY);
if (result != Z_OK) {
if (error) {
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
}
return NO;
}
if (![super open:error]) {
deflateEnd(&_stream);
return NO;
}
return YES;
}
- (NSData*)readData:(NSError**)error {
NSMutableData* encodedData;
if (_finished) {
encodedData = [[NSMutableData alloc] init];
} else {
encodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize];
if (encodedData == nil) {
GWS_DNOT_REACHED();
return nil;
}
NSUInteger length = 0;
do {
NSData* data = [super readData:error];
if (data == nil) {
return nil;
}
_stream.next_in = (Bytef*)data.bytes;
_stream.avail_in = (uInt)data.length;
while (1) {
NSUInteger maxLength = encodedData.length - length;
_stream.next_out = (Bytef*)((char*)encodedData.mutableBytes + length);
_stream.avail_out = (uInt)maxLength;
int result = deflate(&_stream, data.length ? Z_NO_FLUSH : Z_FINISH);
if (result == Z_STREAM_END) {
_finished = YES;
} else if (result != Z_OK) {
if (error) {
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
}
return nil;
}
length += maxLength - _stream.avail_out;
if (_stream.avail_out > 0) {
break;
}
encodedData.length = 2 * encodedData.length; // zlib has used all the output buffer so resize it and try again in case more data is available
}
GWS_DCHECK(_stream.avail_in == 0);
} while (length == 0); // Make sure we don't return an empty NSData if not in finished state
encodedData.length = length;
}
return encodedData;
}
- (void)close {
deflateEnd(&_stream);
[super close];
}
@end
@implementation GCDWebServerResponse {
BOOL _opened;
NSMutableArray<GCDWebServerBodyEncoder*>* _encoders;
id<GCDWebServerBodyReader> __unsafe_unretained _reader;
}
+ (instancetype)response {
return [(GCDWebServerResponse*)[[self class] alloc] init];
}
- (instancetype)init {
if ((self = [super 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 {
[_additionalHeaders setValue:value forKey:header];
}
- (BOOL)hasBody {
return _contentType ? YES : NO;
}
- (BOOL)usesChunkedTransferEncoding {
return (_contentType != nil) && (_contentLength == NSUIntegerMax);
}
- (BOOL)open:(NSError**)error {
return YES;
}
- (NSData*)readData:(NSError**)error {
return [NSData data];
}
- (void)close {
;
}
- (void)prepareForReading {
_reader = self;
if (_gzipContentEncodingEnabled) {
GCDWebServerGZipEncoder* encoder = [[GCDWebServerGZipEncoder alloc] initWithResponse:self reader:_reader];
[_encoders addObject:encoder];
_reader = encoder;
}
}
- (BOOL)performOpen:(NSError**)error {
GWS_DCHECK(_contentType);
GWS_DCHECK(_reader);
if (_opened) {
GWS_DNOT_REACHED();
return NO;
}
_opened = YES;
return [_reader open:error];
}
- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block {
GWS_DCHECK(_opened);
if ([_reader respondsToSelector:@selector(asyncReadDataWithCompletion:)]) {
[_reader asyncReadDataWithCompletion:[block copy]];
} else {
NSError* error = nil;
NSData* data = [_reader readData:&error];
block(data, error);
}
}
- (void)performClose {
GWS_DCHECK(_opened);
[_reader close];
}
- (NSString*)description {
NSMutableString* description = [NSMutableString stringWithFormat:@"Status Code = %i", (int)_statusCode];
if (_contentType) {
[description appendFormat:@"\nContent Type = %@", _contentType];
}
if (_contentLength != NSUIntegerMax) {
[description appendFormat:@"\nContent Length = %lu", (unsigned long)_contentLength];
}
[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 (_additionalHeaders.count) {
[description appendString:@"\n"];
for (NSString* header in [[_additionalHeaders allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
[description appendFormat:@"\n%@: %@", header, [_additionalHeaders objectForKey:header]];
}
}
return description;
}
@end
@implementation GCDWebServerResponse (Extensions)
+ (instancetype)responseWithStatusCode:(NSInteger)statusCode {
return [(GCDWebServerResponse*)[self alloc] initWithStatusCode:statusCode];
}
+ (instancetype)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent {
return [(GCDWebServerResponse*)[self alloc] initWithRedirect:location permanent:permanent];
}
- (instancetype)initWithStatusCode:(NSInteger)statusCode {
if ((self = [self init])) {
self.statusCode = statusCode;
}
return self;
}
- (instancetype)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent {
if ((self = [self init])) {
self.statusCode = permanent ? kGCDWebServerHTTPStatusCode_MovedPermanently : kGCDWebServerHTTPStatusCode_TemporaryRedirect;
[self setValue:[location absoluteString] forAdditionalHeader:@"Location"];
}
return self;
}
@end

View File

@@ -0,0 +1,64 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "GCDWebServerRequest.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerDataRequest subclass of GCDWebServerRequest stores the body
* of the HTTP request in memory.
*/
@interface GCDWebServerDataRequest : GCDWebServerRequest
/**
* Returns the data for the request body.
*/
@property(nonatomic, readonly) NSData* data;
@end
@interface GCDWebServerDataRequest (Extensions)
/**
* Returns the data for the request body interpreted as text. If the content
* type of the body is not a text one, or if an error occurs, nil is returned.
*
* The text encoding used to interpret the data is extracted from the
* "Content-Type" header or defaults to UTF-8.
*/
@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, nullable) id jsonObject;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,104 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif
#import "GCDWebServerPrivate.h"
@interface GCDWebServerDataRequest ()
@property(nonatomic) NSMutableData* data;
@end
@implementation GCDWebServerDataRequest {
NSString* _text;
id _jsonObject;
}
- (BOOL)open:(NSError**)error {
if (self.contentLength != NSUIntegerMax) {
_data = [[NSMutableData alloc] initWithCapacity:self.contentLength];
} else {
_data = [[NSMutableData alloc] init];
}
if (_data == nil) {
if (error) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed allocating memory"}];
}
return NO;
}
return YES;
}
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
[_data appendData:data];
return YES;
}
- (BOOL)close:(NSError**)error {
return YES;
}
- (NSString*)description {
NSMutableString* description = [NSMutableString stringWithString:[super description]];
if (_data) {
[description appendString:@"\n\n"];
[description appendString:GCDWebServerDescribeData(_data, (NSString*)self.contentType)];
}
return description;
}
@end
@implementation GCDWebServerDataRequest (Extensions)
- (NSString*)text {
if (_text == nil) {
if ([self.contentType hasPrefix:@"text/"]) {
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
_text = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)];
} else {
GWS_DNOT_REACHED();
}
}
return _text;
}
- (id)jsonObject {
if (_jsonObject == nil) {
NSString* mimeType = GCDWebServerTruncateHeaderValue(self.contentType);
if ([mimeType isEqualToString:@"application/json"] || [mimeType isEqualToString:@"text/json"] || [mimeType isEqualToString:@"text/javascript"]) {
_jsonObject = [NSJSONSerialization JSONObjectWithData:_data options:0 error:NULL];
} else {
GWS_DNOT_REACHED();
}
}
return _jsonObject;
}
@end

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2013, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -9,14 +9,14 @@
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
@@ -25,32 +25,25 @@
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "AppDelegate.h"
#import "GCDWebServerRequest.h"
@implementation AppDelegate
NS_ASSUME_NONNULL_BEGIN
- (void)dealloc {
[_window release];
[super dealloc];
}
/**
* The GCDWebServerFileRequest subclass of GCDWebServerRequest stores the body
* of the HTTP request to a file on disk.
*/
@interface GCDWebServerFileRequest : GCDWebServerRequest
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
_window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
_window.backgroundColor = [UIColor whiteColor];
[_window makeKeyAndVisible];
_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>"];
}];
[_webServer start];
return YES;
}
/**
* Returns the path to the temporary file containing the request body.
*
* @warning This temporary file will be automatically deleted when the
* GCDWebServerFileRequest is deallocated. If you want to preserve this file,
* you must move it to a different location beforehand.
*/
@property(nonatomic, readonly) NSString* temporaryPath;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,102 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif
#import "GCDWebServerPrivate.h"
@implementation GCDWebServerFileRequest {
int _file;
}
- (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]];
}
return self;
}
- (void)dealloc {
unlink([_temporaryPath fileSystemRepresentation]);
}
- (BOOL)open:(NSError**)error {
_file = open([_temporaryPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (_file <= 0) {
if (error) {
*error = GCDWebServerMakePosixError(errno);
}
return NO;
}
return YES;
}
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
if (write(_file, data.bytes, data.length) != (ssize_t)data.length) {
if (error) {
*error = GCDWebServerMakePosixError(errno);
}
return NO;
}
return YES;
}
- (BOOL)close:(NSError**)error {
if (close(_file) < 0) {
if (error) {
*error = GCDWebServerMakePosixError(errno);
}
return NO;
}
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
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]) {
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]) {
return NO;
}
}
#endif
return YES;
}
- (NSString*)description {
NSMutableString* description = [NSMutableString stringWithString:[super description]];
[description appendFormat:@"\n\n{%@}", _temporaryPath];
return description;
}
@end

View File

@@ -0,0 +1,136 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "GCDWebServerRequest.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerMultiPart class is an abstract class that wraps the content
* of a part.
*/
@interface GCDWebServerMultiPart : NSObject
/**
* Returns the control name retrieved from the part headers.
*/
@property(nonatomic, readonly) NSString* controlName;
/**
* Returns the content type retrieved from the part headers or "text/plain"
* if not available (per HTTP specifications).
*/
@property(nonatomic, readonly) NSString* contentType;
/**
* Returns the MIME type component of the content type for the part.
*/
@property(nonatomic, readonly) NSString* mimeType;
@end
/**
* The GCDWebServerMultiPartArgument subclass of GCDWebServerMultiPart wraps
* the content of a part as data in memory.
*/
@interface GCDWebServerMultiPartArgument : GCDWebServerMultiPart
/**
* Returns the data for the part.
*/
@property(nonatomic, readonly) NSData* data;
/**
* Returns the data for the part interpreted as text. If the content
* type of the part is not a text one, or if an error occurs, nil is returned.
*
* The text encoding used to interpret the data is extracted from the
* "Content-Type" header or defaults to UTF-8.
*/
@property(nonatomic, readonly, nullable) NSString* string;
@end
/**
* The GCDWebServerMultiPartFile subclass of GCDWebServerMultiPart wraps
* the content of a part as a file on disk.
*/
@interface GCDWebServerMultiPartFile : GCDWebServerMultiPart
/**
* Returns the file name retrieved from the part headers.
*/
@property(nonatomic, readonly) NSString* fileName;
/**
* Returns the path to the temporary file containing the part data.
*
* @warning This temporary file will be automatically deleted when the
* GCDWebServerMultiPartFile is deallocated. If you want to preserve this file,
* you must move it to a different location beforehand.
*/
@property(nonatomic, readonly) NSString* temporaryPath;
@end
/**
* The GCDWebServerMultiPartFormRequest subclass of GCDWebServerRequest
* parses the body of the HTTP request as a multipart encoded form.
*/
@interface GCDWebServerMultiPartFormRequest : GCDWebServerRequest
/**
* Returns the argument parts from the multipart encoded form as
* name / GCDWebServerMultiPartArgument pairs.
*/
@property(nonatomic, readonly) NSArray<GCDWebServerMultiPartArgument*>* arguments;
/**
* Returns the files parts from the multipart encoded form as
* name / GCDWebServerMultiPartFile pairs.
*/
@property(nonatomic, readonly) NSArray<GCDWebServerMultiPartFile*>* files;
/**
* Returns the MIME type for multipart encoded forms
* i.e. "multipart/form-data".
*/
+ (NSString*)mimeType;
/**
* Returns the first argument for a given control name or nil if not found.
*/
- (nullable GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name;
/**
* Returns the first file for a given control name or nil if not found.
*/
- (nullable GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,405 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif
#import "GCDWebServerPrivate.h"
#define kMultiPartBufferSize (256 * 1024)
typedef enum {
kParserState_Undefined = 0,
kParserState_Start,
kParserState_Headers,
kParserState_Content,
kParserState_End
} ParserState;
@interface GCDWebServerMIMEStreamParser : NSObject
@end
static NSData* _newlineData = nil;
static NSData* _newlinesData = nil;
static NSData* _dashNewlineData = nil;
@implementation GCDWebServerMultiPart
- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type {
if ((self = [super init])) {
_controlName = [name copy];
_contentType = [type copy];
_mimeType = (NSString*)GCDWebServerTruncateHeaderValue(_contentType);
}
return self;
}
@end
@implementation GCDWebServerMultiPartArgument
- (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)];
}
}
return self;
}
- (NSString*)description {
return [NSString stringWithFormat:@"<%@ | '%@' | %lu bytes>", [self class], self.mimeType, (unsigned long)_data.length];
}
@end
@implementation GCDWebServerMultiPartFile
- (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];
}
return self;
}
- (void)dealloc {
unlink([_temporaryPath fileSystemRepresentation]);
}
- (NSString*)description {
return [NSString stringWithFormat:@"<%@ | '%@' | '%@>'", [self class], self.mimeType, _fileName];
}
@end
@implementation GCDWebServerMIMEStreamParser {
NSData* _boundary;
NSString* _defaultcontrolName;
ParserState _state;
NSMutableData* _data;
NSMutableArray<GCDWebServerMultiPartArgument*>* _arguments;
NSMutableArray<GCDWebServerMultiPartFile*>* _files;
NSString* _controlName;
NSString* _fileName;
NSString* _contentType;
NSString* _tmpPath;
int _tmpFile;
GCDWebServerMIMEStreamParser* _subParser;
}
+ (void)initialize {
if (_newlineData == nil) {
_newlineData = [[NSData alloc] initWithBytes:"\r\n" length:2];
GWS_DCHECK(_newlineData);
}
if (_newlinesData == nil) {
_newlinesData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4];
GWS_DCHECK(_newlinesData);
}
if (_dashNewlineData == nil) {
_dashNewlineData = [[NSData alloc] initWithBytes:"--\r\n" length:4];
GWS_DCHECK(_dashNewlineData);
}
}
- (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();
return nil;
}
if ((self = [super init])) {
_boundary = data;
_defaultcontrolName = name;
_arguments = arguments;
_files = files;
_data = [[NSMutableData alloc] initWithCapacity:kMultiPartBufferSize];
_state = kParserState_Start;
}
return self;
}
- (void)dealloc {
if (_tmpFile > 0) {
close(_tmpFile);
unlink([_tmpPath fileSystemRepresentation]);
}
}
// 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;
_tmpPath = nil;
_subParser = nil;
NSString* headers = [[NSString alloc] initWithData:[_data subdataWithRange:NSMakeRange(0, range.location)] encoding:NSUTF8StringEncoding];
if (headers) {
for (NSString* header in [headers componentsSeparatedByString:@"\r\n"]) {
NSRange subRange = [header rangeOfString:@":"];
if (subRange.location != NSNotFound) {
NSString* name = [header substringToIndex:subRange.location];
NSString* value = [[header substringFromIndex:(subRange.location + subRange.length)] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if ([name caseInsensitiveCompare:@"Content-Type"] == NSOrderedSame) {
_contentType = GCDWebServerNormalizeHeaderValue(value);
} else if ([name caseInsensitiveCompare:@"Content-Disposition"] == NSOrderedSame) {
NSString* contentDisposition = GCDWebServerNormalizeHeaderValue(value);
if ([GCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"form-data"]) {
_controlName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"name");
_fileName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename");
} else if ([GCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"file"]) {
_controlName = _defaultcontrolName;
_fileName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename");
}
}
} else {
GWS_DNOT_REACHED();
}
}
if (_contentType == nil) {
_contentType = @"text/plain";
}
} else {
GWS_LOG_ERROR(@"Failed decoding headers in part of 'multipart/form-data'");
GWS_DNOT_REACHED();
}
if (_controlName) {
if ([GCDWebServerTruncateHeaderValue(_contentType) isEqualToString:@"multipart/mixed"]) {
NSString* boundary = GCDWebServerExtractHeaderValueParameter(_contentType, @"boundary");
_subParser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:_controlName arguments:_arguments files:_files];
if (_subParser == nil) {
GWS_DNOT_REACHED();
success = NO;
}
} else if (_fileName) {
NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
_tmpFile = open([path fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (_tmpFile > 0) {
_tmpPath = [path copy];
} else {
GWS_DNOT_REACHED();
success = NO;
}
}
} else {
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) {
NSRange subRange = NSMakeRange(range.location + range.length, _data.length - range.location - range.length);
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;
if (_subParser) {
if (![_subParser appendBytes:dataBytes length:(dataLength + 2)] || ![_subParser isAtEnd]) {
GWS_DNOT_REACHED();
success = NO;
}
_subParser = nil;
} else if (_tmpPath) {
ssize_t result = write(_tmpFile, dataBytes, dataLength);
if (result == (ssize_t)dataLength) {
if (close(_tmpFile) == 0) {
_tmpFile = 0;
GCDWebServerMultiPartFile* file = [[GCDWebServerMultiPartFile alloc] initWithControlName:_controlName contentType:_contentType fileName:_fileName temporaryPath:_tmpPath];
[_files addObject:file];
} else {
GWS_DNOT_REACHED();
success = NO;
}
} else {
GWS_DNOT_REACHED();
success = NO;
}
_tmpPath = nil;
} else {
NSData* data = [[NSData alloc] initWithBytes:(void*)dataBytes length:dataLength];
GCDWebServerMultiPartArgument* argument = [[GCDWebServerMultiPartArgument alloc] initWithControlName:_controlName contentType:_contentType data:data];
[_arguments addObject:argument];
}
}
if (subRange1.location != NSNotFound) {
[_data replaceBytesInRange:NSMakeRange(0, subRange1.location + subRange1.length) withBytes:NULL length:0];
_state = kParserState_Headers;
success = [self _parseData];
} else {
_state = kParserState_End;
}
}
} else {
NSUInteger margin = 2 * _boundary.length;
if (_data.length > margin) {
NSUInteger length = _data.length - margin;
if (_subParser) {
if ([_subParser appendBytes:_data.bytes length:length]) {
[_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
} else {
GWS_DNOT_REACHED();
success = NO;
}
} else if (_tmpPath) {
ssize_t result = write(_tmpFile, _data.bytes, length);
if (result == (ssize_t)length) {
[_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
} else {
GWS_DNOT_REACHED();
success = NO;
}
}
}
}
}
return success;
}
- (BOOL)appendBytes:(const void*)bytes length:(NSUInteger)length {
[_data appendBytes:bytes length:length];
return [self _parseData];
}
- (BOOL)isAtEnd {
return (_state == kParserState_End);
}
@end
@interface GCDWebServerMultiPartFormRequest ()
@property(nonatomic) NSMutableArray<GCDWebServerMultiPartArgument*>* arguments;
@property(nonatomic) NSMutableArray<GCDWebServerMultiPartFile*>* files;
@end
@implementation GCDWebServerMultiPartFormRequest {
GCDWebServerMIMEStreamParser* _parser;
}
+ (NSString*)mimeType {
return @"multipart/form-data";
}
- (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];
}
return self;
}
- (BOOL)open:(NSError**)error {
NSString* boundary = GCDWebServerExtractHeaderValueParameter(self.contentType, @"boundary");
_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"}];
}
return NO;
}
return YES;
}
- (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"}];
}
return NO;
}
return YES;
}
- (BOOL)close:(NSError**)error {
BOOL atEnd = [_parser isAtEnd];
_parser = nil;
if (!atEnd) {
if (error) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed finishing to parse multipart form data"}];
}
return NO;
}
return YES;
}
- (GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name {
for (GCDWebServerMultiPartArgument* argument in _arguments) {
if ([argument.controlName isEqualToString:name]) {
return argument;
}
}
return nil;
}
- (GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name {
for (GCDWebServerMultiPartFile* file in _files) {
if ([file.controlName isEqualToString:name]) {
return file;
}
}
return nil;
}
- (NSString*)description {
NSMutableString* description = [NSMutableString stringWithString:[super description]];
if (_arguments.count) {
[description appendString:@"\n"];
for (GCDWebServerMultiPartArgument* argument in _arguments) {
[description appendFormat:@"\n%@ (%@)\n", argument.controlName, argument.contentType];
[description appendString:GCDWebServerDescribeData(argument.data, argument.contentType)];
}
}
if (_files.count) {
[description appendString:@"\n"];
for (GCDWebServerMultiPartFile* file in _files) {
[description appendFormat:@"\n%@ (%@): %@\n{%@}", file.controlName, file.contentType, file.fileName, file.temporaryPath];
}
}
return description;
}
@end

View File

@@ -0,0 +1,55 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#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
* GCDWebServerParseURLEncodedForm().
*/
@interface GCDWebServerURLEncodedFormRequest : GCDWebServerDataRequest
/**
* Returns the unescaped control names and values for the URL encoded form.
*
* The text encoding used to interpret the data is extracted from the
* "Content-Type" header or defaults to UTF-8.
*/
@property(nonatomic, readonly) NSDictionary<NSString*, NSString*>* arguments;
/**
* Returns the MIME type for URL encoded forms
* i.e. "application/x-www-form-urlencoded".
*/
+ (NSString*)mimeType;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,60 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif
#import "GCDWebServerPrivate.h"
@implementation GCDWebServerURLEncodedFormRequest
+ (NSString*)mimeType {
return @"application/x-www-form-urlencoded";
}
- (BOOL)close:(NSError**)error {
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);
return YES;
}
- (NSString*)description {
NSMutableString* description = [NSMutableString stringWithString:[super description]];
[description appendString:@"\n"];
for (NSString* argument in [[_arguments allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
[description appendFormat:@"\n%@ = %@", argument, [_arguments objectForKey:argument]];
}
return description;
}
@end

View File

@@ -0,0 +1,113 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#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.
*/
+ (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type;
/**
* This method is the designated initializer for the class.
*/
- (instancetype)initWithData:(NSData*)data contentType:(NSString*)type;
@end
@interface GCDWebServerDataResponse (Extensions)
/**
* Creates a data response from text encoded using UTF-8.
*/
+ (nullable instancetype)responseWithText:(NSString*)text;
/**
* Creates a data response from HTML encoded using UTF-8.
*/
+ (nullable instancetype)responseWithHTML:(NSString*)html;
/**
* Creates a data response from an HTML template encoded using UTF-8.
* See -initWithHTMLTemplate:variables: for details.
*/
+ (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.
*/
+ (nullable instancetype)responseWithJSONObject:(id)object;
/**
* Creates a data response from a serialized JSON object and a custom
* content type.
*/
+ (nullable instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type;
/**
* Initializes a data response from text encoded using UTF-8.
*/
- (nullable instancetype)initWithText:(NSString*)text;
/**
* Initializes a data response from HTML encoded using UTF-8.
*/
- (nullable instancetype)initWithHTML:(NSString*)html;
/**
* Initializes a data response from an HTML template encoded using UTF-8.
*
* All occurences of "%variable%" within the HTML template are replaced with
* their corresponding values.
*/
- (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.
*/
- (nullable instancetype)initWithJSONObject:(id)object;
/**
* Initializes a data response from a serialized JSON object and a custom
* content type.
*/
- (nullable instancetype)initWithJSONObject:(id)object contentType:(NSString*)type;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,136 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif
#import "GCDWebServerPrivate.h"
@implementation GCDWebServerDataResponse {
NSData* _data;
BOOL _done;
}
@dynamic contentType;
+ (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type {
return [(GCDWebServerDataResponse*)[[self class] alloc] initWithData:data contentType:type];
}
- (instancetype)initWithData:(NSData*)data contentType:(NSString*)type {
if ((self = [super init])) {
_data = data;
self.contentType = type;
self.contentLength = data.length;
}
return self;
}
- (NSData*)readData:(NSError**)error {
NSData* data;
if (_done) {
data = [NSData data];
} else {
data = _data;
_done = YES;
}
return data;
}
- (NSString*)description {
NSMutableString* description = [NSMutableString stringWithString:[super description]];
[description appendString:@"\n\n"];
[description appendString:GCDWebServerDescribeData(_data, self.contentType)];
return description;
}
@end
@implementation GCDWebServerDataResponse (Extensions)
+ (instancetype)responseWithText:(NSString*)text {
return [(GCDWebServerDataResponse*)[self alloc] initWithText:text];
}
+ (instancetype)responseWithHTML:(NSString*)html {
return [(GCDWebServerDataResponse*)[self alloc] initWithHTML:html];
}
+ (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary<NSString*, NSString*>*)variables {
return [(GCDWebServerDataResponse*)[self alloc] initWithHTMLTemplate:path variables:variables];
}
+ (instancetype)responseWithJSONObject:(id)object {
return [(GCDWebServerDataResponse*)[self alloc] initWithJSONObject:object];
}
+ (instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type {
return [(GCDWebServerDataResponse*)[self alloc] initWithJSONObject:object contentType:type];
}
- (instancetype)initWithText:(NSString*)text {
NSData* data = [text dataUsingEncoding:NSUTF8StringEncoding];
if (data == nil) {
GWS_DNOT_REACHED();
return nil;
}
return [self initWithData:data contentType:@"text/plain; charset=utf-8"];
}
- (instancetype)initWithHTML:(NSString*)html {
NSData* data = [html dataUsingEncoding:NSUTF8StringEncoding];
if (data == nil) {
GWS_DNOT_REACHED();
return nil;
}
return [self initWithData:data contentType:@"text/html; charset=utf-8"];
}
- (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)];
}];
return [self initWithHTML:html];
}
- (instancetype)initWithJSONObject:(id)object {
return [self initWithJSONObject:object contentType:@"application/json"];
}
- (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];
}
@end

View File

@@ -0,0 +1,85 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#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.
*/
@interface GCDWebServerErrorResponse : GCDWebServerDataResponse
/**
* Creates a client error response with the corresponding HTTP status code.
*/
+ (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);
/**
* Creates a client error response with the corresponding HTTP status code
* and an underlying NSError.
*/
+ (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:(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);
/**
* Initializes a server error response with the corresponding HTTP status code.
*/
- (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:(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:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4);
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,124 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif
#import "GCDWebServerPrivate.h"
@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 = [(GCDWebServerErrorResponse*)[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
va_end(arguments);
return response;
}
+ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
va_list arguments;
va_start(arguments, format);
GCDWebServerErrorResponse* response = [(GCDWebServerErrorResponse*)[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
va_end(arguments);
return response;
}
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... {
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
va_list arguments;
va_start(arguments, format);
GCDWebServerErrorResponse* response = [(GCDWebServerErrorResponse*)[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
va_end(arguments);
return response;
}
+ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... {
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
va_list arguments;
va_start(arguments, format);
GCDWebServerErrorResponse* response = [(GCDWebServerErrorResponse*)[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
va_end(arguments);
return response;
}
static inline NSString* _EscapeHTMLString(NSString* string) {
return [string stringByReplacingOccurrencesOfString:@"\"" withString:@"&quot;"];
}
- (instancetype)initWithStatusCode:(NSInteger)statusCode underlyingError:(NSError*)underlyingError messageFormat:(NSString*)format arguments:(va_list)arguments {
NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments];
NSString* title = [NSString stringWithFormat:@"HTTP Error %i", (int)statusCode];
NSString* error = underlyingError ? [NSString stringWithFormat:@"[%@] %@ (%li)", underlyingError.domain, _EscapeHTMLString(underlyingError.localizedDescription), (long)underlyingError.code] : @"";
NSString* html = [NSString stringWithFormat:@"<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"utf-8\"><title>%@</title></head><body><h1>%@: %@</h1><h3>%@</h3></body></html>",
title, title, _EscapeHTMLString(message), error];
if ((self = [self initWithHTML:html])) {
self.statusCode = statusCode;
}
return self;
}
- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
va_list arguments;
va_start(arguments, format);
self = [self initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
va_end(arguments);
return self;
}
- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
va_list arguments;
va_start(arguments, format);
self = [self initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
va_end(arguments);
return self;
}
- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... {
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
va_list arguments;
va_start(arguments, format);
self = [self initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
va_end(arguments);
return self;
}
- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... {
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
va_list arguments;
va_start(arguments, format);
self = [self initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
va_end(arguments);
return self;
}
@end

View File

@@ -0,0 +1,108 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "GCDWebServerResponse.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerFileResponse subclass of GCDWebServerResponse reads the body
* of the HTTP response from a file on disk.
*
* It will automatically set the contentType, lastModifiedDate and eTag
* properties of the GCDWebServerResponse according to the file extension and
* 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.
*/
+ (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.
*/
+ (nullable instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment;
/**
* Creates a response like +responseWithFile: but restricts the file contents
* to a specific byte range.
*
* See -initWithFile:byteRange: for details.
*/
+ (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.
*/
+ (nullable instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
/**
* Initializes a response with the contents of a file.
*/
- (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.
*/
- (nullable instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment;
/**
* Initializes a response like -initWithFile: but restricts the file contents
* to a specific byte range. This range should be set to (NSUIntegerMax, 0) for
* the full file, (offset, length) if expressed from the beginning of the file,
* or (NSUIntegerMax, length) if expressed from the end of the file. The "offset"
* and "length" values will be automatically adjusted to be compatible with the
* actual size of the file.
*
* This argument would typically be set to the value of the byteRange property
* of the current GCDWebServerRequest.
*/
- (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.
*/
- (nullable instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment mimeTypeOverrides:(nullable NSDictionary<NSString*, NSString*>*)overrides;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,185 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif
#import <sys/stat.h>
#import "GCDWebServerPrivate.h"
#define kFileReadBufferSize (32 * 1024)
@implementation GCDWebServerFileResponse {
NSString* _path;
NSUInteger _offset;
NSUInteger _size;
int _file;
}
@dynamic contentType, lastModifiedDate, eTag;
+ (instancetype)responseWithFile:(NSString*)path {
return [(GCDWebServerFileResponse*)[[self class] alloc] initWithFile:path];
}
+ (instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment {
return [(GCDWebServerFileResponse*)[[self class] alloc] initWithFile:path isAttachment:attachment];
}
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range {
return [(GCDWebServerFileResponse*)[[self class] alloc] initWithFile:path byteRange:range];
}
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)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 mimeTypeOverrides:nil];
}
- (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)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 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 mimeTypeOverrides:(NSDictionary<NSString*, NSString*>*)overrides {
struct stat info;
if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) {
GWS_DNOT_REACHED();
return nil;
}
#ifndef __LP64__
if (info.st_size >= (off_t)4294967295) { // In 32 bit mode, we can't handle files greater than 4 GiBs (don't use "NSUIntegerMax" here to avoid potential unsigned to signed conversion issues)
GWS_DNOT_REACHED();
return nil;
}
#endif
NSUInteger fileSize = (NSUInteger)info.st_size;
BOOL hasByteRange = GCDWebServerIsValidByteRange(range);
if (hasByteRange) {
if (range.location != NSUIntegerMax) {
range.location = MIN(range.location, fileSize);
range.length = MIN(range.length, fileSize - range.location);
} else {
range.length = MIN(range.length, fileSize);
range.location = fileSize - range.length;
}
if (range.length == 0) {
return nil; // TODO: Return 416 status code and "Content-Range: bytes */{file length}" header
}
} else {
range.location = 0;
range.length = fileSize;
}
if ((self = [super init])) {
_path = [path copy];
_offset = range.location;
_size = range.length;
if (hasByteRange) {
[self setStatusCode:kGCDWebServerHTTPStatusCode_PartialContent];
[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];
NSString* lossyFileName = data ? [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding] : nil;
if (lossyFileName) {
NSString* value = [NSString stringWithFormat:@"attachment; filename=\"%@\"; filename*=UTF-8''%@", lossyFileName, GCDWebServerEscapeURLString(fileName)];
[self setValue:value forAdditionalHeader:@"Content-Disposition"];
} else {
GWS_DNOT_REACHED();
}
}
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];
}
return self;
}
- (BOOL)open:(NSError**)error {
_file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY);
if (_file <= 0) {
if (error) {
*error = GCDWebServerMakePosixError(errno);
}
return NO;
}
if (lseek(_file, _offset, SEEK_SET) != (off_t)_offset) {
if (error) {
*error = GCDWebServerMakePosixError(errno);
}
close(_file);
return NO;
}
return YES;
}
- (NSData*)readData:(NSError**)error {
size_t length = MIN((NSUInteger)kFileReadBufferSize, _size);
NSMutableData* data = [[NSMutableData alloc] initWithLength:length];
ssize_t result = read(_file, data.mutableBytes, length);
if (result < 0) {
if (error) {
*error = GCDWebServerMakePosixError(errno);
}
return nil;
}
if (result > 0) {
[data setLength:result];
_size -= result;
}
return data;
}
- (void)close {
close(_file);
}
- (NSString*)description {
NSMutableString* description = [NSMutableString stringWithString:[super description]];
[description appendFormat:@"\n\n{%@}", _path];
return description;
}
@end

View File

@@ -0,0 +1,80 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#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* _Nullable (^GCDWebServerStreamBlock)(NSError** error);
/**
* The GCDWebServerAsyncStreamBlock works like the GCDWebServerStreamBlock
* except the streamed data can be returned at a later time allowing for
* truly asynchronous generation of the data.
*
* The block must call "completionBlock" passing the new chunk of data when ready,
* an empty NSData when done, or nil on error and pass a NSError.
*
* The block cannot call "completionBlock" more than once per invocation.
*/
typedef void (^GCDWebServerAsyncStreamBlock)(GCDWebServerBodyReaderCompletionBlock completionBlock);
/**
* The GCDWebServerStreamedResponse subclass of GCDWebServerResponse streams
* 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.
*/
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block;
/**
* Creates a response with async streamed data and a given content type.
*/
+ (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block;
/**
* Initializes a response with streamed data and a given content type.
*/
- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block;
/**
* This method is the designated initializer for the class.
*/
- (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,76 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif
#import "GCDWebServerPrivate.h"
@implementation GCDWebServerStreamedResponse {
GCDWebServerAsyncStreamBlock _block;
}
@dynamic contentType;
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
return [(GCDWebServerStreamedResponse*)[[self class] alloc] initWithContentType:type streamBlock:block];
}
+ (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)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);
}];
}
- (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block {
if ((self = [super init])) {
_block = [block copy];
self.contentType = type;
}
return self;
}
- (void)asyncReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block {
_block(block);
}
- (NSString*)description {
NSMutableString* description = [NSMutableString stringWithString:[super description]];
[description appendString:@"\n\n<STREAM>"];
return description;
}
@end

View File

@@ -0,0 +1,347 @@
/*!
* Bootstrap v3.1.1 (http://getbootstrap.com)
* Copyright 2011-2014 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
.btn-default,
.btn-primary,
.btn-success,
.btn-info,
.btn-warning,
.btn-danger {
text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
}
.btn-default:active,
.btn-primary:active,
.btn-success:active,
.btn-info:active,
.btn-warning:active,
.btn-danger:active,
.btn-default.active,
.btn-primary.active,
.btn-success.active,
.btn-info.active,
.btn-warning.active,
.btn-danger.active {
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
}
.btn:active,
.btn.active {
background-image: none;
}
.btn-default {
text-shadow: 0 1px 0 #fff;
background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #dbdbdb;
border-color: #ccc;
}
.btn-default:hover,
.btn-default:focus {
background-color: #e0e0e0;
background-position: 0 -15px;
}
.btn-default:active,
.btn-default.active {
background-color: #e0e0e0;
border-color: #dbdbdb;
}
.btn-primary {
background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #2b669a;
}
.btn-primary:hover,
.btn-primary:focus {
background-color: #2d6ca2;
background-position: 0 -15px;
}
.btn-primary:active,
.btn-primary.active {
background-color: #2d6ca2;
border-color: #2b669a;
}
.btn-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #3e8f3e;
}
.btn-success:hover,
.btn-success:focus {
background-color: #419641;
background-position: 0 -15px;
}
.btn-success:active,
.btn-success.active {
background-color: #419641;
border-color: #3e8f3e;
}
.btn-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #28a4c9;
}
.btn-info:hover,
.btn-info:focus {
background-color: #2aabd2;
background-position: 0 -15px;
}
.btn-info:active,
.btn-info.active {
background-color: #2aabd2;
border-color: #28a4c9;
}
.btn-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #e38d13;
}
.btn-warning:hover,
.btn-warning:focus {
background-color: #eb9316;
background-position: 0 -15px;
}
.btn-warning:active,
.btn-warning.active {
background-color: #eb9316;
border-color: #e38d13;
}
.btn-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #b92c28;
}
.btn-danger:hover,
.btn-danger:focus {
background-color: #c12e2a;
background-position: 0 -15px;
}
.btn-danger:active,
.btn-danger.active {
background-color: #c12e2a;
border-color: #b92c28;
}
.thumbnail,
.img-thumbnail {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background-color: #e8e8e8;
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
background-repeat: repeat-x;
}
.dropdown-menu > .active > a,
.dropdown-menu > .active > a:hover,
.dropdown-menu > .active > a:focus {
background-color: #357ebd;
background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
background-repeat: repeat-x;
}
.navbar-default {
background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
}
.navbar-default .navbar-nav > .active > a {
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%);
background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);
background-repeat: repeat-x;
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
}
.navbar-brand,
.navbar-nav > li > a {
text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
}
.navbar-inverse {
background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
}
.navbar-inverse .navbar-nav > .active > a {
background-image: -webkit-linear-gradient(top, #222 0%, #282828 100%);
background-image: linear-gradient(to bottom, #222 0%, #282828 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);
background-repeat: repeat-x;
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
}
.navbar-inverse .navbar-brand,
.navbar-inverse .navbar-nav > li > a {
text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
}
.navbar-static-top,
.navbar-fixed-top,
.navbar-fixed-bottom {
border-radius: 0;
}
.alert {
text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
}
.alert-success {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
background-repeat: repeat-x;
border-color: #b2dba1;
}
.alert-info {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
background-repeat: repeat-x;
border-color: #9acfea;
}
.alert-warning {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
background-repeat: repeat-x;
border-color: #f5e79e;
}
.alert-danger {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
background-repeat: repeat-x;
border-color: #dca7a7;
}
.progress {
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar {
background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%);
background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
background-repeat: repeat-x;
}
.list-group {
border-radius: 4px;
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
}
.list-group-item.active,
.list-group-item.active:hover,
.list-group-item.active:focus {
text-shadow: 0 -1px 0 #3071a9;
background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%);
background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);
background-repeat: repeat-x;
border-color: #3278b3;
}
.panel {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
}
.panel-default > .panel-heading {
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
background-repeat: repeat-x;
}
.panel-primary > .panel-heading {
background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
background-repeat: repeat-x;
}
.panel-success > .panel-heading {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
background-repeat: repeat-x;
}
.panel-info > .panel-heading {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
background-repeat: repeat-x;
}
.panel-warning > .panel-heading {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
background-repeat: repeat-x;
}
.panel-danger > .panel-heading {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
background-repeat: repeat-x;
}
.well {
background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
background-repeat: repeat-x;
border-color: #dcdcdc;
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
}
/*# sourceMappingURL=bootstrap-theme.css.map */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,130 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
.row-file {
height: 40px;
}
.column-icon {
width: 40px;
text-align: center;
}
.column-name {
}
.column-size {
width: 100px;
text-align: right;
}
.column-move {
width: 40px;
text-align: center;
}
.column-delete {
width: 40px;
text-align: center;
}
.column-path {
}
.column-progress {
width: 200px;
}
.footer {
color: #999;
text-align: center;
font-size: 0.9em;
}
#reload {
float: right;
}
#create-input {
width: 50%;
height: 20px;
}
#move-input {
width: 80%;
height: 20px;
}
/* Bootstrap overrides */
.btn:focus {
outline: none; /* FIXME: Work around for Chrome only but still draws focus ring while button pressed */
}
.btn-toolbar {
margin-top: 30px;
margin-bottom: 20px;
}
.table .progress {
margin-top: 0px;
margin-bottom: 0px;
height: 16px;
}
.panel-default > .panel-heading {
color: #555;
}
.breadcrumb {
background-color: transparent;
border-radius: 0px;
margin-bottom: 0px;
padding: 0px;
}
.breadcrumb > .active {
color: #555;
}
.breadcrumb > li + li:before {
color: #999;
}
.table > tbody > tr > td {
vertical-align: middle;
}
.table > tbody > tr > td > p {
margin: 0px;
}
/* Initial state */
.uploading {
display: none;
}

View File

@@ -0,0 +1,36 @@
@charset "UTF-8";
/*
* jQuery File Upload Plugin CSS 1.3.0
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2013, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
.fileinput-button {
position: relative;
overflow: hidden;
}
.fileinput-button input {
position: absolute;
top: 0;
right: 0;
margin: 0;
opacity: 0;
-ms-filter: 'alpha(opacity=0)';
font-size: 200px;
direction: ltr;
cursor: pointer;
}
/* Fixes for IE < 8 */
@media screen\9 {
.fileinput-button input {
filter: alpha(opacity=0);
font-size: 100%;
height: 100%;
}
}

View File

@@ -0,0 +1,3 @@
"PROLOGUE" = "<p>Drag &amp; drop files on this window or use the \"Upload Files&hellip;\" button to upload new files.</p>";
"EPILOGUE" = "";
"FOOTER_FORMAT" = "%@ %@";

View File

@@ -0,0 +1,229 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata></metadata>
<defs>
<font id="glyphicons_halflingsregular" horiz-adv-x="1200" >
<font-face units-per-em="1200" ascent="960" descent="-240" />
<missing-glyph horiz-adv-x="500" />
<glyph />
<glyph />
<glyph unicode="&#xd;" />
<glyph unicode=" " />
<glyph unicode="*" d="M100 500v200h259l-183 183l141 141l183 -183v259h200v-259l183 183l141 -141l-183 -183h259v-200h-259l183 -183l-141 -141l-183 183v-259h-200v259l-183 -183l-141 141l183 183h-259z" />
<glyph unicode="+" d="M0 400v300h400v400h300v-400h400v-300h-400v-400h-300v400h-400z" />
<glyph unicode="&#xa0;" />
<glyph unicode="&#x2000;" horiz-adv-x="652" />
<glyph unicode="&#x2001;" horiz-adv-x="1304" />
<glyph unicode="&#x2002;" horiz-adv-x="652" />
<glyph unicode="&#x2003;" horiz-adv-x="1304" />
<glyph unicode="&#x2004;" horiz-adv-x="434" />
<glyph unicode="&#x2005;" horiz-adv-x="326" />
<glyph unicode="&#x2006;" horiz-adv-x="217" />
<glyph unicode="&#x2007;" horiz-adv-x="217" />
<glyph unicode="&#x2008;" horiz-adv-x="163" />
<glyph unicode="&#x2009;" horiz-adv-x="260" />
<glyph unicode="&#x200a;" horiz-adv-x="72" />
<glyph unicode="&#x202f;" horiz-adv-x="260" />
<glyph unicode="&#x205f;" horiz-adv-x="326" />
<glyph unicode="&#x20ac;" d="M100 500l100 100h113q0 47 5 100h-218l100 100h135q37 167 112 257q117 141 297 141q242 0 354 -189q60 -103 66 -209h-181q0 55 -25.5 99t-63.5 68t-75 36.5t-67 12.5q-24 0 -52.5 -10t-62.5 -32t-65.5 -67t-50.5 -107h379l-100 -100h-300q-6 -46 -6 -100h406l-100 -100 h-300q9 -74 33 -132t52.5 -91t62 -54.5t59 -29t46.5 -7.5q29 0 66 13t75 37t63.5 67.5t25.5 96.5h174q-31 -172 -128 -278q-107 -117 -274 -117q-205 0 -324 158q-36 46 -69 131.5t-45 205.5h-217z" />
<glyph unicode="&#x2212;" d="M200 400h900v300h-900v-300z" />
<glyph unicode="&#x25fc;" horiz-adv-x="500" d="M0 0z" />
<glyph unicode="&#x2601;" d="M-14 494q0 -80 56.5 -137t135.5 -57h750q120 0 205 86.5t85 207.5t-85 207t-205 86q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5z" />
<glyph unicode="&#x2709;" d="M0 100l400 400l200 -200l200 200l400 -400h-1200zM0 300v600l300 -300zM0 1100l600 -603l600 603h-1200zM900 600l300 300v-600z" />
<glyph unicode="&#x270f;" d="M-13 -13l333 112l-223 223zM187 403l214 -214l614 614l-214 214zM887 1103l214 -214l99 92q13 13 13 32.5t-13 33.5l-153 153q-15 13 -33 13t-33 -13z" />
<glyph unicode="&#xe001;" d="M0 1200h1200l-500 -550v-550h300v-100h-800v100h300v550z" />
<glyph unicode="&#xe002;" d="M14 84q18 -55 86 -75.5t147 5.5q65 21 109 69t44 90v606l600 155v-521q-64 16 -138 -7q-79 -26 -122.5 -83t-25.5 -111q18 -55 86 -75.5t147 4.5q70 23 111.5 63.5t41.5 95.5v881q0 10 -7 15.5t-17 2.5l-752 -193q-10 -3 -17 -12.5t-7 -19.5v-689q-64 17 -138 -7 q-79 -25 -122.5 -82t-25.5 -112z" />
<glyph unicode="&#xe003;" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233z" />
<glyph unicode="&#xe005;" d="M100 784q0 64 28 123t73 100.5t104.5 64t119 20.5t120 -38.5t104.5 -104.5q48 69 109.5 105t121.5 38t118.5 -20.5t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-149.5 152.5t-126.5 127.5 t-94 124.5t-33.5 117.5z" />
<glyph unicode="&#xe006;" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1z" />
<glyph unicode="&#xe007;" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1zM237 700l196 -142l-73 -226l192 140l195 -141l-74 229l193 140h-235l-77 211l-78 -211h-239z" />
<glyph unicode="&#xe008;" d="M0 0v143l400 257v100q-37 0 -68.5 74.5t-31.5 125.5v200q0 124 88 212t212 88t212 -88t88 -212v-200q0 -51 -31.5 -125.5t-68.5 -74.5v-100l400 -257v-143h-1200z" />
<glyph unicode="&#xe009;" d="M0 0v1100h1200v-1100h-1200zM100 100h100v100h-100v-100zM100 300h100v100h-100v-100zM100 500h100v100h-100v-100zM100 700h100v100h-100v-100zM100 900h100v100h-100v-100zM300 100h600v400h-600v-400zM300 600h600v400h-600v-400zM1000 100h100v100h-100v-100z M1000 300h100v100h-100v-100zM1000 500h100v100h-100v-100zM1000 700h100v100h-100v-100zM1000 900h100v100h-100v-100z" />
<glyph unicode="&#xe010;" d="M0 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM0 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5zM600 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM600 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5z" />
<glyph unicode="&#xe011;" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 450v200q0 21 14.5 35.5t35.5 14.5h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5z" />
<glyph unicode="&#xe012;" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v200q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5 t-14.5 -35.5v-200zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5z" />
<glyph unicode="&#xe013;" d="M29 454l419 -420l818 820l-212 212l-607 -607l-206 207z" />
<glyph unicode="&#xe014;" d="M106 318l282 282l-282 282l212 212l282 -282l282 282l212 -212l-282 -282l282 -282l-212 -212l-282 282l-282 -282z" />
<glyph unicode="&#xe015;" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233zM300 600v200h100v100h200v-100h100v-200h-100v-100h-200v100h-100z" />
<glyph unicode="&#xe016;" d="M23 694q0 200 142 342t342 142t342 -142t142 -342q0 -141 -78 -262l300 -299q7 -7 7 -18t-7 -18l-109 -109q-8 -8 -18 -8t-18 8l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 694q0 -136 97 -233t234 -97t233.5 97t96.5 233t-96.5 233t-233.5 97t-234 -97 t-97 -233zM300 601h400v200h-400v-200z" />
<glyph unicode="&#xe017;" d="M23 600q0 183 105 331t272 210v-166q-103 -55 -165 -155t-62 -220q0 -177 125 -302t302 -125t302 125t125 302q0 120 -62 220t-165 155v166q167 -62 272 -210t105 -331q0 -118 -45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5 zM500 750q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v400q0 21 -14.5 35.5t-35.5 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-400z" />
<glyph unicode="&#xe018;" d="M100 1h200v300h-200v-300zM400 1v500h200v-500h-200zM700 1v800h200v-800h-200zM1000 1v1200h200v-1200h-200z" />
<glyph unicode="&#xe019;" d="M26 601q0 -33 6 -74l151 -38l2 -6q14 -49 38 -93l3 -5l-80 -134q45 -59 105 -105l133 81l5 -3q45 -26 94 -39l5 -2l38 -151q40 -5 74 -5q27 0 74 5l38 151l6 2q46 13 93 39l5 3l134 -81q56 44 104 105l-80 134l3 5q24 44 39 93l1 6l152 38q5 40 5 74q0 28 -5 73l-152 38 l-1 6q-16 51 -39 93l-3 5l80 134q-44 58 -104 105l-134 -81l-5 3q-45 25 -93 39l-6 1l-38 152q-40 5 -74 5q-27 0 -74 -5l-38 -152l-5 -1q-50 -14 -94 -39l-5 -3l-133 81q-59 -47 -105 -105l80 -134l-3 -5q-25 -47 -38 -93l-2 -6l-151 -38q-6 -48 -6 -73zM385 601 q0 88 63 151t152 63t152 -63t63 -151q0 -89 -63 -152t-152 -63t-152 63t-63 152z" />
<glyph unicode="&#xe020;" d="M100 1025v50q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-50q0 -11 -7 -18t-18 -7h-1050q-11 0 -18 7t-7 18zM200 100v800h900v-800q0 -41 -29.5 -71t-70.5 -30h-700q-41 0 -70.5 30 t-29.5 71zM300 100h100v700h-100v-700zM500 100h100v700h-100v-700zM500 1100h300v100h-300v-100zM700 100h100v700h-100v-700zM900 100h100v700h-100v-700z" />
<glyph unicode="&#xe021;" d="M1 601l656 644l644 -644h-200v-600h-300v400h-300v-400h-300v600h-200z" />
<glyph unicode="&#xe022;" d="M100 25v1150q0 11 7 18t18 7h475v-500h400v-675q0 -11 -7 -18t-18 -7h-850q-11 0 -18 7t-7 18zM700 800v300l300 -300h-300z" />
<glyph unicode="&#xe023;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 500v400h100 v-300h200v-100h-300z" />
<glyph unicode="&#xe024;" d="M-100 0l431 1200h209l-21 -300h162l-20 300h208l431 -1200h-538l-41 400h-242l-40 -400h-539zM488 500h224l-27 300h-170z" />
<glyph unicode="&#xe025;" d="M0 0v400h490l-290 300h200v500h300v-500h200l-290 -300h490v-400h-1100zM813 200h175v100h-175v-100z" />
<glyph unicode="&#xe026;" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM188 600q0 -170 121 -291t291 -121t291 121t121 291t-121 291t-291 121 t-291 -121t-121 -291zM350 600h150v300h200v-300h150l-250 -300z" />
<glyph unicode="&#xe027;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM350 600l250 300 l250 -300h-150v-300h-200v300h-150z" />
<glyph unicode="&#xe028;" d="M0 25v475l200 700h800l199 -700l1 -475q0 -11 -7 -18t-18 -7h-1150q-11 0 -18 7t-7 18zM200 500h200l50 -200h300l50 200h200l-97 500h-606z" />
<glyph unicode="&#xe029;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 397v401 l297 -200z" />
<glyph unicode="&#xe030;" d="M23 600q0 -118 45.5 -224.5t123 -184t184 -123t224.5 -45.5t224.5 45.5t184 123t123 184t45.5 224.5h-150q0 -177 -125 -302t-302 -125t-302 125t-125 302t125 302t302 125q136 0 246 -81l-146 -146h400v400l-145 -145q-157 122 -355 122q-118 0 -224.5 -45.5t-184 -123 t-123 -184t-45.5 -224.5z" />
<glyph unicode="&#xe031;" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5q198 0 355 -122l145 145v-400h-400l147 147q-112 80 -247 80q-177 0 -302 -125t-125 -302h-150zM100 0v400h400l-147 -147q112 -80 247 -80q177 0 302 125t125 302h150q0 -118 -45.5 -224.5t-123 -184t-184 -123 t-224.5 -45.5q-198 0 -355 122z" />
<glyph unicode="&#xe032;" d="M100 0h1100v1200h-1100v-1200zM200 100v900h900v-900h-900zM300 200v100h100v-100h-100zM300 400v100h100v-100h-100zM300 600v100h100v-100h-100zM300 800v100h100v-100h-100zM500 200h500v100h-500v-100zM500 400v100h500v-100h-500zM500 600v100h500v-100h-500z M500 800v100h500v-100h-500z" />
<glyph unicode="&#xe033;" d="M0 100v600q0 41 29.5 70.5t70.5 29.5h100v200q0 82 59 141t141 59h300q82 0 141 -59t59 -141v-200h100q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-900q-41 0 -70.5 29.5t-29.5 70.5zM400 800h300v150q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-150z" />
<glyph unicode="&#xe034;" d="M100 0v1100h100v-1100h-100zM300 400q60 60 127.5 84t127.5 17.5t122 -23t119 -30t110 -11t103 42t91 120.5v500q-40 -81 -101.5 -115.5t-127.5 -29.5t-138 25t-139.5 40t-125.5 25t-103 -29.5t-65 -115.5v-500z" />
<glyph unicode="&#xe035;" d="M0 275q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 127 70.5 231.5t184.5 161.5t245 57t245 -57t184.5 -161.5t70.5 -231.5v-300q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 116 -49.5 227t-131 192.5t-192.5 131t-227 49.5t-227 -49.5t-192.5 -131t-131 -192.5 t-49.5 -227v-300zM200 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14zM800 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14z" />
<glyph unicode="&#xe036;" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM688 459l141 141l-141 141l71 71l141 -141l141 141l71 -71l-141 -141l141 -141l-71 -71l-141 141l-141 -141z" />
<glyph unicode="&#xe037;" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM700 857l69 53q111 -135 111 -310q0 -169 -106 -302l-67 54q86 110 86 248q0 146 -93 257z" />
<glyph unicode="&#xe038;" d="M0 401v400h300l300 200v-800l-300 200h-300zM702 858l69 53q111 -135 111 -310q0 -170 -106 -303l-67 55q86 110 86 248q0 145 -93 257zM889 951l7 -8q123 -151 123 -344q0 -189 -119 -339l-7 -8l81 -66l6 8q142 178 142 405q0 230 -144 408l-6 8z" />
<glyph unicode="&#xe039;" d="M0 0h500v500h-200v100h-100v-100h-200v-500zM0 600h100v100h400v100h100v100h-100v300h-500v-600zM100 100v300h300v-300h-300zM100 800v300h300v-300h-300zM200 200v100h100v-100h-100zM200 900h100v100h-100v-100zM500 500v100h300v-300h200v-100h-100v-100h-200v100 h-100v100h100v200h-200zM600 0v100h100v-100h-100zM600 1000h100v-300h200v-300h300v200h-200v100h200v500h-600v-200zM800 800v300h300v-300h-300zM900 0v100h300v-100h-300zM900 900v100h100v-100h-100zM1100 200v100h100v-100h-100z" />
<glyph unicode="&#xe040;" d="M0 200h100v1000h-100v-1000zM100 0v100h300v-100h-300zM200 200v1000h100v-1000h-100zM500 0v91h100v-91h-100zM500 200v1000h200v-1000h-200zM700 0v91h100v-91h-100zM800 200v1000h100v-1000h-100zM900 0v91h200v-91h-200zM1000 200v1000h200v-1000h-200z" />
<glyph unicode="&#xe041;" d="M0 700l1 475q0 10 7.5 17.5t17.5 7.5h474l700 -700l-500 -500zM148 953q0 -42 29 -71q30 -30 71.5 -30t71.5 30q29 29 29 71t-29 71q-30 30 -71.5 30t-71.5 -30q-29 -29 -29 -71z" />
<glyph unicode="&#xe042;" d="M1 700l1 475q0 11 7 18t18 7h474l700 -700l-500 -500zM148 953q0 -42 30 -71q29 -30 71 -30t71 30q30 29 30 71t-30 71q-29 30 -71 30t-71 -30q-30 -29 -30 -71zM701 1200h100l700 -700l-500 -500l-50 50l450 450z" />
<glyph unicode="&#xe043;" d="M100 0v1025l175 175h925v-1000l-100 -100v1000h-750l-100 -100h750v-1000h-900z" />
<glyph unicode="&#xe044;" d="M200 0l450 444l450 -443v1150q0 20 -14.5 35t-35.5 15h-800q-21 0 -35.5 -15t-14.5 -35v-1151z" />
<glyph unicode="&#xe045;" d="M0 100v700h200l100 -200h600l100 200h200v-700h-200v200h-800v-200h-200zM253 829l40 -124h592l62 124l-94 346q-2 11 -10 18t-18 7h-450q-10 0 -18 -7t-10 -18zM281 24l38 152q2 10 11.5 17t19.5 7h500q10 0 19.5 -7t11.5 -17l38 -152q2 -10 -3.5 -17t-15.5 -7h-600 q-10 0 -15.5 7t-3.5 17z" />
<glyph unicode="&#xe046;" d="M0 200q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-150q-4 8 -11.5 21.5t-33 48t-53 61t-69 48t-83.5 21.5h-200q-41 0 -82 -20.5t-70 -50t-52 -59t-34 -50.5l-12 -20h-150q-41 0 -70.5 -29.5t-29.5 -70.5v-600z M356 500q0 100 72 172t172 72t172 -72t72 -172t-72 -172t-172 -72t-172 72t-72 172zM494 500q0 -44 31 -75t75 -31t75 31t31 75t-31 75t-75 31t-75 -31t-31 -75zM900 700v100h100v-100h-100z" />
<glyph unicode="&#xe047;" d="M53 0h365v66q-41 0 -72 11t-49 38t1 71l92 234h391l82 -222q16 -45 -5.5 -88.5t-74.5 -43.5v-66h417v66q-34 1 -74 43q-18 19 -33 42t-21 37l-6 13l-385 998h-93l-399 -1006q-24 -48 -52 -75q-12 -12 -33 -25t-36 -20l-15 -7v-66zM416 521l178 457l46 -140l116 -317h-340 z" />
<glyph unicode="&#xe048;" d="M100 0v89q41 7 70.5 32.5t29.5 65.5v827q0 28 -1 39.5t-5.5 26t-15.5 21t-29 14t-49 14.5v71l471 -1q120 0 213 -88t93 -228q0 -55 -11.5 -101.5t-28 -74t-33.5 -47.5t-28 -28l-12 -7q8 -3 21.5 -9t48 -31.5t60.5 -58t47.5 -91.5t21.5 -129q0 -84 -59 -156.5t-142 -111 t-162 -38.5h-500zM400 200h161q89 0 153 48.5t64 132.5q0 90 -62.5 154.5t-156.5 64.5h-159v-400zM400 700h139q76 0 130 61.5t54 138.5q0 82 -84 130.5t-239 48.5v-379z" />
<glyph unicode="&#xe049;" d="M200 0v57q77 7 134.5 40.5t65.5 80.5l173 849q10 56 -10 74t-91 37q-6 1 -10.5 2.5t-9.5 2.5v57h425l2 -57q-33 -8 -62 -25.5t-46 -37t-29.5 -38t-17.5 -30.5l-5 -12l-128 -825q-10 -52 14 -82t95 -36v-57h-500z" />
<glyph unicode="&#xe050;" d="M-75 200h75v800h-75l125 167l125 -167h-75v-800h75l-125 -167zM300 900v300h150h700h150v-300h-50q0 29 -8 48.5t-18.5 30t-33.5 15t-39.5 5.5t-50.5 1h-200v-850l100 -50v-100h-400v100l100 50v850h-200q-34 0 -50.5 -1t-40 -5.5t-33.5 -15t-18.5 -30t-8.5 -48.5h-49z " />
<glyph unicode="&#xe051;" d="M33 51l167 125v-75h800v75l167 -125l-167 -125v75h-800v-75zM100 901v300h150h700h150v-300h-50q0 29 -8 48.5t-18 30t-33.5 15t-40 5.5t-50.5 1h-200v-650l100 -50v-100h-400v100l100 50v650h-200q-34 0 -50.5 -1t-39.5 -5.5t-33.5 -15t-18.5 -30t-8 -48.5h-50z" />
<glyph unicode="&#xe052;" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 350q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM0 650q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1000q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 950q0 -20 14.5 -35t35.5 -15h600q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-600q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
<glyph unicode="&#xe053;" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 650q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM200 350q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM200 950q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
<glyph unicode="&#xe054;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1000q-21 0 -35.5 15 t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-600 q-21 0 -35.5 15t-14.5 35z" />
<glyph unicode="&#xe055;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100 q-21 0 -35.5 15t-14.5 35z" />
<glyph unicode="&#xe056;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM300 50v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800 q-21 0 -35.5 15t-14.5 35zM300 650v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 950v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15 h-800q-21 0 -35.5 15t-14.5 35z" />
<glyph unicode="&#xe057;" d="M-101 500v100h201v75l166 -125l-166 -125v75h-201zM300 0h100v1100h-100v-1100zM500 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35 v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 650q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100 q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100z" />
<glyph unicode="&#xe058;" d="M1 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 650 q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM801 0v1100h100v-1100 h-100zM934 550l167 -125v75h200v100h-200v75z" />
<glyph unicode="&#xe059;" d="M0 275v650q0 31 22 53t53 22h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53zM900 600l300 300v-600z" />
<glyph unicode="&#xe060;" d="M0 44v1012q0 18 13 31t31 13h1112q19 0 31.5 -13t12.5 -31v-1012q0 -18 -12.5 -31t-31.5 -13h-1112q-18 0 -31 13t-13 31zM100 263l247 182l298 -131l-74 156l293 318l236 -288v500h-1000v-737zM208 750q0 56 39 95t95 39t95 -39t39 -95t-39 -95t-95 -39t-95 39t-39 95z " />
<glyph unicode="&#xe062;" d="M148 745q0 124 60.5 231.5t165 172t226.5 64.5q123 0 227 -63t164.5 -169.5t60.5 -229.5t-73 -272q-73 -114 -166.5 -237t-150.5 -189l-57 -66q-10 9 -27 26t-66.5 70.5t-96 109t-104 135.5t-100.5 155q-63 139 -63 262zM342 772q0 -107 75.5 -182.5t181.5 -75.5 q107 0 182.5 75.5t75.5 182.5t-75.5 182t-182.5 75t-182 -75.5t-75 -181.5z" />
<glyph unicode="&#xe063;" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM173 600q0 -177 125.5 -302t301.5 -125v854q-176 0 -301.5 -125 t-125.5 -302z" />
<glyph unicode="&#xe064;" d="M117 406q0 94 34 186t88.5 172.5t112 159t115 177t87.5 194.5q21 -71 57.5 -142.5t76 -130.5t83 -118.5t82 -117t70 -116t50 -125.5t18.5 -136q0 -89 -39 -165.5t-102 -126.5t-140 -79.5t-156 -33.5q-114 6 -211.5 53t-161.5 139t-64 210zM243 414q14 -82 59.5 -136 t136.5 -80l16 98q-7 6 -18 17t-34 48t-33 77q-15 73 -14 143.5t10 122.5l9 51q-92 -110 -119.5 -185t-12.5 -156z" />
<glyph unicode="&#xe065;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5q366 -6 397 -14l-186 -186h-311q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v125l200 200v-225q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM436 341l161 50l412 412l-114 113l-405 -405zM995 1015l113 -113l113 113l-21 85l-92 28z" />
<glyph unicode="&#xe066;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h261l2 -80q-133 -32 -218 -120h-145q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-53q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5 zM423 524q30 38 81.5 64t103 35.5t99 14t77.5 3.5l29 -1v-209l360 324l-359 318v-216q-7 0 -19 -1t-48 -8t-69.5 -18.5t-76.5 -37t-76.5 -59t-62 -88t-39.5 -121.5z" />
<glyph unicode="&#xe067;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q61 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-169q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM342 632l283 -284l567 567l-137 137l-430 -431l-146 147z" />
<glyph unicode="&#xe068;" d="M0 603l300 296v-198h200v200h-200l300 300l295 -300h-195v-200h200v198l300 -296l-300 -300v198h-200v-200h195l-295 -300l-300 300h200v200h-200v-198z" />
<glyph unicode="&#xe069;" d="M200 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-1100l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
<glyph unicode="&#xe070;" d="M0 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-487l500 487v-1100l-500 488v-488l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
<glyph unicode="&#xe071;" d="M136 550l564 550v-487l500 487v-1100l-500 488v-488z" />
<glyph unicode="&#xe072;" d="M200 0l900 550l-900 550v-1100z" />
<glyph unicode="&#xe073;" d="M200 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-800zM600 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
<glyph unicode="&#xe074;" d="M200 150q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v800q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
<glyph unicode="&#xe075;" d="M0 0v1100l500 -487v487l564 -550l-564 -550v488z" />
<glyph unicode="&#xe076;" d="M0 0v1100l500 -487v487l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-500 -488v488z" />
<glyph unicode="&#xe077;" d="M300 0v1100l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438z" />
<glyph unicode="&#xe078;" d="M100 250v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5zM100 500h1100l-550 564z" />
<glyph unicode="&#xe079;" d="M185 599l592 -592l240 240l-353 353l353 353l-240 240z" />
<glyph unicode="&#xe080;" d="M272 194l353 353l-353 353l241 240l572 -571l21 -22l-1 -1v-1l-592 -591z" />
<glyph unicode="&#xe081;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM300 500h200v-200h200v200h200v200h-200v200h-200v-200h-200v-200z" />
<glyph unicode="&#xe082;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM300 500h600v200h-600v-200z" />
<glyph unicode="&#xe083;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM246 459l213 -213l141 142l141 -142l213 213l-142 141l142 141l-213 212l-141 -141l-141 142l-212 -213l141 -141 z" />
<glyph unicode="&#xe084;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM270 551l276 -277l411 411l-175 174l-236 -236l-102 102z" />
<glyph unicode="&#xe085;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM364 700h143q4 0 11.5 -1t11 -1t6.5 3t3 9t1 11t3.5 8.5t3.5 6t5.5 4t6.5 2.5t9 1.5t9 0.5h11.5h12.5 q19 0 30 -10t11 -26q0 -22 -4 -28t-27 -22q-5 -1 -12.5 -3t-27 -13.5t-34 -27t-26.5 -46t-11 -68.5h200q5 3 14 8t31.5 25.5t39.5 45.5t31 69t14 94q0 51 -17.5 89t-42 58t-58.5 32t-58.5 15t-51.5 3q-50 0 -90.5 -12t-75 -38.5t-53.5 -74.5t-19 -114zM500 300h200v100h-200 v-100z" />
<glyph unicode="&#xe086;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM400 300h400v100h-100v300h-300v-100h100v-200h-100v-100zM500 800h200v100h-200v-100z" />
<glyph unicode="&#xe087;" d="M0 500v200h195q31 125 98.5 199.5t206.5 100.5v200h200v-200q54 -20 113 -60t112.5 -105.5t71.5 -134.5h203v-200h-203q-25 -102 -116.5 -186t-180.5 -117v-197h-200v197q-140 27 -208 102.5t-98 200.5h-194zM290 500q24 -73 79.5 -127.5t130.5 -78.5v206h200v-206 q149 48 201 206h-201v200h200q-25 74 -75.5 127t-124.5 77v-204h-200v203q-75 -23 -130 -77t-79 -126h209v-200h-210z" />
<glyph unicode="&#xe088;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM356 465l135 135 l-135 135l109 109l135 -135l135 135l109 -109l-135 -135l135 -135l-109 -109l-135 135l-135 -135z" />
<glyph unicode="&#xe089;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM322 537l141 141 l87 -87l204 205l142 -142l-346 -345z" />
<glyph unicode="&#xe090;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -115 62 -215l568 567q-100 62 -216 62q-171 0 -292.5 -121.5t-121.5 -292.5zM391 245q97 -59 209 -59q171 0 292.5 121.5t121.5 292.5 q0 112 -59 209z" />
<glyph unicode="&#xe091;" d="M0 547l600 453v-300h600v-300h-600v-301z" />
<glyph unicode="&#xe092;" d="M0 400v300h600v300l600 -453l-600 -448v301h-600z" />
<glyph unicode="&#xe093;" d="M204 600l450 600l444 -600h-298v-600h-300v600h-296z" />
<glyph unicode="&#xe094;" d="M104 600h296v600h300v-600h298l-449 -600z" />
<glyph unicode="&#xe095;" d="M0 200q6 132 41 238.5t103.5 193t184 138t271.5 59.5v271l600 -453l-600 -448v301q-95 -2 -183 -20t-170 -52t-147 -92.5t-100 -135.5z" />
<glyph unicode="&#xe096;" d="M0 0v400l129 -129l294 294l142 -142l-294 -294l129 -129h-400zM635 777l142 -142l294 294l129 -129v400h-400l129 -129z" />
<glyph unicode="&#xe097;" d="M34 176l295 295l-129 129h400v-400l-129 130l-295 -295zM600 600v400l129 -129l295 295l142 -141l-295 -295l129 -130h-400z" />
<glyph unicode="&#xe101;" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM456 851l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5 t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5h-207q-21 0 -33 -14.5t-8 -34.5zM500 300h200v100h-200v-100z" />
<glyph unicode="&#xe102;" d="M0 800h100v-200h400v300h200v-300h400v200h100v100h-111q1 1 1 6.5t-1.5 15t-3.5 17.5l-34 172q-11 39 -41.5 63t-69.5 24q-32 0 -61 -17l-239 -144q-22 -13 -40 -35q-19 24 -40 36l-238 144q-33 18 -62 18q-39 0 -69.5 -23t-40.5 -61l-35 -177q-2 -8 -3 -18t-1 -15v-6 h-111v-100zM100 0h400v400h-400v-400zM200 900q-3 0 14 48t36 96l18 47l213 -191h-281zM700 0v400h400v-400h-400zM731 900l202 197q5 -12 12 -32.5t23 -64t25 -72t7 -28.5h-269z" />
<glyph unicode="&#xe103;" d="M0 -22v143l216 193q-9 53 -13 83t-5.5 94t9 113t38.5 114t74 124q47 60 99.5 102.5t103 68t127.5 48t145.5 37.5t184.5 43.5t220 58.5q0 -189 -22 -343t-59 -258t-89 -181.5t-108.5 -120t-122 -68t-125.5 -30t-121.5 -1.5t-107.5 12.5t-87.5 17t-56.5 7.5l-99 -55z M238.5 300.5q19.5 -6.5 86.5 76.5q55 66 367 234q70 38 118.5 69.5t102 79t99 111.5t86.5 148q22 50 24 60t-6 19q-7 5 -17 5t-26.5 -14.5t-33.5 -39.5q-35 -51 -113.5 -108.5t-139.5 -89.5l-61 -32q-369 -197 -458 -401q-48 -111 -28.5 -117.5z" />
<glyph unicode="&#xe104;" d="M111 408q0 -33 5 -63q9 -56 44 -119.5t105 -108.5q31 -21 64 -16t62 23.5t57 49.5t48 61.5t35 60.5q32 66 39 184.5t-13 157.5q79 -80 122 -164t26 -184q-5 -33 -20.5 -69.5t-37.5 -80.5q-10 -19 -14.5 -29t-12 -26t-9 -23.5t-3 -19t2.5 -15.5t11 -9.5t19.5 -5t30.5 2.5 t42 8q57 20 91 34t87.5 44.5t87 64t65.5 88.5t47 122q38 172 -44.5 341.5t-246.5 278.5q22 -44 43 -129q39 -159 -32 -154q-15 2 -33 9q-79 33 -120.5 100t-44 175.5t48.5 257.5q-13 -8 -34 -23.5t-72.5 -66.5t-88.5 -105.5t-60 -138t-8 -166.5q2 -12 8 -41.5t8 -43t6 -39.5 t3.5 -39.5t-1 -33.5t-6 -31.5t-13.5 -24t-21 -20.5t-31 -12q-38 -10 -67 13t-40.5 61.5t-15 81.5t10.5 75q-52 -46 -83.5 -101t-39 -107t-7.5 -85z" />
<glyph unicode="&#xe105;" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5t145.5 -23.5t132.5 -59t116.5 -83.5t97 -90t74.5 -85.5t49 -63.5t20 -30l26 -40l-26 -40q-6 -10 -20 -30t-49 -63.5t-74.5 -85.5t-97 -90t-116.5 -83.5t-132.5 -59t-145.5 -23.5 t-145.5 23.5t-132.5 59t-116.5 83.5t-97 90t-74.5 85.5t-49 63.5t-20 30zM120 600q7 -10 40.5 -58t56 -78.5t68 -77.5t87.5 -75t103 -49.5t125 -21.5t123.5 20t100.5 45.5t85.5 71.5t66.5 75.5t58 81.5t47 66q-1 1 -28.5 37.5t-42 55t-43.5 53t-57.5 63.5t-58.5 54 q49 -74 49 -163q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l105 105q-37 24 -75 72t-57 84l-20 36z" />
<glyph unicode="&#xe106;" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5q61 0 121 -17l37 142h148l-314 -1200h-148l37 143q-82 21 -165 71.5t-140 102t-109.5 112t-72 88.5t-29.5 43zM120 600q210 -282 393 -336l37 141q-107 18 -178.5 101.5t-71.5 193.5 q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l47 47l23 87q-30 28 -59 69t-44 68l-14 26zM780 161l38 145q22 15 44.5 34t46 44t40.5 44t41 50.5t33.5 43.5t33 44t24.5 34q-97 127 -140 175l39 146q67 -54 131.5 -125.5t87.5 -103.5t36 -52l26 -40l-26 -40 q-7 -12 -25.5 -38t-63.5 -79.5t-95.5 -102.5t-124 -100t-146.5 -79z" />
<glyph unicode="&#xe107;" d="M-97.5 34q13.5 -34 50.5 -34h1294q37 0 50.5 35.5t-7.5 67.5l-642 1056q-20 34 -48 36.5t-48 -29.5l-642 -1066q-21 -32 -7.5 -66zM155 200l445 723l445 -723h-345v100h-200v-100h-345zM500 600l100 -300l100 300v100h-200v-100z" />
<glyph unicode="&#xe108;" d="M100 262v41q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44t106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -91 100 -113v-64q0 -20 -13 -28.5t-32 0.5l-94 78h-222l-94 -78q-19 -9 -32 -0.5t-13 28.5 v64q0 22 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5z" />
<glyph unicode="&#xe109;" d="M0 50q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v750h-1100v-750zM0 900h1100v150q0 21 -14.5 35.5t-35.5 14.5h-150v100h-100v-100h-500v100h-100v-100h-150q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 100v100h100v-100h-100zM100 300v100h100v-100h-100z M100 500v100h100v-100h-100zM300 100v100h100v-100h-100zM300 300v100h100v-100h-100zM300 500v100h100v-100h-100zM500 100v100h100v-100h-100zM500 300v100h100v-100h-100zM500 500v100h100v-100h-100zM700 100v100h100v-100h-100zM700 300v100h100v-100h-100zM700 500 v100h100v-100h-100zM900 100v100h100v-100h-100zM900 300v100h100v-100h-100zM900 500v100h100v-100h-100z" />
<glyph unicode="&#xe110;" d="M0 200v200h259l600 600h241v198l300 -295l-300 -300v197h-159l-600 -600h-341zM0 800h259l122 -122l141 142l-181 180h-341v-200zM678 381l141 142l122 -123h159v198l300 -295l-300 -300v197h-241z" />
<glyph unicode="&#xe111;" d="M0 400v600q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5z" />
<glyph unicode="&#xe112;" d="M100 600v200h300v-250q0 -113 6 -145q17 -92 102 -117q39 -11 92 -11q37 0 66.5 5.5t50 15.5t36 24t24 31.5t14 37.5t7 42t2.5 45t0 47v25v250h300v-200q0 -42 -3 -83t-15 -104t-31.5 -116t-58 -109.5t-89 -96.5t-129 -65.5t-174.5 -25.5t-174.5 25.5t-129 65.5t-89 96.5 t-58 109.5t-31.5 116t-15 104t-3 83zM100 900v300h300v-300h-300zM800 900v300h300v-300h-300z" />
<glyph unicode="&#xe113;" d="M-30 411l227 -227l352 353l353 -353l226 227l-578 579z" />
<glyph unicode="&#xe114;" d="M70 797l580 -579l578 579l-226 227l-353 -353l-352 353z" />
<glyph unicode="&#xe115;" d="M-198 700l299 283l300 -283h-203v-400h385l215 -200h-800v600h-196zM402 1000l215 -200h381v-400h-198l299 -283l299 283h-200v600h-796z" />
<glyph unicode="&#xe116;" d="M18 939q-5 24 10 42q14 19 39 19h896l38 162q5 17 18.5 27.5t30.5 10.5h94q20 0 35 -14.5t15 -35.5t-15 -35.5t-35 -14.5h-54l-201 -961q-2 -4 -6 -10.5t-19 -17.5t-33 -11h-31v-50q0 -20 -14.5 -35t-35.5 -15t-35.5 15t-14.5 35v50h-300v-50q0 -20 -14.5 -35t-35.5 -15 t-35.5 15t-14.5 35v50h-50q-21 0 -35.5 15t-14.5 35q0 21 14.5 35.5t35.5 14.5h535l48 200h-633q-32 0 -54.5 21t-27.5 43z" />
<glyph unicode="&#xe117;" d="M0 0v800h1200v-800h-1200zM0 900v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-100h-1200z" />
<glyph unicode="&#xe118;" d="M1 0l300 700h1200l-300 -700h-1200zM1 400v600h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-200h-1000z" />
<glyph unicode="&#xe119;" d="M302 300h198v600h-198l298 300l298 -300h-198v-600h198l-298 -300z" />
<glyph unicode="&#xe120;" d="M0 600l300 298v-198h600v198l300 -298l-300 -297v197h-600v-197z" />
<glyph unicode="&#xe121;" d="M0 100v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM31 400l172 739q5 22 23 41.5t38 19.5h672q19 0 37.5 -22.5t23.5 -45.5l172 -732h-1138zM800 100h100v100h-100v-100z M1000 100h100v100h-100v-100z" />
<glyph unicode="&#xe122;" d="M-101 600v50q0 24 25 49t50 38l25 13v-250l-11 5.5t-24 14t-30 21.5t-24 27.5t-11 31.5zM100 500v250v8v8v7t0.5 7t1.5 5.5t2 5t3 4t4.5 3.5t6 1.5t7.5 0.5h200l675 250v-850l-675 200h-38l47 -276q2 -12 -3 -17.5t-11 -6t-21 -0.5h-8h-83q-20 0 -34.5 14t-18.5 35 q-55 337 -55 351zM1100 200v850q0 21 14.5 35.5t35.5 14.5q20 0 35 -14.5t15 -35.5v-850q0 -20 -15 -35t-35 -15q-21 0 -35.5 15t-14.5 35z" />
<glyph unicode="&#xe123;" d="M74 350q0 21 13.5 35.5t33.5 14.5h18l117 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3 32t29 13h94q20 0 29 -10.5t3 -29.5q-18 -36 -18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q20 0 33.5 -14.5t13.5 -35.5q0 -20 -13 -40t-31 -27q-8 -3 -23 -8.5 t-65 -20t-103 -25t-132.5 -19.5t-158.5 -9q-125 0 -245.5 20.5t-178.5 40.5l-58 20q-18 7 -31 27.5t-13 40.5zM497 110q12 -49 40 -79.5t63 -30.5t63 30.5t39 79.5q-48 -6 -102 -6t-103 6z" />
<glyph unicode="&#xe124;" d="M21 445l233 -45l-78 -224l224 78l45 -233l155 179l155 -179l45 233l224 -78l-78 224l234 45l-180 155l180 156l-234 44l78 225l-224 -78l-45 233l-155 -180l-155 180l-45 -233l-224 78l78 -225l-233 -44l179 -156z" />
<glyph unicode="&#xe125;" d="M0 200h200v600h-200v-600zM300 275q0 -75 100 -75h61q124 -100 139 -100h250q46 0 83 57l238 344q29 31 29 74v100q0 44 -30.5 84.5t-69.5 40.5h-328q28 118 28 125v150q0 44 -30.5 84.5t-69.5 40.5h-50q-27 0 -51 -20t-38 -48l-96 -198l-145 -196q-20 -26 -20 -63v-400z M400 300v375l150 213l100 212h50v-175l-50 -225h450v-125l-250 -375h-214l-136 100h-100z" />
<glyph unicode="&#xe126;" d="M0 400v600h200v-600h-200zM300 525v400q0 75 100 75h61q124 100 139 100h250q46 0 83 -57l238 -344q29 -31 29 -74v-100q0 -44 -30.5 -84.5t-69.5 -40.5h-328q28 -118 28 -125v-150q0 -44 -30.5 -84.5t-69.5 -40.5h-50q-27 0 -51 20t-38 48l-96 198l-145 196 q-20 26 -20 63zM400 525l150 -212l100 -213h50v175l-50 225h450v125l-250 375h-214l-136 -100h-100v-375z" />
<glyph unicode="&#xe127;" d="M8 200v600h200v-600h-200zM308 275v525q0 17 14 35.5t28 28.5l14 9l362 230q14 6 25 6q17 0 29 -12l109 -112q14 -14 14 -34q0 -18 -11 -32l-85 -121h302q85 0 138.5 -38t53.5 -110t-54.5 -111t-138.5 -39h-107l-130 -339q-7 -22 -20.5 -41.5t-28.5 -19.5h-341 q-7 0 -90 81t-83 94zM408 289l100 -89h293l131 339q6 21 19.5 41t28.5 20h203q16 0 25 15t9 36q0 20 -9 34.5t-25 14.5h-457h-6.5h-7.5t-6.5 0.5t-6 1t-5 1.5t-5.5 2.5t-4 4t-4 5.5q-5 12 -5 20q0 14 10 27l147 183l-86 83l-339 -236v-503z" />
<glyph unicode="&#xe128;" d="M-101 651q0 72 54 110t139 38l302 -1l-85 121q-11 16 -11 32q0 21 14 34l109 113q13 12 29 12q11 0 25 -6l365 -230q7 -4 17 -10.5t26.5 -26t16.5 -36.5v-526q0 -13 -86 -93.5t-94 -80.5h-341q-16 0 -29.5 20t-19.5 41l-130 339h-107q-84 0 -139 39t-55 111zM-1 601h222 q15 0 28.5 -20.5t19.5 -40.5l131 -339h293l107 89v502l-343 237l-87 -83l145 -184q10 -11 10 -26q0 -11 -5 -20q-1 -3 -3.5 -5.5l-4 -4t-5 -2.5t-5.5 -1.5t-6.5 -1t-6.5 -0.5h-7.5h-6.5h-476v-100zM1000 201v600h200v-600h-200z" />
<glyph unicode="&#xe129;" d="M97 719l230 -363q4 -6 10.5 -15.5t26 -25t36.5 -15.5h525q13 0 94 83t81 90v342q0 15 -20 28.5t-41 19.5l-339 131v106q0 84 -39 139t-111 55t-110 -53.5t-38 -138.5v-302l-121 84q-15 12 -33.5 11.5t-32.5 -13.5l-112 -110q-22 -22 -6 -53zM172 739l83 86l183 -146 q22 -18 47 -5q3 1 5.5 3.5l4 4t2.5 5t1.5 5.5t1 6.5t0.5 6.5v7.5v6.5v456q0 22 25 31t50 -0.5t25 -30.5v-202q0 -16 20 -29.5t41 -19.5l339 -130v-294l-89 -100h-503zM400 0v200h600v-200h-600z" />
<glyph unicode="&#xe130;" d="M2 585q-16 -31 6 -53l112 -110q13 -13 32 -13.5t34 10.5l121 85q0 -51 -0.5 -153.5t-0.5 -148.5q0 -84 38.5 -138t110.5 -54t111 55t39 139v106l339 131q20 6 40.5 19.5t20.5 28.5v342q0 7 -81 90t-94 83h-525q-17 0 -35.5 -14t-28.5 -28l-10 -15zM77 565l236 339h503 l89 -100v-294l-340 -130q-20 -6 -40 -20t-20 -29v-202q0 -22 -25 -31t-50 0t-25 31v456v14.5t-1.5 11.5t-5 12t-9.5 7q-24 13 -46 -5l-184 -146zM305 1104v200h600v-200h-600z" />
<glyph unicode="&#xe131;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM298 701l2 -201h300l-2 -194l402 294l-402 298v-197h-300z" />
<glyph unicode="&#xe132;" d="M0 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t231.5 47.5q122 0 232.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-218 -217.5t-300 -80t-299.5 80t-217.5 217.5t-80 299.5zM200 600l402 -294l-2 194h300l2 201h-300v197z" />
<glyph unicode="&#xe133;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600h200v-300h200v300h200l-300 400z" />
<glyph unicode="&#xe134;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600l300 -400l300 400h-200v300h-200v-300h-200z" />
<glyph unicode="&#xe135;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM254 780q-8 -33 5.5 -92.5t7.5 -87.5q0 -9 17 -44t16 -60 q12 0 23 -5.5t23 -15t20 -13.5q24 -12 108 -42q22 -8 53 -31.5t59.5 -38.5t57.5 -11q8 -18 -15 -55t-20 -57q42 -71 87 -80q0 -6 -3 -15.5t-3.5 -14.5t4.5 -17q104 -3 221 112q30 29 47 47t34.5 49t20.5 62q-14 9 -37 9.5t-36 7.5q-14 7 -49 15t-52 19q-9 0 -39.5 -0.5 t-46.5 -1.5t-39 -6.5t-39 -16.5q-50 -35 -66 -12q-4 2 -3.5 25.5t0.5 25.5q-6 13 -26.5 17t-24.5 7q2 22 -2 41t-16.5 28t-38.5 -20q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q12 -19 32 -37.5t34 -27.5l14 -8q0 3 9.5 39.5t5.5 57.5 q-4 23 14.5 44.5t22.5 31.5q5 14 10 35t8.5 31t15.5 22.5t34 21.5q-6 18 10 37q8 0 23.5 -1.5t24.5 -1.5t20.5 4.5t20.5 15.5q-10 23 -30.5 42.5t-38 30t-49 26.5t-43.5 23q11 39 2 44q31 -13 58 -14.5t39 3.5l11 4q7 36 -16.5 53.5t-64.5 28.5t-56 23q-19 -3 -37 0 q-15 -12 -36.5 -21t-34.5 -12t-44 -8t-39 -6q-15 -3 -45.5 0.5t-45.5 -2.5q-21 -7 -52 -26.5t-34 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -90.5t-29.5 -79.5zM518 916q3 12 16 30t16 25q10 -10 18.5 -10t14 6t14.5 14.5t16 12.5q0 -24 17 -66.5t17 -43.5 q-9 2 -31 5t-36 5t-32 8t-30 14zM692 1003h1h-1z" />
<glyph unicode="&#xe136;" d="M0 164.5q0 21.5 15 37.5l600 599q-33 101 6 201.5t135 154.5q164 92 306 -9l-259 -138l145 -232l251 126q13 -175 -151 -267q-123 -70 -253 -23l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5z" />
<glyph unicode="&#xe137;" horiz-adv-x="1220" d="M0 196v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 596v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5zM0 996v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM600 596h500v100h-500v-100zM800 196h300v100h-300v-100zM900 996h200v100h-200v-100z" />
<glyph unicode="&#xe138;" d="M100 1100v100h1000v-100h-1000zM150 1000h900l-350 -500v-300l-200 -200v500z" />
<glyph unicode="&#xe139;" d="M0 200v200h1200v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500z M500 1000h200v100h-200v-100z" />
<glyph unicode="&#xe140;" d="M0 0v400l129 -129l200 200l142 -142l-200 -200l129 -129h-400zM0 800l129 129l200 -200l142 142l-200 200l129 129h-400v-400zM729 329l142 142l200 -200l129 129v-400h-400l129 129zM729 871l200 200l-129 129h400v-400l-129 129l-200 -200z" />
<glyph unicode="&#xe141;" d="M0 596q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 596q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM291 655 q0 23 15.5 38.5t38.5 15.5t39 -16t16 -38q0 -23 -16 -39t-39 -16q-22 0 -38 16t-16 39zM400 850q0 22 16 38.5t39 16.5q22 0 38 -16t16 -39t-16 -39t-38 -16q-23 0 -39 16.5t-16 38.5zM514 609q0 32 20.5 56.5t51.5 29.5l122 126l1 1q-9 14 -9 28q0 22 16 38.5t39 16.5 q22 0 38 -16t16 -39t-16 -39t-38 -16q-14 0 -29 10l-55 -145q17 -22 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-61.5 25.5t-25.5 61.5zM800 655q0 22 16 38t39 16t38.5 -15.5t15.5 -38.5t-16 -39t-38 -16q-23 0 -39 16t-16 39z" />
<glyph unicode="&#xe142;" d="M-40 375q-13 -95 35 -173q35 -57 94 -89t129 -32q63 0 119 28q33 16 65 40.5t52.5 45.5t59.5 64q40 44 57 61l394 394q35 35 47 84t-3 96q-27 87 -117 104q-20 2 -29 2q-46 0 -78.5 -16.5t-67.5 -51.5l-389 -396l-7 -7l69 -67l377 373q20 22 39 38q23 23 50 23 q38 0 53 -36q16 -39 -20 -75l-547 -547q-52 -52 -125 -52q-55 0 -100 33t-54 96q-5 35 2.5 66t31.5 63t42 50t56 54q24 21 44 41l348 348q52 52 82.5 79.5t84 54t107.5 26.5q25 0 48 -4q95 -17 154 -94.5t51 -175.5q-7 -101 -98 -192l-252 -249l-253 -256l7 -7l69 -60 l517 511q67 67 95 157t11 183q-16 87 -67 154t-130 103q-69 33 -152 33q-107 0 -197 -55q-40 -24 -111 -95l-512 -512q-68 -68 -81 -163z" />
<glyph unicode="&#xe143;" d="M80 784q0 131 98.5 229.5t230.5 98.5q143 0 241 -129q103 129 246 129q129 0 226 -98.5t97 -229.5q0 -46 -17.5 -91t-61 -99t-77 -89.5t-104.5 -105.5q-197 -191 -293 -322l-17 -23l-16 23q-43 58 -100 122.5t-92 99.5t-101 100q-71 70 -104.5 105.5t-77 89.5t-61 99 t-17.5 91zM250 784q0 -27 30.5 -70t61.5 -75.5t95 -94.5l22 -22q93 -90 190 -201q82 92 195 203l12 12q64 62 97.5 97t64.5 79t31 72q0 71 -48 119.5t-105 48.5q-74 0 -132 -83l-118 -171l-114 174q-51 80 -123 80q-60 0 -109.5 -49.5t-49.5 -118.5z" />
<glyph unicode="&#xe144;" d="M57 353q0 -95 66 -159l141 -142q68 -66 159 -66q93 0 159 66l283 283q66 66 66 159t-66 159l-141 141q-8 9 -19 17l-105 -105l212 -212l-389 -389l-247 248l95 95l-18 18q-46 45 -75 101l-55 -55q-66 -66 -66 -159zM269 706q0 -93 66 -159l141 -141q7 -7 19 -17l105 105 l-212 212l389 389l247 -247l-95 -96l18 -17q47 -49 77 -100l29 29q35 35 62.5 88t27.5 96q0 93 -66 159l-141 141q-66 66 -159 66q-95 0 -159 -66l-283 -283q-66 -64 -66 -159z" />
<glyph unicode="&#xe145;" d="M200 100v953q0 21 30 46t81 48t129 38t163 15t162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5zM300 300h600v700h-600v-700zM496 150q0 -43 30.5 -73.5t73.5 -30.5t73.5 30.5t30.5 73.5t-30.5 73.5t-73.5 30.5 t-73.5 -30.5t-30.5 -73.5z" />
<glyph unicode="&#xe146;" d="M0 0l303 380l207 208l-210 212h300l267 279l-35 36q-15 14 -15 35t15 35q14 15 35 15t35 -15l283 -282q15 -15 15 -36t-15 -35q-14 -15 -35 -15t-35 15l-36 35l-279 -267v-300l-212 210l-208 -207z" />
<glyph unicode="&#xe148;" d="M295 433h139q5 -77 48.5 -126.5t117.5 -64.5v335q-6 1 -15.5 4t-11.5 3q-46 14 -79 26.5t-72 36t-62.5 52t-40 72.5t-16.5 99q0 92 44 159.5t109 101t144 40.5v78h100v-79q38 -4 72.5 -13.5t75.5 -31.5t71 -53.5t51.5 -84t24.5 -118.5h-159q-8 72 -35 109.5t-101 50.5 v-307l64 -14q34 -7 64 -16.5t70 -31.5t67.5 -52t47.5 -80.5t20 -112.5q0 -139 -89 -224t-244 -96v-77h-100v78q-152 17 -237 104q-40 40 -52.5 93.5t-15.5 139.5zM466 889q0 -29 8 -51t16.5 -34t29.5 -22.5t31 -13.5t38 -10q7 -2 11 -3v274q-61 -8 -97.5 -37.5t-36.5 -102.5 zM700 237q170 18 170 151q0 64 -44 99.5t-126 60.5v-311z" />
<glyph unicode="&#xe149;" d="M100 600v100h166q-24 49 -44 104q-10 26 -14.5 55.5t-3 72.5t25 90t68.5 87q97 88 263 88q129 0 230 -89t101 -208h-153q0 52 -34 89.5t-74 51.5t-76 14q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -28 16.5 -69.5t28 -62.5t41.5 -72h241v-100h-197q8 -50 -2.5 -115 t-31.5 -94q-41 -59 -99 -113q35 11 84 18t70 7q33 1 103 -16t103 -17q76 0 136 30l50 -147q-41 -25 -80.5 -36.5t-59 -13t-61.5 -1.5q-23 0 -128 33t-155 29q-39 -4 -82 -17t-66 -25l-24 -11l-55 145l16.5 11t15.5 10t13.5 9.5t14.5 12t14.5 14t17.5 18.5q48 55 54 126.5 t-30 142.5h-221z" />
<glyph unicode="&#xe150;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM602 900l298 300l298 -300h-198v-900h-200v900h-198z" />
<glyph unicode="&#xe151;" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v200h100v-100h200v-100h-300zM700 400v100h300v-200h-99v-100h-100v100h99v100h-200zM700 700v500h300v-500h-100v100h-100v-100h-100zM801 900h100v200h-100v-200z" />
<glyph unicode="&#xe152;" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v500h300v-500h-100v100h-100v-100h-100zM700 700v200h100v-100h200v-100h-300zM700 1100v100h300v-200h-99v-100h-100v100h99v100h-200zM801 200h100v200h-100v-200z" />
<glyph unicode="&#xe153;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 100v400h300v-500h-100v100h-200zM800 1100v100h200v-500h-100v400h-100zM901 200h100v200h-100v-200z" />
<glyph unicode="&#xe154;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 400v100h200v-500h-100v400h-100zM800 800v400h300v-500h-100v100h-200zM901 900h100v200h-100v-200z" />
<glyph unicode="&#xe155;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h500v-200h-500zM700 400v200h400v-200h-400zM700 700v200h300v-200h-300zM700 1000v200h200v-200h-200z" />
<glyph unicode="&#xe156;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h200v-200h-200zM700 400v200h300v-200h-300zM700 700v200h400v-200h-400zM700 1000v200h500v-200h-500z" />
<glyph unicode="&#xe157;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q162 0 281 -118.5t119 -281.5v-300q0 -165 -118.5 -282.5t-281.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500z" />
<glyph unicode="&#xe158;" d="M0 400v300q0 163 119 281.5t281 118.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-163 0 -281.5 117.5t-118.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM400 300l333 250l-333 250v-500z" />
<glyph unicode="&#xe159;" d="M0 400v300q0 163 117.5 281.5t282.5 118.5h300q163 0 281.5 -119t118.5 -281v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 700l250 -333l250 333h-500z" />
<glyph unicode="&#xe160;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -162 -118.5 -281t-281.5 -119h-300q-165 0 -282.5 118.5t-117.5 281.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 400h500l-250 333z" />
<glyph unicode="&#xe161;" d="M0 400v300h300v200l400 -350l-400 -350v200h-300zM500 0v200h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-500v200h400q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-400z" />
<glyph unicode="&#xe162;" d="M217 519q8 -19 31 -19h302q-155 -438 -160 -458q-5 -21 4 -32l9 -8h9q14 0 26 15q11 13 274.5 321.5t264.5 308.5q14 19 5 36q-8 17 -31 17l-301 -1q1 4 78 219.5t79 227.5q2 15 -5 27l-9 9h-9q-15 0 -25 -16q-4 -6 -98 -111.5t-228.5 -257t-209.5 -237.5q-16 -19 -6 -41 z" />
<glyph unicode="&#xe163;" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q47 0 100 15v185h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h500v185q-14 4 -114 7.5t-193 5.5l-93 2q-165 0 -282.5 -117.5t-117.5 -282.5v-300zM600 400v300h300v200l400 -350l-400 -350v200h-300z " />
<glyph unicode="&#xe164;" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q163 0 281.5 117.5t118.5 282.5v98l-78 73l-122 -123v-148q0 -41 -29.5 -70.5t-70.5 -29.5h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h156l118 122l-74 78h-100q-165 0 -282.5 -117.5t-117.5 -282.5 v-300zM496 709l353 342l-149 149h500v-500l-149 149l-342 -353z" />
<glyph unicode="&#xe165;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM406 600 q0 80 57 137t137 57t137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137z" />
<glyph unicode="&#xe166;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 800l445 -500l450 500h-295v400h-300v-400h-300zM900 150h100v50h-100v-50z" />
<glyph unicode="&#xe167;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 700h300v-300h300v300h295l-445 500zM900 150h100v50h-100v-50z" />
<glyph unicode="&#xe168;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 705l305 -305l596 596l-154 155l-442 -442l-150 151zM900 150h100v50h-100v-50z" />
<glyph unicode="&#xe169;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 988l97 -98l212 213l-97 97zM200 400l697 1l3 699l-250 -239l-149 149l-212 -212l149 -149zM900 150h100v50h-100v-50z" />
<glyph unicode="&#xe170;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM200 612l212 -212l98 97l-213 212zM300 1200l239 -250l-149 -149l212 -212l149 148l249 -237l-1 697zM900 150h100v50h-100v-50z" />
<glyph unicode="&#xe171;" d="M23 415l1177 784v-1079l-475 272l-310 -393v416h-392zM494 210l672 938l-672 -712v-226z" />
<glyph unicode="&#xe172;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-850q0 -21 -15 -35.5t-35 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200z" />
<glyph unicode="&#xe173;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-218l-276 -275l-120 120l-126 -127h-378v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM581 306l123 123l120 -120l353 352l123 -123l-475 -476zM600 1000h100v200h-100v-200z" />
<glyph unicode="&#xe174;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-269l-103 -103l-170 170l-298 -298h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200zM700 133l170 170l-170 170l127 127l170 -170l170 170l127 -128l-170 -169l170 -170 l-127 -127l-170 170l-170 -170z" />
<glyph unicode="&#xe175;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-300h-400v-200h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300l300 -300l300 300h-200v300h-200v-300h-200zM600 1000v200h100v-200h-100z" />
<glyph unicode="&#xe176;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-402l-200 200l-298 -298h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300h200v-300h200v300h200l-300 300zM600 1000v200h100v-200h-100z" />
<glyph unicode="&#xe177;" d="M0 250q0 -21 14.5 -35.5t35.5 -14.5h1100q21 0 35.5 14.5t14.5 35.5v550h-1200v-550zM0 900h1200v150q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 300v200h400v-200h-400z" />
<glyph unicode="&#xe178;" d="M0 400l300 298v-198h400v-200h-400v-198zM100 800v200h100v-200h-100zM300 800v200h100v-200h-100zM500 800v200h400v198l300 -298l-300 -298v198h-400zM800 300v200h100v-200h-100zM1000 300h100v200h-100v-200z" />
<glyph unicode="&#xe179;" d="M100 700v400l50 100l50 -100v-300h100v300l50 100l50 -100v-300h100v300l50 100l50 -100v-400l-100 -203v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447zM800 597q0 -29 10.5 -55.5t25 -43t29 -28.5t25.5 -18l10 -5v-397q0 -21 14.5 -35.5 t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v1106q0 31 -18 40.5t-44 -7.5l-276 -116q-25 -17 -43.5 -51.5t-18.5 -65.5v-359z" />
<glyph unicode="&#xe180;" d="M100 0h400v56q-75 0 -87.5 6t-12.5 44v394h500v-394q0 -38 -12.5 -44t-87.5 -6v-56h400v56q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v888q0 22 25 34.5t50 13.5l25 2v56h-400v-56q75 0 87.5 -6t12.5 -44v-394h-500v394q0 38 12.5 44t87.5 6v56h-400v-56q4 0 11 -0.5 t24 -3t30 -7t24 -15t11 -24.5v-888q0 -22 -25 -34.5t-50 -13.5l-25 -2v-56z" />
<glyph unicode="&#xe181;" d="M0 300q0 -41 29.5 -70.5t70.5 -29.5h300q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-300q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM100 100h400l200 200h105l295 98v-298h-425l-100 -100h-375zM100 300v200h300v-200h-300zM100 600v200h300v-200h-300z M100 1000h400l200 -200v-98l295 98h105v200h-425l-100 100h-375zM700 402v163l400 133v-163z" />
<glyph unicode="&#xe182;" d="M16.5 974.5q0.5 -21.5 16 -90t46.5 -140t104 -177.5t175 -208q103 -103 207.5 -176t180 -103.5t137 -47t92.5 -16.5l31 1l163 162q17 18 13.5 41t-22.5 37l-192 136q-19 14 -45 12t-42 -19l-118 -118q-142 101 -268 227t-227 268l118 118q17 17 20 41.5t-11 44.5 l-139 194q-14 19 -36.5 22t-40.5 -14l-162 -162q-1 -11 -0.5 -32.5z" />
<glyph unicode="&#xe183;" d="M0 50v212q0 20 10.5 45.5t24.5 39.5l365 303v50q0 4 1 10.5t12 22.5t30 28.5t60 23t97 10.5t97 -10t60 -23.5t30 -27.5t12 -24l1 -10v-50l365 -303q14 -14 24.5 -39.5t10.5 -45.5v-212q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-20 0 -35 14.5t-15 35.5zM0 712 q0 -21 14.5 -33.5t34.5 -8.5l202 33q20 4 34.5 21t14.5 38v146q141 24 300 24t300 -24v-146q0 -21 14.5 -38t34.5 -21l202 -33q20 -4 34.5 8.5t14.5 33.5v200q-6 8 -19 20.5t-63 45t-112 57t-171 45t-235 20.5q-92 0 -175 -10.5t-141.5 -27t-108.5 -36.5t-81.5 -40 t-53.5 -36.5t-31 -27.5l-9 -10v-200z" />
<glyph unicode="&#xe184;" d="M100 0v100h1100v-100h-1100zM175 200h950l-125 150v250l100 100v400h-100v-200h-100v200h-200v-200h-100v200h-200v-200h-100v200h-100v-400l100 -100v-250z" />
<glyph unicode="&#xe185;" d="M100 0h300v400q0 41 -29.5 70.5t-70.5 29.5h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-400zM500 0v1000q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-1000h-300zM900 0v700q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-700h-300z" />
<glyph unicode="&#xe186;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" />
<glyph unicode="&#xe187;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h100v200h100v-200h100v500h-100v-200h-100v200h-100v-500zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" />
<glyph unicode="&#xe188;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v100h-200v300h200v100h-300v-500zM600 300h300v100h-200v300h200v100h-300v-500z" />
<glyph unicode="&#xe189;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 550l300 -150v300zM600 400l300 150l-300 150v-300z" />
<glyph unicode="&#xe190;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300v500h700v-500h-700zM300 400h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130v-300zM575 549 q0 -65 27 -107t68 -42h130v300h-130q-38 0 -66.5 -43t-28.5 -108z" />
<glyph unicode="&#xe191;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" />
<glyph unicode="&#xe192;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v400h-200v100h-100v-500zM301 400v200h100v-200h-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" />
<glyph unicode="&#xe193;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 700v100h300v-300h-99v-100h-100v100h99v200h-200zM201 300v100h100v-100h-100zM601 300v100h100v-100h-100z M700 700v100h200v-500h-100v400h-100z" />
<glyph unicode="&#xe194;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 500v200 l100 100h300v-100h-300v-200h300v-100h-300z" />
<glyph unicode="&#xe195;" d="M0 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 400v400h300 l100 -100v-100h-100v100h-200v-100h200v-100h-200v-100h-100zM700 400v100h100v-100h-100z" />
<glyph unicode="&#xe197;" d="M-14 494q0 -80 56.5 -137t135.5 -57h222v300h400v-300h128q120 0 205 86.5t85 207.5t-85 207t-205 86q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200h200v300h200v-300h200 l-300 -300z" />
<glyph unicode="&#xe198;" d="M-14 494q0 -80 56.5 -137t135.5 -57h8l414 414l403 -403q94 26 154.5 104.5t60.5 178.5q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200l300 300 l300 -300h-200v-300h-200v300h-200z" />
<glyph unicode="&#xe199;" d="M100 200h400v-155l-75 -45h350l-75 45v155h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170z" />
<glyph unicode="&#xe200;" d="M121 700q0 -53 28.5 -97t75.5 -65q-4 -16 -4 -38q0 -74 52.5 -126.5t126.5 -52.5q56 0 100 30v-306l-75 -45h350l-75 45v306q46 -30 100 -30q74 0 126.5 52.5t52.5 126.5q0 24 -9 55q50 32 79.5 83t29.5 112q0 90 -61.5 155.5t-150.5 71.5q-26 89 -99.5 145.5 t-167.5 56.5q-116 0 -197.5 -81.5t-81.5 -197.5q0 -4 1 -11.5t1 -11.5q-14 2 -23 2q-74 0 -126.5 -52.5t-52.5 -126.5z" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 62 KiB

View File

@@ -0,0 +1,196 @@
<!--
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>%title%</title>
<link rel="stylesheet" href="css/bootstrap.css">
<link rel="stylesheet" href="css/bootstrap-theme.css">
<link rel="stylesheet" href="css/jquery.fileupload.css">
<link rel="stylesheet" href="css/index.css">
<!--[if lt IE 9]>
<script type="text/javascript" src="js/html5shiv.min.js"></script>
<script type="text/javascript" src="js/respond.min.js"></script>
<![endif]-->
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript" src="js/jquery.ui.widget.js"></script>
<script type="text/javascript" src="js/jquery.jeditable.js"></script>
<script type="text/javascript" src="js/jquery.fileupload.js"></script>
<script type="text/javascript" src="js/jquery.iframe-transport.js"></script>
<script type="text/javascript" src="js/bootstrap.min.js"></script>
<script type="text/javascript" src="js/tmpl.min.js"></script>
<script type="text/javascript">
var _device = "%device%";
</script>
<script type="text/javascript" src="js/index.js"></script>
</head>
<body>
<div class="container">
<div class="page-header">
<h1>%header%</h1>
</div>
%prologue%
<div id="alerts"></div>
<div class="btn-toolbar">
<button type="button" class="btn btn-primary fileinput-button" id="upload-file">
<span class="glyphicon glyphicon-upload"></span> Upload Files&hellip;
<input id="fileupload" type="file" name="files[]" multiple>
</button>
<button type="button" class="btn btn-success" id="create-folder">
<span class="glyphicon glyphicon-folder-close"></span> Create Folder&hellip;
</button>
<button type="button" class="btn btn-default" id="reload">
<span class="glyphicon glyphicon-refresh"></span> Refresh
</button>
</div>
<div class="panel panel-default uploading">
<div class="panel-heading">File Uploads in Progress</div>
<table class="table table-striped"><tbody id="uploads"></tbody></table>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<ol class="breadcrumb" id="path"></ol>
</div>
<table class="table table-striped"><tbody id="listing"></tbody></table>
</div>
%epilogue%
<p class="footer">%footer%</p>
</div>
<div class="modal fade" id="create-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Create Folder</h4>
</div>
<div class="modal-body">
<p>Please enter the name of the folder to be created:</p>
<form onsubmit="return false">
<input type="text" autocomplete="off" id="create-input">
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="create-confirm">Create Folder</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="move-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Move Item</h4>
</div>
<div class="modal-body">
<p>Please enter the new location for this item:</p>
<form onsubmit="return false">
<input type="text" autocomplete="off" id="move-input">
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="move-confirm">Move Item</button>
</div>
</div>
</div>
</div>
<script type="text/x-tmpl" id="template-listing">
<tr class="row-file">
<td class="column-icon">
{% if (o.size != null) { %}
<button type="button" class="btn btn-default btn-xs button-download">
<span class="glyphicon glyphicon-download-alt"></span>
</button>
{% } else { %}
<button type="button" class="btn btn-default btn-xs button-open">
<span class="glyphicon glyphicon-folder-open"></span>
</button>
{% } %}
</td>
<td class="column-name"><p class="edit">{%=o.name%}</p></td>
<td class="column-size">
{% if (o.size != null) { %}
<p>{%=formatFileSize(o.size)%}</p>
{% } %}
</td>
<td class="column-move">
<button type="button" class="btn btn-default btn-xs button-move">
<span class="glyphicon glyphicon glyphicon-share-alt"></span>
</button>
</td>
<td class="column-delete">
<button type="button" class="btn btn-danger btn-xs button-delete">
<span class="glyphicon glyphicon-trash"></span>
</button>
</td>
</tr>
</script>
<script type="text/x-tmpl" id="template-uploads">
<tr class="row-file">
<td class="column-icon">
<button type="button" class="btn btn-warning btn-xs button-cancel">
<span class="glyphicon glyphicon-ban-circle"></span>
</button>
</td>
<td class="column-path"><p>{%=o.path%}</p></td>
<td class="column-progress">
<div class="progress">
<div class="progress-bar" id="progress-bar"></div>
</div>
</ts>
</tr>
</script>
<script type="text/x-tmpl" id="template-alert">
<div class="alert alert-{%=o.level%} alert-dismissable">
<button type="button" class="close" data-dismiss="alert">&times;</button>
<strong>{%=o.title%}</strong>{%=o.description%}
</div>
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
/*
HTML5 Shiv v3.7.0 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
*/
(function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag();
a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/[\w\-]+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x<style>article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}</style>";
c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="<xyz></xyz>";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode||
"undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:"3.7.0",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);
if(g)return a.createDocumentFragment();for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d<h;d++)c.createElement(e[d]);return c}};l.html5=e;q(f)})(this,document);

View File

@@ -0,0 +1,316 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var ENTER_KEYCODE = 13;
var _path = null;
var _pendingReloads = [];
var _reloadingDisabled = 0;
function formatFileSize(bytes) {
if (bytes >= 1000000000) {
return (bytes / 1000000000).toFixed(2) + ' GB';
}
if (bytes >= 1000000) {
return (bytes / 1000000).toFixed(2) + ' MB';
}
return (bytes / 1000).toFixed(2) + ' KB';
}
function _showError(message, textStatus, errorThrown) {
$("#alerts").prepend(tmpl("template-alert", {
level: "danger",
title: (errorThrown != "" ? errorThrown : textStatus) + ": ",
description: message
}));
}
function _disableReloads() {
_reloadingDisabled += 1;
}
function _enableReloads() {
_reloadingDisabled -= 1;
if (_pendingReloads.length > 0) {
_reload(_pendingReloads.shift());
}
}
function _reload(path) {
if (_reloadingDisabled) {
if ($.inArray(path, _pendingReloads) < 0) {
_pendingReloads.push(path);
}
return;
}
_disableReloads();
$.ajax({
url: 'list',
type: 'GET',
data: {path: path},
dataType: 'json'
}).fail(function(jqXHR, textStatus, errorThrown) {
_showError("Failed retrieving contents of \"" + path + "\"", textStatus, errorThrown);
}).done(function(data, textStatus, jqXHR) {
var scrollPosition = $(document).scrollTop();
if (path != _path) {
$("#path").empty();
if (path == "/") {
$("#path").append('<li class="active">' + _device + '</li>');
} else {
$("#path").append('<li data-path="/"><a>' + _device + '</a></li>');
var components = path.split("/").slice(1, -1);
for (var i = 0; i < components.length - 1; ++i) {
var subpath = "/" + components.slice(0, i + 1).join("/") + "/";
$("#path").append('<li data-path="' + subpath + '"><a>' + components[i] + '</a></li>');
}
$("#path > li").click(function(event) {
_reload($(this).data("path"));
event.preventDefault();
});
$("#path").append('<li class="active">' + components[components.length - 1] + '</li>');
}
_path = path;
}
$("#listing").empty();
for (var i = 0, file; file = data[i]; ++i) {
$(tmpl("template-listing", file)).data(file).appendTo("#listing");
}
$(".edit").editable(function(value, settings) {
var name = $(this).parent().parent().data("name");
if (value != name) {
var path = $(this).parent().parent().data("path");
$.ajax({
url: 'move',
type: 'POST',
data: {oldPath: path, newPath: _path + value},
dataType: 'json'
}).fail(function(jqXHR, textStatus, errorThrown) {
_showError("Failed moving \"" + path + "\" to \"" + _path + value + "\"", textStatus, errorThrown);
}).always(function() {
_reload(_path);
});
}
return value;
}, {
onedit: function(settings, original) {
_disableReloads();
},
onsubmit: function(settings, original) {
_enableReloads();
},
onreset: function(settings, original) {
_enableReloads();
},
tooltip: 'Click to rename...'
});
$(".button-download").click(function(event) {
var path = $(this).parent().parent().data("path");
setTimeout(function() {
window.location = "download?path=" + encodeURIComponent(path);
}, 0);
});
$(".button-open").click(function(event) {
var path = $(this).parent().parent().data("path");
_reload(path);
});
$(".button-move").click(function(event) {
var path = $(this).parent().parent().data("path");
if (path[path.length - 1] == "/") {
path = path.slice(0, path.length - 1);
}
$("#move-input").data("path", path);
$("#move-input").val(path);
$("#move-modal").modal("show");
});
$(".button-delete").click(function(event) {
var path = $(this).parent().parent().data("path");
$.ajax({
url: 'delete',
type: 'POST',
data: {path: path},
dataType: 'json'
}).fail(function(jqXHR, textStatus, errorThrown) {
_showError("Failed deleting \"" + path + "\"", textStatus, errorThrown);
}).always(function() {
_reload(_path);
});
});
$(document).scrollTop(scrollPosition);
}).always(function() {
_enableReloads();
});
}
$(document).ready(function() {
// Workaround Firefox and IE not showing file selection dialog when clicking on "upload-file" <button>
// Making it a <div> instead also works but then it the button doesn't work anymore with tab selection or accessibility
$("#upload-file").click(function(event) {
$("#fileupload").click();
});
// Prevent event bubbling when using workaround above
$("#fileupload").click(function(event) {
event.stopPropagation();
});
$("#fileupload").fileupload({
dropZone: $(document),
pasteZone: null,
autoUpload: true,
sequentialUploads: true,
// limitConcurrentUploads: 2,
// forceIframeTransport: true,
url: 'upload',
type: 'POST',
dataType: 'json',
start: function(e) {
$(".uploading").show();
},
stop: function(e) {
$(".uploading").hide();
},
add: function(e, data) {
var file = data.files[0];
data.formData = {
path: _path
};
data.context = $(tmpl("template-uploads", {
path: _path + file.name
})).appendTo("#uploads");
var jqXHR = data.submit();
data.context.find("button").click(function(event) {
jqXHR.abort();
});
},
progress: function(e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
data.context.find(".progress-bar").css("width", progress + "%");
},
done: function(e, data) {
_reload(_path);
},
fail: function(e, data) {
var file = data.files[0];
if (data.errorThrown != "abort") {
_showError("Failed uploading \"" + file.name + "\" to \"" + _path + "\"", data.textStatus, data.errorThrown);
}
},
always: function(e, data) {
data.context.remove();
},
});
$("#create-input").keypress(function(event) {
if (event.keyCode == ENTER_KEYCODE) {
$("#create-confirm").click();
};
});
$("#create-modal").on("shown.bs.modal", function(event) {
$("#create-input").focus();
$("#create-input").select();
});
$("#create-folder").click(function(event) {
$("#create-input").val("Untitled folder");
$("#create-modal").modal("show");
});
$("#create-confirm").click(function(event) {
$("#create-modal").modal("hide");
var name = $("#create-input").val();
if (name != "") {
$.ajax({
url: 'create',
type: 'POST',
data: {path: _path + name},
dataType: 'json'
}).fail(function(jqXHR, textStatus, errorThrown) {
_showError("Failed creating folder \"" + name + "\" in \"" + _path + "\"", textStatus, errorThrown);
}).always(function() {
_reload(_path);
});
}
});
$("#move-input").keypress(function(event) {
if (event.keyCode == ENTER_KEYCODE) {
$("#move-confirm").click();
};
});
$("#move-modal").on("shown.bs.modal", function(event) {
$("#move-input").focus();
$("#move-input").select();
})
$("#move-confirm").click(function(event) {
$("#move-modal").modal("hide");
var oldPath = $("#move-input").data("path");
var newPath = $("#move-input").val();
if ((newPath != "") && (newPath[0] == "/") && (newPath != oldPath)) {
$.ajax({
url: 'move',
type: 'POST',
data: {oldPath: oldPath, newPath: newPath},
dataType: 'json'
}).fail(function(jqXHR, textStatus, errorThrown) {
_showError("Failed moving \"" + oldPath + "\" to \"" + newPath + "\"", textStatus, errorThrown);
}).always(function() {
_reload(_path);
});
}
});
$("#reload").click(function(event) {
_reload(_path);
});
_reload("/");
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,214 @@
/*
* jQuery Iframe Transport Plugin 1.8.2
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2011, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/* global define, window, document */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define(['jquery'], factory);
} else {
// Browser globals:
factory(window.jQuery);
}
}(function ($) {
'use strict';
// Helper variable to create unique names for the transport iframes:
var counter = 0;
// The iframe transport accepts four additional options:
// options.fileInput: a jQuery collection of file input fields
// options.paramName: the parameter name for the file form data,
// overrides the name property of the file input field(s),
// can be a string or an array of strings.
// options.formData: an array of objects with name and value properties,
// equivalent to the return data of .serializeArray(), e.g.:
// [{name: 'a', value: 1}, {name: 'b', value: 2}]
// options.initialIframeSrc: the URL of the initial iframe src,
// by default set to "javascript:false;"
$.ajaxTransport('iframe', function (options) {
if (options.async) {
// javascript:false as initial iframe src
// prevents warning popups on HTTPS in IE6:
/*jshint scripturl: true */
var initialIframeSrc = options.initialIframeSrc || 'javascript:false;',
/*jshint scripturl: false */
form,
iframe,
addParamChar;
return {
send: function (_, completeCallback) {
form = $('<form style="display:none;"></form>');
form.attr('accept-charset', options.formAcceptCharset);
addParamChar = /\?/.test(options.url) ? '&' : '?';
// XDomainRequest only supports GET and POST:
if (options.type === 'DELETE') {
options.url = options.url + addParamChar + '_method=DELETE';
options.type = 'POST';
} else if (options.type === 'PUT') {
options.url = options.url + addParamChar + '_method=PUT';
options.type = 'POST';
} else if (options.type === 'PATCH') {
options.url = options.url + addParamChar + '_method=PATCH';
options.type = 'POST';
}
// IE versions below IE8 cannot set the name property of
// elements that have already been added to the DOM,
// so we set the name along with the iframe HTML markup:
counter += 1;
iframe = $(
'<iframe src="' + initialIframeSrc +
'" name="iframe-transport-' + counter + '"></iframe>'
).bind('load', function () {
var fileInputClones,
paramNames = $.isArray(options.paramName) ?
options.paramName : [options.paramName];
iframe
.unbind('load')
.bind('load', function () {
var response;
// Wrap in a try/catch block to catch exceptions thrown
// when trying to access cross-domain iframe contents:
try {
response = iframe.contents();
// Google Chrome and Firefox do not throw an
// exception when calling iframe.contents() on
// cross-domain requests, so we unify the response:
if (!response.length || !response[0].firstChild) {
throw new Error();
}
} catch (e) {
response = undefined;
}
// The complete callback returns the
// iframe content document as response object:
completeCallback(
200,
'success',
{'iframe': response}
);
// Fix for IE endless progress bar activity bug
// (happens on form submits to iframe targets):
$('<iframe src="' + initialIframeSrc + '"></iframe>')
.appendTo(form);
window.setTimeout(function () {
// Removing the form in a setTimeout call
// allows Chrome's developer tools to display
// the response result
form.remove();
}, 0);
});
form
.prop('target', iframe.prop('name'))
.prop('action', options.url)
.prop('method', options.type);
if (options.formData) {
$.each(options.formData, function (index, field) {
$('<input type="hidden"/>')
.prop('name', field.name)
.val(field.value)
.appendTo(form);
});
}
if (options.fileInput && options.fileInput.length &&
options.type === 'POST') {
fileInputClones = options.fileInput.clone();
// Insert a clone for each file input field:
options.fileInput.after(function (index) {
return fileInputClones[index];
});
if (options.paramName) {
options.fileInput.each(function (index) {
$(this).prop(
'name',
paramNames[index] || options.paramName
);
});
}
// Appending the file input fields to the hidden form
// removes them from their original location:
form
.append(options.fileInput)
.prop('enctype', 'multipart/form-data')
// enctype must be set as encoding for IE:
.prop('encoding', 'multipart/form-data');
// Remove the HTML5 form attribute from the input(s):
options.fileInput.removeAttr('form');
}
form.submit();
// Insert the file input fields at their original location
// by replacing the clones with the originals:
if (fileInputClones && fileInputClones.length) {
options.fileInput.each(function (index, input) {
var clone = $(fileInputClones[index]);
// Restore the original name and form properties:
$(input)
.prop('name', clone.prop('name'))
.attr('form', clone.attr('form'));
clone.replaceWith(input);
});
}
});
form.append(iframe).appendTo(document.body);
},
abort: function () {
if (iframe) {
// javascript:false as iframe src aborts the request
// and prevents warning popups on HTTPS in IE6.
// concat is used to avoid the "Script URL" JSLint error:
iframe
.unbind('load')
.prop('src', initialIframeSrc);
}
if (form) {
form.remove();
}
}
};
}
});
// The iframe transport returns the iframe content document as response.
// The following adds converters from iframe to text, json, html, xml
// and script.
// Please note that the Content-Type for JSON responses has to be text/plain
// or text/html, if the browser doesn't include application/json in the
// Accept header, else IE will show a download dialog.
// The Content-Type for XML responses on the other hand has to be always
// application/xml or text/xml, so IE properly parses the XML response.
// See also
// https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation
$.ajaxSetup({
converters: {
'iframe text': function (iframe) {
return iframe && $(iframe[0].body).text();
},
'iframe json': function (iframe) {
return iframe && $.parseJSON($(iframe[0].body).text());
},
'iframe html': function (iframe) {
return iframe && $(iframe[0].body).html();
},
'iframe xml': function (iframe) {
var xmlDoc = iframe && iframe[0];
return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc :
$.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) ||
$(xmlDoc.body).html());
},
'iframe script': function (iframe) {
return iframe && $.globalEval($(iframe[0].body).text());
}
}
});
}));

View File

@@ -0,0 +1,546 @@
/*
* Jeditable - jQuery in place edit plugin
*
* Copyright (c) 2006-2013 Mika Tuupola, Dylan Verheul
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php
*
* Project home:
* http://www.appelsiini.net/projects/jeditable
*
* Based on editable by Dylan Verheul <dylan_at_dyve.net>:
* http://www.dyve.net/jquery/?editable
*
*/
/**
* Version 1.7.3
*
* ** means there is basic unit tests for this parameter.
*
* @name Jeditable
* @type jQuery
* @param String target (POST) URL or function to send edited content to **
* @param Hash options additional options
* @param String options[method] method to use to send edited content (POST or PUT) **
* @param Function options[callback] Function to run after submitting edited content **
* @param String options[name] POST parameter name of edited content
* @param String options[id] POST parameter name of edited div id
* @param Hash options[submitdata] Extra parameters to send when submitting edited content.
* @param String options[type] text, textarea or select (or any 3rd party input type) **
* @param Integer options[rows] number of rows if using textarea **
* @param Integer options[cols] number of columns if using textarea **
* @param Mixed options[height] 'auto', 'none' or height in pixels **
* @param Mixed options[width] 'auto', 'none' or width in pixels **
* @param String options[loadurl] URL to fetch input content before editing **
* @param String options[loadtype] Request type for load url. Should be GET or POST.
* @param String options[loadtext] Text to display while loading external content.
* @param Mixed options[loaddata] Extra parameters to pass when fetching content before editing.
* @param Mixed options[data] Or content given as paramameter. String or function.**
* @param String options[indicator] indicator html to show when saving
* @param String options[tooltip] optional tooltip text via title attribute **
* @param String options[event] jQuery event such as 'click' of 'dblclick' **
* @param String options[submit] submit button value, empty means no button **
* @param String options[cancel] cancel button value, empty means no button **
* @param String options[cssclass] CSS class to apply to input form. 'inherit' to copy from parent. **
* @param String options[style] Style to apply to input form 'inherit' to copy from parent. **
* @param String options[select] true or false, when true text is highlighted ??
* @param String options[placeholder] Placeholder text or html to insert when element is empty. **
* @param String options[onblur] 'cancel', 'submit', 'ignore' or function ??
*
* @param Function options[onsubmit] function(settings, original) { ... } called before submit
* @param Function options[onreset] function(settings, original) { ... } called before reset
* @param Function options[onerror] function(settings, original, xhr) { ... } called on error
*
* @param Hash options[ajaxoptions] jQuery Ajax options. See docs.jquery.com.
*
*/
(function($) {
$.fn.editable = function(target, options) {
if ('disable' == target) {
$(this).data('disabled.editable', true);
return;
}
if ('enable' == target) {
$(this).data('disabled.editable', false);
return;
}
if ('destroy' == target) {
$(this)
.unbind($(this).data('event.editable'))
.removeData('disabled.editable')
.removeData('event.editable');
return;
}
var settings = $.extend({}, $.fn.editable.defaults, {target:target}, options);
/* setup some functions */
var plugin = $.editable.types[settings.type].plugin || function() { };
var submit = $.editable.types[settings.type].submit || function() { };
var buttons = $.editable.types[settings.type].buttons
|| $.editable.types['defaults'].buttons;
var content = $.editable.types[settings.type].content
|| $.editable.types['defaults'].content;
var element = $.editable.types[settings.type].element
|| $.editable.types['defaults'].element;
var reset = $.editable.types[settings.type].reset
|| $.editable.types['defaults'].reset;
var callback = settings.callback || function() { };
var onedit = settings.onedit || function() { };
var onsubmit = settings.onsubmit || function() { };
var onreset = settings.onreset || function() { };
var onerror = settings.onerror || reset;
/* Show tooltip. */
if (settings.tooltip) {
$(this).attr('title', settings.tooltip);
}
settings.autowidth = 'auto' == settings.width;
settings.autoheight = 'auto' == settings.height;
return this.each(function() {
/* Save this to self because this changes when scope changes. */
var self = this;
/* Inlined block elements lose their width and height after first edit. */
/* Save them for later use as workaround. */
var savedwidth = $(self).width();
var savedheight = $(self).height();
/* Save so it can be later used by $.editable('destroy') */
$(this).data('event.editable', settings.event);
/* If element is empty add something clickable (if requested) */
if (!$.trim($(this).html())) {
$(this).html(settings.placeholder);
}
$(this).bind(settings.event, function(e) {
/* Abort if element is disabled. */
if (true === $(this).data('disabled.editable')) {
return;
}
/* Prevent throwing an exeption if edit field is clicked again. */
if (self.editing) {
return;
}
/* Abort if onedit hook returns false. */
if (false === onedit.apply(this, [settings, self])) {
return;
}
/* Prevent default action and bubbling. */
e.preventDefault();
e.stopPropagation();
/* Remove tooltip. */
if (settings.tooltip) {
$(self).removeAttr('title');
}
/* Figure out how wide and tall we are, saved width and height. */
/* Workaround for http://dev.jquery.com/ticket/2190 */
if (0 == $(self).width()) {
settings.width = savedwidth;
settings.height = savedheight;
} else {
if (settings.width != 'none') {
settings.width =
settings.autowidth ? $(self).width() : settings.width;
}
if (settings.height != 'none') {
settings.height =
settings.autoheight ? $(self).height() : settings.height;
}
}
/* Remove placeholder text, replace is here because of IE. */
if ($(this).html().toLowerCase().replace(/(;|"|\/)/g, '') ==
settings.placeholder.toLowerCase().replace(/(;|"|\/)/g, '')) {
$(this).html('');
}
self.editing = true;
self.revert = $(self).html();
$(self).html('');
/* Create the form object. */
var form = $('<form />');
/* Apply css or style or both. */
if (settings.cssclass) {
if ('inherit' == settings.cssclass) {
form.attr('class', $(self).attr('class'));
} else {
form.attr('class', settings.cssclass);
}
}
if (settings.style) {
if ('inherit' == settings.style) {
form.attr('style', $(self).attr('style'));
/* IE needs the second line or display wont be inherited. */
form.css('display', $(self).css('display'));
} else {
form.attr('style', settings.style);
}
}
/* Add main input element to form and store it in input. */
var input = element.apply(form, [settings, self]);
/* Set input content via POST, GET, given data or existing value. */
var input_content;
if (settings.loadurl) {
var t = setTimeout(function() {
input.disabled = true;
content.apply(form, [settings.loadtext, settings, self]);
}, 100);
var loaddata = {};
loaddata[settings.id] = self.id;
if ($.isFunction(settings.loaddata)) {
$.extend(loaddata, settings.loaddata.apply(self, [self.revert, settings]));
} else {
$.extend(loaddata, settings.loaddata);
}
$.ajax({
type : settings.loadtype,
url : settings.loadurl,
data : loaddata,
async : false,
success: function(result) {
window.clearTimeout(t);
input_content = result;
input.disabled = false;
}
});
} else if (settings.data) {
input_content = settings.data;
if ($.isFunction(settings.data)) {
input_content = settings.data.apply(self, [self.revert, settings]);
}
} else {
input_content = self.revert;
}
content.apply(form, [input_content, settings, self]);
input.attr('name', settings.name);
/* Add buttons to the form. */
buttons.apply(form, [settings, self]);
/* Add created form to self. */
$(self).append(form);
/* Attach 3rd party plugin if requested. */
plugin.apply(form, [settings, self]);
/* Focus to first visible form element. */
$(':input:visible:enabled:first', form).focus();
/* Highlight input contents when requested. */
if (settings.select) {
input.select();
}
/* discard changes if pressing esc */
input.keydown(function(e) {
if (e.keyCode == 27) {
e.preventDefault();
reset.apply(form, [settings, self]);
}
});
/* Discard, submit or nothing with changes when clicking outside. */
/* Do nothing is usable when navigating with tab. */
var t;
if ('cancel' == settings.onblur) {
input.blur(function(e) {
/* Prevent canceling if submit was clicked. */
t = setTimeout(function() {
reset.apply(form, [settings, self]);
}, 500);
});
} else if ('submit' == settings.onblur) {
input.blur(function(e) {
/* Prevent double submit if submit was clicked. */
t = setTimeout(function() {
form.submit();
}, 200);
});
} else if ($.isFunction(settings.onblur)) {
input.blur(function(e) {
settings.onblur.apply(self, [input.val(), settings]);
});
} else {
input.blur(function(e) {
/* TODO: maybe something here */
});
}
form.submit(function(e) {
if (t) {
clearTimeout(t);
}
/* Do no submit. */
e.preventDefault();
/* Call before submit hook. */
/* If it returns false abort submitting. */
if (false !== onsubmit.apply(form, [settings, self])) {
/* Custom inputs call before submit hook. */
/* If it returns false abort submitting. */
if (false !== submit.apply(form, [settings, self])) {
/* Check if given target is function */
if ($.isFunction(settings.target)) {
var str = settings.target.apply(self, [input.val(), settings]);
$(self).html(str);
self.editing = false;
callback.apply(self, [self.innerHTML, settings]);
/* TODO: this is not dry */
if (!$.trim($(self).html())) {
$(self).html(settings.placeholder);
}
} else {
/* Add edited content and id of edited element to POST. */
var submitdata = {};
submitdata[settings.name] = input.val();
submitdata[settings.id] = self.id;
/* Add extra data to be POST:ed. */
if ($.isFunction(settings.submitdata)) {
$.extend(submitdata, settings.submitdata.apply(self, [self.revert, settings]));
} else {
$.extend(submitdata, settings.submitdata);
}
/* Quick and dirty PUT support. */
if ('PUT' == settings.method) {
submitdata['_method'] = 'put';
}
/* Show the saving indicator. */
$(self).html(settings.indicator);
/* Defaults for ajaxoptions. */
var ajaxoptions = {
type : 'POST',
data : submitdata,
dataType: 'html',
url : settings.target,
success : function(result, status) {
if (ajaxoptions.dataType == 'html') {
$(self).html(result);
}
self.editing = false;
callback.apply(self, [result, settings]);
if (!$.trim($(self).html())) {
$(self).html(settings.placeholder);
}
},
error : function(xhr, status, error) {
onerror.apply(form, [settings, self, xhr]);
}
};
/* Override with what is given in settings.ajaxoptions. */
$.extend(ajaxoptions, settings.ajaxoptions);
$.ajax(ajaxoptions);
}
}
}
/* Show tooltip again. */
$(self).attr('title', settings.tooltip);
return false;
});
});
/* Privileged methods */
this.reset = function(form) {
/* Prevent calling reset twice when blurring. */
if (this.editing) {
/* Before reset hook, if it returns false abort reseting. */
if (false !== onreset.apply(form, [settings, self])) {
$(self).html(self.revert);
self.editing = false;
if (!$.trim($(self).html())) {
$(self).html(settings.placeholder);
}
/* Show tooltip again. */
if (settings.tooltip) {
$(self).attr('title', settings.tooltip);
}
}
}
};
});
};
$.editable = {
types: {
defaults: {
element : function(settings, original) {
var input = $('<input type="hidden"></input>');
$(this).append(input);
return(input);
},
content : function(string, settings, original) {
$(':input:first', this).val(string);
},
reset : function(settings, original) {
original.reset(this);
},
buttons : function(settings, original) {
var form = this;
if (settings.submit) {
/* If given html string use that. */
if (settings.submit.match(/>$/)) {
var submit = $(settings.submit).click(function() {
if (submit.attr("type") != "submit") {
form.submit();
}
});
/* Otherwise use button with given string as text. */
} else {
var submit = $('<button type="submit" />');
submit.html(settings.submit);
}
$(this).append(submit);
}
if (settings.cancel) {
/* If given html string use that. */
if (settings.cancel.match(/>$/)) {
var cancel = $(settings.cancel);
/* otherwise use button with given string as text */
} else {
var cancel = $('<button type="cancel" />');
cancel.html(settings.cancel);
}
$(this).append(cancel);
$(cancel).click(function(event) {
if ($.isFunction($.editable.types[settings.type].reset)) {
var reset = $.editable.types[settings.type].reset;
} else {
var reset = $.editable.types['defaults'].reset;
}
reset.apply(form, [settings, original]);
return false;
});
}
}
},
text: {
element : function(settings, original) {
var input = $('<input />');
if (settings.width != 'none') { input.width(settings.width); }
if (settings.height != 'none') { input.height(settings.height); }
/* https://bugzilla.mozilla.org/show_bug.cgi?id=236791 */
//input[0].setAttribute('autocomplete','off');
input.attr('autocomplete','off');
$(this).append(input);
return(input);
}
},
textarea: {
element : function(settings, original) {
var textarea = $('<textarea />');
if (settings.rows) {
textarea.attr('rows', settings.rows);
} else if (settings.height != "none") {
textarea.height(settings.height);
}
if (settings.cols) {
textarea.attr('cols', settings.cols);
} else if (settings.width != "none") {
textarea.width(settings.width);
}
$(this).append(textarea);
return(textarea);
}
},
select: {
element : function(settings, original) {
var select = $('<select />');
$(this).append(select);
return(select);
},
content : function(data, settings, original) {
/* If it is string assume it is json. */
if (String == data.constructor) {
eval ('var json = ' + data);
} else {
/* Otherwise assume it is a hash already. */
var json = data;
}
for (var key in json) {
if (!json.hasOwnProperty(key)) {
continue;
}
if ('selected' == key) {
continue;
}
var option = $('<option />').val(key).append(json[key]);
$('select', this).append(option);
}
/* Loop option again to set selected. IE needed this... */
$('select', this).children().each(function() {
if ($(this).val() == json['selected'] ||
$(this).text() == $.trim(original.revert)) {
$(this).attr('selected', 'selected');
}
});
/* Submit on change if no submit button defined. */
if (!settings.submit) {
var form = this;
$('select', this).change(function() {
form.submit();
});
}
}
}
},
/* Add new input type */
addInputType: function(name, input) {
$.editable.types[name] = input;
}
};
/* Publicly accessible defaults. */
$.fn.editable.defaults = {
name : 'value',
id : 'id',
type : 'text',
width : 'auto',
height : 'auto',
event : 'click.editable',
onblur : 'cancel',
loadtype : 'GET',
loadtext : 'Loading...',
placeholder: 'Click to edit',
loaddata : {},
submitdata : {},
ajaxoptions: {}
};
})(jQuery);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,521 @@
/*!
* jQuery UI Widget 1.10.4
* http://jqueryui.com
*
* Copyright 2014 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*
* http://api.jqueryui.com/jQuery.widget/
*/
(function( $, undefined ) {
var uuid = 0,
slice = Array.prototype.slice,
_cleanData = $.cleanData;
$.cleanData = function( elems ) {
for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
try {
$( elem ).triggerHandler( "remove" );
// http://bugs.jquery.com/ticket/8235
} catch( e ) {}
}
_cleanData( elems );
};
$.widget = function( name, base, prototype ) {
var fullName, existingConstructor, constructor, basePrototype,
// proxiedPrototype allows the provided prototype to remain unmodified
// so that it can be used as a mixin for multiple widgets (#8876)
proxiedPrototype = {},
namespace = name.split( "." )[ 0 ];
name = name.split( "." )[ 1 ];
fullName = namespace + "-" + name;
if ( !prototype ) {
prototype = base;
base = $.Widget;
}
// create selector for plugin
$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
return !!$.data( elem, fullName );
};
$[ namespace ] = $[ namespace ] || {};
existingConstructor = $[ namespace ][ name ];
constructor = $[ namespace ][ name ] = function( options, element ) {
// allow instantiation without "new" keyword
if ( !this._createWidget ) {
return new constructor( options, element );
}
// allow instantiation without initializing for simple inheritance
// must use "new" keyword (the code above always passes args)
if ( arguments.length ) {
this._createWidget( options, element );
}
};
// extend with the existing constructor to carry over any static properties
$.extend( constructor, existingConstructor, {
version: prototype.version,
// copy the object used to create the prototype in case we need to
// redefine the widget later
_proto: $.extend( {}, prototype ),
// track widgets that inherit from this widget in case this widget is
// redefined after a widget inherits from it
_childConstructors: []
});
basePrototype = new base();
// we need to make the options hash a property directly on the new instance
// otherwise we'll modify the options hash on the prototype that we're
// inheriting from
basePrototype.options = $.widget.extend( {}, basePrototype.options );
$.each( prototype, function( prop, value ) {
if ( !$.isFunction( value ) ) {
proxiedPrototype[ prop ] = value;
return;
}
proxiedPrototype[ prop ] = (function() {
var _super = function() {
return base.prototype[ prop ].apply( this, arguments );
},
_superApply = function( args ) {
return base.prototype[ prop ].apply( this, args );
};
return function() {
var __super = this._super,
__superApply = this._superApply,
returnValue;
this._super = _super;
this._superApply = _superApply;
returnValue = value.apply( this, arguments );
this._super = __super;
this._superApply = __superApply;
return returnValue;
};
})();
});
constructor.prototype = $.widget.extend( basePrototype, {
// TODO: remove support for widgetEventPrefix
// always use the name + a colon as the prefix, e.g., draggable:start
// don't prefix for widgets that aren't DOM-based
widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name
}, proxiedPrototype, {
constructor: constructor,
namespace: namespace,
widgetName: name,
widgetFullName: fullName
});
// If this widget is being redefined then we need to find all widgets that
// are inheriting from it and redefine all of them so that they inherit from
// the new version of this widget. We're essentially trying to replace one
// level in the prototype chain.
if ( existingConstructor ) {
$.each( existingConstructor._childConstructors, function( i, child ) {
var childPrototype = child.prototype;
// redefine the child widget using the same prototype that was
// originally used, but inherit from the new version of the base
$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
});
// remove the list of existing child constructors from the old constructor
// so the old child constructors can be garbage collected
delete existingConstructor._childConstructors;
} else {
base._childConstructors.push( constructor );
}
$.widget.bridge( name, constructor );
};
$.widget.extend = function( target ) {
var input = slice.call( arguments, 1 ),
inputIndex = 0,
inputLength = input.length,
key,
value;
for ( ; inputIndex < inputLength; inputIndex++ ) {
for ( key in input[ inputIndex ] ) {
value = input[ inputIndex ][ key ];
if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
// Clone objects
if ( $.isPlainObject( value ) ) {
target[ key ] = $.isPlainObject( target[ key ] ) ?
$.widget.extend( {}, target[ key ], value ) :
// Don't extend strings, arrays, etc. with objects
$.widget.extend( {}, value );
// Copy everything else by reference
} else {
target[ key ] = value;
}
}
}
}
return target;
};
$.widget.bridge = function( name, object ) {
var fullName = object.prototype.widgetFullName || name;
$.fn[ name ] = function( options ) {
var isMethodCall = typeof options === "string",
args = slice.call( arguments, 1 ),
returnValue = this;
// allow multiple hashes to be passed on init
options = !isMethodCall && args.length ?
$.widget.extend.apply( null, [ options ].concat(args) ) :
options;
if ( isMethodCall ) {
this.each(function() {
var methodValue,
instance = $.data( this, fullName );
if ( !instance ) {
return $.error( "cannot call methods on " + name + " prior to initialization; " +
"attempted to call method '" + options + "'" );
}
if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
return $.error( "no such method '" + options + "' for " + name + " widget instance" );
}
methodValue = instance[ options ].apply( instance, args );
if ( methodValue !== instance && methodValue !== undefined ) {
returnValue = methodValue && methodValue.jquery ?
returnValue.pushStack( methodValue.get() ) :
methodValue;
return false;
}
});
} else {
this.each(function() {
var instance = $.data( this, fullName );
if ( instance ) {
instance.option( options || {} )._init();
} else {
$.data( this, fullName, new object( options, this ) );
}
});
}
return returnValue;
};
};
$.Widget = function( /* options, element */ ) {};
$.Widget._childConstructors = [];
$.Widget.prototype = {
widgetName: "widget",
widgetEventPrefix: "",
defaultElement: "<div>",
options: {
disabled: false,
// callbacks
create: null
},
_createWidget: function( options, element ) {
element = $( element || this.defaultElement || this )[ 0 ];
this.element = $( element );
this.uuid = uuid++;
this.eventNamespace = "." + this.widgetName + this.uuid;
this.options = $.widget.extend( {},
this.options,
this._getCreateOptions(),
options );
this.bindings = $();
this.hoverable = $();
this.focusable = $();
if ( element !== this ) {
$.data( element, this.widgetFullName, this );
this._on( true, this.element, {
remove: function( event ) {
if ( event.target === element ) {
this.destroy();
}
}
});
this.document = $( element.style ?
// element within the document
element.ownerDocument :
// element is window or document
element.document || element );
this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
}
this._create();
this._trigger( "create", null, this._getCreateEventData() );
this._init();
},
_getCreateOptions: $.noop,
_getCreateEventData: $.noop,
_create: $.noop,
_init: $.noop,
destroy: function() {
this._destroy();
// we can probably remove the unbind calls in 2.0
// all event bindings should go through this._on()
this.element
.unbind( this.eventNamespace )
// 1.9 BC for #7810
// TODO remove dual storage
.removeData( this.widgetName )
.removeData( this.widgetFullName )
// support: jquery <1.6.3
// http://bugs.jquery.com/ticket/9413
.removeData( $.camelCase( this.widgetFullName ) );
this.widget()
.unbind( this.eventNamespace )
.removeAttr( "aria-disabled" )
.removeClass(
this.widgetFullName + "-disabled " +
"ui-state-disabled" );
// clean up events and states
this.bindings.unbind( this.eventNamespace );
this.hoverable.removeClass( "ui-state-hover" );
this.focusable.removeClass( "ui-state-focus" );
},
_destroy: $.noop,
widget: function() {
return this.element;
},
option: function( key, value ) {
var options = key,
parts,
curOption,
i;
if ( arguments.length === 0 ) {
// don't return a reference to the internal hash
return $.widget.extend( {}, this.options );
}
if ( typeof key === "string" ) {
// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
options = {};
parts = key.split( "." );
key = parts.shift();
if ( parts.length ) {
curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
for ( i = 0; i < parts.length - 1; i++ ) {
curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
curOption = curOption[ parts[ i ] ];
}
key = parts.pop();
if ( arguments.length === 1 ) {
return curOption[ key ] === undefined ? null : curOption[ key ];
}
curOption[ key ] = value;
} else {
if ( arguments.length === 1 ) {
return this.options[ key ] === undefined ? null : this.options[ key ];
}
options[ key ] = value;
}
}
this._setOptions( options );
return this;
},
_setOptions: function( options ) {
var key;
for ( key in options ) {
this._setOption( key, options[ key ] );
}
return this;
},
_setOption: function( key, value ) {
this.options[ key ] = value;
if ( key === "disabled" ) {
this.widget()
.toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
.attr( "aria-disabled", value );
this.hoverable.removeClass( "ui-state-hover" );
this.focusable.removeClass( "ui-state-focus" );
}
return this;
},
enable: function() {
return this._setOption( "disabled", false );
},
disable: function() {
return this._setOption( "disabled", true );
},
_on: function( suppressDisabledCheck, element, handlers ) {
var delegateElement,
instance = this;
// no suppressDisabledCheck flag, shuffle arguments
if ( typeof suppressDisabledCheck !== "boolean" ) {
handlers = element;
element = suppressDisabledCheck;
suppressDisabledCheck = false;
}
// no element argument, shuffle and use this.element
if ( !handlers ) {
handlers = element;
element = this.element;
delegateElement = this.widget();
} else {
// accept selectors, DOM elements
element = delegateElement = $( element );
this.bindings = this.bindings.add( element );
}
$.each( handlers, function( event, handler ) {
function handlerProxy() {
// allow widgets to customize the disabled handling
// - disabled as an array instead of boolean
// - disabled class as method for disabling individual parts
if ( !suppressDisabledCheck &&
( instance.options.disabled === true ||
$( this ).hasClass( "ui-state-disabled" ) ) ) {
return;
}
return ( typeof handler === "string" ? instance[ handler ] : handler )
.apply( instance, arguments );
}
// copy the guid so direct unbinding works
if ( typeof handler !== "string" ) {
handlerProxy.guid = handler.guid =
handler.guid || handlerProxy.guid || $.guid++;
}
var match = event.match( /^(\w+)\s*(.*)$/ ),
eventName = match[1] + instance.eventNamespace,
selector = match[2];
if ( selector ) {
delegateElement.delegate( selector, eventName, handlerProxy );
} else {
element.bind( eventName, handlerProxy );
}
});
},
_off: function( element, eventName ) {
eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
element.unbind( eventName ).undelegate( eventName );
},
_delay: function( handler, delay ) {
function handlerProxy() {
return ( typeof handler === "string" ? instance[ handler ] : handler )
.apply( instance, arguments );
}
var instance = this;
return setTimeout( handlerProxy, delay || 0 );
},
_hoverable: function( element ) {
this.hoverable = this.hoverable.add( element );
this._on( element, {
mouseenter: function( event ) {
$( event.currentTarget ).addClass( "ui-state-hover" );
},
mouseleave: function( event ) {
$( event.currentTarget ).removeClass( "ui-state-hover" );
}
});
},
_focusable: function( element ) {
this.focusable = this.focusable.add( element );
this._on( element, {
focusin: function( event ) {
$( event.currentTarget ).addClass( "ui-state-focus" );
},
focusout: function( event ) {
$( event.currentTarget ).removeClass( "ui-state-focus" );
}
});
},
_trigger: function( type, event, data ) {
var prop, orig,
callback = this.options[ type ];
data = data || {};
event = $.Event( event );
event.type = ( type === this.widgetEventPrefix ?
type :
this.widgetEventPrefix + type ).toLowerCase();
// the original event may come from any element
// so we need to reset the target on the new event
event.target = this.element[ 0 ];
// copy original event properties over to the new event
orig = event.originalEvent;
if ( orig ) {
for ( prop in orig ) {
if ( !( prop in event ) ) {
event[ prop ] = orig[ prop ];
}
}
}
this.element.trigger( event, data );
return !( $.isFunction( callback ) &&
callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
event.isDefaultPrevented() );
}
};
$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
if ( typeof options === "string" ) {
options = { effect: options };
}
var hasOptions,
effectName = !options ?
method :
options === true || typeof options === "number" ?
defaultEffect :
options.effect || defaultEffect;
options = options || {};
if ( typeof options === "number" ) {
options = { duration: options };
}
hasOptions = !$.isEmptyObject( options );
options.complete = callback;
if ( options.delay ) {
element.delay( options.delay );
}
if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
element[ method ]( options );
} else if ( effectName !== method && element[ effectName ] ) {
element[ effectName ]( options.duration, options.easing, callback );
} else {
element.queue(function( next ) {
$( this )[ method ]();
if ( callback ) {
callback.call( element[ 0 ] );
}
next();
});
}
};
});
})( jQuery );

View File

@@ -0,0 +1,5 @@
/*! Respond.js v1.4.2: min/max-width media query polyfill * Copyright 2013 Scott Jehl
* Licensed under https://github.com/scottjehl/Respond/blob/master/LICENSE-MIT
* */
!function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='&shy;<style media="'+a+'"> #mq-test-1 { width: 42px; }</style>',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b<s.length;b++){var c=s[b],e=c.href,f=c.media,g=c.rel&&"stylesheet"===c.rel.toLowerCase();e&&g&&!o[e]&&(c.styleSheet&&c.styleSheet.rawCssText?(v(c.styleSheet.rawCssText,e,f),o[e]=!0):(!/^([a-zA-Z:]*\/\/)/.test(e)&&!r||e.replace(RegExp.$1,"").split("/")[0]===a.location.host)&&("//"===e.substring(0,2)&&(e=a.location.protocol+e),d.push({href:e,media:f})))}w()};x(),c.update=x,c.getEmValue=t,a.addEventListener?a.addEventListener("resize",b,!1):a.attachEvent&&a.attachEvent("onresize",b)}}(this);

View File

@@ -0,0 +1 @@
!function(a){"use strict";var b=function(a,c){var d=/[^\w\-\.:]/.test(a)?new Function(b.arg+",tmpl","var _e=tmpl.encode"+b.helper+",_s='"+a.replace(b.regexp,b.func)+"';return _s;"):b.cache[a]=b.cache[a]||b(b.load(a));return c?d(c,b):function(a){return d(a,b)}};b.cache={},b.load=function(a){return document.getElementById(a).innerHTML},b.regexp=/([\s'\\])(?!(?:[^{]|\{(?!%))*%\})|(?:\{%(=|#)([\s\S]+?)%\})|(\{%)|(%\})/g,b.func=function(a,b,c,d,e,f){return b?{"\n":"\\n","\r":"\\r"," ":"\\t"," ":" "}[b]||"\\"+b:c?"="===c?"'+_e("+d+")+'":"'+("+d+"==null?'':"+d+")+'":e?"';":f?"_s+='":void 0},b.encReg=/[<>&"'\x00]/g,b.encMap={"<":"&lt;",">":"&gt;","&":"&amp;",'"':"&quot;","'":"&#39;"},b.encode=function(a){return(null==a?"":""+a).replace(b.encReg,function(a){return b.encMap[a]||""})},b.arg="o",b.helper=",print=function(s,e){_s+=e?(s==null?'':s):_e(s);},include=function(s,d){_s+=tmpl(s,d);}","function"==typeof define&&define.amd?define(function(){return b}):a.tmpl=b}(this);

View File

@@ -0,0 +1,201 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "GCDWebServer.h"
NS_ASSUME_NONNULL_BEGIN
@class GCDWebUploader;
/**
* Delegate methods for GCDWebUploader.
*
* @warning These methods are always called on the main thread in a serialized way.
*/
@protocol GCDWebUploaderDelegate <GCDWebServerDelegate>
@optional
/**
* This method is called whenever a file has been downloaded.
*/
- (void)webUploader:(GCDWebUploader*)uploader didDownloadFileAtPath:(NSString*)path;
/**
* This method is called whenever a file has been uploaded.
*/
- (void)webUploader:(GCDWebUploader*)uploader didUploadFileAtPath:(NSString*)path;
/**
* This method is called whenever a file or directory has been moved.
*/
- (void)webUploader:(GCDWebUploader*)uploader didMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath;
/**
* This method is called whenever a file or directory has been deleted.
*/
- (void)webUploader:(GCDWebUploader*)uploader didDeleteItemAtPath:(NSString*)path;
/**
* This method is called whenever a directory has been created.
*/
- (void)webUploader:(GCDWebUploader*)uploader didCreateDirectoryAtPath:(NSString*)path;
@end
/**
* The GCDWebUploader subclass of GCDWebServer implements an HTML 5 web browser
* interface for uploading or downloading files, and moving or deleting files
* or directories.
*
* See the README.md file for more information about the features of GCDWebUploader.
*
* @warning For GCDWebUploader to work, "GCDWebUploader.bundle" must be added
* to the resources of the Xcode target.
*/
@interface GCDWebUploader : GCDWebServer
/**
* Returns the upload directory as specified when the uploader was initialized.
*/
@property(nonatomic, readonly) NSString* uploadDirectory;
/**
* Sets the delegate for the uploader.
*/
@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<NSString*>* allowedFileExtensions;
/**
* Sets if files and directories whose name start with a period are allowed to
* be operated on.
*
* The default value is NO.
*/
@property(nonatomic) BOOL allowHiddenItems;
/**
* Sets the title for the uploader web interface.
*
* The default value is the application name.
*
* @warning Any reserved HTML characters in the string value for this property
* must have been replaced by character entities e.g. "&" becomes "&amp;".
*/
@property(nonatomic, copy) NSString* title;
/**
* Sets the header for the uploader web interface.
*
* The default value is the same as the title property.
*
* @warning Any reserved HTML characters in the string value for this property
* must have been replaced by character entities e.g. "&" becomes "&amp;".
*/
@property(nonatomic, copy) NSString* header;
/**
* Sets the prologue for the uploader web interface.
*
* The default value is a short help text.
*
* @warning The string value for this property must be raw HTML
* e.g. "<p>Some text</p>"
*/
@property(nonatomic, copy) NSString* prologue;
/**
* Sets the epilogue for the uploader web interface.
*
* The default value is nil i.e. no epilogue.
*
* @warning The string value for this property must be raw HTML
* e.g. "<p>Some text</p>"
*/
@property(nonatomic, copy) NSString* epilogue;
/**
* Sets the footer for the uploader web interface.
*
* The default value is the application name and version.
*
* @warning Any reserved HTML characters in the string value for this property
* must have been replaced by character entities e.g. "&" becomes "&amp;".
*/
@property(nonatomic, copy) NSString* footer;
/**
* This method is the designated initializer for the class.
*/
- (instancetype)initWithUploadDirectory:(NSString*)path;
@end
/**
* Hooks to customize the behavior of GCDWebUploader.
*
* @warning These methods can be called on any GCD thread.
*/
@interface GCDWebUploader (Subclassing)
/**
* This method is called to check if a file upload is allowed to complete.
* The uploaded file is available for inspection at "tempPath".
*
* The default implementation returns YES.
*/
- (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath;
/**
* This method is called to check if a file or directory is allowed to be moved.
*
* The default implementation returns YES.
*/
- (BOOL)shouldMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath;
/**
* This method is called to check if a file or directory is allowed to be deleted.
*
* The default implementation returns YES.
*/
- (BOOL)shouldDeleteItemAtPath:(NSString*)path;
/**
* This method is called to check if a directory is allowed to be created.
*
* The default implementation returns YES.
*/
- (BOOL)shouldCreateDirectoryAtPath:(NSString*)path;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,429 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebUploader requires ARC
#endif
#import <TargetConditionals.h>
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#else
#import <SystemConfiguration/SystemConfiguration.h>
#endif
#import "GCDWebUploader.h"
#import "GCDWebServerFunctions.h"
#import "GCDWebServerDataRequest.h"
#import "GCDWebServerMultiPartFormRequest.h"
#import "GCDWebServerURLEncodedFormRequest.h"
#import "GCDWebServerDataResponse.h"
#import "GCDWebServerErrorResponse.h"
#import "GCDWebServerFileResponse.h"
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)
- (BOOL)_checkFileExtension:(NSString*)fileName {
if (_allowedFileExtensions && ![_allowedFileExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
return NO;
}
return YES;
}
- (NSString*)_uniquePathForPath:(NSString*)path {
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSString* directory = [path stringByDeletingLastPathComponent];
NSString* file = [path lastPathComponent];
NSString* base = [file stringByDeletingPathExtension];
NSString* extension = [file pathExtension];
int retries = 0;
do {
if (extension.length) {
path = [directory stringByAppendingPathComponent:(NSString*)[[base stringByAppendingFormat:@" (%i)", ++retries] stringByAppendingPathExtension:extension]];
} else {
path = [directory stringByAppendingPathComponent:[base stringByAppendingFormat:@" (%i)", ++retries]];
}
} while ([[NSFileManager defaultManager] fileExistsAtPath:path]);
}
return path;
}
- (GCDWebServerResponse*)listDirectory:(GCDWebServerRequest*)request {
NSString* relativePath = [[request query] objectForKey:@"path"];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
BOOL isDirectory = NO;
if (!absolutePath || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
if (!isDirectory) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"\"%@\" is not a directory", relativePath];
}
NSString* directoryName = [absolutePath lastPathComponent];
if (!_allowHiddenItems && [directoryName hasPrefix:@"."]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Listing directory name \"%@\" is not allowed", directoryName];
}
NSError* error = nil;
NSArray* contents = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:absolutePath error:&error] sortedArrayUsingSelector:@selector(localizedStandardCompare:)];
if (contents == nil) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed listing directory \"%@\"", relativePath];
}
NSMutableArray* array = [NSMutableArray array];
for (NSString* item in [contents sortedArrayUsingSelector:@selector(localizedStandardCompare:)]) {
if (_allowHiddenItems || ![item hasPrefix:@"."]) {
NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[absolutePath stringByAppendingPathComponent:item] error:NULL];
NSString* type = [attributes objectForKey:NSFileType];
if ([type isEqualToString:NSFileTypeRegular] && [self _checkFileExtension:item]) {
[array addObject:@{
@"path" : [relativePath stringByAppendingPathComponent:item],
@"name" : item,
@"size" : (NSNumber*)[attributes objectForKey:NSFileSize]
}];
} else if ([type isEqualToString:NSFileTypeDirectory]) {
[array addObject:@{
@"path" : [[relativePath stringByAppendingPathComponent:item] stringByAppendingString:@"/"],
@"name" : item
}];
}
}
}
return [GCDWebServerDataResponse responseWithJSONObject:array];
}
- (GCDWebServerResponse*)downloadFile:(GCDWebServerRequest*)request {
NSString* relativePath = [[request query] objectForKey:@"path"];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
BOOL isDirectory = NO;
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
if (isDirectory) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"\"%@\" is a directory", relativePath];
}
NSString* fileName = [absolutePath lastPathComponent];
if (([fileName hasPrefix:@"."] && !_allowHiddenItems) || ![self _checkFileExtension:fileName]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Downlading file name \"%@\" is not allowed", fileName];
}
if ([self.delegate respondsToSelector:@selector(webUploader:didDownloadFileAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didDownloadFileAtPath:absolutePath];
});
}
return [GCDWebServerFileResponse responseWithFile:absolutePath isAttachment:YES];
}
- (GCDWebServerResponse*)uploadFile:(GCDWebServerMultiPartFormRequest*)request {
NSRange range = [[request.headers objectForKey:@"Accept"] rangeOfString:@"application/json" options:NSCaseInsensitiveSearch];
NSString* contentType = (range.location != NSNotFound ? @"application/json" : @"text/plain; charset=utf-8"); // Required when using iFrame transport (see https://github.com/blueimp/jQuery-File-Upload/wiki/Setup)
GCDWebServerMultiPartFile* file = [request firstFileForControlName:@"files[]"];
if ((!_allowHiddenItems && [file.fileName hasPrefix:@"."]) || ![self _checkFileExtension:file.fileName]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploaded file name \"%@\" is not allowed", file.fileName];
}
NSString* relativePath = [[request firstArgumentForControlName:@"path"] string];
NSString* absolutePath = [self _uniquePathForPath:[[_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)] stringByAppendingPathComponent:file.fileName]];
if (![self shouldUploadFileAtPath:absolutePath withTemporaryFile:file.temporaryPath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file \"%@\" to \"%@\" is not permitted", file.fileName, relativePath];
}
NSError* error = nil;
if (![[NSFileManager defaultManager] moveItemAtPath:file.temporaryPath toPath:absolutePath error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving uploaded file to \"%@\"", relativePath];
}
if ([self.delegate respondsToSelector:@selector(webUploader:didUploadFileAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didUploadFileAtPath:absolutePath];
});
}
return [GCDWebServerDataResponse responseWithJSONObject:@{} contentType:contentType];
}
- (GCDWebServerResponse*)moveItem:(GCDWebServerURLEncodedFormRequest*)request {
NSString* oldRelativePath = [request.arguments objectForKey:@"oldPath"];
NSString* oldAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(oldRelativePath)];
BOOL isDirectory = NO;
if (![[NSFileManager defaultManager] fileExistsAtPath:oldAbsolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", oldRelativePath];
}
NSString* newRelativePath = [request.arguments objectForKey:@"newPath"];
NSString* newAbsolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(newRelativePath)]];
NSString* itemName = [newAbsolutePath lastPathComponent];
if ((!_allowHiddenItems && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving to item name \"%@\" is not allowed", itemName];
}
if (![self shouldMoveItemFromPath:oldAbsolutePath toPath:newAbsolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving \"%@\" to \"%@\" is not permitted", oldRelativePath, newRelativePath];
}
NSError* error = nil;
if (![[NSFileManager defaultManager] moveItemAtPath:oldAbsolutePath toPath:newAbsolutePath error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving \"%@\" to \"%@\"", oldRelativePath, newRelativePath];
}
if ([self.delegate respondsToSelector:@selector(webUploader:didMoveItemFromPath:toPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didMoveItemFromPath:oldAbsolutePath toPath:newAbsolutePath];
});
}
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
}
- (GCDWebServerResponse*)deleteItem:(GCDWebServerURLEncodedFormRequest*)request {
NSString* relativePath = [request.arguments objectForKey:@"path"];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
BOOL isDirectory = NO;
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
NSString* itemName = [absolutePath lastPathComponent];
if (([itemName hasPrefix:@"."] && !_allowHiddenItems) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting item name \"%@\" is not allowed", itemName];
}
if (![self shouldDeleteItemAtPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting \"%@\" is not permitted", relativePath];
}
NSError* error = nil;
if (![[NSFileManager defaultManager] removeItemAtPath:absolutePath error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed deleting \"%@\"", relativePath];
}
if ([self.delegate respondsToSelector:@selector(webUploader:didDeleteItemAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didDeleteItemAtPath:absolutePath];
});
}
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
}
- (GCDWebServerResponse*)createDirectory:(GCDWebServerURLEncodedFormRequest*)request {
NSString* relativePath = [request.arguments objectForKey:@"path"];
NSString* absolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)]];
NSString* directoryName = [absolutePath lastPathComponent];
if (!_allowHiddenItems && [directoryName hasPrefix:@"."]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory name \"%@\" is not allowed", directoryName];
}
if (![self shouldCreateDirectoryAtPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory \"%@\" is not permitted", relativePath];
}
NSError* error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtPath:absolutePath withIntermediateDirectories:NO attributes:nil error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed creating directory \"%@\"", relativePath];
}
if ([self.delegate respondsToSelector:@selector(webUploader:didCreateDirectoryAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didCreateDirectoryAtPath:absolutePath];
});
}
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
}
@end
@implementation GCDWebUploader (Subclassing)
- (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath {
return YES;
}
- (BOOL)shouldMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath {
return YES;
}
- (BOOL)shouldDeleteItemAtPath:(NSString*)path {
return YES;
}
- (BOOL)shouldCreateDirectoryAtPath:(NSString*)path {
return YES;
}
@end

10
LICENSE
View File

@@ -1,4 +1,4 @@
Copyright (c) 2012-2013, Pierre-Olivier Latour
Copyright (c) 2012-2014, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -8,14 +8,14 @@ modification, are permitted provided that the following conditions are met:
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2013, Pierre-Olivier Latour
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -9,14 +9,14 @@
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
@@ -25,37 +25,214 @@
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <libgen.h>
#import "GCDWebServer.h"
#import "GCDWebServerDataRequest.h"
#import "GCDWebServerURLEncodedFormRequest.h"
#import "GCDWebServerMultiPartFormRequest.h"
#import "GCDWebServerDataResponse.h"
#import "GCDWebServerStreamedResponse.h"
#import "GCDWebDAVServer.h"
#import "GCDWebUploader.h"
#ifndef __GCDWEBSERVER_ENABLE_TESTING__
#error __GCDWEBSERVER_ENABLE_TESTING__ must be defined
#endif
typedef enum {
kMode_WebServer = 0,
kMode_HTMLPage,
kMode_HTMLForm,
kMode_HTMLFileUpload,
kMode_WebDAV,
kMode_WebUploader,
kMode_StreamingResponse,
kMode_AsyncResponse
} Mode;
@interface Delegate : NSObject <GCDWebServerDelegate, GCDWebDAVServerDelegate, GCDWebUploaderDelegate>
@end
@implementation Delegate
- (void)_logDelegateCall:(SEL)selector {
fprintf(stdout, "<DELEGATE METHOD \"%s\" CALLED>\n", [NSStringFromSelector(selector) UTF8String]);
}
- (void)webServerDidStart:(GCDWebServer*)server {
[self _logDelegateCall:_cmd];
}
- (void)webServerDidCompleteBonjourRegistration:(GCDWebServer*)server {
[self _logDelegateCall:_cmd];
}
- (void)webServerDidUpdateNATPortMapping:(GCDWebServer*)server {
[self _logDelegateCall:_cmd];
}
- (void)webServerDidConnect:(GCDWebServer*)server {
[self _logDelegateCall:_cmd];
}
- (void)webServerDidDisconnect:(GCDWebServer*)server {
[self _logDelegateCall:_cmd];
}
- (void)webServerDidStop:(GCDWebServer*)server {
[self _logDelegateCall:_cmd];
}
- (void)davServer:(GCDWebDAVServer*)server didDownloadFileAtPath:(NSString*)path {
[self _logDelegateCall:_cmd];
}
- (void)davServer:(GCDWebDAVServer*)server didUploadFileAtPath:(NSString*)path {
[self _logDelegateCall:_cmd];
}
- (void)davServer:(GCDWebDAVServer*)server didMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath {
[self _logDelegateCall:_cmd];
}
- (void)davServer:(GCDWebDAVServer*)server didCopyItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath {
[self _logDelegateCall:_cmd];
}
- (void)davServer:(GCDWebDAVServer*)server didDeleteItemAtPath:(NSString*)path {
[self _logDelegateCall:_cmd];
}
- (void)davServer:(GCDWebDAVServer*)server didCreateDirectoryAtPath:(NSString*)path {
[self _logDelegateCall:_cmd];
}
- (void)webUploader:(GCDWebUploader*)uploader didDownloadFileAtPath:(NSString*)path {
[self _logDelegateCall:_cmd];
}
- (void)webUploader:(GCDWebUploader*)uploader didUploadFileAtPath:(NSString*)path {
[self _logDelegateCall:_cmd];
}
- (void)webUploader:(GCDWebUploader*)uploader didMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath {
[self _logDelegateCall:_cmd];
}
- (void)webUploader:(GCDWebUploader*)uploader didDeleteItemAtPath:(NSString*)path {
[self _logDelegateCall:_cmd];
}
- (void)webUploader:(GCDWebUploader*)uploader didCreateDirectoryAtPath:(NSString*)path {
[self _logDelegateCall:_cmd];
}
@end
int main(int argc, const char* argv[]) {
BOOL success = NO;
int result = -1;
@autoreleasepool {
GCDWebServer* webServer = [[GCDWebServer alloc] init];
switch (0) {
case 0: {
[webServer addHandlerForBasePath:@"/" localPath:NSHomeDirectory() indexFilename:nil cacheAge:0];
Mode mode = kMode_WebServer;
BOOL recording = NO;
NSString* rootDirectory = NSHomeDirectory();
NSString* testDirectory = nil;
NSString* authenticationMethod = nil;
NSString* authenticationRealm = nil;
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 {
for (int i = 1; i < argc; ++i) {
if (argv[i][0] != '-') {
continue;
}
if (!strcmp(argv[i], "-mode") && (i + 1 < argc)) {
++i;
if (!strcmp(argv[i], "webServer")) {
mode = kMode_WebServer;
} else if (!strcmp(argv[i], "htmlPage")) {
mode = kMode_HTMLPage;
} else if (!strcmp(argv[i], "htmlForm")) {
mode = kMode_HTMLForm;
} else if (!strcmp(argv[i], "htmlFileUpload")) {
mode = kMode_HTMLFileUpload;
} else if (!strcmp(argv[i], "webDAV")) {
mode = kMode_WebDAV;
} else if (!strcmp(argv[i], "webUploader")) {
mode = kMode_WebUploader;
} else if (!strcmp(argv[i], "streamingResponse")) {
mode = kMode_StreamingResponse;
} else if (!strcmp(argv[i], "asyncResponse")) {
mode = kMode_AsyncResponse;
}
} else if (!strcmp(argv[i], "-record")) {
recording = YES;
} else if (!strcmp(argv[i], "-root") && (i + 1 < argc)) {
++i;
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])];
} else if (!strcmp(argv[i], "-authenticationMethod") && (i + 1 < argc)) {
++i;
authenticationMethod = [NSString stringWithUTF8String:argv[i]];
} else if (!strcmp(argv[i], "-authenticationRealm") && (i + 1 < argc)) {
++i;
authenticationRealm = [NSString stringWithUTF8String:argv[i]];
} else if (!strcmp(argv[i], "-authenticationUser") && (i + 1 < argc)) {
++i;
authenticationUser = [NSString stringWithUTF8String:argv[i]];
} else if (!strcmp(argv[i], "-authenticationPassword") && (i + 1 < argc)) {
++i;
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\"\n", [rootDirectory UTF8String]);
webServer = [[GCDWebServer alloc] init];
[webServer addGETHandlerForBasePath:@"/" directoryPath:rootDirectory indexFilename:nil cacheAge:0 allowRangeRequests:YES];
break;
}
case 1: {
// Renders a HTML page
case kMode_HTMLPage: {
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;
}
case 2: {
// Implements an HTML form
case kMode_HTMLForm: {
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\"> \
@@ -63,25 +240,174 @@ 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\n");
webServer = [[GCDWebServer alloc] init];
NSString* formHTML = @" \
<form name=\"input\" action=\"/\" method=\"post\" enctype=\"multipart/form-data\"> \
<input type=\"hidden\" name=\"secret\" value=\"42\"> \
<input type=\"file\" name=\"files\" multiple><br/> \
<input type=\"submit\" value=\"Submit\"> \
</form> \
";
[webServer addHandlerForMethod:@"GET"
path:@"/"
requestClass:[GCDWebServerRequest class]
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];
}];
break;
}
// Serve home directory through WebDAV
case kMode_WebDAV: {
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\"\n", [rootDirectory UTF8String]);
webServer = [[GCDWebUploader alloc] initWithUploadDirectory:rootDirectory];
break;
}
// Test streaming responses
case kMode_StreamingResponse: {
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];
}
}];
}];
[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);
});
}];
}];
break;
}
// Test async responses
case kMode_AsyncResponse: {
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:(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);
});
}];
break;
}
}
success = [webServer runWithPort:8080];
[webServer release];
if (webServer) {
Delegate* delegate = [[Delegate alloc] init];
if (testDirectory) {
#if DEBUG
webServer.delegate = delegate;
#endif
fprintf(stdout, "<RUNNING TESTS FROM \"%s\">\n\n", [testDirectory UTF8String]);
result = (int)[webServer runTestsWithOptions:@{ GCDWebServerOption_Port : @8080 } inDirectory:testDirectory];
} else {
webServer.delegate = delegate;
if (recording) {
fprintf(stdout, "<RECORDING ENABLED>\n");
webServer.recordingEnabled = YES;
}
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];
if ([authenticationMethod isEqualToString:@"Basic"]) {
[options setObject:GCDWebServerAuthenticationMethod_Basic forKey:GCDWebServerOption_AuthenticationMethod];
} else if ([authenticationMethod isEqualToString:@"Digest"]) {
[options setObject:GCDWebServerAuthenticationMethod_DigestAccess forKey:GCDWebServerOption_AuthenticationMethod];
}
}
if ([webServer runWithOptions:options error:NULL]) {
result = 0;
}
}
webServer.delegate = nil;
}
}
return success ? 0 : -1;
return result;
}

345
README.md
View File

@@ -1,33 +1,90 @@
Overview
========
GCDWebServer is a lightweight GCD based HTTP 1.1 server designed to be embedded in Mac & iOS apps. It was written from scratch with the following goals in mind:
* Entirely built with an event-driven design using [Grand Central Dispatch](http://en.wikipedia.org/wiki/Grand_Central_Dispatch) for maximum performance and concurrency
* Well designed API for easy integration and customization
* Support for streaming large HTTP bodies for requests and responses to minimize memory usage
* Built-in parser for web forms submitted using "application/x-www-form-urlencoded" or "multipart/form-data" encodings (including file uploads)
* Minimal number of source files and no dependencies on third-party source code
* Available under a friendly [New BSD License](GCDWebServer/blob/master/LICENSE)
[![Build Status](https://travis-ci.org/swisspol/GCDWebServer.svg?branch=master)](https://travis-ci.org/swisspol/GCDWebServer)
[![Version](http://cocoapod-badges.herokuapp.com/v/GCDWebServer/badge.png)](http://cocoadocs.org/docsets/GCDWebServer)
[![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)
What's not available out of the box but can be implemented on top of the API:
* Authentication like Basic Authentication
* Web forms submitted using "multipart/mixed"
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:
* 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
* No dependencies on third-party source code
* Available under a friendly [New BSD License](LICENSE)
Extra built-in features:
* Allow implementation of fully asynchronous handlers of incoming HTTP requests
* Minimize memory usage with disk streaming of large HTTP request or response bodies
* Parser for [web forms](http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4) submitted using "application/x-www-form-urlencoded" or "multipart/form-data" encodings (including file uploads)
* [JSON](http://www.json.org/) parsing and serialization for request and response HTTP bodies
* [Chunked transfer encoding](https://en.wikipedia.org/wiki/Chunked_transfer_encoding) for request and response HTTP bodies
* [HTTP compression](https://en.wikipedia.org/wiki/HTTP_compression) with gzip for request and response HTTP bodies
* [HTTP range](https://en.wikipedia.org/wiki/Byte_serving) support for requests of local files
* [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)
What's not supported (but not really required from an embedded HTTP server):
* Keep-alive connections
* HTTPS
Requirements:
* OS X 10.7 or later
* iOS 5.0 or later
* 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. 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 Podfile:
```
pod "GCDWebServer", "~> 3.0"
```
If you want to use GCDWebUploader, use this line instead:
```
pod "GCDWebServer/WebUploader", "~> 3.0"
```
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
===========
This code snippet shows how to implement a custom HTTP server that runs on port 8080 and returns a "Hello World" HTML page to any request &mdash; Because GCDWebServer uses GCD blocks to handle requests, no subclassing or delegates are needed:
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.
**IMPORTANT:** If not using CocoaPods, be sure to add the `libz` shared system library to the Xcode target for your app.
**OS X version (command line tool):**
```objectivec
#import "GCDWebServer.h"
#import "GCDWebServerDataResponse.h"
int main(int argc, const char* argv[]) {
@autoreleasepool {
@@ -35,7 +92,7 @@ int main(int argc, const char* argv[]) {
// Create server
GCDWebServer* webServer = [[GCDWebServer alloc] init];
// Add a handler to respond to requests on any URL
// Add a handler to respond to GET requests on any URL
[webServer addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
@@ -44,22 +101,144 @@ int main(int argc, const char* argv[]) {
}];
// Use convenience method that runs server on port 8080 until SIGINT received
[webServer runWithPort:8080];
// Destroy server
[webServer release];
// Use convenience method that runs server on port 8080
// until SIGINT (Ctrl-C in Terminal) or SIGTERM is received
[webServer runWithPort:8080 bonjourName:nil];
NSLog(@"Visit %@ in your web browser", webServer.serverURL);
}
return 0;
}
```
**iOS version:**
```objectivec
#import "GCDWebServer.h"
#import "GCDWebServerDataResponse.h"
@interface AppDelegate : NSObject <UIApplicationDelegate> {
GCDWebServer* _webServer;
}
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
// Create server
_webServer = [[GCDWebServer alloc] init];
// Add a handler to respond to GET requests on any URL
[_webServer addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
}];
// Start server on port 8080
[_webServer startWithPort:8080 bonjourName:nil];
NSLog(@"Visit %@ in your web browser", _webServer.serverURL);
return YES;
}
@end
```
**OS X Swift version (command line tool):**
***webServer.swift***
```swift
import Foundation
import GCDWebServer
func initWebServer() {
let webServer = GCDWebServer()
webServer.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self, processBlock: {request in
return GCDWebServerDataResponse(HTML:"<html><body><p>Hello World</p></body></html>")
})
webServer.runWithPort(8080, bonjourName: "GCD Web Server")
print("Visit \(webServer.serverURL) in your web browser")
}
```
***WebServer-Bridging-Header.h***
```objectivec
#import <GCDWebServer/GCDWebServer.h>
#import <GCDWebServer/GCDWebServerDataResponse.h>
```
Web Based Uploads in iOS Apps
=============================
GCDWebUploader is a subclass of ```GCDWebServer``` that provides a ready-to-use HTML 5 file uploader & downloader. This lets users upload, download, delete files and create directories from a directory inside your iOS app's sandbox using a clean user interface in their web browser.
Simply instantiate and run a ```GCDWebUploader``` instance then visit ```http://{YOUR-IOS-DEVICE-IP-ADDRESS}/``` from your web browser:
```objectivec
#import "GCDWebUploader.h"
@interface AppDelegate : NSObject <UIApplicationDelegate> {
GCDWebUploader* _webUploader;
}
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
_webUploader = [[GCDWebUploader alloc] initWithUploadDirectory:documentsPath];
[_webUploader start];
NSLog(@"Visit %@ in your web browser", _webUploader.serverURL);
return YES;
}
@end
```
WebDAV Server in iOS Apps
=========================
GCDWebDAVServer is a subclass of ```GCDWebServer``` that provides a class 1 compliant [WebDAV](https://en.wikipedia.org/wiki/WebDAV) server. This lets users upload, download, delete files and create directories from a directory inside your iOS app's sandbox using any WebDAV client like [Transmit](https://panic.com/transmit/) (Mac), [ForkLift](http://binarynights.com/forklift/) (Mac) or [CyberDuck](http://cyberduck.io/) (Mac / Windows).
GCDWebDAVServer should also work with the [OS X Finder](http://support.apple.com/kb/PH13859) as it is partially class 2 compliant (but only when the client is the OS X WebDAV implementation).
Simply instantiate and run a ```GCDWebDAVServer``` instance then connect to ```http://{YOUR-IOS-DEVICE-IP-ADDRESS}/``` using a WebDAV client:
```objectivec
#import "GCDWebDAVServer.h"
@interface AppDelegate : NSObject <UIApplicationDelegate> {
GCDWebDAVServer* _davServer;
}
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
_davServer = [[GCDWebDAVServer alloc] initWithUploadDirectory:documentsPath];
[_davServer start];
NSLog(@"Visit %@ in your WebDAV client", _davServer.serverURL);
return YES;
}
@end
```
Serving a Static Website
========================
GCDWebServer includes a built-in handler that can recursively serve a directory (it also lets you control how the "Cache-Control" header should be set):
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):
**OS X version (command line tool):**
```objectivec
#import "GCDWebServer.h"
@@ -67,9 +246,8 @@ int main(int argc, const char* argv[]) {
@autoreleasepool {
GCDWebServer* webServer = [[GCDWebServer alloc] init];
[webServer addHandlerForBasePath:@"/" localPath:NSHomeDirectory() indexFilename:nil cacheAge:3600];
[webServer addGETHandlerForBasePath:@"/" directoryPath:NSHomeDirectory() indexFilename:nil cacheAge:3600 allowRangeRequests:YES];
[webServer runWithPort:8080];
[webServer release];
}
return 0;
@@ -79,36 +257,121 @@ int main(int argc, const char* argv[]) {
Using GCDWebServer
==================
You start by creating an instance of the 'GCDWebServer' class. Note that you can have multiple web servers running in the same app as long as they listen on different ports.
You start by creating an instance of the ```GCDWebServer``` class. Note that you can have multiple web servers running in the same app as long as they listen on different ports.
Then you add one or more "handlers" to the server: each handler gets a chance to handle an incoming web request and provide a response. Handlers are called in a LIFO queue, so the latest added handler overrides any previously added ones.
Finally you start the server on a given port.
Understanding GCDWebServer Architecture
=======================================
Understanding GCDWebServer's Architecture
=========================================
GCDWebServer is made of only 4 core classes:
* 'GCDWebServer' manages the socket that listens for new HTTP connections and the list of handlers used by the server.
* 'GCDWebServerConnection' is instantiated by 'GCDWebServer' to handle each new HTTP connection. Each instance stays alive until the connection is closed. You cannot use this class directly, but it is exposed so you can subclass it to override some hooks.
* 'GCDWebServerRequest' is created by the 'GCDWebServerConnection' instance after HTTP headers have been received. It wraps the request and handles the HTTP body if any. GCDWebServer comes with several subclasses of 'GCDWebServerRequest' to handle common cases like storing the body in memory or stream it to a file on disk. See [GCDWebServerRequest.h](GCDWebServer/blob/master/CGDWebServer/GCDWebServerRequest.h) for the full list.
* 'GCDWebServerResponse' is created by the request handler and wraps the response HTTP headers and optional body. GCDWebServer provides several subclasses of 'GCDWebServerResponse' to handle common cases like HTML text in memory or streaming a file from disk. See [GCDWebServerResponse.h](GCDWebServer/blob/master/CGDWebServer/GCDWebServerResponse.h) for the full list.
GCDWebServer's architecture consists of only 4 core classes:
* [GCDWebServer](GCDWebServer/Core/GCDWebServer.h) manages the socket that listens for new HTTP connections and the list of handlers used by the server.
* [GCDWebServerConnection](GCDWebServer/Core/GCDWebServerConnection.h) is instantiated by ```GCDWebServer``` to handle each new HTTP connection. Each instance stays alive until the connection is closed. You cannot use this class directly, but it is exposed so you can subclass it to override some hooks.
* [GCDWebServerRequest](GCDWebServer/Core/GCDWebServerRequest.h) is created by the ```GCDWebServerConnection``` instance after HTTP headers have been received. It wraps the request and handles the HTTP body if any. GCDWebServer comes with [several subclasses](GCDWebServer/Requests) of ```GCDWebServerRequest``` to handle common cases like storing the body in memory or stream it to a file on disk.
* [GCDWebServerResponse](GCDWebServer/Core/GCDWebServerResponse.h) is created by the request handler and wraps the response HTTP headers and optional body. GCDWebServer comes with [several subclasses](GCDWebServer/Responses) of ```GCDWebServerResponse``` to handle common cases like HTML text in memory or streaming a file from disk.
Implementing Handlers
=====================
GCDWebServer relies on "handlers" to process incoming web requests and generating responses. Handlers are implemented with GCD blocks which makes it very easy to provide your owns. However, they are executed on arbitrary threads within GCD so __special attention must be paid to thread-safety and re-entrancy__.
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 'GCDWebServerRequest' instance (see above). Otherwise, it simply returns nil.
* The 'GCDWebServerProcessBlock' is called after the web request has been fully received and is passed the 'GCDWebServerRequest' instance created at the previous step. It must return a 'GCDWebServerResponse' instance (see above) or nil on error.
* The ```GCDWebServerMatchBlock``` is called on every handler added to the ```GCDWebServer``` instance whenever a web request has started (i.e. HTTP headers have been received). It is passed the basic info for the web request (HTTP method, URL, headers...) and must decide if it wants to handle it or not. If yes, it must return a new ```GCDWebServerRequest``` instance (see above) created with this info. Otherwise, it simply returns nil.
* The ```GCDWebServerProcessBlock``` or ```GCDWebServerAsyncProcessBlock``` is called after the web request has been fully received and is passed the ```GCDWebServerRequest``` instance created at the previous step. It must return synchronously (if using ```GCDWebServerProcessBlock```) or asynchronously (if using ```GCDWebServerAsyncProcessBlock```) a ```GCDWebServerResponse``` instance (see above) or nil on error, which will result in a 500 HTTP status code returned to the client. It's however recommended to return an instance of [GCDWebServerErrorResponse](GCDWebServer/Responses/GCDWebServerErrorResponse.h) on error so more useful information can be returned to the client.
Note that most methods on 'GCDWebServer' to add handlers only require the 'GCDWebServerProcessBlock' as they already provide a built-in 'GCDWebServerMatchBlock' e.g. to match a URL path with a Regex.
Note that most methods on ```GCDWebServer``` to add handlers only require the ```GCDWebServerProcessBlock``` or ```GCDWebServerAsyncProcessBlock``` as they already provide a built-in ```GCDWebServerMatchBlock``` e.g. to match a URL path with a Regex.
Asynchronous HTTP Responses
===========================
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
[webServer addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
return response;
}];
```
**(Asynchronous version)** The handler returns immediately and calls back GCDWebServer later with the generated HTTP response:
```objectivec
[webServer addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class]
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
// Do some async operation like network access or file I/O (simulated here using dispatch_after())
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
completionBlock(response);
});
}];
```
**(Advanced asynchronous version)** The handler returns immediately a streamed HTTP response which itself generates its contents asynchronously:
```objectivec
[webServer addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
NSMutableArray* contents = [NSMutableArray arrayWithObjects:@"<html><body><p>\n", @"Hello World!\n", @"</p></body></html>\n", nil]; // Fake data source we are reading from
GCDWebServerStreamedResponse* response = [GCDWebServerStreamedResponse responseWithContentType:@"text/html" asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) {
// Simulate a delay reading from the fake data source
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString* string = contents.firstObject;
if (string) {
[contents removeObjectAtIndex:0];
completionBlock([string dataUsingEncoding:NSUTF8StringEncoding], nil); // Generate the 2nd part of the stream data
} else {
completionBlock([NSData data], nil); // Must pass an empty NSData to signal the end of the stream
}
});
}];
return response;
}];
```
*Note that you can even combine both the asynchronous and advanced asynchronous versions to return asynchronously an asynchronous HTTP response!*
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/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```.
HTTP connections are often initiated in batches (or bursts), for instance when loading a web page with multiple resources. This makes it difficult to accurately detect when the *very last* HTTP connection has been closed: it's possible 2 consecutive HTTP connections part of the same batch would be separated by a small delay instead of overlapping. It would be bad for the client if GCDWebServer suspended itself right in between. The ```GCDWebServerOption_ConnectedStateCoalescingInterval``` option solves this problem elegantly by forcing GCDWebServer to wait some extra delay before performing any action after the last HTTP connection has been closed, just in case a new HTTP connection is initiated within this delay.
Logging in GCDWebServer
=======================
Both for debugging and informational purpose, GCDWebServer logs messages extensively whenever something happens. Furthermore, when building GCDWebServer in "Debug" mode versus "Release" mode, it logs even more information but also performs a number of internal consistency checks. To enable this behavior, define the preprocessor constant ```DEBUG=1``` when compiling GCDWebServer. In Xcode target settings, this can be done by adding ```DEBUG=1``` to the build setting ```GCC_PREPROCESSOR_DEFINITIONS``` when building in "Debug" configuration. Finally, you can also control the logging verbosity at run time by calling ```+[GCDWebServer setLogLevel:]```.
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): 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.
Advanced Example 1: Implementing HTTP Redirects
===============================================
Here's an example handler that redirects "/" to "/index.html" using the convenience method on 'GCDWebServerResponse' (it sets the HTTP status code and 'Location' header automatically):
Here's an example handler that redirects "/" to "/index.html" using the convenience method on ```GCDWebServerResponse``` (it sets the HTTP status code and "Location" header automatically):
```objectivec
[self addHandlerForMethod:@"GET"
@@ -126,8 +389,8 @@ Advanced Example 2: Implementing Forms
======================================
To implement an HTTP form, you need a pair of handlers:
* The GET handler does not expect any body in the HTTP request and therefore uses the 'GCDWebServerRequest' class. The handler generates a response containing a simple HTML form.
* The POST handler expects the form values to be in the body of the HTTP request and percent-encoded. Fortunately, GCDWebServer provides the request class 'GCDWebServerURLEncodedFormRequest' which can automatically parse such bodies. The handler simply echoes back the value from the user submitted form.
* The GET handler does not expect any body in the HTTP request and therefore uses the ```GCDWebServerRequest``` class. The handler generates a response containing a simple HTML form.
* The POST handler expects the form values to be in the body of the HTTP request and percent-encoded. Fortunately, GCDWebServer provides the request class ```GCDWebServerURLEncodedFormRequest``` which can automatically parse such bodies. The handler simply echoes back the value from the user submitted form.
```objectivec
[webServer addHandlerForMethod:@"GET"
@@ -162,7 +425,7 @@ To implement an HTTP form, you need a pair of handlers:
Advanced Example 3: Serving a Dynamic Website
=============================================
GCDWebServer provides an extension to the 'GCDWebServerDataResponse' class that can return HTML content generated from a template and a set of variables (using the format '%variable%'). It is a very basic template system and is really intended as a starting point to building more advanced template systems by subclassing 'GCDWebServerResponse'.
GCDWebServer provides an extension to the ```GCDWebServerDataResponse``` class that can return HTML content generated from a template and a set of variables (using the format ```%variable%```). It is a very basic template system and is really intended as a starting point to building more advanced template systems by subclassing ```GCDWebServerResponse```.
Assuming you have a website directory in your app containing HTML template files along with the corresponding CSS, scripts and images, it's pretty easy to turn it into a dynamic website:
@@ -171,7 +434,7 @@ Assuming you have a website directory in your app containing HTML template files
NSString* websitePath = [[NSBundle mainBundle] pathForResource:@"Website" ofType:nil];
// Add a default handler to serve static files (i.e. anything other than HTML files)
[self addHandlerForBasePath:@"/" localPath:websitePath indexFilename:nil cacheAge:3600];
[self addGETHandlerForBasePath:@"/" directoryPath:websitePath indexFilename:nil cacheAge:3600 allowRangeRequests:YES];
// Add an override handler for all requests to "*.html" URLs to do the special HTML templatization
[self addHandlerForMethod:@"GET"
@@ -201,6 +464,6 @@ NSString* websitePath = [[NSBundle mainBundle] pathForResource:@"Website" ofType
Final Example: File Downloads and Uploads From iOS App
======================================================
GCDWebServer was originally written for the [ComicFlow](http://itunes.apple.com/us/app/comicflow/id409290355?mt=8) comic reader app for iPad. It uses it to provide a web server for people to upload and download comic files directly over WiFi to and from the app.
GCDWebServer was originally written for the [ComicFlow](http://itunes.apple.com/us/app/comicflow/id409290355?mt=8) comic reader app for iPad. It allow users to connect to their iPad with their web browser over WiFi and then upload, download and organize comic files inside the app.
ComicFlow is [entirely open-source](https://code.google.com/p/comicflow/) and you can see how it uses GCDWebServer in the [WebServer.m](http://code.google.com/p/comicflow/source/browse/Classes/WebServer.m) file.
ComicFlow is [entirely open-source](https://github.com/swisspol/ComicFlow) and you can see how it uses GCDWebServer in the [WebServer.h](https://github.com/swisspol/ComicFlow/blob/master/Classes/WebServer.h) and [WebServer.m](https://github.com/swisspol/ComicFlow/blob/master/Classes/WebServer.m) files.

83
Run-Tests.sh Executable file
View File

@@ -0,0 +1,83 @@
#!/bin/bash -exu -o pipefail
if [[ -f "/usr/local/bin/xcpretty" ]]; then
PRETTYFIER="xcpretty"
else
PRETTYFIER="tee" # Passthrough stdout
fi
OSX_SDK="macosx"
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"
OSX_TEST_SCHEME="GCDWebServers (Mac)"
BUILD_DIR="`pwd`/build"
PRODUCT="$BUILD_DIR/$CONFIGURATION/GCDWebServer"
PAYLOAD_ZIP="Tests/Payload.zip"
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 [ "$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 "$FILE"`
popd
fi
logLevel=2 $EXECUTABLE -mode "$MODE" -root "$PAYLOAD_DIR/Payload" -tests "$TESTS"
}
# Run built-in OS X tests
rm -rf "$BUILD_DIR"
xcodebuild test -scheme "$OSX_TEST_SCHEME" "SYMROOT=$BUILD_DIR"
# Build for OS X for oldest supported deployment target
rm -rf "$BUILD_DIR"
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"
runTests $PRODUCT "htmlFileUpload" "Tests/HTMLFileUpload"
runTests $PRODUCT "webServer" "Tests/WebServer"
runTests $PRODUCT "webDAV" "Tests/WebDAV-Transmit"
runTests $PRODUCT "webDAV" "Tests/WebDAV-Cyberduck"
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!"

View File

@@ -0,0 +1,9 @@
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Length: 299
Content-Type: text/html; charset=utf-8
Connection: Close
Server: GCDWebServer
Date: Fri, 25 Apr 2014 14:15:11 GMT
<html><body> <form name="input" action="/" method="post" enctype="multipart/form-data"> <input type="hidden" name="secret" value="42"> <input type="file" name="files" multiple><br/> <input type="submit" value="Submit"> </form> </body></html>

View File

@@ -0,0 +1,10 @@
GET / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36
DNT: 1
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,fr;q=0.6

View File

@@ -0,0 +1,9 @@
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Length: 447
Content-Type: text/html; charset=utf-8
Connection: Close
Server: GCDWebServer
Date: Fri, 25 Apr 2014 14:15:21 GMT
<html><body><p>secret = 42<br>files = &quot;hero_mba_11.jpg&quot; (image/jpeg | 106 KB)<br>files = &quot;Test File.txt&quot; (text/plain | 21 Bytes)<br></p><hr> <form name="input" action="/" method="post" enctype="multipart/form-data"> <input type="hidden" name="secret" value="42"> <input type="file" name="files" multiple><br/> <input type="submit" value="Submit"> </form> </body></html>

Binary file not shown.

View File

@@ -0,0 +1,9 @@
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Length: 293
Content-Type: text/html; charset=utf-8
Connection: Close
Server: GCDWebServer
Date: Fri, 25 Apr 2014 14:12:09 GMT
<html><body> <form name="input" action="/" method="post" enctype="application/x-www-form-urlencoded"> Value: <input type="text" name="value"> <input type="submit" value="Submit"> </form> </body></html>

View File

@@ -0,0 +1,10 @@
GET / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36
DNT: 1
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,fr;q=0.6

View File

@@ -0,0 +1,9 @@
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Length: 47
Content-Type: text/html; charset=utf-8
Connection: Close
Server: GCDWebServer
Date: Fri, 25 Apr 2014 14:12:20 GMT
<html><body><p>Hellø Wörld!</p></body></html>

View File

@@ -0,0 +1,15 @@
POST / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 30
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36
Content-Type: application/x-www-form-urlencoded
DNT: 1
Referer: http://localhost:8080/
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,fr;q=0.6
value=Hell%C3%B8+W%C3%B6rld%21

BIN
Tests/Payload.zip Normal file

Binary file not shown.

BIN
Tests/Sample-Movie.mp4 Normal file

Binary file not shown.

View File

@@ -0,0 +1,6 @@
HTTP/1.1 200 OK
Cache-Control: no-cache
Connection: Close
Server: GCDWebDAVServer
Date: Sat, 12 Apr 2014 04:52:42 GMT

View File

@@ -0,0 +1,6 @@
HEAD / HTTP/1.1
Host: localhost:8080
Connection: Keep-Alive
User-Agent: Cyberduck/4.4.3 (Mac OS X/10.9.2) (x86_64)
Accept-Encoding: gzip,deflate

View File

@@ -0,0 +1,14 @@
HTTP/1.1 207 Multi-Status
Cache-Control: no-cache
Content-Length: 1106
Content-Type: application/xml; charset="utf-8"
Connection: Close
Server: GCDWebDAVServer
Date: Sat, 12 Apr 2014 04:52:42 GMT
<?xml version="1.0" encoding="utf-8" ?><D:multistatus xmlns:D="DAV:">
<D:response><D:href>/</D:href><D:propstat><D:prop><D:resourcetype><D:collection/></D:resourcetype><D:creationdate>2014-01-01T00:00:00+00:00</D:creationdate></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>
<D:response><D:href>/Copy.txt</D:href><D:propstat><D:prop><D:resourcetype/><D:creationdate>2014-04-10T11:10:14+00:00</D:creationdate><D:getlastmodified>Thu, 10 Apr 2014 11:10:14 GMT</D:getlastmodified><D:getcontentlength>271</D:getcontentlength></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>
<D:response><D:href>/images</D:href><D:propstat><D:prop><D:resourcetype><D:collection/></D:resourcetype><D:creationdate>2014-01-01T00:00:00+00:00</D:creationdate></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>
<D:response><D:href>/PDF%20Reports</D:href><D:propstat><D:prop><D:resourcetype><D:collection/></D:resourcetype><D:creationdate>2014-01-01T00:00:00+00:00</D:creationdate></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>
</D:multistatus>

View File

@@ -0,0 +1,10 @@
PROPFIND / HTTP/1.1
Depth: 1
Content-Type: text/xml; charset=utf-8
Content-Length: 99
Host: localhost:8080
Connection: Keep-Alive
User-Agent: Cyberduck/4.4.3 (Mac OS X/10.9.2) (x86_64)
Accept-Encoding: gzip,deflate
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><propfind xmlns="DAV:"><allprop/></propfind>

View File

@@ -0,0 +1,12 @@
HTTP/1.1 207 Multi-Status
Cache-Control: no-cache
Content-Length: 700
Content-Type: application/xml; charset="utf-8"
Connection: Close
Server: GCDWebDAVServer
Date: Sat, 12 Apr 2014 04:52:47 GMT
<?xml version="1.0" encoding="utf-8" ?><D:multistatus xmlns:D="DAV:">
<D:response><D:href>/PDF%20Reports/</D:href><D:propstat><D:prop><D:resourcetype><D:collection/></D:resourcetype><D:creationdate>2014-01-01T00:00:00+00:00</D:creationdate></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>
<D:response><D:href>/PDF%20Reports/Apple%20Economic%20Impact%20on%20Cupertino.pdf</D:href><D:propstat><D:prop><D:resourcetype/><D:creationdate>2013-05-01T12:01:13+00:00</D:creationdate><D:getlastmodified>Wed, 01 May 2013 12:01:13 GMT</D:getlastmodified><D:getcontentlength>181952</D:getcontentlength></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>
</D:multistatus>

View File

@@ -0,0 +1,10 @@
PROPFIND /PDF%20Reports/ HTTP/1.1
Depth: 1
Content-Type: text/xml; charset=utf-8
Content-Length: 99
Host: localhost:8080
Connection: Keep-Alive
User-Agent: Cyberduck/4.4.3 (Mac OS X/10.9.2) (x86_64)
Accept-Encoding: gzip,deflate
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><propfind xmlns="DAV:"><allprop/></propfind>

View File

@@ -0,0 +1,13 @@
HTTP/1.1 207 Multi-Status
Cache-Control: no-cache
Content-Length: 998
Content-Type: application/xml; charset="utf-8"
Connection: Close
Server: GCDWebDAVServer
Date: Sat, 12 Apr 2014 04:52:47 GMT
<?xml version="1.0" encoding="utf-8" ?><D:multistatus xmlns:D="DAV:">
<D:response><D:href>/images/</D:href><D:propstat><D:prop><D:resourcetype><D:collection/></D:resourcetype><D:creationdate>2014-01-01T00:00:00+00:00</D:creationdate></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>
<D:response><D:href>/images/capable_green_ipad_l.png</D:href><D:propstat><D:prop><D:resourcetype/><D:creationdate>2014-04-10T21:46:56+00:00</D:creationdate><D:getlastmodified>Thu, 10 Apr 2014 21:46:56 GMT</D:getlastmodified><D:getcontentlength>116066</D:getcontentlength></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>
<D:response><D:href>/images/hero_mba_11.jpg</D:href><D:propstat><D:prop><D:resourcetype/><D:creationdate>2014-04-10T21:51:14+00:00</D:creationdate><D:getlastmodified>Thu, 10 Apr 2014 21:51:14 GMT</D:getlastmodified><D:getcontentlength>106799</D:getcontentlength></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>
</D:multistatus>

View File

@@ -0,0 +1,10 @@
PROPFIND /images/ HTTP/1.1
Depth: 1
Content-Type: text/xml; charset=utf-8
Content-Length: 99
Host: localhost:8080
Connection: Keep-Alive
User-Agent: Cyberduck/4.4.3 (Mac OS X/10.9.2) (x86_64)
Accept-Encoding: gzip,deflate
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><propfind xmlns="DAV:"><allprop/></propfind>

View File

@@ -0,0 +1,6 @@
HTTP/1.1 200 OK
Cache-Control: no-cache
Connection: Close
Server: GCDWebDAVServer
Date: Sat, 12 Apr 2014 04:52:51 GMT

View File

@@ -0,0 +1,6 @@
HEAD / HTTP/1.1
Host: localhost:8080
Connection: Keep-Alive
User-Agent: Cyberduck/4.4.3 (Mac OS X/10.9.2) (x86_64)
Accept-Encoding: gzip,deflate

View File

@@ -0,0 +1,7 @@
HTTP/1.1 404 Not Found
Content-Length: 204
Content-Type: text/html; charset=utf-8
Connection: Close
Server: GCDWebDAVServer
Date: Sat, 12 Apr 2014 04:52:51 GMT

View File

@@ -0,0 +1,6 @@
HEAD /Copy%20%284%3A11%3A14%2C%209%3A52%20PM%29.txt HTTP/1.1
Host: localhost:8080
Connection: Keep-Alive
User-Agent: Cyberduck/4.4.3 (Mac OS X/10.9.2) (x86_64)
Accept-Encoding: gzip,deflate

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