rhodes 7.4.1 → 7.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (165) hide show
  1. checksums.yaml +5 -5
  2. data/CREDITS +3 -1
  3. data/LICENSE +1 -1
  4. data/README.md +3 -1
  5. data/Rakefile +52 -43
  6. data/SECURITY.md +19 -0
  7. data/appveyor.yml +60 -7
  8. data/azure-pipelines.yml +67 -0
  9. data/extensions/rhoconnect-push/ext/rhoconnect-push/platform/android/src/com/rhomobile/rhoelements/ans/ANSManager.java +2 -2
  10. data/lib/commonAPI/barcode/ext/platform/android/AndroidManifest.xml +3 -1
  11. data/lib/commonAPI/barcode/ext/platform/android/src/com/google/barcodereader/BarcodeCaptureActivity.java +11 -13
  12. data/lib/commonAPI/barcode/ext/platform/android/src/com/google/barcodereader/BarcodeGraphicTracker.java +1 -1
  13. data/lib/commonAPI/barcode/ext/platform/android/src/com/google/barcodereader/ui/camera/CameraSource.java +3 -3
  14. data/lib/commonAPI/barcode/ext/platform/android/src/com/google/barcodereader/ui/camera/CameraSourcePreview.java +1 -1
  15. data/lib/commonAPI/barcode/ext.yml +4 -8
  16. data/lib/commonAPI/coreapi/ext/platform/android/Rakefile +0 -12
  17. data/lib/commonAPI/coreapi/ext/platform/android/src/com/rho/notification/Notification.java +4 -2
  18. data/lib/commonAPI/coreapi/ext/platform/android/src/com/rho/notification/NotificationScheduler.java +3 -3
  19. data/lib/commonAPI/coreapi/ext/platform/android/src/com/rho/notification/NotificationSingleton.java +1 -1
  20. data/lib/commonAPI/coreapi/ext/push.xml +5 -2
  21. data/lib/commonAPI/mediacapture/ext/camera.xml +4 -9
  22. data/lib/commonAPI/mediacapture/ext/platform/android/ApplicationFileProvider.erb +1 -1
  23. data/lib/commonAPI/mediacapture/ext/platform/android/ext_java.files +1 -9
  24. data/lib/commonAPI/mediacapture/ext/platform/android/src/com/rho/camera/CameraExtension.java +0 -2
  25. data/lib/commonAPI/mediacapture/ext/platform/android/src/com/rho/camera/CameraFactory.java +19 -24
  26. data/lib/commonAPI/mediacapture/ext/platform/android/src/com/rho/camera/CameraObject.java +317 -729
  27. data/lib/commonAPI/mediacapture/ext/platform/android/src/com/rho/camera/CameraRhoListener.java +240 -434
  28. data/lib/commonAPI/mediacapture/ext/platform/android/src/com/rho/camera/{CameraSingletonObject.java → CameraSingleton.java} +68 -74
  29. data/lib/commonAPI/printing_zebra/ext/platform/android/src/com/rhomobile/printing/zebra/impl/ZebraPrintingProviderImpl.java +2 -1
  30. data/lib/extensions/fcm-push/ext/android/ApplicationManifestAdds.erb +15 -17
  31. data/lib/extensions/fcm-push/ext/android/Rakefile +59 -34
  32. data/lib/extensions/fcm-push/ext/android/ext_java.files +0 -1
  33. data/lib/extensions/fcm-push/ext/android/src/com/rhomobile/rhodes/fcm/FCMFacade.java +13 -17
  34. data/lib/extensions/fcm-push/ext/android/src/com/rhomobile/rhodes/fcm/FCMIntentService.java +81 -80
  35. data/lib/extensions/fcm-push/ext/android/src/com/rhomobile/rhodes/fcm/FCMListener.java +0 -16
  36. data/lib/extensions/fcm-push/ext.yml +1 -1
  37. data/lib/extensions/gmaps/ext/platform/android/ApplicationManifestAdds.erb +1 -0
  38. data/lib/extensions/gmaps/ext/platform/android/src/com/rhomobile/rhodes/gmaps/GMapActivity.java +12 -4
  39. data/lib/extensions/gmaps/ext.yml +3 -1
  40. data/platform/android/Rhodes/jni/src/MethodResultJni.cpp +4 -0
  41. data/platform/android/Rhodes/src/com/rhomobile/rhodes/RhodesActivity.java +3 -3
  42. data/platform/android/Rhodes/src/com/rhomobile/rhodes/RhodesService.java +3 -3
  43. data/platform/android/Rhodes/src/com/rhomobile/rhodes/alert/StatusNotification.java +1 -1
  44. data/platform/android/Rhodes/src/com/rhomobile/rhodes/geolocation/GeoLocation.java +14 -14
  45. data/platform/android/Rhodes/src/com/rhomobile/rhodes/geolocation/GeoLocationImpl.java +18 -9
  46. data/platform/android/Rhodes/src/com/rhomobile/rhodes/osfunctionality/AndroidFunctionality26.java +1 -1
  47. data/platform/android/build/{aab_builder.rb → aapt2_helper.rb} +94 -37
  48. data/platform/android/build/android.rake +124 -191
  49. data/platform/android/build/android_tools.rb +9 -9
  50. data/platform/android/build/androidcommon.rb +18 -7
  51. data/platform/android/build/build_tools_finder.rb +46 -0
  52. data/platform/android/build/config.yml +5 -0
  53. data/platform/android/build/dex_builder.rb +88 -0
  54. data/platform/android/build/hostplatform.rb +9 -0
  55. data/platform/android/build/manifest_generator.rb +1 -0
  56. data/platform/android/build/maven_deps_extractor.rb +22 -21
  57. data/platform/android/build/ndkwrapper.rb +80 -51
  58. data/platform/iphone/Classes/AppManager/AppManager.m +3 -1
  59. data/platform/iphone/Classes/Camera/PickImageDelegate.h +2 -0
  60. data/platform/iphone/Classes/Camera/PickImageDelegate.m +45 -23
  61. data/platform/iphone/Classes/CocoaServer/CCocoaServer.h +27 -0
  62. data/platform/iphone/Classes/CocoaServer/CCocoaServer.m +107 -0
  63. data/platform/iphone/Classes/CocoaServer/RhoHTTPConnection.h +16 -0
  64. data/platform/iphone/Classes/CocoaServer/RhoHTTPConnection.m +226 -0
  65. data/platform/iphone/Classes/RhoWKWebView.mm +78 -11
  66. data/platform/iphone/Classes/RhoWebViewFabrique.m +6 -1
  67. data/platform/iphone/Classes/Rhodes.m +3 -0
  68. data/platform/iphone/CocoaHTTPServer/Core/Categories/DDData.h +14 -0
  69. data/platform/iphone/CocoaHTTPServer/Core/Categories/DDData.m +158 -0
  70. data/platform/iphone/CocoaHTTPServer/Core/Categories/DDNumber.h +12 -0
  71. data/platform/iphone/CocoaHTTPServer/Core/Categories/DDNumber.m +88 -0
  72. data/platform/iphone/CocoaHTTPServer/Core/Categories/DDRange.h +56 -0
  73. data/platform/iphone/CocoaHTTPServer/Core/Categories/DDRange.m +104 -0
  74. data/platform/iphone/CocoaHTTPServer/Core/HTTPAuthenticationRequest.h +45 -0
  75. data/platform/iphone/CocoaHTTPServer/Core/HTTPAuthenticationRequest.m +195 -0
  76. data/platform/iphone/CocoaHTTPServer/Core/HTTPConnection.h +120 -0
  77. data/platform/iphone/CocoaHTTPServer/Core/HTTPConnection.m +2708 -0
  78. data/platform/iphone/CocoaHTTPServer/Core/HTTPLogging.h +136 -0
  79. data/platform/iphone/CocoaHTTPServer/Core/HTTPMessage.h +48 -0
  80. data/platform/iphone/CocoaHTTPServer/Core/HTTPMessage.m +113 -0
  81. data/platform/iphone/CocoaHTTPServer/Core/HTTPResponse.h +149 -0
  82. data/platform/iphone/CocoaHTTPServer/Core/HTTPServer.h +205 -0
  83. data/platform/iphone/CocoaHTTPServer/Core/HTTPServer.m +772 -0
  84. data/platform/iphone/CocoaHTTPServer/Core/Mime/MultipartFormDataParser.h +65 -0
  85. data/platform/iphone/CocoaHTTPServer/Core/Mime/MultipartFormDataParser.m +529 -0
  86. data/platform/iphone/CocoaHTTPServer/Core/Mime/MultipartMessageHeader.h +33 -0
  87. data/platform/iphone/CocoaHTTPServer/Core/Mime/MultipartMessageHeader.m +86 -0
  88. data/platform/iphone/CocoaHTTPServer/Core/Mime/MultipartMessageHeaderField.h +23 -0
  89. data/platform/iphone/CocoaHTTPServer/Core/Mime/MultipartMessageHeaderField.m +211 -0
  90. data/platform/iphone/CocoaHTTPServer/Core/Responses/HTTPAsyncFileResponse.h +75 -0
  91. data/platform/iphone/CocoaHTTPServer/Core/Responses/HTTPAsyncFileResponse.m +405 -0
  92. data/platform/iphone/CocoaHTTPServer/Core/Responses/HTTPDataResponse.h +13 -0
  93. data/platform/iphone/CocoaHTTPServer/Core/Responses/HTTPDataResponse.m +79 -0
  94. data/platform/iphone/CocoaHTTPServer/Core/Responses/HTTPDynamicFileResponse.h +52 -0
  95. data/platform/iphone/CocoaHTTPServer/Core/Responses/HTTPDynamicFileResponse.m +292 -0
  96. data/platform/iphone/CocoaHTTPServer/Core/Responses/HTTPErrorResponse.h +9 -0
  97. data/platform/iphone/CocoaHTTPServer/Core/Responses/HTTPErrorResponse.m +38 -0
  98. data/platform/iphone/CocoaHTTPServer/Core/Responses/HTTPFileResponse.h +25 -0
  99. data/platform/iphone/CocoaHTTPServer/Core/Responses/HTTPFileResponse.m +237 -0
  100. data/platform/iphone/CocoaHTTPServer/Core/Responses/HTTPRedirectResponse.h +12 -0
  101. data/platform/iphone/CocoaHTTPServer/Core/Responses/HTTPRedirectResponse.m +73 -0
  102. data/platform/iphone/CocoaHTTPServer/Core/WebSocket.h +105 -0
  103. data/platform/iphone/CocoaHTTPServer/Core/WebSocket.m +791 -0
  104. data/platform/iphone/CocoaHTTPServer/Extensions/WebDAV/DAVConnection.h +7 -0
  105. data/platform/iphone/CocoaHTTPServer/Extensions/WebDAV/DAVConnection.m +160 -0
  106. data/platform/iphone/CocoaHTTPServer/Extensions/WebDAV/DAVResponse.h +11 -0
  107. data/platform/iphone/CocoaHTTPServer/Extensions/WebDAV/DAVResponse.m +372 -0
  108. data/platform/iphone/CocoaHTTPServer/Extensions/WebDAV/DELETEResponse.h +7 -0
  109. data/platform/iphone/CocoaHTTPServer/Extensions/WebDAV/DELETEResponse.m +49 -0
  110. data/platform/iphone/CocoaHTTPServer/Extensions/WebDAV/PUTResponse.h +8 -0
  111. data/platform/iphone/CocoaHTTPServer/Extensions/WebDAV/PUTResponse.m +69 -0
  112. data/platform/iphone/CocoaHTTPServer/LICENSE.txt +18 -0
  113. data/platform/iphone/CocoaHTTPServer/Vendor/CocoaAsyncSocket/About.txt +4 -0
  114. data/platform/iphone/CocoaHTTPServer/Vendor/CocoaAsyncSocket/GCDAsyncSocket.h +1226 -0
  115. data/platform/iphone/CocoaHTTPServer/Vendor/CocoaAsyncSocket/GCDAsyncSocket.m +8528 -0
  116. data/platform/iphone/CocoaHTTPServer/Vendor/CocoaLumberjack/About.txt +33 -0
  117. data/platform/iphone/CocoaHTTPServer/Vendor/CocoaLumberjack/DDASLLogger.h +41 -0
  118. data/platform/iphone/CocoaHTTPServer/Vendor/CocoaLumberjack/DDASLLogger.m +99 -0
  119. data/platform/iphone/CocoaHTTPServer/Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.h +102 -0
  120. data/platform/iphone/CocoaHTTPServer/Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.m +727 -0
  121. data/platform/iphone/CocoaHTTPServer/Vendor/CocoaLumberjack/DDFileLogger.h +334 -0
  122. data/platform/iphone/CocoaHTTPServer/Vendor/CocoaLumberjack/DDFileLogger.m +1353 -0
  123. data/platform/iphone/CocoaHTTPServer/Vendor/CocoaLumberjack/DDLog.h +601 -0
  124. data/platform/iphone/CocoaHTTPServer/Vendor/CocoaLumberjack/DDLog.m +1083 -0
  125. data/platform/iphone/CocoaHTTPServer/Vendor/CocoaLumberjack/DDTTYLogger.h +167 -0
  126. data/platform/iphone/CocoaHTTPServer/Vendor/CocoaLumberjack/DDTTYLogger.m +1479 -0
  127. data/platform/iphone/CocoaHTTPServer/Vendor/CocoaLumberjack/Extensions/ContextFilterLogFormatter.h +65 -0
  128. data/platform/iphone/CocoaHTTPServer/Vendor/CocoaLumberjack/Extensions/ContextFilterLogFormatter.m +191 -0
  129. data/platform/iphone/CocoaHTTPServer/Vendor/CocoaLumberjack/Extensions/DispatchQueueLogFormatter.h +116 -0
  130. data/platform/iphone/CocoaHTTPServer/Vendor/CocoaLumberjack/Extensions/DispatchQueueLogFormatter.m +251 -0
  131. data/platform/iphone/CocoaHTTPServer/Vendor/CocoaLumberjack/Extensions/README.txt +7 -0
  132. data/platform/iphone/Framework/Rhodes/Rhodes.xcodeproj/project.pbxproj +1 -1
  133. data/platform/iphone/RhoAppBaseLib/RhoAppBaseLib.xcodeproj/project.pbxproj +364 -0
  134. data/platform/iphone/rbuild/iphone.rake +4 -0
  135. data/platform/osx/bin/RhoSimulator/RhoSimulator.app.zip +0 -0
  136. data/platform/shared/common/RhodesApp.cpp +26 -1
  137. data/platform/shared/net/HttpServer.cpp +20 -0
  138. data/platform/shared/qt/RhoSimulator.pro +1 -1
  139. data/platform/shared/ruby/osx/ruby/config.h +2 -0
  140. data/platform/shared/ruby/win32/win32.c +10 -3
  141. data/platform/win32/RhoSimulator/RhoSimulator.exe +0 -0
  142. data/rakefile.rb +52 -43
  143. data/res/build-tools/RhoRuby.exe +0 -0
  144. data/res/build-tools/aapt2/linux/aapt2 +0 -0
  145. data/res/build-tools/aapt2/osx/aapt2 +0 -0
  146. data/res/generators/templates/application/AndroidManifest.erb +2 -1
  147. data/res/generators/templates/application/build.yml +4 -1
  148. data/res/generators/templates/application/rhoconfig.txt +5 -0
  149. data/rhobuild.yml.example +4 -4
  150. data/rhodes.gemspec +1 -2
  151. data/version +1 -1
  152. metadata +80 -35
  153. data/lib/commonAPI/mediacapture/ext/platform/android/adds/res/drawable/camera_btn.xml +0 -14
  154. data/lib/commonAPI/mediacapture/ext/platform/android/adds/res/layout/camera_land.xml +0 -23
  155. data/lib/commonAPI/mediacapture/ext/platform/android/adds/res/layout/camera_port.xml +0 -23
  156. data/lib/commonAPI/mediacapture/ext/platform/android/src/com/rho/camera/CameraActivity.java +0 -156
  157. data/lib/commonAPI/mediacapture/ext/platform/android/src/com/rho/camera/CameraEclair.java +0 -227
  158. data/lib/commonAPI/mediacapture/ext/platform/android/src/com/rho/camera/CameraGingerbread.java +0 -152
  159. data/lib/commonAPI/mediacapture/ext/platform/android/src/com/rho/camera/CameraPreview.java +0 -183
  160. data/lib/commonAPI/mediacapture/ext/platform/android/src/com/rho/camera/CameraSingletonEclair.java +0 -14
  161. data/lib/commonAPI/mediacapture/ext/platform/android/src/com/rho/camera/CameraSingletonGingerbread.java +0 -60
  162. data/lib/commonAPI/mediacapture/ext/platform/android/src/com/rho/camera/ICameraObject.java +0 -20
  163. data/lib/commonAPI/mediacapture/ext/platform/android/src/com/rho/camera/ICameraSingletonObject.java +0 -8
  164. data/lib/extensions/fcm-push/ext/android/src/com/rhomobile/rhodes/fcm/FCMTokenRefresherService.java +0 -27
  165. data/platform/android/Rhodes/AndroidManifest.xml.erb +0 -89
@@ -0,0 +1,2708 @@
1
+ #import "GCDAsyncSocket.h"
2
+ #import "HTTPServer.h"
3
+ #import "HTTPConnection.h"
4
+ #import "HTTPMessage.h"
5
+ #import "HTTPResponse.h"
6
+ #import "HTTPAuthenticationRequest.h"
7
+ #import "DDNumber.h"
8
+ #import "DDRange.h"
9
+ #import "DDData.h"
10
+ #import "HTTPFileResponse.h"
11
+ #import "HTTPAsyncFileResponse.h"
12
+ #import "WebSocket.h"
13
+ #import "HTTPLogging.h"
14
+
15
+ #if ! __has_feature(objc_arc)
16
+ #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
17
+ #endif
18
+
19
+ // Log levels: off, error, warn, info, verbose
20
+ // Other flags: trace
21
+ static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE;
22
+
23
+ // Define chunk size used to read in data for responses
24
+ // This is how much data will be read from disk into RAM at a time
25
+ #if TARGET_OS_IPHONE
26
+ #define READ_CHUNKSIZE (1024 * 256)
27
+ #else
28
+ #define READ_CHUNKSIZE (1024 * 512)
29
+ #endif
30
+
31
+ // Define chunk size used to read in POST upload data
32
+ #if TARGET_OS_IPHONE
33
+ #define POST_CHUNKSIZE (1024 * 256)
34
+ #else
35
+ #define POST_CHUNKSIZE (1024 * 512)
36
+ #endif
37
+
38
+ // Define the various timeouts (in seconds) for various parts of the HTTP process
39
+ #define TIMEOUT_READ_FIRST_HEADER_LINE 30
40
+ #define TIMEOUT_READ_SUBSEQUENT_HEADER_LINE 30
41
+ #define TIMEOUT_READ_BODY -1
42
+ #define TIMEOUT_WRITE_HEAD 30
43
+ #define TIMEOUT_WRITE_BODY -1
44
+ #define TIMEOUT_WRITE_ERROR 30
45
+ #define TIMEOUT_NONCE 300
46
+
47
+ // Define the various limits
48
+ // MAX_HEADER_LINE_LENGTH: Max length (in bytes) of any single line in a header (including \r\n)
49
+ // MAX_HEADER_LINES : Max number of lines in a single header (including first GET line)
50
+ #define MAX_HEADER_LINE_LENGTH 8190
51
+ #define MAX_HEADER_LINES 100
52
+ // MAX_CHUNK_LINE_LENGTH : For accepting chunked transfer uploads, max length of chunk size line (including \r\n)
53
+ #define MAX_CHUNK_LINE_LENGTH 200
54
+
55
+ // Define the various tags we'll use to differentiate what it is we're currently doing
56
+ #define HTTP_REQUEST_HEADER 10
57
+ #define HTTP_REQUEST_BODY 11
58
+ #define HTTP_REQUEST_CHUNK_SIZE 12
59
+ #define HTTP_REQUEST_CHUNK_DATA 13
60
+ #define HTTP_REQUEST_CHUNK_TRAILER 14
61
+ #define HTTP_REQUEST_CHUNK_FOOTER 15
62
+ #define HTTP_PARTIAL_RESPONSE 20
63
+ #define HTTP_PARTIAL_RESPONSE_HEADER 21
64
+ #define HTTP_PARTIAL_RESPONSE_BODY 22
65
+ #define HTTP_CHUNKED_RESPONSE_HEADER 30
66
+ #define HTTP_CHUNKED_RESPONSE_BODY 31
67
+ #define HTTP_CHUNKED_RESPONSE_FOOTER 32
68
+ #define HTTP_PARTIAL_RANGE_RESPONSE_BODY 40
69
+ #define HTTP_PARTIAL_RANGES_RESPONSE_BODY 50
70
+ #define HTTP_RESPONSE 90
71
+ #define HTTP_FINAL_RESPONSE 91
72
+
73
+ // A quick note about the tags:
74
+ //
75
+ // The HTTP_RESPONSE and HTTP_FINAL_RESPONSE are designated tags signalling that the response is completely sent.
76
+ // That is, in the onSocket:didWriteDataWithTag: method, if the tag is HTTP_RESPONSE or HTTP_FINAL_RESPONSE,
77
+ // it is assumed that the response is now completely sent.
78
+ // Use HTTP_RESPONSE if it's the end of a response, and you want to start reading more requests afterwards.
79
+ // Use HTTP_FINAL_RESPONSE if you wish to terminate the connection after sending the response.
80
+ //
81
+ // If you are sending multiple data segments in a custom response, make sure that only the last segment has
82
+ // the HTTP_RESPONSE tag. For all other segments prior to the last segment use HTTP_PARTIAL_RESPONSE, or some other
83
+ // tag of your own invention.
84
+
85
+ @interface HTTPConnection (PrivateAPI)
86
+ - (void)startReadingRequest;
87
+ - (void)sendResponseHeadersAndBody;
88
+ @end
89
+
90
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
91
+ #pragma mark -
92
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
93
+
94
+ @implementation HTTPConnection
95
+
96
+ static dispatch_queue_t recentNonceQueue;
97
+ static NSMutableArray *recentNonces;
98
+
99
+ /**
100
+ * This method is automatically called (courtesy of Cocoa) before the first instantiation of this class.
101
+ * We use it to initialize any static variables.
102
+ **/
103
+ + (void)initialize
104
+ {
105
+ static dispatch_once_t onceToken;
106
+ dispatch_once(&onceToken, ^{
107
+
108
+ // Initialize class variables
109
+ recentNonceQueue = dispatch_queue_create("HTTPConnection-Nonce", NULL);
110
+ recentNonces = [[NSMutableArray alloc] initWithCapacity:5];
111
+ });
112
+ }
113
+
114
+ /**
115
+ * Generates and returns an authentication nonce.
116
+ * A nonce is a server-specified string uniquely generated for each 401 response.
117
+ * The default implementation uses a single nonce for each session.
118
+ **/
119
+ + (NSString *)generateNonce
120
+ {
121
+ // We use the Core Foundation UUID class to generate a nonce value for us
122
+ // UUIDs (Universally Unique Identifiers) are 128-bit values guaranteed to be unique.
123
+ CFUUIDRef theUUID = CFUUIDCreate(NULL);
124
+ NSString *newNonce = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, theUUID);
125
+ CFRelease(theUUID);
126
+
127
+ // We have to remember that the HTTP protocol is stateless.
128
+ // Even though with version 1.1 persistent connections are the norm, they are not guaranteed.
129
+ // Thus if we generate a nonce for this connection,
130
+ // it should be honored for other connections in the near future.
131
+ //
132
+ // In fact, this is absolutely necessary in order to support QuickTime.
133
+ // When QuickTime makes it's initial connection, it will be unauthorized, and will receive a nonce.
134
+ // It then disconnects, and creates a new connection with the nonce, and proper authentication.
135
+ // If we don't honor the nonce for the second connection, QuickTime will repeat the process and never connect.
136
+
137
+ dispatch_async(recentNonceQueue, ^{ @autoreleasepool {
138
+
139
+ [recentNonces addObject:newNonce];
140
+ }});
141
+
142
+ double delayInSeconds = TIMEOUT_NONCE;
143
+ dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
144
+ dispatch_after(popTime, recentNonceQueue, ^{ @autoreleasepool {
145
+
146
+ [recentNonces removeObject:newNonce];
147
+ }});
148
+
149
+ return newNonce;
150
+ }
151
+
152
+ /**
153
+ * Returns whether or not the given nonce is in the list of recently generated nonce's.
154
+ **/
155
+ + (BOOL)hasRecentNonce:(NSString *)recentNonce
156
+ {
157
+ __block BOOL result = NO;
158
+
159
+ dispatch_sync(recentNonceQueue, ^{ @autoreleasepool {
160
+
161
+ result = [recentNonces containsObject:recentNonce];
162
+ }});
163
+
164
+ return result;
165
+ }
166
+
167
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
168
+ #pragma mark Init, Dealloc:
169
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
170
+
171
+ /**
172
+ * Sole Constructor.
173
+ * Associates this new HTTP connection with the given AsyncSocket.
174
+ * This HTTP connection object will become the socket's delegate and take over responsibility for the socket.
175
+ **/
176
+ - (id)initWithAsyncSocket:(GCDAsyncSocket *)newSocket configuration:(HTTPConfig *)aConfig
177
+ {
178
+ if ((self = [super init]))
179
+ {
180
+ HTTPLogTrace();
181
+
182
+ if (aConfig.queue)
183
+ {
184
+ connectionQueue = aConfig.queue;
185
+ #if !OS_OBJECT_USE_OBJC
186
+ dispatch_retain(connectionQueue);
187
+ #endif
188
+ }
189
+ else
190
+ {
191
+ connectionQueue = dispatch_queue_create("HTTPConnection", NULL);
192
+ }
193
+
194
+ // Take over ownership of the socket
195
+ asyncSocket = newSocket;
196
+ [asyncSocket setDelegate:self delegateQueue:connectionQueue];
197
+
198
+ // Store configuration
199
+ config = aConfig;
200
+
201
+ // Initialize lastNC (last nonce count).
202
+ // Used with digest access authentication.
203
+ // These must increment for each request from the client.
204
+ lastNC = 0;
205
+
206
+ // Create a new HTTP message
207
+ request = [[HTTPMessage alloc] initEmptyRequest];
208
+
209
+ numHeaderLines = 0;
210
+
211
+ responseDataSizes = [[NSMutableArray alloc] initWithCapacity:5];
212
+ }
213
+ return self;
214
+ }
215
+
216
+ /**
217
+ * Standard Deconstructor.
218
+ **/
219
+ - (void)dealloc
220
+ {
221
+ HTTPLogTrace();
222
+
223
+ #if !OS_OBJECT_USE_OBJC
224
+ dispatch_release(connectionQueue);
225
+ #endif
226
+
227
+ [asyncSocket setDelegate:nil delegateQueue:NULL];
228
+ [asyncSocket disconnect];
229
+
230
+ if ([httpResponse respondsToSelector:@selector(connectionDidClose)])
231
+ {
232
+ [httpResponse connectionDidClose];
233
+ }
234
+ }
235
+
236
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
237
+ #pragma mark Method Support
238
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
239
+
240
+ /**
241
+ * Returns whether or not the server will accept messages of a given method
242
+ * at a particular URI.
243
+ **/
244
+ - (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path
245
+ {
246
+ HTTPLogTrace();
247
+
248
+ // Override me to support methods such as POST.
249
+ //
250
+ // Things you may want to consider:
251
+ // - Does the given path represent a resource that is designed to accept this method?
252
+ // - If accepting an upload, is the size of the data being uploaded too big?
253
+ // To do this you can check the requestContentLength variable.
254
+ //
255
+ // For more information, you can always access the HTTPMessage request variable.
256
+ //
257
+ // You should fall through with a call to [super supportsMethod:method atPath:path]
258
+ //
259
+ // See also: expectsRequestBodyFromMethod:atPath:
260
+
261
+ if ([method isEqualToString:@"GET"])
262
+ return YES;
263
+
264
+ if ([method isEqualToString:@"HEAD"])
265
+ return YES;
266
+
267
+ return NO;
268
+ }
269
+
270
+ /**
271
+ * Returns whether or not the server expects a body from the given method.
272
+ *
273
+ * In other words, should the server expect a content-length header and associated body from this method.
274
+ * This would be true in the case of a POST, where the client is sending data,
275
+ * or for something like PUT where the client is supposed to be uploading a file.
276
+ **/
277
+ - (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path
278
+ {
279
+ HTTPLogTrace();
280
+
281
+ // Override me to add support for other methods that expect the client
282
+ // to send a body along with the request header.
283
+ //
284
+ // You should fall through with a call to [super expectsRequestBodyFromMethod:method atPath:path]
285
+ //
286
+ // See also: supportsMethod:atPath:
287
+
288
+ if ([method isEqualToString:@"POST"])
289
+ return YES;
290
+
291
+ if ([method isEqualToString:@"PUT"])
292
+ return YES;
293
+
294
+ return NO;
295
+ }
296
+
297
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
298
+ #pragma mark HTTPS
299
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
300
+
301
+ /**
302
+ * Returns whether or not the server is configured to be a secure server.
303
+ * In other words, all connections to this server are immediately secured, thus only secure connections are allowed.
304
+ * This is the equivalent of having an https server, where it is assumed that all connections must be secure.
305
+ * If this is the case, then unsecure connections will not be allowed on this server, and a separate unsecure server
306
+ * would need to be run on a separate port in order to support unsecure connections.
307
+ *
308
+ * Note: In order to support secure connections, the sslIdentityAndCertificates method must be implemented.
309
+ **/
310
+ - (BOOL)isSecureServer
311
+ {
312
+ HTTPLogTrace();
313
+
314
+ // Override me to create an https server...
315
+
316
+ return NO;
317
+ }
318
+
319
+ /**
320
+ * This method is expected to returns an array appropriate for use in kCFStreamSSLCertificates SSL Settings.
321
+ * It should be an array of SecCertificateRefs except for the first element in the array, which is a SecIdentityRef.
322
+ **/
323
+ - (NSArray *)sslIdentityAndCertificates
324
+ {
325
+ HTTPLogTrace();
326
+
327
+ // Override me to provide the proper required SSL identity.
328
+
329
+ return nil;
330
+ }
331
+
332
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
333
+ #pragma mark Password Protection
334
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
335
+
336
+ /**
337
+ * Returns whether or not the requested resource is password protected.
338
+ * In this generic implementation, nothing is password protected.
339
+ **/
340
+ - (BOOL)isPasswordProtected:(NSString *)path
341
+ {
342
+ HTTPLogTrace();
343
+
344
+ // Override me to provide password protection...
345
+ // You can configure it for the entire server, or based on the current request
346
+
347
+ return NO;
348
+ }
349
+
350
+ /**
351
+ * Returns whether or not the authentication challenge should use digest access authentication.
352
+ * The alternative is basic authentication.
353
+ *
354
+ * If at all possible, digest access authentication should be used because it's more secure.
355
+ * Basic authentication sends passwords in the clear and should be avoided unless using SSL/TLS.
356
+ **/
357
+ - (BOOL)useDigestAccessAuthentication
358
+ {
359
+ HTTPLogTrace();
360
+
361
+ // Override me to customize the authentication scheme
362
+ // Make sure you understand the security risks of using the weaker basic authentication
363
+
364
+ return YES;
365
+ }
366
+
367
+ /**
368
+ * Returns the authentication realm.
369
+ * In this generic implmentation, a default realm is used for the entire server.
370
+ **/
371
+ - (NSString *)realm
372
+ {
373
+ HTTPLogTrace();
374
+
375
+ // Override me to provide a custom realm...
376
+ // You can configure it for the entire server, or based on the current request
377
+
378
+ return @"defaultRealm@host.com";
379
+ }
380
+
381
+ /**
382
+ * Returns the password for the given username.
383
+ **/
384
+ - (NSString *)passwordForUser:(NSString *)username
385
+ {
386
+ HTTPLogTrace();
387
+
388
+ // Override me to provide proper password authentication
389
+ // You can configure a password for the entire server, or custom passwords for users and/or resources
390
+
391
+ // Security Note:
392
+ // A nil password means no access at all. (Such as for user doesn't exist)
393
+ // An empty string password is allowed, and will be treated as any other password. (To support anonymous access)
394
+
395
+ return nil;
396
+ }
397
+
398
+ /**
399
+ * Returns whether or not the user is properly authenticated.
400
+ **/
401
+ - (BOOL)isAuthenticated
402
+ {
403
+ HTTPLogTrace();
404
+
405
+ // Extract the authentication information from the Authorization header
406
+ HTTPAuthenticationRequest *auth = [[HTTPAuthenticationRequest alloc] initWithRequest:request];
407
+
408
+ if ([self useDigestAccessAuthentication])
409
+ {
410
+ // Digest Access Authentication (RFC 2617)
411
+
412
+ if(![auth isDigest])
413
+ {
414
+ // User didn't send proper digest access authentication credentials
415
+ return NO;
416
+ }
417
+
418
+ if ([auth username] == nil)
419
+ {
420
+ // The client didn't provide a username
421
+ // Most likely they didn't provide any authentication at all
422
+ return NO;
423
+ }
424
+
425
+ NSString *password = [self passwordForUser:[auth username]];
426
+ if (password == nil)
427
+ {
428
+ // No access allowed (username doesn't exist in system)
429
+ return NO;
430
+ }
431
+
432
+ NSString *url = [[request url] relativeString];
433
+
434
+ if (![url isEqualToString:[auth uri]])
435
+ {
436
+ // Requested URL and Authorization URI do not match
437
+ // This could be a replay attack
438
+ // IE - attacker provides same authentication information, but requests a different resource
439
+ return NO;
440
+ }
441
+
442
+ // The nonce the client provided will most commonly be stored in our local (cached) nonce variable
443
+ if (![nonce isEqualToString:[auth nonce]])
444
+ {
445
+ // The given nonce may be from another connection
446
+ // We need to search our list of recent nonce strings that have been recently distributed
447
+ if ([[self class] hasRecentNonce:[auth nonce]])
448
+ {
449
+ // Store nonce in local (cached) nonce variable to prevent array searches in the future
450
+ nonce = [[auth nonce] copy];
451
+
452
+ // The client has switched to using a different nonce value
453
+ // This may happen if the client tries to get a file in a directory with different credentials.
454
+ // The previous credentials wouldn't work, and the client would receive a 401 error
455
+ // along with a new nonce value. The client then uses this new nonce value and requests the file again.
456
+ // Whatever the case may be, we need to reset lastNC, since that variable is on a per nonce basis.
457
+ lastNC = 0;
458
+ }
459
+ else
460
+ {
461
+ // We have no knowledge of ever distributing such a nonce.
462
+ // This could be a replay attack from a previous connection in the past.
463
+ return NO;
464
+ }
465
+ }
466
+
467
+ long authNC = strtol([[auth nc] UTF8String], NULL, 16);
468
+
469
+ if (authNC <= lastNC)
470
+ {
471
+ // The nc value (nonce count) hasn't been incremented since the last request.
472
+ // This could be a replay attack.
473
+ return NO;
474
+ }
475
+ lastNC = authNC;
476
+
477
+ NSString *HA1str = [NSString stringWithFormat:@"%@:%@:%@", [auth username], [auth realm], password];
478
+ NSString *HA2str = [NSString stringWithFormat:@"%@:%@", [request method], [auth uri]];
479
+
480
+ NSString *HA1 = [[[HA1str dataUsingEncoding:NSUTF8StringEncoding] md5Digest] hexStringValue];
481
+
482
+ NSString *HA2 = [[[HA2str dataUsingEncoding:NSUTF8StringEncoding] md5Digest] hexStringValue];
483
+
484
+ NSString *responseStr = [NSString stringWithFormat:@"%@:%@:%@:%@:%@:%@",
485
+ HA1, [auth nonce], [auth nc], [auth cnonce], [auth qop], HA2];
486
+
487
+ NSString *response = [[[responseStr dataUsingEncoding:NSUTF8StringEncoding] md5Digest] hexStringValue];
488
+
489
+ return [response isEqualToString:[auth response]];
490
+ }
491
+ else
492
+ {
493
+ // Basic Authentication
494
+
495
+ if (![auth isBasic])
496
+ {
497
+ // User didn't send proper base authentication credentials
498
+ return NO;
499
+ }
500
+
501
+ // Decode the base 64 encoded credentials
502
+ NSString *base64Credentials = [auth base64Credentials];
503
+
504
+ NSData *temp = [[base64Credentials dataUsingEncoding:NSUTF8StringEncoding] base64Decoded];
505
+
506
+ NSString *credentials = [[NSString alloc] initWithData:temp encoding:NSUTF8StringEncoding];
507
+
508
+ // The credentials should be of the form "username:password"
509
+ // The username is not allowed to contain a colon
510
+
511
+ NSRange colonRange = [credentials rangeOfString:@":"];
512
+
513
+ if (colonRange.length == 0)
514
+ {
515
+ // Malformed credentials
516
+ return NO;
517
+ }
518
+
519
+ NSString *credUsername = [credentials substringToIndex:colonRange.location];
520
+ NSString *credPassword = [credentials substringFromIndex:(colonRange.location + colonRange.length)];
521
+
522
+ NSString *password = [self passwordForUser:credUsername];
523
+ if (password == nil)
524
+ {
525
+ // No access allowed (username doesn't exist in system)
526
+ return NO;
527
+ }
528
+
529
+ return [password isEqualToString:credPassword];
530
+ }
531
+ }
532
+
533
+ /**
534
+ * Adds a digest access authentication challenge to the given response.
535
+ **/
536
+ - (void)addDigestAuthChallenge:(HTTPMessage *)response
537
+ {
538
+ HTTPLogTrace();
539
+
540
+ NSString *authFormat = @"Digest realm=\"%@\", qop=\"auth\", nonce=\"%@\"";
541
+ NSString *authInfo = [NSString stringWithFormat:authFormat, [self realm], [[self class] generateNonce]];
542
+
543
+ [response setHeaderField:@"WWW-Authenticate" value:authInfo];
544
+ }
545
+
546
+ /**
547
+ * Adds a basic authentication challenge to the given response.
548
+ **/
549
+ - (void)addBasicAuthChallenge:(HTTPMessage *)response
550
+ {
551
+ HTTPLogTrace();
552
+
553
+ NSString *authFormat = @"Basic realm=\"%@\"";
554
+ NSString *authInfo = [NSString stringWithFormat:authFormat, [self realm]];
555
+
556
+ [response setHeaderField:@"WWW-Authenticate" value:authInfo];
557
+ }
558
+
559
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
560
+ #pragma mark Core
561
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
562
+
563
+ /**
564
+ * Starting point for the HTTP connection after it has been fully initialized (including subclasses).
565
+ * This method is called by the HTTP server.
566
+ **/
567
+ - (void)start
568
+ {
569
+ dispatch_async(connectionQueue, ^{ @autoreleasepool {
570
+
571
+ if (!started)
572
+ {
573
+ started = YES;
574
+ [self startConnection];
575
+ }
576
+ }});
577
+ }
578
+
579
+ /**
580
+ * This method is called by the HTTPServer if it is asked to stop.
581
+ * The server, in turn, invokes stop on each HTTPConnection instance.
582
+ **/
583
+ - (void)stop
584
+ {
585
+ dispatch_async(connectionQueue, ^{ @autoreleasepool {
586
+
587
+ // Disconnect the socket.
588
+ // The socketDidDisconnect delegate method will handle everything else.
589
+ [asyncSocket disconnect];
590
+ }});
591
+ }
592
+
593
+ /**
594
+ * Starting point for the HTTP connection.
595
+ **/
596
+ - (void)startConnection
597
+ {
598
+ // Override me to do any custom work before the connection starts.
599
+ //
600
+ // Be sure to invoke [super startConnection] when you're done.
601
+
602
+ HTTPLogTrace();
603
+
604
+ if ([self isSecureServer])
605
+ {
606
+ // We are configured to be an HTTPS server.
607
+ // That is, we secure via SSL/TLS the connection prior to any communication.
608
+
609
+ NSArray *certificates = [self sslIdentityAndCertificates];
610
+
611
+ if ([certificates count] > 0)
612
+ {
613
+ // All connections are assumed to be secure. Only secure connections are allowed on this server.
614
+ NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:3];
615
+
616
+ // Configure this connection as the server
617
+ [settings setObject:[NSNumber numberWithBool:YES]
618
+ forKey:(NSString *)kCFStreamSSLIsServer];
619
+
620
+ [settings setObject:certificates
621
+ forKey:(NSString *)kCFStreamSSLCertificates];
622
+
623
+ // Configure this connection to use the highest possible SSL level
624
+ [settings setObject:(NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL
625
+ forKey:(NSString *)kCFStreamSSLLevel];
626
+
627
+ [asyncSocket startTLS:settings];
628
+ }
629
+ }
630
+
631
+ [self startReadingRequest];
632
+ }
633
+
634
+ /**
635
+ * Starts reading an HTTP request.
636
+ **/
637
+ - (void)startReadingRequest
638
+ {
639
+ HTTPLogTrace();
640
+
641
+ [asyncSocket readDataToData:[GCDAsyncSocket CRLFData]
642
+ withTimeout:TIMEOUT_READ_FIRST_HEADER_LINE
643
+ maxLength:MAX_HEADER_LINE_LENGTH
644
+ tag:HTTP_REQUEST_HEADER];
645
+ }
646
+
647
+ /**
648
+ * Parses the given query string.
649
+ *
650
+ * For example, if the query is "q=John%20Mayer%20Trio&num=50"
651
+ * then this method would return the following dictionary:
652
+ * {
653
+ * q = "John Mayer Trio"
654
+ * num = "50"
655
+ * }
656
+ **/
657
+ - (NSDictionary *)parseParams:(NSString *)query
658
+ {
659
+ NSArray *components = [query componentsSeparatedByString:@"&"];
660
+ NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:[components count]];
661
+
662
+ NSUInteger i;
663
+ for (i = 0; i < [components count]; i++)
664
+ {
665
+ NSString *component = [components objectAtIndex:i];
666
+ if ([component length] > 0)
667
+ {
668
+ NSRange range = [component rangeOfString:@"="];
669
+ if (range.location != NSNotFound)
670
+ {
671
+ NSString *escapedKey = [component substringToIndex:(range.location + 0)];
672
+ NSString *escapedValue = [component substringFromIndex:(range.location + 1)];
673
+
674
+ if ([escapedKey length] > 0)
675
+ {
676
+ CFStringRef k, v;
677
+
678
+ k = CFURLCreateStringByReplacingPercentEscapes(NULL, (__bridge CFStringRef)escapedKey, CFSTR(""));
679
+ v = CFURLCreateStringByReplacingPercentEscapes(NULL, (__bridge CFStringRef)escapedValue, CFSTR(""));
680
+
681
+ NSString *key, *value;
682
+
683
+ key = (__bridge_transfer NSString *)k;
684
+ value = (__bridge_transfer NSString *)v;
685
+
686
+ if (key)
687
+ {
688
+ if (value)
689
+ [result setObject:value forKey:key];
690
+ else
691
+ [result setObject:[NSNull null] forKey:key];
692
+ }
693
+ }
694
+ }
695
+ }
696
+ }
697
+
698
+ return result;
699
+ }
700
+
701
+ /**
702
+ * Parses the query variables in the request URI.
703
+ *
704
+ * For example, if the request URI was "/search.html?q=John%20Mayer%20Trio&num=50"
705
+ * then this method would return the following dictionary:
706
+ * {
707
+ * q = "John Mayer Trio"
708
+ * num = "50"
709
+ * }
710
+ **/
711
+ - (NSDictionary *)parseGetParams
712
+ {
713
+ if(![request isHeaderComplete]) return nil;
714
+
715
+ NSDictionary *result = nil;
716
+
717
+ NSURL *url = [request url];
718
+ if(url)
719
+ {
720
+ NSString *query = [url query];
721
+ if (query)
722
+ {
723
+ result = [self parseParams:query];
724
+ }
725
+ }
726
+
727
+ return result;
728
+ }
729
+
730
+ /**
731
+ * Attempts to parse the given range header into a series of sequential non-overlapping ranges.
732
+ * If successfull, the variables 'ranges' and 'rangeIndex' will be updated, and YES will be returned.
733
+ * Otherwise, NO is returned, and the range request should be ignored.
734
+ **/
735
+ - (BOOL)parseRangeRequest:(NSString *)rangeHeader withContentLength:(UInt64)contentLength
736
+ {
737
+ HTTPLogTrace();
738
+
739
+ // Examples of byte-ranges-specifier values (assuming an entity-body of length 10000):
740
+ //
741
+ // - The first 500 bytes (byte offsets 0-499, inclusive): bytes=0-499
742
+ //
743
+ // - The second 500 bytes (byte offsets 500-999, inclusive): bytes=500-999
744
+ //
745
+ // - The final 500 bytes (byte offsets 9500-9999, inclusive): bytes=-500
746
+ //
747
+ // - Or bytes=9500-
748
+ //
749
+ // - The first and last bytes only (bytes 0 and 9999): bytes=0-0,-1
750
+ //
751
+ // - Several legal but not canonical specifications of the second 500 bytes (byte offsets 500-999, inclusive):
752
+ // bytes=500-600,601-999
753
+ // bytes=500-700,601-999
754
+ //
755
+
756
+ NSRange eqsignRange = [rangeHeader rangeOfString:@"="];
757
+
758
+ if(eqsignRange.location == NSNotFound) return NO;
759
+
760
+ NSUInteger tIndex = eqsignRange.location;
761
+ NSUInteger fIndex = eqsignRange.location + eqsignRange.length;
762
+
763
+ NSMutableString *rangeType = [[rangeHeader substringToIndex:tIndex] mutableCopy];
764
+ NSMutableString *rangeValue = [[rangeHeader substringFromIndex:fIndex] mutableCopy];
765
+
766
+ CFStringTrimWhitespace((__bridge CFMutableStringRef)rangeType);
767
+ CFStringTrimWhitespace((__bridge CFMutableStringRef)rangeValue);
768
+
769
+ if([rangeType caseInsensitiveCompare:@"bytes"] != NSOrderedSame) return NO;
770
+
771
+ NSArray *rangeComponents = [rangeValue componentsSeparatedByString:@","];
772
+
773
+ if([rangeComponents count] == 0) return NO;
774
+
775
+ ranges = [[NSMutableArray alloc] initWithCapacity:[rangeComponents count]];
776
+
777
+ rangeIndex = 0;
778
+
779
+ // Note: We store all range values in the form of DDRange structs, wrapped in NSValue objects.
780
+ // Since DDRange consists of UInt64 values, the range extends up to 16 exabytes.
781
+
782
+ NSUInteger i;
783
+ for (i = 0; i < [rangeComponents count]; i++)
784
+ {
785
+ NSString *rangeComponent = [rangeComponents objectAtIndex:i];
786
+
787
+ NSRange dashRange = [rangeComponent rangeOfString:@"-"];
788
+
789
+ if (dashRange.location == NSNotFound)
790
+ {
791
+ // We're dealing with an individual byte number
792
+
793
+ UInt64 byteIndex;
794
+ if(![NSNumber parseString:rangeComponent intoUInt64:&byteIndex]) return NO;
795
+
796
+ if(byteIndex >= contentLength) return NO;
797
+
798
+ [ranges addObject:[NSValue valueWithDDRange:DDMakeRange(byteIndex, 1)]];
799
+ }
800
+ else
801
+ {
802
+ // We're dealing with a range of bytes
803
+
804
+ tIndex = dashRange.location;
805
+ fIndex = dashRange.location + dashRange.length;
806
+
807
+ NSString *r1str = [rangeComponent substringToIndex:tIndex];
808
+ NSString *r2str = [rangeComponent substringFromIndex:fIndex];
809
+
810
+ UInt64 r1, r2;
811
+
812
+ BOOL hasR1 = [NSNumber parseString:r1str intoUInt64:&r1];
813
+ BOOL hasR2 = [NSNumber parseString:r2str intoUInt64:&r2];
814
+
815
+ if (!hasR1)
816
+ {
817
+ // We're dealing with a "-[#]" range
818
+ //
819
+ // r2 is the number of ending bytes to include in the range
820
+
821
+ if(!hasR2) return NO;
822
+ if(r2 > contentLength) return NO;
823
+
824
+ UInt64 startIndex = contentLength - r2;
825
+
826
+ [ranges addObject:[NSValue valueWithDDRange:DDMakeRange(startIndex, r2)]];
827
+ }
828
+ else if (!hasR2)
829
+ {
830
+ // We're dealing with a "[#]-" range
831
+ //
832
+ // r1 is the starting index of the range, which goes all the way to the end
833
+
834
+ if(r1 >= contentLength) return NO;
835
+
836
+ [ranges addObject:[NSValue valueWithDDRange:DDMakeRange(r1, contentLength - r1)]];
837
+ }
838
+ else
839
+ {
840
+ // We're dealing with a normal "[#]-[#]" range
841
+ //
842
+ // Note: The range is inclusive. So 0-1 has a length of 2 bytes.
843
+
844
+ if(r1 > r2) return NO;
845
+ if(r2 >= contentLength) return NO;
846
+
847
+ [ranges addObject:[NSValue valueWithDDRange:DDMakeRange(r1, r2 - r1 + 1)]];
848
+ }
849
+ }
850
+ }
851
+
852
+ if([ranges count] == 0) return NO;
853
+
854
+ // Now make sure none of the ranges overlap
855
+
856
+ for (i = 0; i < [ranges count] - 1; i++)
857
+ {
858
+ DDRange range1 = [[ranges objectAtIndex:i] ddrangeValue];
859
+
860
+ NSUInteger j;
861
+ for (j = i+1; j < [ranges count]; j++)
862
+ {
863
+ DDRange range2 = [[ranges objectAtIndex:j] ddrangeValue];
864
+
865
+ DDRange iRange = DDIntersectionRange(range1, range2);
866
+
867
+ if(iRange.length != 0)
868
+ {
869
+ return NO;
870
+ }
871
+ }
872
+ }
873
+
874
+ // Sort the ranges
875
+
876
+ [ranges sortUsingSelector:@selector(ddrangeCompare:)];
877
+
878
+ return YES;
879
+ }
880
+
881
+ - (NSString *)requestURI
882
+ {
883
+ if(request == nil) return nil;
884
+
885
+ return [[request url] relativeString];
886
+ }
887
+
888
+ /**
889
+ * This method is called after a full HTTP request has been received.
890
+ * The current request is in the HTTPMessage request variable.
891
+ **/
892
+ - (void)replyToHTTPRequest
893
+ {
894
+ HTTPLogTrace();
895
+
896
+ if (HTTP_LOG_VERBOSE)
897
+ {
898
+ NSData *tempData = [request messageData];
899
+
900
+ NSString *tempStr = [[NSString alloc] initWithData:tempData encoding:NSUTF8StringEncoding];
901
+ HTTPLogVerbose(@"%@[%p]: Received HTTP request:\n%@", THIS_FILE, self, tempStr);
902
+ }
903
+
904
+ // Check the HTTP version
905
+ // We only support version 1.0 and 1.1
906
+
907
+ NSString *version = [request version];
908
+ if (![version isEqualToString:HTTPVersion1_1] && ![version isEqualToString:HTTPVersion1_0])
909
+ {
910
+ [self handleVersionNotSupported:version];
911
+ return;
912
+ }
913
+
914
+ // Extract requested URI
915
+ NSString *uri = [self requestURI];
916
+
917
+ // Check for WebSocket request
918
+ if ([WebSocket isWebSocketRequest:request])
919
+ {
920
+ HTTPLogVerbose(@"isWebSocket");
921
+
922
+ WebSocket *ws = [self webSocketForURI:uri];
923
+
924
+ if (ws == nil)
925
+ {
926
+ [self handleResourceNotFound];
927
+ }
928
+ else
929
+ {
930
+ [ws start];
931
+
932
+ [[config server] addWebSocket:ws];
933
+
934
+ // The WebSocket should now be the delegate of the underlying socket.
935
+ // But gracefully handle the situation if it forgot.
936
+ if ([asyncSocket delegate] == self)
937
+ {
938
+ HTTPLogWarn(@"%@[%p]: WebSocket forgot to set itself as socket delegate", THIS_FILE, self);
939
+
940
+ // Disconnect the socket.
941
+ // The socketDidDisconnect delegate method will handle everything else.
942
+ [asyncSocket disconnect];
943
+ }
944
+ else
945
+ {
946
+ // The WebSocket is using the socket,
947
+ // so make sure we don't disconnect it in the dealloc method.
948
+ asyncSocket = nil;
949
+
950
+ [self die];
951
+
952
+ // Note: There is a timing issue here that should be pointed out.
953
+ //
954
+ // A bug that existed in previous versions happend like so:
955
+ // - We invoked [self die]
956
+ // - This caused us to get released, and our dealloc method to start executing
957
+ // - Meanwhile, AsyncSocket noticed a disconnect, and began to dispatch a socketDidDisconnect at us
958
+ // - The dealloc method finishes execution, and our instance gets freed
959
+ // - The socketDidDisconnect gets run, and a crash occurs
960
+ //
961
+ // So the issue we want to avoid is releasing ourself when there is a possibility
962
+ // that AsyncSocket might be gearing up to queue a socketDidDisconnect for us.
963
+ //
964
+ // In this particular situation notice that we invoke [asyncSocket delegate].
965
+ // This method is synchronous concerning AsyncSocket's internal socketQueue.
966
+ // Which means we can be sure, when it returns, that AsyncSocket has already
967
+ // queued any delegate methods for us if it was going to.
968
+ // And if the delegate methods are queued, then we've been properly retained.
969
+ // Meaning we won't get released / dealloc'd until the delegate method has finished executing.
970
+ //
971
+ // In this rare situation, the die method will get invoked twice.
972
+ }
973
+ }
974
+
975
+ return;
976
+ }
977
+
978
+ // Check Authentication (if needed)
979
+ // If not properly authenticated for resource, issue Unauthorized response
980
+ if ([self isPasswordProtected:uri] && ![self isAuthenticated])
981
+ {
982
+ [self handleAuthenticationFailed];
983
+ return;
984
+ }
985
+
986
+ // Extract the method
987
+ NSString *method = [request method];
988
+
989
+ // Note: We already checked to ensure the method was supported in onSocket:didReadData:withTag:
990
+
991
+ // Respond properly to HTTP 'GET' and 'HEAD' commands
992
+ httpResponse = [self httpResponseForMethod:method URI:uri];
993
+
994
+ if (httpResponse == nil)
995
+ {
996
+ [self handleResourceNotFound];
997
+ return;
998
+ }
999
+
1000
+ [self sendResponseHeadersAndBody];
1001
+ }
1002
+
1003
+ /**
1004
+ * Prepares a single-range response.
1005
+ *
1006
+ * Note: The returned HTTPMessage is owned by the sender, who is responsible for releasing it.
1007
+ **/
1008
+ - (HTTPMessage *)newUniRangeResponse:(UInt64)contentLength
1009
+ {
1010
+ HTTPLogTrace();
1011
+
1012
+ // Status Code 206 - Partial Content
1013
+ HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:206 description:nil version:HTTPVersion1_1];
1014
+
1015
+ DDRange range = [[ranges objectAtIndex:0] ddrangeValue];
1016
+
1017
+ NSString *contentLengthStr = [NSString stringWithFormat:@"%qu", range.length];
1018
+ [response setHeaderField:@"Content-Length" value:contentLengthStr];
1019
+
1020
+ NSString *rangeStr = [NSString stringWithFormat:@"%qu-%qu", range.location, DDMaxRange(range) - 1];
1021
+ NSString *contentRangeStr = [NSString stringWithFormat:@"bytes %@/%qu", rangeStr, contentLength];
1022
+ [response setHeaderField:@"Content-Range" value:contentRangeStr];
1023
+
1024
+ return response;
1025
+ }
1026
+
1027
+ /**
1028
+ * Prepares a multi-range response.
1029
+ *
1030
+ * Note: The returned HTTPMessage is owned by the sender, who is responsible for releasing it.
1031
+ **/
1032
+ - (HTTPMessage *)newMultiRangeResponse:(UInt64)contentLength
1033
+ {
1034
+ HTTPLogTrace();
1035
+
1036
+ // Status Code 206 - Partial Content
1037
+ HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:206 description:nil version:HTTPVersion1_1];
1038
+
1039
+ // We have to send each range using multipart/byteranges
1040
+ // So each byterange has to be prefix'd and suffix'd with the boundry
1041
+ // Example:
1042
+ //
1043
+ // HTTP/1.1 206 Partial Content
1044
+ // Content-Length: 220
1045
+ // Content-Type: multipart/byteranges; boundary=4554d24e986f76dd6
1046
+ //
1047
+ //
1048
+ // --4554d24e986f76dd6
1049
+ // Content-Range: bytes 0-25/4025
1050
+ //
1051
+ // [...]
1052
+ // --4554d24e986f76dd6
1053
+ // Content-Range: bytes 3975-4024/4025
1054
+ //
1055
+ // [...]
1056
+ // --4554d24e986f76dd6--
1057
+
1058
+ ranges_headers = [[NSMutableArray alloc] initWithCapacity:[ranges count]];
1059
+
1060
+ CFUUIDRef theUUID = CFUUIDCreate(NULL);
1061
+ ranges_boundry = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, theUUID);
1062
+ CFRelease(theUUID);
1063
+
1064
+ NSString *startingBoundryStr = [NSString stringWithFormat:@"\r\n--%@\r\n", ranges_boundry];
1065
+ NSString *endingBoundryStr = [NSString stringWithFormat:@"\r\n--%@--\r\n", ranges_boundry];
1066
+
1067
+ UInt64 actualContentLength = 0;
1068
+
1069
+ NSUInteger i;
1070
+ for (i = 0; i < [ranges count]; i++)
1071
+ {
1072
+ DDRange range = [[ranges objectAtIndex:i] ddrangeValue];
1073
+
1074
+ NSString *rangeStr = [NSString stringWithFormat:@"%qu-%qu", range.location, DDMaxRange(range) - 1];
1075
+ NSString *contentRangeVal = [NSString stringWithFormat:@"bytes %@/%qu", rangeStr, contentLength];
1076
+ NSString *contentRangeStr = [NSString stringWithFormat:@"Content-Range: %@\r\n\r\n", contentRangeVal];
1077
+
1078
+ NSString *fullHeader = [startingBoundryStr stringByAppendingString:contentRangeStr];
1079
+ NSData *fullHeaderData = [fullHeader dataUsingEncoding:NSUTF8StringEncoding];
1080
+
1081
+ [ranges_headers addObject:fullHeaderData];
1082
+
1083
+ actualContentLength += [fullHeaderData length];
1084
+ actualContentLength += range.length;
1085
+ }
1086
+
1087
+ NSData *endingBoundryData = [endingBoundryStr dataUsingEncoding:NSUTF8StringEncoding];
1088
+
1089
+ actualContentLength += [endingBoundryData length];
1090
+
1091
+ NSString *contentLengthStr = [NSString stringWithFormat:@"%qu", actualContentLength];
1092
+ [response setHeaderField:@"Content-Length" value:contentLengthStr];
1093
+
1094
+ NSString *contentTypeStr = [NSString stringWithFormat:@"multipart/byteranges; boundary=%@", ranges_boundry];
1095
+ [response setHeaderField:@"Content-Type" value:contentTypeStr];
1096
+
1097
+ return response;
1098
+ }
1099
+
1100
+ /**
1101
+ * Returns the chunk size line that must precede each chunk of data when using chunked transfer encoding.
1102
+ * This consists of the size of the data, in hexadecimal, followed by a CRLF.
1103
+ **/
1104
+ - (NSData *)chunkedTransferSizeLineForLength:(NSUInteger)length
1105
+ {
1106
+ return [[NSString stringWithFormat:@"%lx\r\n", (unsigned long)length] dataUsingEncoding:NSUTF8StringEncoding];
1107
+ }
1108
+
1109
+ /**
1110
+ * Returns the data that signals the end of a chunked transfer.
1111
+ **/
1112
+ - (NSData *)chunkedTransferFooter
1113
+ {
1114
+ // Each data chunk is preceded by a size line (in hex and including a CRLF),
1115
+ // followed by the data itself, followed by another CRLF.
1116
+ // After every data chunk has been sent, a zero size line is sent,
1117
+ // followed by optional footer (which are just more headers),
1118
+ // and followed by a CRLF on a line by itself.
1119
+
1120
+ return [@"\r\n0\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding];
1121
+ }
1122
+
1123
+ - (void)sendResponseHeadersAndBody
1124
+ {
1125
+ if ([httpResponse respondsToSelector:@selector(delayResponseHeaders)])
1126
+ {
1127
+ if ([httpResponse delayResponseHeaders])
1128
+ {
1129
+ return;
1130
+ }
1131
+ }
1132
+
1133
+ BOOL isChunked = NO;
1134
+
1135
+ if ([httpResponse respondsToSelector:@selector(isChunked)])
1136
+ {
1137
+ isChunked = [httpResponse isChunked];
1138
+ }
1139
+
1140
+ // If a response is "chunked", this simply means the HTTPResponse object
1141
+ // doesn't know the content-length in advance.
1142
+
1143
+ UInt64 contentLength = 0;
1144
+
1145
+ if (!isChunked)
1146
+ {
1147
+ contentLength = [httpResponse contentLength];
1148
+ }
1149
+
1150
+ // Check for specific range request
1151
+ NSString *rangeHeader = [request headerField:@"Range"];
1152
+
1153
+ BOOL isRangeRequest = NO;
1154
+
1155
+ // If the response is "chunked" then we don't know the exact content-length.
1156
+ // This means we'll be unable to process any range requests.
1157
+ // This is because range requests might include a range like "give me the last 100 bytes"
1158
+
1159
+ if (!isChunked && rangeHeader)
1160
+ {
1161
+ if ([self parseRangeRequest:rangeHeader withContentLength:contentLength])
1162
+ {
1163
+ isRangeRequest = YES;
1164
+ }
1165
+ }
1166
+
1167
+ HTTPMessage *response;
1168
+
1169
+ if (!isRangeRequest)
1170
+ {
1171
+ // Create response
1172
+ // Default status code: 200 - OK
1173
+ NSInteger status = 200;
1174
+
1175
+ if ([httpResponse respondsToSelector:@selector(status)])
1176
+ {
1177
+ status = [httpResponse status];
1178
+ }
1179
+ response = [[HTTPMessage alloc] initResponseWithStatusCode:status description:nil version:HTTPVersion1_1];
1180
+
1181
+ if (isChunked)
1182
+ {
1183
+ [response setHeaderField:@"Transfer-Encoding" value:@"chunked"];
1184
+ }
1185
+ else
1186
+ {
1187
+ NSString *contentLengthStr = [NSString stringWithFormat:@"%qu", contentLength];
1188
+ [response setHeaderField:@"Content-Length" value:contentLengthStr];
1189
+ }
1190
+ }
1191
+ else
1192
+ {
1193
+ if ([ranges count] == 1)
1194
+ {
1195
+ response = [self newUniRangeResponse:contentLength];
1196
+ }
1197
+ else
1198
+ {
1199
+ response = [self newMultiRangeResponse:contentLength];
1200
+ }
1201
+ }
1202
+
1203
+ BOOL isZeroLengthResponse = !isChunked && (contentLength == 0);
1204
+
1205
+ // If they issue a 'HEAD' command, we don't have to include the file
1206
+ // If they issue a 'GET' command, we need to include the file
1207
+
1208
+ if ([[request method] isEqualToString:@"HEAD"] || isZeroLengthResponse)
1209
+ {
1210
+ NSData *responseData = [self preprocessResponse:response];
1211
+ [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE];
1212
+
1213
+ sentResponseHeaders = YES;
1214
+ }
1215
+ else
1216
+ {
1217
+ // Write the header response
1218
+ NSData *responseData = [self preprocessResponse:response];
1219
+ [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_PARTIAL_RESPONSE_HEADER];
1220
+
1221
+ sentResponseHeaders = YES;
1222
+
1223
+ // Now we need to send the body of the response
1224
+ if (!isRangeRequest)
1225
+ {
1226
+ // Regular request
1227
+ NSData *data = [httpResponse readDataOfLength:READ_CHUNKSIZE];
1228
+
1229
+ if ([data length] > 0)
1230
+ {
1231
+ [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
1232
+
1233
+ if (isChunked)
1234
+ {
1235
+ NSData *chunkSize = [self chunkedTransferSizeLineForLength:[data length]];
1236
+ [asyncSocket writeData:chunkSize withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_HEADER];
1237
+
1238
+ [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_CHUNKED_RESPONSE_BODY];
1239
+
1240
+ if ([httpResponse isDone])
1241
+ {
1242
+ NSData *footer = [self chunkedTransferFooter];
1243
+ [asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE];
1244
+ }
1245
+ else
1246
+ {
1247
+ NSData *footer = [GCDAsyncSocket CRLFData];
1248
+ [asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_FOOTER];
1249
+ }
1250
+ }
1251
+ else
1252
+ {
1253
+ long tag = [httpResponse isDone] ? HTTP_RESPONSE : HTTP_PARTIAL_RESPONSE_BODY;
1254
+ [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag];
1255
+ }
1256
+ }
1257
+ }
1258
+ else
1259
+ {
1260
+ // Client specified a byte range in request
1261
+
1262
+ if ([ranges count] == 1)
1263
+ {
1264
+ // Client is requesting a single range
1265
+ DDRange range = [[ranges objectAtIndex:0] ddrangeValue];
1266
+
1267
+ [httpResponse setOffset:range.location];
1268
+
1269
+ NSUInteger bytesToRead = range.length < READ_CHUNKSIZE ? (NSUInteger)range.length : READ_CHUNKSIZE;
1270
+
1271
+ NSData *data = [httpResponse readDataOfLength:bytesToRead];
1272
+
1273
+ if ([data length] > 0)
1274
+ {
1275
+ [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
1276
+
1277
+ long tag = [data length] == range.length ? HTTP_RESPONSE : HTTP_PARTIAL_RANGE_RESPONSE_BODY;
1278
+ [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag];
1279
+ }
1280
+ }
1281
+ else
1282
+ {
1283
+ // Client is requesting multiple ranges
1284
+ // We have to send each range using multipart/byteranges
1285
+
1286
+ // Write range header
1287
+ NSData *rangeHeaderData = [ranges_headers objectAtIndex:0];
1288
+ [asyncSocket writeData:rangeHeaderData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_PARTIAL_RESPONSE_HEADER];
1289
+
1290
+ // Start writing range body
1291
+ DDRange range = [[ranges objectAtIndex:0] ddrangeValue];
1292
+
1293
+ [httpResponse setOffset:range.location];
1294
+
1295
+ NSUInteger bytesToRead = range.length < READ_CHUNKSIZE ? (NSUInteger)range.length : READ_CHUNKSIZE;
1296
+
1297
+ NSData *data = [httpResponse readDataOfLength:bytesToRead];
1298
+
1299
+ if ([data length] > 0)
1300
+ {
1301
+ [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
1302
+
1303
+ [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_PARTIAL_RANGES_RESPONSE_BODY];
1304
+ }
1305
+ }
1306
+ }
1307
+ }
1308
+
1309
+ }
1310
+
1311
+ /**
1312
+ * Returns the number of bytes of the http response body that are sitting in asyncSocket's write queue.
1313
+ *
1314
+ * We keep track of this information in order to keep our memory footprint low while
1315
+ * working with asynchronous HTTPResponse objects.
1316
+ **/
1317
+ - (NSUInteger)writeQueueSize
1318
+ {
1319
+ NSUInteger result = 0;
1320
+
1321
+ NSUInteger i;
1322
+ for(i = 0; i < [responseDataSizes count]; i++)
1323
+ {
1324
+ result += [[responseDataSizes objectAtIndex:i] unsignedIntegerValue];
1325
+ }
1326
+
1327
+ return result;
1328
+ }
1329
+
1330
+ /**
1331
+ * Sends more data, if needed, without growing the write queue over its approximate size limit.
1332
+ * The last chunk of the response body will be sent with a tag of HTTP_RESPONSE.
1333
+ *
1334
+ * This method should only be called for standard (non-range) responses.
1335
+ **/
1336
+ - (void)continueSendingStandardResponseBody
1337
+ {
1338
+ HTTPLogTrace();
1339
+
1340
+ // This method is called when either asyncSocket has finished writing one of the response data chunks,
1341
+ // or when an asynchronous HTTPResponse object informs us that it has more available data for us to send.
1342
+ // In the case of the asynchronous HTTPResponse, we don't want to blindly grab the new data,
1343
+ // and shove it onto asyncSocket's write queue.
1344
+ // Doing so could negatively affect the memory footprint of the application.
1345
+ // Instead, we always ensure that we place no more than READ_CHUNKSIZE bytes onto the write queue.
1346
+ //
1347
+ // Note that this does not affect the rate at which the HTTPResponse object may generate data.
1348
+ // The HTTPResponse is free to do as it pleases, and this is up to the application's developer.
1349
+ // If the memory footprint is a concern, the developer creating the custom HTTPResponse object may freely
1350
+ // use the calls to readDataOfLength as an indication to start generating more data.
1351
+ // This provides an easy way for the HTTPResponse object to throttle its data allocation in step with the rate
1352
+ // at which the socket is able to send it.
1353
+
1354
+ NSUInteger writeQueueSize = [self writeQueueSize];
1355
+
1356
+ if(writeQueueSize >= READ_CHUNKSIZE) return;
1357
+
1358
+ NSUInteger available = READ_CHUNKSIZE - writeQueueSize;
1359
+ NSData *data = [httpResponse readDataOfLength:available];
1360
+
1361
+ if ([data length] > 0)
1362
+ {
1363
+ [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
1364
+
1365
+ BOOL isChunked = NO;
1366
+
1367
+ if ([httpResponse respondsToSelector:@selector(isChunked)])
1368
+ {
1369
+ isChunked = [httpResponse isChunked];
1370
+ }
1371
+
1372
+ if (isChunked)
1373
+ {
1374
+ NSData *chunkSize = [self chunkedTransferSizeLineForLength:[data length]];
1375
+ [asyncSocket writeData:chunkSize withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_HEADER];
1376
+
1377
+ [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_CHUNKED_RESPONSE_BODY];
1378
+
1379
+ if([httpResponse isDone])
1380
+ {
1381
+ NSData *footer = [self chunkedTransferFooter];
1382
+ [asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE];
1383
+ }
1384
+ else
1385
+ {
1386
+ NSData *footer = [GCDAsyncSocket CRLFData];
1387
+ [asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_FOOTER];
1388
+ }
1389
+ }
1390
+ else
1391
+ {
1392
+ long tag = [httpResponse isDone] ? HTTP_RESPONSE : HTTP_PARTIAL_RESPONSE_BODY;
1393
+ [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag];
1394
+ }
1395
+ }
1396
+ }
1397
+
1398
+ /**
1399
+ * Sends more data, if needed, without growing the write queue over its approximate size limit.
1400
+ * The last chunk of the response body will be sent with a tag of HTTP_RESPONSE.
1401
+ *
1402
+ * This method should only be called for single-range responses.
1403
+ **/
1404
+ - (void)continueSendingSingleRangeResponseBody
1405
+ {
1406
+ HTTPLogTrace();
1407
+
1408
+ // This method is called when either asyncSocket has finished writing one of the response data chunks,
1409
+ // or when an asynchronous response informs us that is has more available data for us to send.
1410
+ // In the case of the asynchronous response, we don't want to blindly grab the new data,
1411
+ // and shove it onto asyncSocket's write queue.
1412
+ // Doing so could negatively affect the memory footprint of the application.
1413
+ // Instead, we always ensure that we place no more than READ_CHUNKSIZE bytes onto the write queue.
1414
+ //
1415
+ // Note that this does not affect the rate at which the HTTPResponse object may generate data.
1416
+ // The HTTPResponse is free to do as it pleases, and this is up to the application's developer.
1417
+ // If the memory footprint is a concern, the developer creating the custom HTTPResponse object may freely
1418
+ // use the calls to readDataOfLength as an indication to start generating more data.
1419
+ // This provides an easy way for the HTTPResponse object to throttle its data allocation in step with the rate
1420
+ // at which the socket is able to send it.
1421
+
1422
+ NSUInteger writeQueueSize = [self writeQueueSize];
1423
+
1424
+ if(writeQueueSize >= READ_CHUNKSIZE) return;
1425
+
1426
+ DDRange range = [[ranges objectAtIndex:0] ddrangeValue];
1427
+
1428
+ UInt64 offset = [httpResponse offset];
1429
+ UInt64 bytesRead = offset - range.location;
1430
+ UInt64 bytesLeft = range.length - bytesRead;
1431
+
1432
+ if (bytesLeft > 0)
1433
+ {
1434
+ NSUInteger available = READ_CHUNKSIZE - writeQueueSize;
1435
+ NSUInteger bytesToRead = bytesLeft < available ? (NSUInteger)bytesLeft : available;
1436
+
1437
+ NSData *data = [httpResponse readDataOfLength:bytesToRead];
1438
+
1439
+ if ([data length] > 0)
1440
+ {
1441
+ [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
1442
+
1443
+ long tag = [data length] == bytesLeft ? HTTP_RESPONSE : HTTP_PARTIAL_RANGE_RESPONSE_BODY;
1444
+ [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag];
1445
+ }
1446
+ }
1447
+ }
1448
+
1449
+ /**
1450
+ * Sends more data, if needed, without growing the write queue over its approximate size limit.
1451
+ * The last chunk of the response body will be sent with a tag of HTTP_RESPONSE.
1452
+ *
1453
+ * This method should only be called for multi-range responses.
1454
+ **/
1455
+ - (void)continueSendingMultiRangeResponseBody
1456
+ {
1457
+ HTTPLogTrace();
1458
+
1459
+ // This method is called when either asyncSocket has finished writing one of the response data chunks,
1460
+ // or when an asynchronous HTTPResponse object informs us that is has more available data for us to send.
1461
+ // In the case of the asynchronous HTTPResponse, we don't want to blindly grab the new data,
1462
+ // and shove it onto asyncSocket's write queue.
1463
+ // Doing so could negatively affect the memory footprint of the application.
1464
+ // Instead, we always ensure that we place no more than READ_CHUNKSIZE bytes onto the write queue.
1465
+ //
1466
+ // Note that this does not affect the rate at which the HTTPResponse object may generate data.
1467
+ // The HTTPResponse is free to do as it pleases, and this is up to the application's developer.
1468
+ // If the memory footprint is a concern, the developer creating the custom HTTPResponse object may freely
1469
+ // use the calls to readDataOfLength as an indication to start generating more data.
1470
+ // This provides an easy way for the HTTPResponse object to throttle its data allocation in step with the rate
1471
+ // at which the socket is able to send it.
1472
+
1473
+ NSUInteger writeQueueSize = [self writeQueueSize];
1474
+
1475
+ if(writeQueueSize >= READ_CHUNKSIZE) return;
1476
+
1477
+ DDRange range = [[ranges objectAtIndex:rangeIndex] ddrangeValue];
1478
+
1479
+ UInt64 offset = [httpResponse offset];
1480
+ UInt64 bytesRead = offset - range.location;
1481
+ UInt64 bytesLeft = range.length - bytesRead;
1482
+
1483
+ if (bytesLeft > 0)
1484
+ {
1485
+ NSUInteger available = READ_CHUNKSIZE - writeQueueSize;
1486
+ NSUInteger bytesToRead = bytesLeft < available ? (NSUInteger)bytesLeft : available;
1487
+
1488
+ NSData *data = [httpResponse readDataOfLength:bytesToRead];
1489
+
1490
+ if ([data length] > 0)
1491
+ {
1492
+ [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
1493
+
1494
+ [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_PARTIAL_RANGES_RESPONSE_BODY];
1495
+ }
1496
+ }
1497
+ else
1498
+ {
1499
+ if (++rangeIndex < [ranges count])
1500
+ {
1501
+ // Write range header
1502
+ NSData *rangeHeader = [ranges_headers objectAtIndex:rangeIndex];
1503
+ [asyncSocket writeData:rangeHeader withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_PARTIAL_RESPONSE_HEADER];
1504
+
1505
+ // Start writing range body
1506
+ range = [[ranges objectAtIndex:rangeIndex] ddrangeValue];
1507
+
1508
+ [httpResponse setOffset:range.location];
1509
+
1510
+ NSUInteger available = READ_CHUNKSIZE - writeQueueSize;
1511
+ NSUInteger bytesToRead = range.length < available ? (NSUInteger)range.length : available;
1512
+
1513
+ NSData *data = [httpResponse readDataOfLength:bytesToRead];
1514
+
1515
+ if ([data length] > 0)
1516
+ {
1517
+ [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
1518
+
1519
+ [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_PARTIAL_RANGES_RESPONSE_BODY];
1520
+ }
1521
+ }
1522
+ else
1523
+ {
1524
+ // We're not done yet - we still have to send the closing boundry tag
1525
+ NSString *endingBoundryStr = [NSString stringWithFormat:@"\r\n--%@--\r\n", ranges_boundry];
1526
+ NSData *endingBoundryData = [endingBoundryStr dataUsingEncoding:NSUTF8StringEncoding];
1527
+
1528
+ [asyncSocket writeData:endingBoundryData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE];
1529
+ }
1530
+ }
1531
+ }
1532
+
1533
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1534
+ #pragma mark Responses
1535
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1536
+
1537
+ /**
1538
+ * Returns an array of possible index pages.
1539
+ * For example: {"index.html", "index.htm"}
1540
+ **/
1541
+ - (NSArray *)directoryIndexFileNames
1542
+ {
1543
+ HTTPLogTrace();
1544
+
1545
+ // Override me to support other index pages.
1546
+
1547
+ return [NSArray arrayWithObjects:@"index.html", @"index.htm", nil];
1548
+ }
1549
+
1550
+ - (NSString *)filePathForURI:(NSString *)path
1551
+ {
1552
+ return [self filePathForURI:path allowDirectory:NO];
1553
+ }
1554
+
1555
+ /**
1556
+ * Converts relative URI path into full file-system path.
1557
+ **/
1558
+ - (NSString *)filePathForURI:(NSString *)path allowDirectory:(BOOL)allowDirectory
1559
+ {
1560
+ HTTPLogTrace();
1561
+
1562
+ // Override me to perform custom path mapping.
1563
+ // For example you may want to use a default file other than index.html, or perhaps support multiple types.
1564
+
1565
+ NSString *documentRoot = [config documentRoot];
1566
+
1567
+ // Part 0: Validate document root setting.
1568
+ //
1569
+ // If there is no configured documentRoot,
1570
+ // then it makes no sense to try to return anything.
1571
+
1572
+ if (documentRoot == nil)
1573
+ {
1574
+ HTTPLogWarn(@"%@[%p]: No configured document root", THIS_FILE, self);
1575
+ return nil;
1576
+ }
1577
+
1578
+ // Part 1: Strip parameters from the url
1579
+ //
1580
+ // E.g.: /page.html?q=22&var=abc -> /page.html
1581
+
1582
+ NSURL *docRoot = [NSURL fileURLWithPath:documentRoot isDirectory:YES];
1583
+ if (docRoot == nil)
1584
+ {
1585
+ HTTPLogWarn(@"%@[%p]: Document root is invalid file path", THIS_FILE, self);
1586
+ return nil;
1587
+ }
1588
+
1589
+ NSString *relativePath = [[NSURL URLWithString:path relativeToURL:docRoot] relativePath];
1590
+
1591
+ // Part 2: Append relative path to document root (base path)
1592
+ //
1593
+ // E.g.: relativePath="/images/icon.png"
1594
+ // documentRoot="/Users/robbie/Sites"
1595
+ // fullPath="/Users/robbie/Sites/images/icon.png"
1596
+ //
1597
+ // We also standardize the path.
1598
+ //
1599
+ // E.g.: "Users/robbie/Sites/images/../index.html" -> "/Users/robbie/Sites/index.html"
1600
+
1601
+ NSString *fullPath = [[documentRoot stringByAppendingPathComponent:relativePath] stringByStandardizingPath];
1602
+
1603
+ if ([relativePath isEqualToString:@"/"])
1604
+ {
1605
+ fullPath = [fullPath stringByAppendingString:@"/"];
1606
+ }
1607
+
1608
+ // Part 3: Prevent serving files outside the document root.
1609
+ //
1610
+ // Sneaky requests may include ".." in the path.
1611
+ //
1612
+ // E.g.: relativePath="../Documents/TopSecret.doc"
1613
+ // documentRoot="/Users/robbie/Sites"
1614
+ // fullPath="/Users/robbie/Documents/TopSecret.doc"
1615
+ //
1616
+ // E.g.: relativePath="../Sites_Secret/TopSecret.doc"
1617
+ // documentRoot="/Users/robbie/Sites"
1618
+ // fullPath="/Users/robbie/Sites_Secret/TopSecret"
1619
+
1620
+ if (![documentRoot hasSuffix:@"/"])
1621
+ {
1622
+ documentRoot = [documentRoot stringByAppendingString:@"/"];
1623
+ }
1624
+
1625
+ if (![fullPath hasPrefix:documentRoot])
1626
+ {
1627
+ HTTPLogWarn(@"%@[%p]: Request for file outside document root", THIS_FILE, self);
1628
+ return nil;
1629
+ }
1630
+
1631
+ // Part 4: Search for index page if path is pointing to a directory
1632
+ if (!allowDirectory)
1633
+ {
1634
+ BOOL isDir = NO;
1635
+ if ([[NSFileManager defaultManager] fileExistsAtPath:fullPath isDirectory:&isDir] && isDir)
1636
+ {
1637
+ NSArray *indexFileNames = [self directoryIndexFileNames];
1638
+
1639
+ for (NSString *indexFileName in indexFileNames)
1640
+ {
1641
+ NSString *indexFilePath = [fullPath stringByAppendingPathComponent:indexFileName];
1642
+
1643
+ if ([[NSFileManager defaultManager] fileExistsAtPath:indexFilePath isDirectory:&isDir] && !isDir)
1644
+ {
1645
+ return indexFilePath;
1646
+ }
1647
+ }
1648
+
1649
+ // No matching index files found in directory
1650
+ return nil;
1651
+ }
1652
+ }
1653
+
1654
+ return fullPath;
1655
+ }
1656
+
1657
+ /**
1658
+ * This method is called to get a response for a request.
1659
+ * You may return any object that adopts the HTTPResponse protocol.
1660
+ * The HTTPServer comes with two such classes: HTTPFileResponse and HTTPDataResponse.
1661
+ * HTTPFileResponse is a wrapper for an NSFileHandle object, and is the preferred way to send a file response.
1662
+ * HTTPDataResponse is a wrapper for an NSData object, and may be used to send a custom response.
1663
+ **/
1664
+ - (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path
1665
+ {
1666
+ HTTPLogTrace();
1667
+
1668
+ // Override me to provide custom responses.
1669
+
1670
+ NSString *filePath = [self filePathForURI:path allowDirectory:NO];
1671
+
1672
+ BOOL isDir = NO;
1673
+
1674
+ if (filePath && [[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDir] && !isDir)
1675
+ {
1676
+ return [[HTTPFileResponse alloc] initWithFilePath:filePath forConnection:self];
1677
+
1678
+ // Use me instead for asynchronous file IO.
1679
+ // Generally better for larger files.
1680
+
1681
+ // return [[[HTTPAsyncFileResponse alloc] initWithFilePath:filePath forConnection:self] autorelease];
1682
+ }
1683
+
1684
+ return nil;
1685
+ }
1686
+
1687
+ - (WebSocket *)webSocketForURI:(NSString *)path
1688
+ {
1689
+ HTTPLogTrace();
1690
+
1691
+ // Override me to provide custom WebSocket responses.
1692
+ // To do so, simply override the base WebSocket implementation, and add your custom functionality.
1693
+ // Then return an instance of your custom WebSocket here.
1694
+ //
1695
+ // For example:
1696
+ //
1697
+ // if ([path isEqualToString:@"/myAwesomeWebSocketStream"])
1698
+ // {
1699
+ // return [[[MyWebSocket alloc] initWithRequest:request socket:asyncSocket] autorelease];
1700
+ // }
1701
+ //
1702
+ // return [super webSocketForURI:path];
1703
+
1704
+ return nil;
1705
+ }
1706
+
1707
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1708
+ #pragma mark Uploads
1709
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1710
+
1711
+ /**
1712
+ * This method is called after receiving all HTTP headers, but before reading any of the request body.
1713
+ **/
1714
+ - (void)prepareForBodyWithSize:(UInt64)contentLength
1715
+ {
1716
+ // Override me to allocate buffers, file handles, etc.
1717
+ }
1718
+
1719
+ /**
1720
+ * This method is called to handle data read from a POST / PUT.
1721
+ * The given data is part of the request body.
1722
+ **/
1723
+ - (void)processBodyData:(NSData *)postDataChunk
1724
+ {
1725
+ // Override me to do something useful with a POST / PUT.
1726
+ // If the post is small, such as a simple form, you may want to simply append the data to the request.
1727
+ // If the post is big, such as a file upload, you may want to store the file to disk.
1728
+ //
1729
+ // Remember: In order to support LARGE POST uploads, the data is read in chunks.
1730
+ // This prevents a 50 MB upload from being stored in RAM.
1731
+ // The size of the chunks are limited by the POST_CHUNKSIZE definition.
1732
+ // Therefore, this method may be called multiple times for the same POST request.
1733
+ }
1734
+
1735
+ /**
1736
+ * This method is called after the request body has been fully read but before the HTTP request is processed.
1737
+ **/
1738
+ - (void)finishBody
1739
+ {
1740
+ // Override me to perform any final operations on an upload.
1741
+ // For example, if you were saving the upload to disk this would be
1742
+ // the hook to flush any pending data to disk and maybe close the file.
1743
+ }
1744
+
1745
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1746
+ #pragma mark Errors
1747
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1748
+
1749
+ /**
1750
+ * Called if the HTML version is other than what is supported
1751
+ **/
1752
+ - (void)handleVersionNotSupported:(NSString *)version
1753
+ {
1754
+ // Override me for custom error handling of unsupported http version responses
1755
+ // If you simply want to add a few extra header fields, see the preprocessErrorResponse: method.
1756
+ // You can also use preprocessErrorResponse: to add an optional HTML body.
1757
+
1758
+ HTTPLogWarn(@"HTTP Server: Error 505 - Version Not Supported: %@ (%@)", version, [self requestURI]);
1759
+
1760
+ HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:505 description:nil version:HTTPVersion1_1];
1761
+ [response setHeaderField:@"Content-Length" value:@"0"];
1762
+
1763
+ NSData *responseData = [self preprocessErrorResponse:response];
1764
+ [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_RESPONSE];
1765
+
1766
+ }
1767
+
1768
+ /**
1769
+ * Called if the authentication information was required and absent, or if authentication failed.
1770
+ **/
1771
+ - (void)handleAuthenticationFailed
1772
+ {
1773
+ // Override me for custom handling of authentication challenges
1774
+ // If you simply want to add a few extra header fields, see the preprocessErrorResponse: method.
1775
+ // You can also use preprocessErrorResponse: to add an optional HTML body.
1776
+
1777
+ HTTPLogInfo(@"HTTP Server: Error 401 - Unauthorized (%@)", [self requestURI]);
1778
+
1779
+ // Status Code 401 - Unauthorized
1780
+ HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:401 description:nil version:HTTPVersion1_1];
1781
+ [response setHeaderField:@"Content-Length" value:@"0"];
1782
+
1783
+ if ([self useDigestAccessAuthentication])
1784
+ {
1785
+ [self addDigestAuthChallenge:response];
1786
+ }
1787
+ else
1788
+ {
1789
+ [self addBasicAuthChallenge:response];
1790
+ }
1791
+
1792
+ NSData *responseData = [self preprocessErrorResponse:response];
1793
+ [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_RESPONSE];
1794
+
1795
+ }
1796
+
1797
+ /**
1798
+ * Called if we receive some sort of malformed HTTP request.
1799
+ * The data parameter is the invalid HTTP header line, including CRLF, as read from GCDAsyncSocket.
1800
+ * The data parameter may also be nil if the request as a whole was invalid, such as a POST with no Content-Length.
1801
+ **/
1802
+ - (void)handleInvalidRequest:(NSData *)data
1803
+ {
1804
+ // Override me for custom error handling of invalid HTTP requests
1805
+ // If you simply want to add a few extra header fields, see the preprocessErrorResponse: method.
1806
+ // You can also use preprocessErrorResponse: to add an optional HTML body.
1807
+
1808
+ HTTPLogWarn(@"HTTP Server: Error 400 - Bad Request (%@)", [self requestURI]);
1809
+
1810
+ // Status Code 400 - Bad Request
1811
+ HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:400 description:nil version:HTTPVersion1_1];
1812
+ [response setHeaderField:@"Content-Length" value:@"0"];
1813
+ [response setHeaderField:@"Connection" value:@"close"];
1814
+
1815
+ NSData *responseData = [self preprocessErrorResponse:response];
1816
+ [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_FINAL_RESPONSE];
1817
+
1818
+
1819
+ // Note: We used the HTTP_FINAL_RESPONSE tag to disconnect after the response is sent.
1820
+ // We do this because we couldn't parse the request,
1821
+ // so we won't be able to recover and move on to another request afterwards.
1822
+ // In other words, we wouldn't know where the first request ends and the second request begins.
1823
+ }
1824
+
1825
+ /**
1826
+ * Called if we receive a HTTP request with a method other than GET or HEAD.
1827
+ **/
1828
+ - (void)handleUnknownMethod:(NSString *)method
1829
+ {
1830
+ // Override me for custom error handling of 405 method not allowed responses.
1831
+ // If you simply want to add a few extra header fields, see the preprocessErrorResponse: method.
1832
+ // You can also use preprocessErrorResponse: to add an optional HTML body.
1833
+ //
1834
+ // See also: supportsMethod:atPath:
1835
+
1836
+ HTTPLogWarn(@"HTTP Server: Error 405 - Method Not Allowed: %@ (%@)", method, [self requestURI]);
1837
+
1838
+ // Status code 405 - Method Not Allowed
1839
+ HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:405 description:nil version:HTTPVersion1_1];
1840
+ [response setHeaderField:@"Content-Length" value:@"0"];
1841
+ [response setHeaderField:@"Connection" value:@"close"];
1842
+
1843
+ NSData *responseData = [self preprocessErrorResponse:response];
1844
+ [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_FINAL_RESPONSE];
1845
+
1846
+
1847
+ // Note: We used the HTTP_FINAL_RESPONSE tag to disconnect after the response is sent.
1848
+ // We do this because the method may include an http body.
1849
+ // Since we can't be sure, we should close the connection.
1850
+ }
1851
+
1852
+ /**
1853
+ * Called if we're unable to find the requested resource.
1854
+ **/
1855
+ - (void)handleResourceNotFound
1856
+ {
1857
+ // Override me for custom error handling of 404 not found responses
1858
+ // If you simply want to add a few extra header fields, see the preprocessErrorResponse: method.
1859
+ // You can also use preprocessErrorResponse: to add an optional HTML body.
1860
+
1861
+ HTTPLogInfo(@"HTTP Server: Error 404 - Not Found (%@)", [self requestURI]);
1862
+
1863
+ // Status Code 404 - Not Found
1864
+ HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:404 description:nil version:HTTPVersion1_1];
1865
+ [response setHeaderField:@"Content-Length" value:@"0"];
1866
+
1867
+ NSData *responseData = [self preprocessErrorResponse:response];
1868
+ [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_RESPONSE];
1869
+
1870
+ }
1871
+
1872
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1873
+ #pragma mark Headers
1874
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1875
+
1876
+ /**
1877
+ * Gets the current date and time, formatted properly (according to RFC) for insertion into an HTTP header.
1878
+ **/
1879
+ - (NSString *)dateAsString:(NSDate *)date
1880
+ {
1881
+ // From Apple's Documentation (Data Formatting Guide -> Date Formatters -> Cache Formatters for Efficiency):
1882
+ //
1883
+ // "Creating a date formatter is not a cheap operation. If you are likely to use a formatter frequently,
1884
+ // it is typically more efficient to cache a single instance than to create and dispose of multiple instances.
1885
+ // One approach is to use a static variable."
1886
+ //
1887
+ // This was discovered to be true in massive form via issue #46:
1888
+ //
1889
+ // "Was doing some performance benchmarking using instruments and httperf. Using this single optimization
1890
+ // I got a 26% speed improvement - from 1000req/sec to 3800req/sec. Not insignificant.
1891
+ // The culprit? Why, NSDateFormatter, of course!"
1892
+ //
1893
+ // Thus, we are using a static NSDateFormatter here.
1894
+
1895
+ static NSDateFormatter *df;
1896
+
1897
+ static dispatch_once_t onceToken;
1898
+ dispatch_once(&onceToken, ^{
1899
+
1900
+ // Example: Sun, 06 Nov 1994 08:49:37 GMT
1901
+
1902
+ df = [[NSDateFormatter alloc] init];
1903
+ [df setFormatterBehavior:NSDateFormatterBehavior10_4];
1904
+ [df setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]];
1905
+ [df setDateFormat:@"EEE, dd MMM y HH:mm:ss 'GMT'"];
1906
+ [df setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]];
1907
+
1908
+ // For some reason, using zzz in the format string produces GMT+00:00
1909
+ });
1910
+
1911
+ return [df stringFromDate:date];
1912
+ }
1913
+
1914
+ /**
1915
+ * This method is called immediately prior to sending the response headers.
1916
+ * This method adds standard header fields, and then converts the response to an NSData object.
1917
+ **/
1918
+ - (NSData *)preprocessResponse:(HTTPMessage *)response
1919
+ {
1920
+ HTTPLogTrace();
1921
+
1922
+ // Override me to customize the response headers
1923
+ // You'll likely want to add your own custom headers, and then return [super preprocessResponse:response]
1924
+
1925
+ // Add standard headers
1926
+ NSString *now = [self dateAsString:[NSDate date]];
1927
+ [response setHeaderField:@"Date" value:now];
1928
+
1929
+ // Add server capability headers
1930
+ [response setHeaderField:@"Accept-Ranges" value:@"bytes"];
1931
+
1932
+ // Add optional response headers
1933
+ if ([httpResponse respondsToSelector:@selector(httpHeaders)])
1934
+ {
1935
+ NSDictionary *responseHeaders = [httpResponse httpHeaders];
1936
+
1937
+ NSEnumerator *keyEnumerator = [responseHeaders keyEnumerator];
1938
+ NSString *key;
1939
+
1940
+ while ((key = [keyEnumerator nextObject]))
1941
+ {
1942
+ NSString *value = [responseHeaders objectForKey:key];
1943
+
1944
+ [response setHeaderField:key value:value];
1945
+ }
1946
+ }
1947
+
1948
+ return [response messageData];
1949
+ }
1950
+
1951
+ /**
1952
+ * This method is called immediately prior to sending the response headers (for an error).
1953
+ * This method adds standard header fields, and then converts the response to an NSData object.
1954
+ **/
1955
+ - (NSData *)preprocessErrorResponse:(HTTPMessage *)response
1956
+ {
1957
+ HTTPLogTrace();
1958
+
1959
+ // Override me to customize the error response headers
1960
+ // You'll likely want to add your own custom headers, and then return [super preprocessErrorResponse:response]
1961
+ //
1962
+ // Notes:
1963
+ // You can use [response statusCode] to get the type of error.
1964
+ // You can use [response setBody:data] to add an optional HTML body.
1965
+ // If you add a body, don't forget to update the Content-Length.
1966
+ //
1967
+ // if ([response statusCode] == 404)
1968
+ // {
1969
+ // NSString *msg = @"<html><body>Error 404 - Not Found</body></html>";
1970
+ // NSData *msgData = [msg dataUsingEncoding:NSUTF8StringEncoding];
1971
+ //
1972
+ // [response setBody:msgData];
1973
+ //
1974
+ // NSString *contentLengthStr = [NSString stringWithFormat:@"%lu", (unsigned long)[msgData length]];
1975
+ // [response setHeaderField:@"Content-Length" value:contentLengthStr];
1976
+ // }
1977
+
1978
+ // Add standard headers
1979
+ NSString *now = [self dateAsString:[NSDate date]];
1980
+ [response setHeaderField:@"Date" value:now];
1981
+
1982
+ // Add server capability headers
1983
+ [response setHeaderField:@"Accept-Ranges" value:@"bytes"];
1984
+
1985
+ // Add optional response headers
1986
+ if ([httpResponse respondsToSelector:@selector(httpHeaders)])
1987
+ {
1988
+ NSDictionary *responseHeaders = [httpResponse httpHeaders];
1989
+
1990
+ NSEnumerator *keyEnumerator = [responseHeaders keyEnumerator];
1991
+ NSString *key;
1992
+
1993
+ while((key = [keyEnumerator nextObject]))
1994
+ {
1995
+ NSString *value = [responseHeaders objectForKey:key];
1996
+
1997
+ [response setHeaderField:key value:value];
1998
+ }
1999
+ }
2000
+
2001
+ return [response messageData];
2002
+ }
2003
+
2004
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2005
+ #pragma mark GCDAsyncSocket Delegate
2006
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2007
+
2008
+ /**
2009
+ * This method is called after the socket has successfully read data from the stream.
2010
+ * Remember that this method will only be called after the socket reaches a CRLF, or after it's read the proper length.
2011
+ **/
2012
+ - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData*)data withTag:(long)tag
2013
+ {
2014
+ if (tag == HTTP_REQUEST_HEADER)
2015
+ {
2016
+ // Append the header line to the http message
2017
+ BOOL result = [request appendData:data];
2018
+ if (!result)
2019
+ {
2020
+ HTTPLogWarn(@"%@[%p]: Malformed request", THIS_FILE, self);
2021
+
2022
+ [self handleInvalidRequest:data];
2023
+ }
2024
+ else if (![request isHeaderComplete])
2025
+ {
2026
+ // We don't have a complete header yet
2027
+ // That is, we haven't yet received a CRLF on a line by itself, indicating the end of the header
2028
+ if (++numHeaderLines > MAX_HEADER_LINES)
2029
+ {
2030
+ // Reached the maximum amount of header lines in a single HTTP request
2031
+ // This could be an attempted DOS attack
2032
+ [asyncSocket disconnect];
2033
+
2034
+ // Explictly return to ensure we don't do anything after the socket disconnect
2035
+ return;
2036
+ }
2037
+ else
2038
+ {
2039
+ [asyncSocket readDataToData:[GCDAsyncSocket CRLFData]
2040
+ withTimeout:TIMEOUT_READ_SUBSEQUENT_HEADER_LINE
2041
+ maxLength:MAX_HEADER_LINE_LENGTH
2042
+ tag:HTTP_REQUEST_HEADER];
2043
+ }
2044
+ }
2045
+ else
2046
+ {
2047
+ // We have an entire HTTP request header from the client
2048
+
2049
+ // Extract the method (such as GET, HEAD, POST, etc)
2050
+ NSString *method = [request method];
2051
+
2052
+ // Extract the uri (such as "/index.html")
2053
+ NSString *uri = [self requestURI];
2054
+
2055
+ // Check for a Transfer-Encoding field
2056
+ NSString *transferEncoding = [request headerField:@"Transfer-Encoding"];
2057
+
2058
+ // Check for a Content-Length field
2059
+ NSString *contentLength = [request headerField:@"Content-Length"];
2060
+
2061
+ // Content-Length MUST be present for upload methods (such as POST or PUT)
2062
+ // and MUST NOT be present for other methods.
2063
+ BOOL expectsUpload = [self expectsRequestBodyFromMethod:method atPath:uri];
2064
+
2065
+ if (expectsUpload)
2066
+ {
2067
+ if (transferEncoding && ![transferEncoding caseInsensitiveCompare:@"Chunked"])
2068
+ {
2069
+ requestContentLength = -1;
2070
+ }
2071
+ else
2072
+ {
2073
+ if (contentLength == nil)
2074
+ {
2075
+ HTTPLogWarn(@"%@[%p]: Method expects request body, but had no specified Content-Length",
2076
+ THIS_FILE, self);
2077
+
2078
+ [self handleInvalidRequest:nil];
2079
+ return;
2080
+ }
2081
+
2082
+ if (![NSNumber parseString:(NSString *)contentLength intoUInt64:&requestContentLength])
2083
+ {
2084
+ HTTPLogWarn(@"%@[%p]: Unable to parse Content-Length header into a valid number",
2085
+ THIS_FILE, self);
2086
+
2087
+ [self handleInvalidRequest:nil];
2088
+ return;
2089
+ }
2090
+ }
2091
+ }
2092
+ else
2093
+ {
2094
+ if (contentLength != nil)
2095
+ {
2096
+ // Received Content-Length header for method not expecting an upload.
2097
+ // This better be zero...
2098
+
2099
+ if (![NSNumber parseString:(NSString *)contentLength intoUInt64:&requestContentLength])
2100
+ {
2101
+ HTTPLogWarn(@"%@[%p]: Unable to parse Content-Length header into a valid number",
2102
+ THIS_FILE, self);
2103
+
2104
+ [self handleInvalidRequest:nil];
2105
+ return;
2106
+ }
2107
+
2108
+ if (requestContentLength > 0)
2109
+ {
2110
+ HTTPLogWarn(@"%@[%p]: Method not expecting request body had non-zero Content-Length",
2111
+ THIS_FILE, self);
2112
+
2113
+ [self handleInvalidRequest:nil];
2114
+ return;
2115
+ }
2116
+ }
2117
+
2118
+ requestContentLength = 0;
2119
+ requestContentLengthReceived = 0;
2120
+ }
2121
+
2122
+ // Check to make sure the given method is supported
2123
+ if (![self supportsMethod:method atPath:uri])
2124
+ {
2125
+ // The method is unsupported - either in general, or for this specific request
2126
+ // Send a 405 - Method not allowed response
2127
+ [self handleUnknownMethod:method];
2128
+ return;
2129
+ }
2130
+
2131
+ if (expectsUpload)
2132
+ {
2133
+ // Reset the total amount of data received for the upload
2134
+ requestContentLengthReceived = 0;
2135
+
2136
+ // Prepare for the upload
2137
+ [self prepareForBodyWithSize:requestContentLength];
2138
+
2139
+ if (requestContentLength > 0)
2140
+ {
2141
+ // Start reading the request body
2142
+ if (requestContentLength == -1)
2143
+ {
2144
+ // Chunked transfer
2145
+
2146
+ [asyncSocket readDataToData:[GCDAsyncSocket CRLFData]
2147
+ withTimeout:TIMEOUT_READ_BODY
2148
+ maxLength:MAX_CHUNK_LINE_LENGTH
2149
+ tag:HTTP_REQUEST_CHUNK_SIZE];
2150
+ }
2151
+ else
2152
+ {
2153
+ NSUInteger bytesToRead;
2154
+ if (requestContentLength < POST_CHUNKSIZE)
2155
+ bytesToRead = (NSUInteger)requestContentLength;
2156
+ else
2157
+ bytesToRead = POST_CHUNKSIZE;
2158
+
2159
+ [asyncSocket readDataToLength:bytesToRead
2160
+ withTimeout:TIMEOUT_READ_BODY
2161
+ tag:HTTP_REQUEST_BODY];
2162
+ }
2163
+ }
2164
+ else
2165
+ {
2166
+ // Empty upload
2167
+ [self finishBody];
2168
+ [self replyToHTTPRequest];
2169
+ }
2170
+ }
2171
+ else
2172
+ {
2173
+ // Now we need to reply to the request
2174
+ [self replyToHTTPRequest];
2175
+ }
2176
+ }
2177
+ }
2178
+ else
2179
+ {
2180
+ BOOL doneReadingRequest = NO;
2181
+
2182
+ // A chunked message body contains a series of chunks,
2183
+ // followed by a line with "0" (zero),
2184
+ // followed by optional footers (just like headers),
2185
+ // and a blank line.
2186
+ //
2187
+ // Each chunk consists of two parts:
2188
+ //
2189
+ // 1. A line with the size of the chunk data, in hex,
2190
+ // possibly followed by a semicolon and extra parameters you can ignore (none are currently standard),
2191
+ // and ending with CRLF.
2192
+ // 2. The data itself, followed by CRLF.
2193
+ //
2194
+ // Part 1 is represented by HTTP_REQUEST_CHUNK_SIZE
2195
+ // Part 2 is represented by HTTP_REQUEST_CHUNK_DATA and HTTP_REQUEST_CHUNK_TRAILER
2196
+ // where the trailer is the CRLF that follows the data.
2197
+ //
2198
+ // The optional footers and blank line are represented by HTTP_REQUEST_CHUNK_FOOTER.
2199
+
2200
+ if (tag == HTTP_REQUEST_CHUNK_SIZE)
2201
+ {
2202
+ // We have just read in a line with the size of the chunk data, in hex,
2203
+ // possibly followed by a semicolon and extra parameters that can be ignored,
2204
+ // and ending with CRLF.
2205
+
2206
+ NSString *sizeLine = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
2207
+
2208
+ errno = 0; // Reset errno before calling strtoull() to ensure it is always zero on success
2209
+ requestChunkSize = (UInt64)strtoull([sizeLine UTF8String], NULL, 16);
2210
+ requestChunkSizeReceived = 0;
2211
+
2212
+ if (errno != 0)
2213
+ {
2214
+ HTTPLogWarn(@"%@[%p]: Method expects chunk size, but received something else", THIS_FILE, self);
2215
+
2216
+ [self handleInvalidRequest:nil];
2217
+ return;
2218
+ }
2219
+
2220
+ if (requestChunkSize > 0)
2221
+ {
2222
+ NSUInteger bytesToRead;
2223
+ bytesToRead = (requestChunkSize < POST_CHUNKSIZE) ? (NSUInteger)requestChunkSize : POST_CHUNKSIZE;
2224
+
2225
+ [asyncSocket readDataToLength:bytesToRead
2226
+ withTimeout:TIMEOUT_READ_BODY
2227
+ tag:HTTP_REQUEST_CHUNK_DATA];
2228
+ }
2229
+ else
2230
+ {
2231
+ // This is the "0" (zero) line,
2232
+ // which is to be followed by optional footers (just like headers) and finally a blank line.
2233
+
2234
+ [asyncSocket readDataToData:[GCDAsyncSocket CRLFData]
2235
+ withTimeout:TIMEOUT_READ_BODY
2236
+ maxLength:MAX_HEADER_LINE_LENGTH
2237
+ tag:HTTP_REQUEST_CHUNK_FOOTER];
2238
+ }
2239
+
2240
+ return;
2241
+ }
2242
+ else if (tag == HTTP_REQUEST_CHUNK_DATA)
2243
+ {
2244
+ // We just read part of the actual data.
2245
+
2246
+ requestContentLengthReceived += [data length];
2247
+ requestChunkSizeReceived += [data length];
2248
+
2249
+ [self processBodyData:data];
2250
+
2251
+ UInt64 bytesLeft = requestChunkSize - requestChunkSizeReceived;
2252
+ if (bytesLeft > 0)
2253
+ {
2254
+ NSUInteger bytesToRead = (bytesLeft < POST_CHUNKSIZE) ? (NSUInteger)bytesLeft : POST_CHUNKSIZE;
2255
+
2256
+ [asyncSocket readDataToLength:bytesToRead
2257
+ withTimeout:TIMEOUT_READ_BODY
2258
+ tag:HTTP_REQUEST_CHUNK_DATA];
2259
+ }
2260
+ else
2261
+ {
2262
+ // We've read in all the data for this chunk.
2263
+ // The data is followed by a CRLF, which we need to read (and basically ignore)
2264
+
2265
+ [asyncSocket readDataToLength:2
2266
+ withTimeout:TIMEOUT_READ_BODY
2267
+ tag:HTTP_REQUEST_CHUNK_TRAILER];
2268
+ }
2269
+
2270
+ return;
2271
+ }
2272
+ else if (tag == HTTP_REQUEST_CHUNK_TRAILER)
2273
+ {
2274
+ // This should be the CRLF following the data.
2275
+ // Just ensure it's a CRLF.
2276
+
2277
+ if (![data isEqualToData:[GCDAsyncSocket CRLFData]])
2278
+ {
2279
+ HTTPLogWarn(@"%@[%p]: Method expects chunk trailer, but is missing", THIS_FILE, self);
2280
+
2281
+ [self handleInvalidRequest:nil];
2282
+ return;
2283
+ }
2284
+
2285
+ // Now continue with the next chunk
2286
+
2287
+ [asyncSocket readDataToData:[GCDAsyncSocket CRLFData]
2288
+ withTimeout:TIMEOUT_READ_BODY
2289
+ maxLength:MAX_CHUNK_LINE_LENGTH
2290
+ tag:HTTP_REQUEST_CHUNK_SIZE];
2291
+
2292
+ }
2293
+ else if (tag == HTTP_REQUEST_CHUNK_FOOTER)
2294
+ {
2295
+ if (++numHeaderLines > MAX_HEADER_LINES)
2296
+ {
2297
+ // Reached the maximum amount of header lines in a single HTTP request
2298
+ // This could be an attempted DOS attack
2299
+ [asyncSocket disconnect];
2300
+
2301
+ // Explictly return to ensure we don't do anything after the socket disconnect
2302
+ return;
2303
+ }
2304
+
2305
+ if ([data length] > 2)
2306
+ {
2307
+ // We read in a footer.
2308
+ // In the future we may want to append these to the request.
2309
+ // For now we ignore, and continue reading the footers, waiting for the final blank line.
2310
+
2311
+ [asyncSocket readDataToData:[GCDAsyncSocket CRLFData]
2312
+ withTimeout:TIMEOUT_READ_BODY
2313
+ maxLength:MAX_HEADER_LINE_LENGTH
2314
+ tag:HTTP_REQUEST_CHUNK_FOOTER];
2315
+ }
2316
+ else
2317
+ {
2318
+ doneReadingRequest = YES;
2319
+ }
2320
+ }
2321
+ else // HTTP_REQUEST_BODY
2322
+ {
2323
+ // Handle a chunk of data from the POST body
2324
+
2325
+ requestContentLengthReceived += [data length];
2326
+ [self processBodyData:data];
2327
+
2328
+ if (requestContentLengthReceived < requestContentLength)
2329
+ {
2330
+ // We're not done reading the post body yet...
2331
+
2332
+ UInt64 bytesLeft = requestContentLength - requestContentLengthReceived;
2333
+
2334
+ NSUInteger bytesToRead = bytesLeft < POST_CHUNKSIZE ? (NSUInteger)bytesLeft : POST_CHUNKSIZE;
2335
+
2336
+ [asyncSocket readDataToLength:bytesToRead
2337
+ withTimeout:TIMEOUT_READ_BODY
2338
+ tag:HTTP_REQUEST_BODY];
2339
+ }
2340
+ else
2341
+ {
2342
+ doneReadingRequest = YES;
2343
+ }
2344
+ }
2345
+
2346
+ // Now that the entire body has been received, we need to reply to the request
2347
+
2348
+ if (doneReadingRequest)
2349
+ {
2350
+ [self finishBody];
2351
+ [self replyToHTTPRequest];
2352
+ }
2353
+ }
2354
+ }
2355
+
2356
+ /**
2357
+ * This method is called after the socket has successfully written data to the stream.
2358
+ **/
2359
+ - (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
2360
+ {
2361
+ BOOL doneSendingResponse = NO;
2362
+
2363
+ if (tag == HTTP_PARTIAL_RESPONSE_BODY)
2364
+ {
2365
+ // Update the amount of data we have in asyncSocket's write queue
2366
+ if ([responseDataSizes count] > 0) {
2367
+ [responseDataSizes removeObjectAtIndex:0];
2368
+ }
2369
+
2370
+ // We only wrote a part of the response - there may be more
2371
+ [self continueSendingStandardResponseBody];
2372
+ }
2373
+ else if (tag == HTTP_CHUNKED_RESPONSE_BODY)
2374
+ {
2375
+ // Update the amount of data we have in asyncSocket's write queue.
2376
+ // This will allow asynchronous responses to continue sending more data.
2377
+ if ([responseDataSizes count] > 0) {
2378
+ [responseDataSizes removeObjectAtIndex:0];
2379
+ }
2380
+ // Don't continue sending the response yet.
2381
+ // The chunked footer that was sent after the body will tell us if we have more data to send.
2382
+ }
2383
+ else if (tag == HTTP_CHUNKED_RESPONSE_FOOTER)
2384
+ {
2385
+ // Normal chunked footer indicating we have more data to send (non final footer).
2386
+ [self continueSendingStandardResponseBody];
2387
+ }
2388
+ else if (tag == HTTP_PARTIAL_RANGE_RESPONSE_BODY)
2389
+ {
2390
+ // Update the amount of data we have in asyncSocket's write queue
2391
+ if ([responseDataSizes count] > 0) {
2392
+ [responseDataSizes removeObjectAtIndex:0];
2393
+ }
2394
+ // We only wrote a part of the range - there may be more
2395
+ [self continueSendingSingleRangeResponseBody];
2396
+ }
2397
+ else if (tag == HTTP_PARTIAL_RANGES_RESPONSE_BODY)
2398
+ {
2399
+ // Update the amount of data we have in asyncSocket's write queue
2400
+ if ([responseDataSizes count] > 0) {
2401
+ [responseDataSizes removeObjectAtIndex:0];
2402
+ }
2403
+ // We only wrote part of the range - there may be more, or there may be more ranges
2404
+ [self continueSendingMultiRangeResponseBody];
2405
+ }
2406
+ else if (tag == HTTP_RESPONSE || tag == HTTP_FINAL_RESPONSE)
2407
+ {
2408
+ // Update the amount of data we have in asyncSocket's write queue
2409
+ if ([responseDataSizes count] > 0)
2410
+ {
2411
+ [responseDataSizes removeObjectAtIndex:0];
2412
+ }
2413
+
2414
+ doneSendingResponse = YES;
2415
+ }
2416
+
2417
+ if (doneSendingResponse)
2418
+ {
2419
+ // Inform the http response that we're done
2420
+ if ([httpResponse respondsToSelector:@selector(connectionDidClose)])
2421
+ {
2422
+ [httpResponse connectionDidClose];
2423
+ }
2424
+
2425
+
2426
+ if (tag == HTTP_FINAL_RESPONSE)
2427
+ {
2428
+ // Cleanup after the last request
2429
+ [self finishResponse];
2430
+
2431
+ // Terminate the connection
2432
+ [asyncSocket disconnect];
2433
+
2434
+ // Explictly return to ensure we don't do anything after the socket disconnects
2435
+ return;
2436
+ }
2437
+ else
2438
+ {
2439
+ if ([self shouldDie])
2440
+ {
2441
+ // Cleanup after the last request
2442
+ // Note: Don't do this before calling shouldDie, as it needs the request object still.
2443
+ [self finishResponse];
2444
+
2445
+ // The only time we should invoke [self die] is from socketDidDisconnect,
2446
+ // or if the socket gets taken over by someone else like a WebSocket.
2447
+
2448
+ [asyncSocket disconnect];
2449
+ }
2450
+ else
2451
+ {
2452
+ // Cleanup after the last request
2453
+ [self finishResponse];
2454
+
2455
+ // Prepare for the next request
2456
+
2457
+ // If this assertion fails, it likely means you overrode the
2458
+ // finishBody method and forgot to call [super finishBody].
2459
+ NSAssert(request == nil, @"Request not properly released in finishBody");
2460
+
2461
+ request = [[HTTPMessage alloc] initEmptyRequest];
2462
+
2463
+ numHeaderLines = 0;
2464
+ sentResponseHeaders = NO;
2465
+
2466
+ // And start listening for more requests
2467
+ [self startReadingRequest];
2468
+ }
2469
+ }
2470
+ }
2471
+ }
2472
+
2473
+ /**
2474
+ * Sent after the socket has been disconnected.
2475
+ **/
2476
+ - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
2477
+ {
2478
+ HTTPLogTrace();
2479
+
2480
+ asyncSocket = nil;
2481
+
2482
+ [self die];
2483
+ }
2484
+
2485
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2486
+ #pragma mark HTTPResponse Notifications
2487
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2488
+
2489
+ /**
2490
+ * This method may be called by asynchronous HTTPResponse objects.
2491
+ * That is, HTTPResponse objects that return YES in their "- (BOOL)isAsynchronous" method.
2492
+ *
2493
+ * This informs us that the response object has generated more data that we may be able to send.
2494
+ **/
2495
+ - (void)responseHasAvailableData:(NSObject<HTTPResponse> *)sender
2496
+ {
2497
+ HTTPLogTrace();
2498
+
2499
+ // We always dispatch this asynchronously onto our connectionQueue,
2500
+ // even if the connectionQueue is the current queue.
2501
+ //
2502
+ // We do this to give the HTTPResponse classes the flexibility to call
2503
+ // this method whenever they want, even from within a readDataOfLength method.
2504
+
2505
+ dispatch_async(connectionQueue, ^{ @autoreleasepool {
2506
+
2507
+ if (sender != httpResponse)
2508
+ {
2509
+ HTTPLogWarn(@"%@[%p]: %@ - Sender is not current httpResponse", THIS_FILE, self, THIS_METHOD);
2510
+ return;
2511
+ }
2512
+
2513
+ if (!sentResponseHeaders)
2514
+ {
2515
+ [self sendResponseHeadersAndBody];
2516
+ }
2517
+ else
2518
+ {
2519
+ if (ranges == nil)
2520
+ {
2521
+ [self continueSendingStandardResponseBody];
2522
+ }
2523
+ else
2524
+ {
2525
+ if ([ranges count] == 1)
2526
+ [self continueSendingSingleRangeResponseBody];
2527
+ else
2528
+ [self continueSendingMultiRangeResponseBody];
2529
+ }
2530
+ }
2531
+ }});
2532
+ }
2533
+
2534
+ /**
2535
+ * This method is called if the response encounters some critical error,
2536
+ * and it will be unable to fullfill the request.
2537
+ **/
2538
+ - (void)responseDidAbort:(NSObject<HTTPResponse> *)sender
2539
+ {
2540
+ HTTPLogTrace();
2541
+
2542
+ // We always dispatch this asynchronously onto our connectionQueue,
2543
+ // even if the connectionQueue is the current queue.
2544
+ //
2545
+ // We do this to give the HTTPResponse classes the flexibility to call
2546
+ // this method whenever they want, even from within a readDataOfLength method.
2547
+
2548
+ dispatch_async(connectionQueue, ^{ @autoreleasepool {
2549
+
2550
+ if (sender != httpResponse)
2551
+ {
2552
+ HTTPLogWarn(@"%@[%p]: %@ - Sender is not current httpResponse", THIS_FILE, self, THIS_METHOD);
2553
+ return;
2554
+ }
2555
+
2556
+ [asyncSocket disconnectAfterWriting];
2557
+ }});
2558
+ }
2559
+
2560
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2561
+ #pragma mark Post Request
2562
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2563
+
2564
+ /**
2565
+ * This method is called after each response has been fully sent.
2566
+ * Since a single connection may handle multiple request/responses, this method may be called multiple times.
2567
+ * That is, it will be called after completion of each response.
2568
+ **/
2569
+ - (void)finishResponse
2570
+ {
2571
+ HTTPLogTrace();
2572
+
2573
+ // Override me if you want to perform any custom actions after a response has been fully sent.
2574
+ // This is the place to release memory or resources associated with the last request.
2575
+ //
2576
+ // If you override this method, you should take care to invoke [super finishResponse] at some point.
2577
+
2578
+ request = nil;
2579
+
2580
+ httpResponse = nil;
2581
+
2582
+ ranges = nil;
2583
+ ranges_headers = nil;
2584
+ ranges_boundry = nil;
2585
+ }
2586
+
2587
+ /**
2588
+ * This method is called after each successful response has been fully sent.
2589
+ * It determines whether the connection should stay open and handle another request.
2590
+ **/
2591
+ - (BOOL)shouldDie
2592
+ {
2593
+ HTTPLogTrace();
2594
+
2595
+ // Override me if you have any need to force close the connection.
2596
+ // You may do so by simply returning YES.
2597
+ //
2598
+ // If you override this method, you should take care to fall through with [super shouldDie]
2599
+ // instead of returning NO.
2600
+
2601
+
2602
+ BOOL shouldDie = NO;
2603
+
2604
+ NSString *version = [request version];
2605
+ if ([version isEqualToString:HTTPVersion1_1])
2606
+ {
2607
+ // HTTP version 1.1
2608
+ // Connection should only be closed if request included "Connection: close" header
2609
+
2610
+ NSString *connection = [request headerField:@"Connection"];
2611
+
2612
+ shouldDie = (connection && ([connection caseInsensitiveCompare:@"close"] == NSOrderedSame));
2613
+ }
2614
+ else if ([version isEqualToString:HTTPVersion1_0])
2615
+ {
2616
+ // HTTP version 1.0
2617
+ // Connection should be closed unless request included "Connection: Keep-Alive" header
2618
+
2619
+ NSString *connection = [request headerField:@"Connection"];
2620
+
2621
+ if (connection == nil)
2622
+ shouldDie = YES;
2623
+ else
2624
+ shouldDie = [connection caseInsensitiveCompare:@"Keep-Alive"] != NSOrderedSame;
2625
+ }
2626
+
2627
+ return shouldDie;
2628
+ }
2629
+
2630
+ - (void)die
2631
+ {
2632
+ HTTPLogTrace();
2633
+
2634
+ // Override me if you want to perform any custom actions when a connection is closed.
2635
+ // Then call [super die] when you're done.
2636
+ //
2637
+ // See also the finishResponse method.
2638
+ //
2639
+ // Important: There is a rare timing condition where this method might get invoked twice.
2640
+ // If you override this method, you should be prepared for this situation.
2641
+
2642
+ // Inform the http response that we're done
2643
+ if ([httpResponse respondsToSelector:@selector(connectionDidClose)])
2644
+ {
2645
+ [httpResponse connectionDidClose];
2646
+ }
2647
+
2648
+ // Release the http response so we don't call it's connectionDidClose method again in our dealloc method
2649
+ httpResponse = nil;
2650
+
2651
+ // Post notification of dead connection
2652
+ // This will allow our server to release us from its array of connections
2653
+ [[NSNotificationCenter defaultCenter] postNotificationName:HTTPConnectionDidDieNotification object:self];
2654
+ }
2655
+
2656
+ @end
2657
+
2658
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2659
+ #pragma mark -
2660
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2661
+
2662
+ @implementation HTTPConfig
2663
+
2664
+ @synthesize server;
2665
+ @synthesize documentRoot;
2666
+ @synthesize queue;
2667
+
2668
+ - (id)initWithServer:(HTTPServer *)aServer documentRoot:(NSString *)aDocumentRoot
2669
+ {
2670
+ if ((self = [super init]))
2671
+ {
2672
+ server = aServer;
2673
+ documentRoot = aDocumentRoot;
2674
+ }
2675
+ return self;
2676
+ }
2677
+
2678
+ - (id)initWithServer:(HTTPServer *)aServer documentRoot:(NSString *)aDocumentRoot queue:(dispatch_queue_t)q
2679
+ {
2680
+ if ((self = [super init]))
2681
+ {
2682
+ server = aServer;
2683
+
2684
+ documentRoot = [aDocumentRoot stringByStandardizingPath];
2685
+ if ([documentRoot hasSuffix:@"/"])
2686
+ {
2687
+ documentRoot = [documentRoot stringByAppendingString:@"/"];
2688
+ }
2689
+
2690
+ if (q)
2691
+ {
2692
+ queue = q;
2693
+ #if !OS_OBJECT_USE_OBJC
2694
+ dispatch_retain(queue);
2695
+ #endif
2696
+ }
2697
+ }
2698
+ return self;
2699
+ }
2700
+
2701
+ - (void)dealloc
2702
+ {
2703
+ #if !OS_OBJECT_USE_OBJC
2704
+ if (queue) dispatch_release(queue);
2705
+ #endif
2706
+ }
2707
+
2708
+ @end