appium-webdriveragent 16.6.0 → 16.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/WebDriverAgent.xcodeproj/project.pbxproj +45 -303
- package/WebDriverAgentLib/Info.plist +2 -2
- package/WebDriverAgentLib/Routing/{WatchOS/FBWatchHTTPServer.h → FBHTTPServer.h} +11 -7
- package/WebDriverAgentLib/Routing/{WatchOS/FBWatchHTTPServer.m → FBHTTPServer.m} +26 -13
- package/WebDriverAgentLib/Routing/FBTCPSocket.h +10 -45
- package/WebDriverAgentLib/Routing/FBTCPSocket.m +12 -94
- package/WebDriverAgentLib/Routing/FBWebServer.h +1 -1
- package/WebDriverAgentLib/Routing/FBWebServer.m +4 -49
- package/WebDriverAgentLib/Routing/{WatchOS/RouteRequest.h → RouteRequest.h} +2 -4
- package/WebDriverAgentLib/Routing/{WatchOS/RouteResponse.h → RouteResponse.h} +2 -2
- package/WebDriverAgentLib/Utilities/FBMjpegServer.h +3 -5
- package/WebDriverAgentLib/Utilities/FBMjpegServer.m +1 -66
- package/package.json +1 -1
- package/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.h +0 -1220
- package/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.m +0 -8786
- package/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.h +0 -1036
- package/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.m +0 -5868
- package/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDNumber.h +0 -12
- package/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDNumber.m +0 -88
- package/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDRange.h +0 -56
- package/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDRange.m +0 -100
- package/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPConnection.h +0 -109
- package/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPConnection.m +0 -2281
- package/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPLogging.h +0 -122
- package/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPMessage.h +0 -53
- package/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPMessage.m +0 -357
- package/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPResponse.h +0 -149
- package/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPServer.h +0 -126
- package/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPServer.m +0 -372
- package/WebDriverAgentLib/Vendor/CocoaHTTPServer/LICENSE +0 -18
- package/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPDataResponse.h +0 -13
- package/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPDataResponse.m +0 -83
- package/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPErrorResponse.h +0 -9
- package/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPErrorResponse.m +0 -38
- package/WebDriverAgentLib/Vendor/RoutingHTTPServer/HTTPResponseProxy.h +0 -13
- package/WebDriverAgentLib/Vendor/RoutingHTTPServer/HTTPResponseProxy.m +0 -84
- package/WebDriverAgentLib/Vendor/RoutingHTTPServer/LICENSE +0 -19
- package/WebDriverAgentLib/Vendor/RoutingHTTPServer/Route.h +0 -18
- package/WebDriverAgentLib/Vendor/RoutingHTTPServer/Route.m +0 -11
- package/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteRequest.h +0 -16
- package/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteRequest.m +0 -50
- package/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteResponse.h +0 -20
- package/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteResponse.m +0 -66
- package/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingConnection.h +0 -5
- package/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingConnection.m +0 -142
- package/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingHTTPServer.h +0 -55
- package/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingHTTPServer.m +0 -303
- /package/WebDriverAgentLib/Routing/{WatchOS/RouteRequest.m → RouteRequest.m} +0 -0
- /package/WebDriverAgentLib/Routing/{WatchOS/RouteResponse.m → RouteResponse.m} +0 -0
|
@@ -1,2281 +0,0 @@
|
|
|
1
|
-
#import "HTTPServer.h"
|
|
2
|
-
#import "HTTPConnection.h"
|
|
3
|
-
#import "HTTPMessage.h"
|
|
4
|
-
#import "HTTPResponse.h"
|
|
5
|
-
#import "DDNumber.h"
|
|
6
|
-
#import "DDRange.h"
|
|
7
|
-
#import "HTTPLogging.h"
|
|
8
|
-
|
|
9
|
-
#import "GCDAsyncSocket.h"
|
|
10
|
-
|
|
11
|
-
#if ! __has_feature(objc_arc)
|
|
12
|
-
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
|
13
|
-
#endif
|
|
14
|
-
|
|
15
|
-
#pragma clang diagnostic ignored "-Wunknown-warning-option"
|
|
16
|
-
#pragma clang diagnostic ignored "-Wdirect-ivar-access"
|
|
17
|
-
#pragma clang diagnostic ignored "-Wimplicit-retain-self"
|
|
18
|
-
#pragma clang diagnostic ignored "-Wformat-non-iso"
|
|
19
|
-
#pragma clang diagnostic ignored "-Wunused-variable"
|
|
20
|
-
#pragma clang diagnostic ignored "-Wsign-compare"
|
|
21
|
-
#pragma clang diagnostic ignored "-Wformat-nonliteral"
|
|
22
|
-
#pragma clang diagnostic ignored "-Wunreachable-code"
|
|
23
|
-
#pragma clang diagnostic ignored "-Wfloat-conversion"
|
|
24
|
-
|
|
25
|
-
// Log levels: off, error, warn, info, verbose
|
|
26
|
-
// Other flags: trace
|
|
27
|
-
static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE;
|
|
28
|
-
|
|
29
|
-
// Define chunk size used to read in data for responses
|
|
30
|
-
// This is how much data will be read from disk into RAM at a time
|
|
31
|
-
#if TARGET_OS_IPHONE
|
|
32
|
-
#define READ_CHUNKSIZE (1024 * 256)
|
|
33
|
-
#else
|
|
34
|
-
#define READ_CHUNKSIZE (1024 * 512)
|
|
35
|
-
#endif
|
|
36
|
-
|
|
37
|
-
// Define chunk size used to read in POST upload data
|
|
38
|
-
#if TARGET_OS_IPHONE
|
|
39
|
-
#define POST_CHUNKSIZE (1024 * 256)
|
|
40
|
-
#else
|
|
41
|
-
#define POST_CHUNKSIZE (1024 * 512)
|
|
42
|
-
#endif
|
|
43
|
-
|
|
44
|
-
// Define the various timeouts (in seconds) for various parts of the HTTP process
|
|
45
|
-
#define TIMEOUT_READ_FIRST_HEADER_LINE 30
|
|
46
|
-
#define TIMEOUT_READ_SUBSEQUENT_HEADER_LINE 30
|
|
47
|
-
#define TIMEOUT_READ_BODY -1
|
|
48
|
-
#define TIMEOUT_WRITE_HEAD 30
|
|
49
|
-
#define TIMEOUT_WRITE_BODY -1
|
|
50
|
-
#define TIMEOUT_WRITE_ERROR 30
|
|
51
|
-
#define TIMEOUT_NONCE 300
|
|
52
|
-
|
|
53
|
-
// Define the various limits
|
|
54
|
-
// MAX_HEADER_LINE_LENGTH: Max length (in bytes) of any single line in a header (including \r\n)
|
|
55
|
-
// MAX_HEADER_LINES : Max number of lines in a single header (including first GET line)
|
|
56
|
-
#define MAX_HEADER_LINE_LENGTH 8190
|
|
57
|
-
#define MAX_HEADER_LINES 100
|
|
58
|
-
// MAX_CHUNK_LINE_LENGTH : For accepting chunked transfer uploads, max length of chunk size line (including \r\n)
|
|
59
|
-
#define MAX_CHUNK_LINE_LENGTH 200
|
|
60
|
-
|
|
61
|
-
// Define the various tags we'll use to differentiate what it is we're currently doing
|
|
62
|
-
#define HTTP_REQUEST_HEADER 10
|
|
63
|
-
#define HTTP_REQUEST_BODY 11
|
|
64
|
-
#define HTTP_REQUEST_CHUNK_SIZE 12
|
|
65
|
-
#define HTTP_REQUEST_CHUNK_DATA 13
|
|
66
|
-
#define HTTP_REQUEST_CHUNK_TRAILER 14
|
|
67
|
-
#define HTTP_REQUEST_CHUNK_FOOTER 15
|
|
68
|
-
#define HTTP_PARTIAL_RESPONSE 20
|
|
69
|
-
#define HTTP_PARTIAL_RESPONSE_HEADER 21
|
|
70
|
-
#define HTTP_PARTIAL_RESPONSE_BODY 22
|
|
71
|
-
#define HTTP_CHUNKED_RESPONSE_HEADER 30
|
|
72
|
-
#define HTTP_CHUNKED_RESPONSE_BODY 31
|
|
73
|
-
#define HTTP_CHUNKED_RESPONSE_FOOTER 32
|
|
74
|
-
#define HTTP_PARTIAL_RANGE_RESPONSE_BODY 40
|
|
75
|
-
#define HTTP_PARTIAL_RANGES_RESPONSE_BODY 50
|
|
76
|
-
#define HTTP_RESPONSE 90
|
|
77
|
-
#define HTTP_FINAL_RESPONSE 91
|
|
78
|
-
|
|
79
|
-
// A quick note about the tags:
|
|
80
|
-
//
|
|
81
|
-
// The HTTP_RESPONSE and HTTP_FINAL_RESPONSE are designated tags signalling that the response is completely sent.
|
|
82
|
-
// That is, in the onSocket:didWriteDataWithTag: method, if the tag is HTTP_RESPONSE or HTTP_FINAL_RESPONSE,
|
|
83
|
-
// it is assumed that the response is now completely sent.
|
|
84
|
-
// Use HTTP_RESPONSE if it's the end of a response, and you want to start reading more requests afterwards.
|
|
85
|
-
// Use HTTP_FINAL_RESPONSE if you wish to terminate the connection after sending the response.
|
|
86
|
-
//
|
|
87
|
-
// If you are sending multiple data segments in a custom response, make sure that only the last segment has
|
|
88
|
-
// the HTTP_RESPONSE tag. For all other segments prior to the last segment use HTTP_PARTIAL_RESPONSE, or some other
|
|
89
|
-
// tag of your own invention.
|
|
90
|
-
|
|
91
|
-
@interface HTTPConnection (PrivateAPI)
|
|
92
|
-
- (void)startReadingRequest;
|
|
93
|
-
- (void)sendResponseHeadersAndBody;
|
|
94
|
-
@end
|
|
95
|
-
|
|
96
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
97
|
-
#pragma mark -
|
|
98
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
99
|
-
|
|
100
|
-
@implementation HTTPConnection
|
|
101
|
-
|
|
102
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
103
|
-
#pragma mark Init, Dealloc:
|
|
104
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Sole Constructor.
|
|
108
|
-
* Associates this new HTTP connection with the given AsyncSocket.
|
|
109
|
-
* This HTTP connection object will become the socket's delegate and take over responsibility for the socket.
|
|
110
|
-
**/
|
|
111
|
-
- (id)initWithAsyncSocket:(GCDAsyncSocket *)newSocket configuration:(HTTPConfig *)aConfig
|
|
112
|
-
{
|
|
113
|
-
if ((self = [super init]))
|
|
114
|
-
{
|
|
115
|
-
HTTPLogTrace();
|
|
116
|
-
|
|
117
|
-
if (aConfig.queue)
|
|
118
|
-
{
|
|
119
|
-
connectionQueue = aConfig.queue;
|
|
120
|
-
#if !OS_OBJECT_USE_OBJC
|
|
121
|
-
dispatch_retain(connectionQueue);
|
|
122
|
-
#endif
|
|
123
|
-
}
|
|
124
|
-
else
|
|
125
|
-
{
|
|
126
|
-
connectionQueue = dispatch_queue_create("HTTPConnection", NULL);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Take over ownership of the socket
|
|
130
|
-
asyncSocket = newSocket;
|
|
131
|
-
[asyncSocket setDelegate:(id<GCDAsyncSocketDelegate>)self delegateQueue:connectionQueue];
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
// Store configuration
|
|
135
|
-
config = aConfig;
|
|
136
|
-
|
|
137
|
-
// Create a new HTTP message
|
|
138
|
-
request = [[HTTPMessage alloc] initEmptyRequest];
|
|
139
|
-
|
|
140
|
-
numHeaderLines = 0;
|
|
141
|
-
|
|
142
|
-
responseDataSizes = [[NSMutableArray alloc] initWithCapacity:5];
|
|
143
|
-
}
|
|
144
|
-
return self;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Standard Deconstructor.
|
|
149
|
-
**/
|
|
150
|
-
- (void)dealloc
|
|
151
|
-
{
|
|
152
|
-
HTTPLogTrace();
|
|
153
|
-
|
|
154
|
-
#if !OS_OBJECT_USE_OBJC
|
|
155
|
-
dispatch_release(connectionQueue);
|
|
156
|
-
#endif
|
|
157
|
-
|
|
158
|
-
[asyncSocket setDelegate:nil delegateQueue:NULL];
|
|
159
|
-
[asyncSocket disconnect];
|
|
160
|
-
|
|
161
|
-
if ([httpResponse respondsToSelector:@selector(connectionDidClose)])
|
|
162
|
-
{
|
|
163
|
-
[httpResponse connectionDidClose];
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
168
|
-
#pragma mark Method Support
|
|
169
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Returns whether or not the server will accept messages of a given method
|
|
173
|
-
* at a particular URI.
|
|
174
|
-
**/
|
|
175
|
-
- (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path
|
|
176
|
-
{
|
|
177
|
-
HTTPLogTrace();
|
|
178
|
-
|
|
179
|
-
// Override me to support methods such as POST.
|
|
180
|
-
//
|
|
181
|
-
// Things you may want to consider:
|
|
182
|
-
// - Does the given path represent a resource that is designed to accept this method?
|
|
183
|
-
// - If accepting an upload, is the size of the data being uploaded too big?
|
|
184
|
-
// To do this you can check the requestContentLength variable.
|
|
185
|
-
//
|
|
186
|
-
// For more information, you can always access the HTTPMessage request variable.
|
|
187
|
-
//
|
|
188
|
-
// You should fall through with a call to [super supportsMethod:method atPath:path]
|
|
189
|
-
//
|
|
190
|
-
// See also: expectsRequestBodyFromMethod:atPath:
|
|
191
|
-
|
|
192
|
-
if ([method isEqualToString:@"GET"])
|
|
193
|
-
return YES;
|
|
194
|
-
|
|
195
|
-
if ([method isEqualToString:@"HEAD"])
|
|
196
|
-
return YES;
|
|
197
|
-
|
|
198
|
-
return NO;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Returns whether or not the server expects a body from the given method.
|
|
203
|
-
*
|
|
204
|
-
* In other words, should the server expect a content-length header and associated body from this method.
|
|
205
|
-
* This would be true in the case of a POST, where the client is sending data,
|
|
206
|
-
* or for something like PUT where the client is supposed to be uploading a file.
|
|
207
|
-
**/
|
|
208
|
-
- (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path
|
|
209
|
-
{
|
|
210
|
-
HTTPLogTrace();
|
|
211
|
-
|
|
212
|
-
// Override me to add support for other methods that expect the client
|
|
213
|
-
// to send a body along with the request header.
|
|
214
|
-
//
|
|
215
|
-
// You should fall through with a call to [super expectsRequestBodyFromMethod:method atPath:path]
|
|
216
|
-
//
|
|
217
|
-
// See also: supportsMethod:atPath:
|
|
218
|
-
|
|
219
|
-
if ([method isEqualToString:@"POST"])
|
|
220
|
-
return YES;
|
|
221
|
-
|
|
222
|
-
if ([method isEqualToString:@"PUT"])
|
|
223
|
-
return YES;
|
|
224
|
-
|
|
225
|
-
return NO;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
229
|
-
#pragma mark Core
|
|
230
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Starting point for the HTTP connection after it has been fully initialized (including subclasses).
|
|
234
|
-
* This method is called by the HTTP server.
|
|
235
|
-
**/
|
|
236
|
-
- (void)start
|
|
237
|
-
{
|
|
238
|
-
dispatch_async(connectionQueue, ^{ @autoreleasepool {
|
|
239
|
-
|
|
240
|
-
if (!started)
|
|
241
|
-
{
|
|
242
|
-
started = YES;
|
|
243
|
-
[self startConnection];
|
|
244
|
-
}
|
|
245
|
-
}});
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* This method is called by the HTTPServer if it is asked to stop.
|
|
250
|
-
* The server, in turn, invokes stop on each HTTPConnection instance.
|
|
251
|
-
**/
|
|
252
|
-
- (void)stop
|
|
253
|
-
{
|
|
254
|
-
dispatch_async(connectionQueue, ^{ @autoreleasepool {
|
|
255
|
-
|
|
256
|
-
// Disconnect the socket.
|
|
257
|
-
// The socketDidDisconnect delegate method will handle everything else.
|
|
258
|
-
[asyncSocket disconnect];
|
|
259
|
-
}});
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Starting point for the HTTP connection.
|
|
264
|
-
**/
|
|
265
|
-
- (void)startConnection
|
|
266
|
-
{
|
|
267
|
-
// Override me to do any custom work before the connection starts.
|
|
268
|
-
//
|
|
269
|
-
// Be sure to invoke [super startConnection] when you're done.
|
|
270
|
-
|
|
271
|
-
HTTPLogTrace();
|
|
272
|
-
|
|
273
|
-
[self startReadingRequest];
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Starts reading an HTTP request.
|
|
278
|
-
**/
|
|
279
|
-
- (void)startReadingRequest
|
|
280
|
-
{
|
|
281
|
-
HTTPLogTrace();
|
|
282
|
-
|
|
283
|
-
[asyncSocket readDataToData:[GCDAsyncSocket CRLFData]
|
|
284
|
-
withTimeout:TIMEOUT_READ_FIRST_HEADER_LINE
|
|
285
|
-
maxLength:MAX_HEADER_LINE_LENGTH
|
|
286
|
-
tag:HTTP_REQUEST_HEADER];
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Parses the given query string.
|
|
291
|
-
*
|
|
292
|
-
* For example, if the query is "q=John%20Mayer%20Trio&num=50"
|
|
293
|
-
* then this method would return the following dictionary:
|
|
294
|
-
* {
|
|
295
|
-
* q = "John Mayer Trio"
|
|
296
|
-
* num = "50"
|
|
297
|
-
* }
|
|
298
|
-
**/
|
|
299
|
-
- (NSDictionary *)parseParams:(NSString *)query
|
|
300
|
-
{
|
|
301
|
-
NSArray *components = [query componentsSeparatedByString:@"&"];
|
|
302
|
-
NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:[components count]];
|
|
303
|
-
|
|
304
|
-
NSUInteger i;
|
|
305
|
-
for (i = 0; i < [components count]; i++)
|
|
306
|
-
{
|
|
307
|
-
NSString *component = [components objectAtIndex:i];
|
|
308
|
-
if ([component length] > 0)
|
|
309
|
-
{
|
|
310
|
-
NSRange range = [component rangeOfString:@"="];
|
|
311
|
-
if (range.location != NSNotFound)
|
|
312
|
-
{
|
|
313
|
-
NSString *escapedKey = [component substringToIndex:(range.location + 0)];
|
|
314
|
-
NSString *escapedValue = [component substringFromIndex:(range.location + 1)];
|
|
315
|
-
|
|
316
|
-
if ([escapedKey length] > 0)
|
|
317
|
-
{
|
|
318
|
-
CFStringRef k, v;
|
|
319
|
-
|
|
320
|
-
k = CFURLCreateStringByReplacingPercentEscapes(NULL, (__bridge CFStringRef)escapedKey, CFSTR(""));
|
|
321
|
-
v = CFURLCreateStringByReplacingPercentEscapes(NULL, (__bridge CFStringRef)escapedValue, CFSTR(""));
|
|
322
|
-
|
|
323
|
-
NSString *key, *value;
|
|
324
|
-
|
|
325
|
-
key = (__bridge_transfer NSString *)k;
|
|
326
|
-
value = (__bridge_transfer NSString *)v;
|
|
327
|
-
|
|
328
|
-
if (key)
|
|
329
|
-
{
|
|
330
|
-
if (value)
|
|
331
|
-
[result setObject:value forKey:key];
|
|
332
|
-
else
|
|
333
|
-
[result setObject:[NSNull null] forKey:key];
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
return result;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* Parses the query variables in the request URI.
|
|
345
|
-
*
|
|
346
|
-
* For example, if the request URI was "/search.html?q=John%20Mayer%20Trio&num=50"
|
|
347
|
-
* then this method would return the following dictionary:
|
|
348
|
-
* {
|
|
349
|
-
* q = "John Mayer Trio"
|
|
350
|
-
* num = "50"
|
|
351
|
-
* }
|
|
352
|
-
**/
|
|
353
|
-
- (NSDictionary *)parseGetParams
|
|
354
|
-
{
|
|
355
|
-
if(![request isHeaderComplete]) return nil;
|
|
356
|
-
|
|
357
|
-
NSDictionary *result = nil;
|
|
358
|
-
|
|
359
|
-
NSURL *url = [request url];
|
|
360
|
-
if(url)
|
|
361
|
-
{
|
|
362
|
-
NSString *query = [url query];
|
|
363
|
-
if (query)
|
|
364
|
-
{
|
|
365
|
-
result = [self parseParams:query];
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
return result;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Attempts to parse the given range header into a series of sequential non-overlapping ranges.
|
|
374
|
-
* If successfull, the variables 'ranges' and 'rangeIndex' will be updated, and YES will be returned.
|
|
375
|
-
* Otherwise, NO is returned, and the range request should be ignored.
|
|
376
|
-
**/
|
|
377
|
-
- (BOOL)parseRangeRequest:(NSString *)rangeHeader withContentLength:(UInt64)contentLength
|
|
378
|
-
{
|
|
379
|
-
HTTPLogTrace();
|
|
380
|
-
|
|
381
|
-
// Examples of byte-ranges-specifier values (assuming an entity-body of length 10000):
|
|
382
|
-
//
|
|
383
|
-
// - The first 500 bytes (byte offsets 0-499, inclusive): bytes=0-499
|
|
384
|
-
//
|
|
385
|
-
// - The second 500 bytes (byte offsets 500-999, inclusive): bytes=500-999
|
|
386
|
-
//
|
|
387
|
-
// - The final 500 bytes (byte offsets 9500-9999, inclusive): bytes=-500
|
|
388
|
-
//
|
|
389
|
-
// - Or bytes=9500-
|
|
390
|
-
//
|
|
391
|
-
// - The first and last bytes only (bytes 0 and 9999): bytes=0-0,-1
|
|
392
|
-
//
|
|
393
|
-
// - Several legal but not canonical specifications of the second 500 bytes (byte offsets 500-999, inclusive):
|
|
394
|
-
// bytes=500-600,601-999
|
|
395
|
-
// bytes=500-700,601-999
|
|
396
|
-
//
|
|
397
|
-
|
|
398
|
-
NSRange eqsignRange = [rangeHeader rangeOfString:@"="];
|
|
399
|
-
|
|
400
|
-
if(eqsignRange.location == NSNotFound) return NO;
|
|
401
|
-
|
|
402
|
-
NSUInteger tIndex = eqsignRange.location;
|
|
403
|
-
NSUInteger fIndex = eqsignRange.location + eqsignRange.length;
|
|
404
|
-
|
|
405
|
-
NSMutableString *rangeType = [[rangeHeader substringToIndex:tIndex] mutableCopy];
|
|
406
|
-
NSMutableString *rangeValue = [[rangeHeader substringFromIndex:fIndex] mutableCopy];
|
|
407
|
-
|
|
408
|
-
CFStringTrimWhitespace((__bridge CFMutableStringRef)rangeType);
|
|
409
|
-
CFStringTrimWhitespace((__bridge CFMutableStringRef)rangeValue);
|
|
410
|
-
|
|
411
|
-
if([rangeType caseInsensitiveCompare:@"bytes"] != NSOrderedSame) return NO;
|
|
412
|
-
|
|
413
|
-
NSArray *rangeComponents = [rangeValue componentsSeparatedByString:@","];
|
|
414
|
-
|
|
415
|
-
if([rangeComponents count] == 0) return NO;
|
|
416
|
-
|
|
417
|
-
ranges = [[NSMutableArray alloc] initWithCapacity:[rangeComponents count]];
|
|
418
|
-
|
|
419
|
-
rangeIndex = 0;
|
|
420
|
-
|
|
421
|
-
// Note: We store all range values in the form of DDRange structs, wrapped in NSValue objects.
|
|
422
|
-
// Since DDRange consists of UInt64 values, the range extends up to 16 exabytes.
|
|
423
|
-
|
|
424
|
-
NSUInteger i;
|
|
425
|
-
for (i = 0; i < [rangeComponents count]; i++)
|
|
426
|
-
{
|
|
427
|
-
NSString *rangeComponent = [rangeComponents objectAtIndex:i];
|
|
428
|
-
|
|
429
|
-
NSRange dashRange = [rangeComponent rangeOfString:@"-"];
|
|
430
|
-
|
|
431
|
-
if (dashRange.location == NSNotFound)
|
|
432
|
-
{
|
|
433
|
-
// We're dealing with an individual byte number
|
|
434
|
-
|
|
435
|
-
UInt64 byteIndex;
|
|
436
|
-
if(![NSNumber parseString:rangeComponent intoUInt64:&byteIndex]) return NO;
|
|
437
|
-
|
|
438
|
-
if(byteIndex >= contentLength) return NO;
|
|
439
|
-
|
|
440
|
-
[ranges addObject:[NSValue valueWithDDRange:DDMakeRange(byteIndex, 1)]];
|
|
441
|
-
}
|
|
442
|
-
else
|
|
443
|
-
{
|
|
444
|
-
// We're dealing with a range of bytes
|
|
445
|
-
|
|
446
|
-
tIndex = dashRange.location;
|
|
447
|
-
fIndex = dashRange.location + dashRange.length;
|
|
448
|
-
|
|
449
|
-
NSString *r1str = [rangeComponent substringToIndex:tIndex];
|
|
450
|
-
NSString *r2str = [rangeComponent substringFromIndex:fIndex];
|
|
451
|
-
|
|
452
|
-
UInt64 r1, r2;
|
|
453
|
-
|
|
454
|
-
BOOL hasR1 = [NSNumber parseString:r1str intoUInt64:&r1];
|
|
455
|
-
BOOL hasR2 = [NSNumber parseString:r2str intoUInt64:&r2];
|
|
456
|
-
|
|
457
|
-
if (!hasR1)
|
|
458
|
-
{
|
|
459
|
-
// We're dealing with a "-[#]" range
|
|
460
|
-
//
|
|
461
|
-
// r2 is the number of ending bytes to include in the range
|
|
462
|
-
|
|
463
|
-
if(!hasR2) return NO;
|
|
464
|
-
if(r2 > contentLength) return NO;
|
|
465
|
-
|
|
466
|
-
UInt64 startIndex = contentLength - r2;
|
|
467
|
-
|
|
468
|
-
[ranges addObject:[NSValue valueWithDDRange:DDMakeRange(startIndex, r2)]];
|
|
469
|
-
}
|
|
470
|
-
else if (!hasR2)
|
|
471
|
-
{
|
|
472
|
-
// We're dealing with a "[#]-" range
|
|
473
|
-
//
|
|
474
|
-
// r1 is the starting index of the range, which goes all the way to the end
|
|
475
|
-
|
|
476
|
-
if(r1 >= contentLength) return NO;
|
|
477
|
-
|
|
478
|
-
[ranges addObject:[NSValue valueWithDDRange:DDMakeRange(r1, contentLength - r1)]];
|
|
479
|
-
}
|
|
480
|
-
else
|
|
481
|
-
{
|
|
482
|
-
// We're dealing with a normal "[#]-[#]" range
|
|
483
|
-
//
|
|
484
|
-
// Note: The range is inclusive. So 0-1 has a length of 2 bytes.
|
|
485
|
-
|
|
486
|
-
if(r1 > r2) return NO;
|
|
487
|
-
if(r2 >= contentLength) return NO;
|
|
488
|
-
|
|
489
|
-
[ranges addObject:[NSValue valueWithDDRange:DDMakeRange(r1, r2 - r1 + 1)]];
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
if([ranges count] == 0) return NO;
|
|
495
|
-
|
|
496
|
-
// Now make sure none of the ranges overlap
|
|
497
|
-
|
|
498
|
-
for (i = 0; i < [ranges count] - 1; i++)
|
|
499
|
-
{
|
|
500
|
-
DDRange range1 = [[ranges objectAtIndex:i] ddrangeValue];
|
|
501
|
-
|
|
502
|
-
NSUInteger j;
|
|
503
|
-
for (j = i+1; j < [ranges count]; j++)
|
|
504
|
-
{
|
|
505
|
-
DDRange range2 = [[ranges objectAtIndex:j] ddrangeValue];
|
|
506
|
-
|
|
507
|
-
DDRange iRange = DDIntersectionRange(range1, range2);
|
|
508
|
-
|
|
509
|
-
if(iRange.length != 0)
|
|
510
|
-
{
|
|
511
|
-
return NO;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Sort the ranges
|
|
517
|
-
|
|
518
|
-
[ranges sortUsingSelector:@selector(ddrangeCompare:)];
|
|
519
|
-
|
|
520
|
-
return YES;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
- (NSString *)requestURI
|
|
524
|
-
{
|
|
525
|
-
if(request == nil) return nil;
|
|
526
|
-
|
|
527
|
-
return [[request url] relativeString];
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* This method is called after a full HTTP request has been received.
|
|
532
|
-
* The current request is in the HTTPMessage request variable.
|
|
533
|
-
**/
|
|
534
|
-
- (void)replyToHTTPRequest
|
|
535
|
-
{
|
|
536
|
-
HTTPLogTrace();
|
|
537
|
-
|
|
538
|
-
if (HTTP_LOG_VERBOSE)
|
|
539
|
-
{
|
|
540
|
-
NSData *tempData = [request messageData];
|
|
541
|
-
|
|
542
|
-
NSString *tempStr = [[NSString alloc] initWithData:tempData encoding:NSUTF8StringEncoding];
|
|
543
|
-
HTTPLogVerbose(@"%@[%p]: Received HTTP request:\n%@", THIS_FILE, self, tempStr);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// Check the HTTP version
|
|
547
|
-
// We only support version 1.0 and 1.1
|
|
548
|
-
|
|
549
|
-
NSString *version = [request version];
|
|
550
|
-
if (![version isEqualToString:HTTPVersion1_1] && ![version isEqualToString:HTTPVersion1_0])
|
|
551
|
-
{
|
|
552
|
-
[self handleVersionNotSupported:version];
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// Extract requested URI
|
|
557
|
-
NSString *uri = [self requestURI];
|
|
558
|
-
|
|
559
|
-
// Extract the method
|
|
560
|
-
NSString *method = [request method];
|
|
561
|
-
|
|
562
|
-
// Note: We already checked to ensure the method was supported in onSocket:didReadData:withTag:
|
|
563
|
-
|
|
564
|
-
// Respond properly to HTTP 'GET' and 'HEAD' commands
|
|
565
|
-
httpResponse = [self httpResponseForMethod:method URI:uri];
|
|
566
|
-
|
|
567
|
-
if (httpResponse == nil)
|
|
568
|
-
{
|
|
569
|
-
[self handleResourceNotFound];
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
[self sendResponseHeadersAndBody];
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
/**
|
|
577
|
-
* Prepares a single-range response.
|
|
578
|
-
*
|
|
579
|
-
* Note: The returned HTTPMessage is owned by the sender, who is responsible for releasing it.
|
|
580
|
-
**/
|
|
581
|
-
- (HTTPMessage *)newUniRangeResponse:(UInt64)contentLength
|
|
582
|
-
{
|
|
583
|
-
HTTPLogTrace();
|
|
584
|
-
|
|
585
|
-
// Status Code 206 - Partial Content
|
|
586
|
-
HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:206 description:nil version:HTTPVersion1_1];
|
|
587
|
-
|
|
588
|
-
DDRange range = [[ranges objectAtIndex:0] ddrangeValue];
|
|
589
|
-
|
|
590
|
-
NSString *contentLengthStr = [NSString stringWithFormat:@"%qu", range.length];
|
|
591
|
-
[response setHeaderField:@"Content-Length" value:contentLengthStr];
|
|
592
|
-
|
|
593
|
-
NSString *rangeStr = [NSString stringWithFormat:@"%qu-%qu", range.location, DDMaxRange(range) - 1];
|
|
594
|
-
NSString *contentRangeStr = [NSString stringWithFormat:@"bytes %@/%qu", rangeStr, contentLength];
|
|
595
|
-
[response setHeaderField:@"Content-Range" value:contentRangeStr];
|
|
596
|
-
|
|
597
|
-
return response;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
/**
|
|
601
|
-
* Prepares a multi-range response.
|
|
602
|
-
*
|
|
603
|
-
* Note: The returned HTTPMessage is owned by the sender, who is responsible for releasing it.
|
|
604
|
-
**/
|
|
605
|
-
- (HTTPMessage *)newMultiRangeResponse:(UInt64)contentLength
|
|
606
|
-
{
|
|
607
|
-
HTTPLogTrace();
|
|
608
|
-
|
|
609
|
-
// Status Code 206 - Partial Content
|
|
610
|
-
HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:206 description:nil version:HTTPVersion1_1];
|
|
611
|
-
|
|
612
|
-
// We have to send each range using multipart/byteranges
|
|
613
|
-
// So each byterange has to be prefix'd and suffix'd with the boundry
|
|
614
|
-
// Example:
|
|
615
|
-
//
|
|
616
|
-
// HTTP/1.1 206 Partial Content
|
|
617
|
-
// Content-Length: 220
|
|
618
|
-
// Content-Type: multipart/byteranges; boundary=4554d24e986f76dd6
|
|
619
|
-
//
|
|
620
|
-
//
|
|
621
|
-
// --4554d24e986f76dd6
|
|
622
|
-
// Content-Range: bytes 0-25/4025
|
|
623
|
-
//
|
|
624
|
-
// [...]
|
|
625
|
-
// --4554d24e986f76dd6
|
|
626
|
-
// Content-Range: bytes 3975-4024/4025
|
|
627
|
-
//
|
|
628
|
-
// [...]
|
|
629
|
-
// --4554d24e986f76dd6--
|
|
630
|
-
|
|
631
|
-
ranges_headers = [[NSMutableArray alloc] initWithCapacity:[ranges count]];
|
|
632
|
-
|
|
633
|
-
CFUUIDRef theUUID = CFUUIDCreate(NULL);
|
|
634
|
-
ranges_boundry = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, theUUID);
|
|
635
|
-
CFRelease(theUUID);
|
|
636
|
-
|
|
637
|
-
NSString *startingBoundryStr = [NSString stringWithFormat:@"\r\n--%@\r\n", ranges_boundry];
|
|
638
|
-
NSString *endingBoundryStr = [NSString stringWithFormat:@"\r\n--%@--\r\n", ranges_boundry];
|
|
639
|
-
|
|
640
|
-
UInt64 actualContentLength = 0;
|
|
641
|
-
|
|
642
|
-
NSUInteger i;
|
|
643
|
-
for (i = 0; i < [ranges count]; i++)
|
|
644
|
-
{
|
|
645
|
-
DDRange range = [[ranges objectAtIndex:i] ddrangeValue];
|
|
646
|
-
|
|
647
|
-
NSString *rangeStr = [NSString stringWithFormat:@"%qu-%qu", range.location, DDMaxRange(range) - 1];
|
|
648
|
-
NSString *contentRangeVal = [NSString stringWithFormat:@"bytes %@/%qu", rangeStr, contentLength];
|
|
649
|
-
NSString *contentRangeStr = [NSString stringWithFormat:@"Content-Range: %@\r\n\r\n", contentRangeVal];
|
|
650
|
-
|
|
651
|
-
NSString *fullHeader = [startingBoundryStr stringByAppendingString:contentRangeStr];
|
|
652
|
-
NSData *fullHeaderData = [fullHeader dataUsingEncoding:NSUTF8StringEncoding];
|
|
653
|
-
|
|
654
|
-
[ranges_headers addObject:fullHeaderData];
|
|
655
|
-
|
|
656
|
-
actualContentLength += [fullHeaderData length];
|
|
657
|
-
actualContentLength += range.length;
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
NSData *endingBoundryData = [endingBoundryStr dataUsingEncoding:NSUTF8StringEncoding];
|
|
661
|
-
|
|
662
|
-
actualContentLength += [endingBoundryData length];
|
|
663
|
-
|
|
664
|
-
NSString *contentLengthStr = [NSString stringWithFormat:@"%qu", actualContentLength];
|
|
665
|
-
[response setHeaderField:@"Content-Length" value:contentLengthStr];
|
|
666
|
-
|
|
667
|
-
NSString *contentTypeStr = [NSString stringWithFormat:@"multipart/byteranges; boundary=%@", ranges_boundry];
|
|
668
|
-
[response setHeaderField:@"Content-Type" value:contentTypeStr];
|
|
669
|
-
|
|
670
|
-
return response;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
/**
|
|
674
|
-
* Returns the chunk size line that must precede each chunk of data when using chunked transfer encoding.
|
|
675
|
-
* This consists of the size of the data, in hexadecimal, followed by a CRLF.
|
|
676
|
-
**/
|
|
677
|
-
- (NSData *)chunkedTransferSizeLineForLength:(NSUInteger)length
|
|
678
|
-
{
|
|
679
|
-
return [[NSString stringWithFormat:@"%lx\r\n", (unsigned long)length] dataUsingEncoding:NSUTF8StringEncoding];
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
/**
|
|
683
|
-
* Returns the data that signals the end of a chunked transfer.
|
|
684
|
-
**/
|
|
685
|
-
- (NSData *)chunkedTransferFooter
|
|
686
|
-
{
|
|
687
|
-
// Each data chunk is preceded by a size line (in hex and including a CRLF),
|
|
688
|
-
// followed by the data itself, followed by another CRLF.
|
|
689
|
-
// After every data chunk has been sent, a zero size line is sent,
|
|
690
|
-
// followed by optional footer (which are just more headers),
|
|
691
|
-
// and followed by a CRLF on a line by itself.
|
|
692
|
-
|
|
693
|
-
return [@"\r\n0\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding];
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
- (void)sendResponseHeadersAndBody
|
|
697
|
-
{
|
|
698
|
-
if ([httpResponse respondsToSelector:@selector(delayResponseHeaders)])
|
|
699
|
-
{
|
|
700
|
-
if ([httpResponse delayResponseHeaders])
|
|
701
|
-
{
|
|
702
|
-
return;
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
BOOL isChunked = NO;
|
|
707
|
-
|
|
708
|
-
if ([httpResponse respondsToSelector:@selector(isChunked)])
|
|
709
|
-
{
|
|
710
|
-
isChunked = [httpResponse isChunked];
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// If a response is "chunked", this simply means the HTTPResponse object
|
|
714
|
-
// doesn't know the content-length in advance.
|
|
715
|
-
|
|
716
|
-
UInt64 contentLength = 0;
|
|
717
|
-
|
|
718
|
-
if (!isChunked)
|
|
719
|
-
{
|
|
720
|
-
contentLength = [httpResponse contentLength];
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
// Check for specific range request
|
|
724
|
-
NSString *rangeHeader = [request headerField:@"Range"];
|
|
725
|
-
|
|
726
|
-
BOOL isRangeRequest = NO;
|
|
727
|
-
|
|
728
|
-
// If the response is "chunked" then we don't know the exact content-length.
|
|
729
|
-
// This means we'll be unable to process any range requests.
|
|
730
|
-
// This is because range requests might include a range like "give me the last 100 bytes"
|
|
731
|
-
|
|
732
|
-
if (!isChunked && rangeHeader)
|
|
733
|
-
{
|
|
734
|
-
if ([self parseRangeRequest:rangeHeader withContentLength:contentLength])
|
|
735
|
-
{
|
|
736
|
-
isRangeRequest = YES;
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
HTTPMessage *response;
|
|
741
|
-
|
|
742
|
-
if (!isRangeRequest)
|
|
743
|
-
{
|
|
744
|
-
// Create response
|
|
745
|
-
// Default status code: 200 - OK
|
|
746
|
-
NSInteger status = 200;
|
|
747
|
-
|
|
748
|
-
if ([httpResponse respondsToSelector:@selector(status)])
|
|
749
|
-
{
|
|
750
|
-
status = [httpResponse status];
|
|
751
|
-
}
|
|
752
|
-
response = [[HTTPMessage alloc] initResponseWithStatusCode:status description:nil version:HTTPVersion1_1];
|
|
753
|
-
|
|
754
|
-
if (isChunked)
|
|
755
|
-
{
|
|
756
|
-
[response setHeaderField:@"Transfer-Encoding" value:@"chunked"];
|
|
757
|
-
}
|
|
758
|
-
else
|
|
759
|
-
{
|
|
760
|
-
NSString *contentLengthStr = [NSString stringWithFormat:@"%qu", contentLength];
|
|
761
|
-
[response setHeaderField:@"Content-Length" value:contentLengthStr];
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
else
|
|
765
|
-
{
|
|
766
|
-
if ([ranges count] == 1)
|
|
767
|
-
{
|
|
768
|
-
response = [self newUniRangeResponse:contentLength];
|
|
769
|
-
}
|
|
770
|
-
else
|
|
771
|
-
{
|
|
772
|
-
response = [self newMultiRangeResponse:contentLength];
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
BOOL isZeroLengthResponse = !isChunked && (contentLength == 0);
|
|
777
|
-
|
|
778
|
-
// If they issue a 'HEAD' command, we don't have to include the file
|
|
779
|
-
// If they issue a 'GET' command, we need to include the file
|
|
780
|
-
|
|
781
|
-
if ([[request method] isEqualToString:@"HEAD"] || isZeroLengthResponse)
|
|
782
|
-
{
|
|
783
|
-
NSData *responseData = [self preprocessResponse:response];
|
|
784
|
-
[asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE];
|
|
785
|
-
|
|
786
|
-
sentResponseHeaders = YES;
|
|
787
|
-
}
|
|
788
|
-
else
|
|
789
|
-
{
|
|
790
|
-
// Write the header response
|
|
791
|
-
NSData *responseData = [self preprocessResponse:response];
|
|
792
|
-
[asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_PARTIAL_RESPONSE_HEADER];
|
|
793
|
-
|
|
794
|
-
sentResponseHeaders = YES;
|
|
795
|
-
|
|
796
|
-
// Now we need to send the body of the response
|
|
797
|
-
if (!isRangeRequest)
|
|
798
|
-
{
|
|
799
|
-
// Regular request
|
|
800
|
-
NSData *data = [httpResponse readDataOfLength:READ_CHUNKSIZE];
|
|
801
|
-
|
|
802
|
-
if ([data length] > 0)
|
|
803
|
-
{
|
|
804
|
-
[responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
|
|
805
|
-
|
|
806
|
-
if (isChunked)
|
|
807
|
-
{
|
|
808
|
-
NSData *chunkSize = [self chunkedTransferSizeLineForLength:[data length]];
|
|
809
|
-
[asyncSocket writeData:chunkSize withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_HEADER];
|
|
810
|
-
|
|
811
|
-
[asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_CHUNKED_RESPONSE_BODY];
|
|
812
|
-
|
|
813
|
-
if ([httpResponse isDone])
|
|
814
|
-
{
|
|
815
|
-
NSData *footer = [self chunkedTransferFooter];
|
|
816
|
-
[asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE];
|
|
817
|
-
}
|
|
818
|
-
else
|
|
819
|
-
{
|
|
820
|
-
NSData *footer = [GCDAsyncSocket CRLFData];
|
|
821
|
-
[asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_FOOTER];
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
else
|
|
825
|
-
{
|
|
826
|
-
long tag = [httpResponse isDone] ? HTTP_RESPONSE : HTTP_PARTIAL_RESPONSE_BODY;
|
|
827
|
-
[asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag];
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
else
|
|
832
|
-
{
|
|
833
|
-
// Client specified a byte range in request
|
|
834
|
-
|
|
835
|
-
if ([ranges count] == 1)
|
|
836
|
-
{
|
|
837
|
-
// Client is requesting a single range
|
|
838
|
-
DDRange range = [[ranges objectAtIndex:0] ddrangeValue];
|
|
839
|
-
|
|
840
|
-
[httpResponse setOffset:range.location];
|
|
841
|
-
|
|
842
|
-
NSUInteger bytesToRead = range.length < READ_CHUNKSIZE ? (NSUInteger)range.length : READ_CHUNKSIZE;
|
|
843
|
-
|
|
844
|
-
NSData *data = [httpResponse readDataOfLength:bytesToRead];
|
|
845
|
-
|
|
846
|
-
if ([data length] > 0)
|
|
847
|
-
{
|
|
848
|
-
[responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
|
|
849
|
-
|
|
850
|
-
long tag = [data length] == range.length ? HTTP_RESPONSE : HTTP_PARTIAL_RANGE_RESPONSE_BODY;
|
|
851
|
-
[asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag];
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
else
|
|
855
|
-
{
|
|
856
|
-
// Client is requesting multiple ranges
|
|
857
|
-
// We have to send each range using multipart/byteranges
|
|
858
|
-
|
|
859
|
-
// Write range header
|
|
860
|
-
NSData *rangeHeaderData = [ranges_headers objectAtIndex:0];
|
|
861
|
-
[asyncSocket writeData:rangeHeaderData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_PARTIAL_RESPONSE_HEADER];
|
|
862
|
-
|
|
863
|
-
// Start writing range body
|
|
864
|
-
DDRange range = [[ranges objectAtIndex:0] ddrangeValue];
|
|
865
|
-
|
|
866
|
-
[httpResponse setOffset:range.location];
|
|
867
|
-
|
|
868
|
-
NSUInteger bytesToRead = range.length < READ_CHUNKSIZE ? (NSUInteger)range.length : READ_CHUNKSIZE;
|
|
869
|
-
|
|
870
|
-
NSData *data = [httpResponse readDataOfLength:bytesToRead];
|
|
871
|
-
|
|
872
|
-
if ([data length] > 0)
|
|
873
|
-
{
|
|
874
|
-
[responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
|
|
875
|
-
|
|
876
|
-
[asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_PARTIAL_RANGES_RESPONSE_BODY];
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
/**
|
|
885
|
-
* Returns the number of bytes of the http response body that are sitting in asyncSocket's write queue.
|
|
886
|
-
*
|
|
887
|
-
* We keep track of this information in order to keep our memory footprint low while
|
|
888
|
-
* working with asynchronous HTTPResponse objects.
|
|
889
|
-
**/
|
|
890
|
-
- (NSUInteger)writeQueueSize
|
|
891
|
-
{
|
|
892
|
-
NSUInteger result = 0;
|
|
893
|
-
|
|
894
|
-
NSUInteger i;
|
|
895
|
-
for(i = 0; i < [responseDataSizes count]; i++)
|
|
896
|
-
{
|
|
897
|
-
result += [[responseDataSizes objectAtIndex:i] unsignedIntegerValue];
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
return result;
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
/**
|
|
904
|
-
* Sends more data, if needed, without growing the write queue over its approximate size limit.
|
|
905
|
-
* The last chunk of the response body will be sent with a tag of HTTP_RESPONSE.
|
|
906
|
-
*
|
|
907
|
-
* This method should only be called for standard (non-range) responses.
|
|
908
|
-
**/
|
|
909
|
-
- (void)continueSendingStandardResponseBody
|
|
910
|
-
{
|
|
911
|
-
HTTPLogTrace();
|
|
912
|
-
|
|
913
|
-
// This method is called when either asyncSocket has finished writing one of the response data chunks,
|
|
914
|
-
// or when an asynchronous HTTPResponse object informs us that it has more available data for us to send.
|
|
915
|
-
// In the case of the asynchronous HTTPResponse, we don't want to blindly grab the new data,
|
|
916
|
-
// and shove it onto asyncSocket's write queue.
|
|
917
|
-
// Doing so could negatively affect the memory footprint of the application.
|
|
918
|
-
// Instead, we always ensure that we place no more than READ_CHUNKSIZE bytes onto the write queue.
|
|
919
|
-
//
|
|
920
|
-
// Note that this does not affect the rate at which the HTTPResponse object may generate data.
|
|
921
|
-
// The HTTPResponse is free to do as it pleases, and this is up to the application's developer.
|
|
922
|
-
// If the memory footprint is a concern, the developer creating the custom HTTPResponse object may freely
|
|
923
|
-
// use the calls to readDataOfLength as an indication to start generating more data.
|
|
924
|
-
// This provides an easy way for the HTTPResponse object to throttle its data allocation in step with the rate
|
|
925
|
-
// at which the socket is able to send it.
|
|
926
|
-
|
|
927
|
-
NSUInteger writeQueueSize = [self writeQueueSize];
|
|
928
|
-
|
|
929
|
-
if(writeQueueSize >= READ_CHUNKSIZE) return;
|
|
930
|
-
|
|
931
|
-
NSUInteger available = READ_CHUNKSIZE - writeQueueSize;
|
|
932
|
-
NSData *data = [httpResponse readDataOfLength:available];
|
|
933
|
-
|
|
934
|
-
if ([data length] > 0)
|
|
935
|
-
{
|
|
936
|
-
[responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
|
|
937
|
-
|
|
938
|
-
BOOL isChunked = NO;
|
|
939
|
-
|
|
940
|
-
if ([httpResponse respondsToSelector:@selector(isChunked)])
|
|
941
|
-
{
|
|
942
|
-
isChunked = [httpResponse isChunked];
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
if (isChunked)
|
|
946
|
-
{
|
|
947
|
-
NSData *chunkSize = [self chunkedTransferSizeLineForLength:[data length]];
|
|
948
|
-
[asyncSocket writeData:chunkSize withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_HEADER];
|
|
949
|
-
|
|
950
|
-
[asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_CHUNKED_RESPONSE_BODY];
|
|
951
|
-
|
|
952
|
-
if([httpResponse isDone])
|
|
953
|
-
{
|
|
954
|
-
NSData *footer = [self chunkedTransferFooter];
|
|
955
|
-
[asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE];
|
|
956
|
-
}
|
|
957
|
-
else
|
|
958
|
-
{
|
|
959
|
-
NSData *footer = [GCDAsyncSocket CRLFData];
|
|
960
|
-
[asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_FOOTER];
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
else
|
|
964
|
-
{
|
|
965
|
-
long tag = [httpResponse isDone] ? HTTP_RESPONSE : HTTP_PARTIAL_RESPONSE_BODY;
|
|
966
|
-
[asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag];
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
/**
|
|
972
|
-
* Sends more data, if needed, without growing the write queue over its approximate size limit.
|
|
973
|
-
* The last chunk of the response body will be sent with a tag of HTTP_RESPONSE.
|
|
974
|
-
*
|
|
975
|
-
* This method should only be called for single-range responses.
|
|
976
|
-
**/
|
|
977
|
-
- (void)continueSendingSingleRangeResponseBody
|
|
978
|
-
{
|
|
979
|
-
HTTPLogTrace();
|
|
980
|
-
|
|
981
|
-
// This method is called when either asyncSocket has finished writing one of the response data chunks,
|
|
982
|
-
// or when an asynchronous response informs us that is has more available data for us to send.
|
|
983
|
-
// In the case of the asynchronous response, we don't want to blindly grab the new data,
|
|
984
|
-
// and shove it onto asyncSocket's write queue.
|
|
985
|
-
// Doing so could negatively affect the memory footprint of the application.
|
|
986
|
-
// Instead, we always ensure that we place no more than READ_CHUNKSIZE bytes onto the write queue.
|
|
987
|
-
//
|
|
988
|
-
// Note that this does not affect the rate at which the HTTPResponse object may generate data.
|
|
989
|
-
// The HTTPResponse is free to do as it pleases, and this is up to the application's developer.
|
|
990
|
-
// If the memory footprint is a concern, the developer creating the custom HTTPResponse object may freely
|
|
991
|
-
// use the calls to readDataOfLength as an indication to start generating more data.
|
|
992
|
-
// This provides an easy way for the HTTPResponse object to throttle its data allocation in step with the rate
|
|
993
|
-
// at which the socket is able to send it.
|
|
994
|
-
|
|
995
|
-
NSUInteger writeQueueSize = [self writeQueueSize];
|
|
996
|
-
|
|
997
|
-
if(writeQueueSize >= READ_CHUNKSIZE) return;
|
|
998
|
-
|
|
999
|
-
DDRange range = [[ranges objectAtIndex:0] ddrangeValue];
|
|
1000
|
-
|
|
1001
|
-
UInt64 offset = [httpResponse offset];
|
|
1002
|
-
UInt64 bytesRead = offset - range.location;
|
|
1003
|
-
UInt64 bytesLeft = range.length - bytesRead;
|
|
1004
|
-
|
|
1005
|
-
if (bytesLeft > 0)
|
|
1006
|
-
{
|
|
1007
|
-
NSUInteger available = READ_CHUNKSIZE - writeQueueSize;
|
|
1008
|
-
NSUInteger bytesToRead = bytesLeft < available ? (NSUInteger)bytesLeft : available;
|
|
1009
|
-
|
|
1010
|
-
NSData *data = [httpResponse readDataOfLength:bytesToRead];
|
|
1011
|
-
|
|
1012
|
-
if ([data length] > 0)
|
|
1013
|
-
{
|
|
1014
|
-
[responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
|
|
1015
|
-
|
|
1016
|
-
long tag = [data length] == bytesLeft ? HTTP_RESPONSE : HTTP_PARTIAL_RANGE_RESPONSE_BODY;
|
|
1017
|
-
[asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag];
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
/**
|
|
1023
|
-
* Sends more data, if needed, without growing the write queue over its approximate size limit.
|
|
1024
|
-
* The last chunk of the response body will be sent with a tag of HTTP_RESPONSE.
|
|
1025
|
-
*
|
|
1026
|
-
* This method should only be called for multi-range responses.
|
|
1027
|
-
**/
|
|
1028
|
-
- (void)continueSendingMultiRangeResponseBody
|
|
1029
|
-
{
|
|
1030
|
-
HTTPLogTrace();
|
|
1031
|
-
|
|
1032
|
-
// This method is called when either asyncSocket has finished writing one of the response data chunks,
|
|
1033
|
-
// or when an asynchronous HTTPResponse object informs us that is has more available data for us to send.
|
|
1034
|
-
// In the case of the asynchronous HTTPResponse, we don't want to blindly grab the new data,
|
|
1035
|
-
// and shove it onto asyncSocket's write queue.
|
|
1036
|
-
// Doing so could negatively affect the memory footprint of the application.
|
|
1037
|
-
// Instead, we always ensure that we place no more than READ_CHUNKSIZE bytes onto the write queue.
|
|
1038
|
-
//
|
|
1039
|
-
// Note that this does not affect the rate at which the HTTPResponse object may generate data.
|
|
1040
|
-
// The HTTPResponse is free to do as it pleases, and this is up to the application's developer.
|
|
1041
|
-
// If the memory footprint is a concern, the developer creating the custom HTTPResponse object may freely
|
|
1042
|
-
// use the calls to readDataOfLength as an indication to start generating more data.
|
|
1043
|
-
// This provides an easy way for the HTTPResponse object to throttle its data allocation in step with the rate
|
|
1044
|
-
// at which the socket is able to send it.
|
|
1045
|
-
|
|
1046
|
-
NSUInteger writeQueueSize = [self writeQueueSize];
|
|
1047
|
-
|
|
1048
|
-
if(writeQueueSize >= READ_CHUNKSIZE) return;
|
|
1049
|
-
|
|
1050
|
-
DDRange range = [[ranges objectAtIndex:rangeIndex] ddrangeValue];
|
|
1051
|
-
|
|
1052
|
-
UInt64 offset = [httpResponse offset];
|
|
1053
|
-
UInt64 bytesRead = offset - range.location;
|
|
1054
|
-
UInt64 bytesLeft = range.length - bytesRead;
|
|
1055
|
-
|
|
1056
|
-
if (bytesLeft > 0)
|
|
1057
|
-
{
|
|
1058
|
-
NSUInteger available = READ_CHUNKSIZE - writeQueueSize;
|
|
1059
|
-
NSUInteger bytesToRead = bytesLeft < available ? (NSUInteger)bytesLeft : available;
|
|
1060
|
-
|
|
1061
|
-
NSData *data = [httpResponse readDataOfLength:bytesToRead];
|
|
1062
|
-
|
|
1063
|
-
if ([data length] > 0)
|
|
1064
|
-
{
|
|
1065
|
-
[responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
|
|
1066
|
-
|
|
1067
|
-
[asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_PARTIAL_RANGES_RESPONSE_BODY];
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
else
|
|
1071
|
-
{
|
|
1072
|
-
if (++rangeIndex < [ranges count])
|
|
1073
|
-
{
|
|
1074
|
-
// Write range header
|
|
1075
|
-
NSData *rangeHeader = [ranges_headers objectAtIndex:rangeIndex];
|
|
1076
|
-
[asyncSocket writeData:rangeHeader withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_PARTIAL_RESPONSE_HEADER];
|
|
1077
|
-
|
|
1078
|
-
// Start writing range body
|
|
1079
|
-
range = [[ranges objectAtIndex:rangeIndex] ddrangeValue];
|
|
1080
|
-
|
|
1081
|
-
[httpResponse setOffset:range.location];
|
|
1082
|
-
|
|
1083
|
-
NSUInteger available = READ_CHUNKSIZE - writeQueueSize;
|
|
1084
|
-
NSUInteger bytesToRead = range.length < available ? (NSUInteger)range.length : available;
|
|
1085
|
-
|
|
1086
|
-
NSData *data = [httpResponse readDataOfLength:bytesToRead];
|
|
1087
|
-
|
|
1088
|
-
if ([data length] > 0)
|
|
1089
|
-
{
|
|
1090
|
-
[responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
|
|
1091
|
-
|
|
1092
|
-
[asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_PARTIAL_RANGES_RESPONSE_BODY];
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
else
|
|
1096
|
-
{
|
|
1097
|
-
// We're not done yet - we still have to send the closing boundry tag
|
|
1098
|
-
NSString *endingBoundryStr = [NSString stringWithFormat:@"\r\n--%@--\r\n", ranges_boundry];
|
|
1099
|
-
NSData *endingBoundryData = [endingBoundryStr dataUsingEncoding:NSUTF8StringEncoding];
|
|
1100
|
-
|
|
1101
|
-
[asyncSocket writeData:endingBoundryData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE];
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1107
|
-
#pragma mark Responses
|
|
1108
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1109
|
-
|
|
1110
|
-
/**
|
|
1111
|
-
* Returns an array of possible index pages.
|
|
1112
|
-
* For example: {"index.html", "index.htm"}
|
|
1113
|
-
**/
|
|
1114
|
-
- (NSArray *)directoryIndexFileNames
|
|
1115
|
-
{
|
|
1116
|
-
HTTPLogTrace();
|
|
1117
|
-
|
|
1118
|
-
// Override me to support other index pages.
|
|
1119
|
-
|
|
1120
|
-
return [NSArray arrayWithObjects:@"index.html", @"index.htm", nil];
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
- (NSString *)filePathForURI:(NSString *)path
|
|
1124
|
-
{
|
|
1125
|
-
return [self filePathForURI:path allowDirectory:NO];
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
/**
|
|
1129
|
-
* Converts relative URI path into full file-system path.
|
|
1130
|
-
**/
|
|
1131
|
-
- (NSString *)filePathForURI:(NSString *)path allowDirectory:(BOOL)allowDirectory
|
|
1132
|
-
{
|
|
1133
|
-
HTTPLogTrace();
|
|
1134
|
-
|
|
1135
|
-
// Override me to perform custom path mapping.
|
|
1136
|
-
// For example you may want to use a default file other than index.html, or perhaps support multiple types.
|
|
1137
|
-
|
|
1138
|
-
NSString *documentRoot = [config documentRoot];
|
|
1139
|
-
|
|
1140
|
-
// Part 0: Validate document root setting.
|
|
1141
|
-
//
|
|
1142
|
-
// If there is no configured documentRoot,
|
|
1143
|
-
// then it makes no sense to try to return anything.
|
|
1144
|
-
|
|
1145
|
-
if (documentRoot == nil)
|
|
1146
|
-
{
|
|
1147
|
-
HTTPLogWarn(@"%@[%p]: No configured document root", THIS_FILE, self);
|
|
1148
|
-
return nil;
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
// Part 1: Strip parameters from the url
|
|
1152
|
-
//
|
|
1153
|
-
// E.g.: /page.html?q=22&var=abc -> /page.html
|
|
1154
|
-
|
|
1155
|
-
NSURL *docRoot = [NSURL fileURLWithPath:documentRoot isDirectory:YES];
|
|
1156
|
-
if (docRoot == nil)
|
|
1157
|
-
{
|
|
1158
|
-
HTTPLogWarn(@"%@[%p]: Document root is invalid file path", THIS_FILE, self);
|
|
1159
|
-
return nil;
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
NSString *relativePath = [[NSURL URLWithString:path relativeToURL:docRoot] relativePath];
|
|
1163
|
-
|
|
1164
|
-
// Part 2: Append relative path to document root (base path)
|
|
1165
|
-
//
|
|
1166
|
-
// E.g.: relativePath="/images/icon.png"
|
|
1167
|
-
// documentRoot="/Users/robbie/Sites"
|
|
1168
|
-
// fullPath="/Users/robbie/Sites/images/icon.png"
|
|
1169
|
-
//
|
|
1170
|
-
// We also standardize the path.
|
|
1171
|
-
//
|
|
1172
|
-
// E.g.: "Users/robbie/Sites/images/../index.html" -> "/Users/robbie/Sites/index.html"
|
|
1173
|
-
|
|
1174
|
-
NSString *fullPath = [[documentRoot stringByAppendingPathComponent:relativePath] stringByStandardizingPath];
|
|
1175
|
-
|
|
1176
|
-
if ([relativePath isEqualToString:@"/"])
|
|
1177
|
-
{
|
|
1178
|
-
fullPath = [fullPath stringByAppendingString:@"/"];
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
// Part 3: Prevent serving files outside the document root.
|
|
1182
|
-
//
|
|
1183
|
-
// Sneaky requests may include ".." in the path.
|
|
1184
|
-
//
|
|
1185
|
-
// E.g.: relativePath="../Documents/TopSecret.doc"
|
|
1186
|
-
// documentRoot="/Users/robbie/Sites"
|
|
1187
|
-
// fullPath="/Users/robbie/Documents/TopSecret.doc"
|
|
1188
|
-
//
|
|
1189
|
-
// E.g.: relativePath="../Sites_Secret/TopSecret.doc"
|
|
1190
|
-
// documentRoot="/Users/robbie/Sites"
|
|
1191
|
-
// fullPath="/Users/robbie/Sites_Secret/TopSecret"
|
|
1192
|
-
|
|
1193
|
-
if (![documentRoot hasSuffix:@"/"])
|
|
1194
|
-
{
|
|
1195
|
-
documentRoot = [documentRoot stringByAppendingString:@"/"];
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
if (![fullPath hasPrefix:documentRoot])
|
|
1199
|
-
{
|
|
1200
|
-
HTTPLogWarn(@"%@[%p]: Request for file outside document root", THIS_FILE, self);
|
|
1201
|
-
return nil;
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
// Part 4: Search for index page if path is pointing to a directory
|
|
1205
|
-
if (!allowDirectory)
|
|
1206
|
-
{
|
|
1207
|
-
BOOL isDir = NO;
|
|
1208
|
-
if ([[NSFileManager defaultManager] fileExistsAtPath:fullPath isDirectory:&isDir] && isDir)
|
|
1209
|
-
{
|
|
1210
|
-
NSArray *indexFileNames = [self directoryIndexFileNames];
|
|
1211
|
-
|
|
1212
|
-
for (NSString *indexFileName in indexFileNames)
|
|
1213
|
-
{
|
|
1214
|
-
NSString *indexFilePath = [fullPath stringByAppendingPathComponent:indexFileName];
|
|
1215
|
-
|
|
1216
|
-
if ([[NSFileManager defaultManager] fileExistsAtPath:indexFilePath isDirectory:&isDir] && !isDir)
|
|
1217
|
-
{
|
|
1218
|
-
return indexFilePath;
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
// No matching index files found in directory
|
|
1223
|
-
return nil;
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
return fullPath;
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
/**
|
|
1231
|
-
* This method is called to get a response for a request.
|
|
1232
|
-
* You may return any object that adopts the HTTPResponse protocol.
|
|
1233
|
-
* The HTTPServer comes with two such classes: HTTPFileResponse and HTTPDataResponse.
|
|
1234
|
-
* HTTPFileResponse is a wrapper for an NSFileHandle object, and is the preferred way to send a file response.
|
|
1235
|
-
* HTTPDataResponse is a wrapper for an NSData object, and may be used to send a custom response.
|
|
1236
|
-
**/
|
|
1237
|
-
- (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path
|
|
1238
|
-
{
|
|
1239
|
-
HTTPLogTrace();
|
|
1240
|
-
|
|
1241
|
-
// Override me to provide custom responses.
|
|
1242
|
-
|
|
1243
|
-
return nil;
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
- (WebSocket *)webSocketForURI:(NSString *)path
|
|
1247
|
-
{
|
|
1248
|
-
HTTPLogTrace();
|
|
1249
|
-
|
|
1250
|
-
// Override me to provide custom WebSocket responses.
|
|
1251
|
-
// To do so, simply override the base WebSocket implementation, and add your custom functionality.
|
|
1252
|
-
// Then return an instance of your custom WebSocket here.
|
|
1253
|
-
//
|
|
1254
|
-
// For example:
|
|
1255
|
-
//
|
|
1256
|
-
// if ([path isEqualToString:@"/myAwesomeWebSocketStream"])
|
|
1257
|
-
// {
|
|
1258
|
-
// return [[[MyWebSocket alloc] initWithRequest:request socket:asyncSocket] autorelease];
|
|
1259
|
-
// }
|
|
1260
|
-
//
|
|
1261
|
-
// return [super webSocketForURI:path];
|
|
1262
|
-
|
|
1263
|
-
return nil;
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1267
|
-
#pragma mark Uploads
|
|
1268
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1269
|
-
|
|
1270
|
-
/**
|
|
1271
|
-
* This method is called after receiving all HTTP headers, but before reading any of the request body.
|
|
1272
|
-
**/
|
|
1273
|
-
- (void)prepareForBodyWithSize:(UInt64)contentLength
|
|
1274
|
-
{
|
|
1275
|
-
// Override me to allocate buffers, file handles, etc.
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
/**
|
|
1279
|
-
* This method is called to handle data read from a POST / PUT.
|
|
1280
|
-
* The given data is part of the request body.
|
|
1281
|
-
**/
|
|
1282
|
-
- (void)processBodyData:(NSData *)postDataChunk
|
|
1283
|
-
{
|
|
1284
|
-
// Override me to do something useful with a POST / PUT.
|
|
1285
|
-
// If the post is small, such as a simple form, you may want to simply append the data to the request.
|
|
1286
|
-
// If the post is big, such as a file upload, you may want to store the file to disk.
|
|
1287
|
-
//
|
|
1288
|
-
// Remember: In order to support LARGE POST uploads, the data is read in chunks.
|
|
1289
|
-
// This prevents a 50 MB upload from being stored in RAM.
|
|
1290
|
-
// The size of the chunks are limited by the POST_CHUNKSIZE definition.
|
|
1291
|
-
// Therefore, this method may be called multiple times for the same POST request.
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
/**
|
|
1295
|
-
* This method is called after the request body has been fully read but before the HTTP request is processed.
|
|
1296
|
-
**/
|
|
1297
|
-
- (void)finishBody
|
|
1298
|
-
{
|
|
1299
|
-
// Override me to perform any final operations on an upload.
|
|
1300
|
-
// For example, if you were saving the upload to disk this would be
|
|
1301
|
-
// the hook to flush any pending data to disk and maybe close the file.
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
/**
|
|
1305
|
-
* Returns the maximum request body size this connection accepts.
|
|
1306
|
-
**/
|
|
1307
|
-
- (UInt64)maxRequestBodySize
|
|
1308
|
-
{
|
|
1309
|
-
return (UInt64)-1;
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1312
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1313
|
-
#pragma mark Errors
|
|
1314
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1315
|
-
|
|
1316
|
-
/**
|
|
1317
|
-
* Called if the HTML version is other than what is supported
|
|
1318
|
-
**/
|
|
1319
|
-
- (void)handleVersionNotSupported:(NSString *)version
|
|
1320
|
-
{
|
|
1321
|
-
// Override me for custom error handling of unsupported http version responses
|
|
1322
|
-
// If you simply want to add a few extra header fields, see the preprocessErrorResponse: method.
|
|
1323
|
-
// You can also use preprocessErrorResponse: to add an optional HTML body.
|
|
1324
|
-
|
|
1325
|
-
HTTPLogWarn(@"HTTP Server: Error 505 - Version Not Supported: %@ (%@)", version, [self requestURI]);
|
|
1326
|
-
|
|
1327
|
-
HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:505 description:nil version:HTTPVersion1_1];
|
|
1328
|
-
[response setHeaderField:@"Content-Length" value:@"0"];
|
|
1329
|
-
|
|
1330
|
-
NSData *responseData = [self preprocessErrorResponse:response];
|
|
1331
|
-
[asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_RESPONSE];
|
|
1332
|
-
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
/**
|
|
1336
|
-
* Called if the HTTP request body is larger than the configured limit.
|
|
1337
|
-
**/
|
|
1338
|
-
- (void)handleRequestBodyTooLarge
|
|
1339
|
-
{
|
|
1340
|
-
HTTPLogWarn(@"HTTP Server: Error 413 - Request Entity Too Large (%@)", [self requestURI]);
|
|
1341
|
-
|
|
1342
|
-
HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:413 description:nil version:HTTPVersion1_1];
|
|
1343
|
-
[response setHeaderField:@"Content-Length" value:@"0"];
|
|
1344
|
-
[response setHeaderField:@"Connection" value:@"close"];
|
|
1345
|
-
|
|
1346
|
-
NSData *responseData = [self preprocessErrorResponse:response];
|
|
1347
|
-
[asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_FINAL_RESPONSE];
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
/**
|
|
1351
|
-
* Called if we receive some sort of malformed HTTP request.
|
|
1352
|
-
* The data parameter is the invalid HTTP header line, including CRLF, as read from GCDAsyncSocket.
|
|
1353
|
-
* The data parameter may also be nil if the request as a whole was invalid, such as a POST with no Content-Length.
|
|
1354
|
-
**/
|
|
1355
|
-
- (void)handleInvalidRequest:(NSData *)data
|
|
1356
|
-
{
|
|
1357
|
-
// Override me for custom error handling of invalid HTTP requests
|
|
1358
|
-
// If you simply want to add a few extra header fields, see the preprocessErrorResponse: method.
|
|
1359
|
-
// You can also use preprocessErrorResponse: to add an optional HTML body.
|
|
1360
|
-
|
|
1361
|
-
HTTPLogWarn(@"HTTP Server: Error 400 - Bad Request (%@)", [self requestURI]);
|
|
1362
|
-
|
|
1363
|
-
// Status Code 400 - Bad Request
|
|
1364
|
-
HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:400 description:nil version:HTTPVersion1_1];
|
|
1365
|
-
[response setHeaderField:@"Content-Length" value:@"0"];
|
|
1366
|
-
[response setHeaderField:@"Connection" value:@"close"];
|
|
1367
|
-
|
|
1368
|
-
NSData *responseData = [self preprocessErrorResponse:response];
|
|
1369
|
-
[asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_FINAL_RESPONSE];
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
// Note: We used the HTTP_FINAL_RESPONSE tag to disconnect after the response is sent.
|
|
1373
|
-
// We do this because we couldn't parse the request,
|
|
1374
|
-
// so we won't be able to recover and move on to another request afterwards.
|
|
1375
|
-
// In other words, we wouldn't know where the first request ends and the second request begins.
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
/**
|
|
1379
|
-
* Called if we receive a HTTP request with a method other than GET or HEAD.
|
|
1380
|
-
**/
|
|
1381
|
-
- (void)handleUnknownMethod:(NSString *)method
|
|
1382
|
-
{
|
|
1383
|
-
// Override me for custom error handling of 405 method not allowed responses.
|
|
1384
|
-
// If you simply want to add a few extra header fields, see the preprocessErrorResponse: method.
|
|
1385
|
-
// You can also use preprocessErrorResponse: to add an optional HTML body.
|
|
1386
|
-
//
|
|
1387
|
-
// See also: supportsMethod:atPath:
|
|
1388
|
-
|
|
1389
|
-
HTTPLogWarn(@"HTTP Server: Error 405 - Method Not Allowed: %@ (%@)", method, [self requestURI]);
|
|
1390
|
-
|
|
1391
|
-
// Status code 405 - Method Not Allowed
|
|
1392
|
-
HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:405 description:nil version:HTTPVersion1_1];
|
|
1393
|
-
[response setHeaderField:@"Content-Length" value:@"0"];
|
|
1394
|
-
[response setHeaderField:@"Connection" value:@"close"];
|
|
1395
|
-
|
|
1396
|
-
NSData *responseData = [self preprocessErrorResponse:response];
|
|
1397
|
-
[asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_FINAL_RESPONSE];
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
// Note: We used the HTTP_FINAL_RESPONSE tag to disconnect after the response is sent.
|
|
1401
|
-
// We do this because the method may include an http body.
|
|
1402
|
-
// Since we can't be sure, we should close the connection.
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
/**
|
|
1406
|
-
* Called if we're unable to find the requested resource.
|
|
1407
|
-
**/
|
|
1408
|
-
- (void)handleResourceNotFound
|
|
1409
|
-
{
|
|
1410
|
-
// Override me for custom error handling of 404 not found responses
|
|
1411
|
-
// If you simply want to add a few extra header fields, see the preprocessErrorResponse: method.
|
|
1412
|
-
// You can also use preprocessErrorResponse: to add an optional HTML body.
|
|
1413
|
-
|
|
1414
|
-
HTTPLogInfo(@"HTTP Server: Error 404 - Not Found (%@)", [self requestURI]);
|
|
1415
|
-
|
|
1416
|
-
// Status Code 404 - Not Found
|
|
1417
|
-
HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:404 description:nil version:HTTPVersion1_1];
|
|
1418
|
-
[response setHeaderField:@"Content-Length" value:@"0"];
|
|
1419
|
-
|
|
1420
|
-
NSData *responseData = [self preprocessErrorResponse:response];
|
|
1421
|
-
[asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_RESPONSE];
|
|
1422
|
-
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1426
|
-
#pragma mark Headers
|
|
1427
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1428
|
-
|
|
1429
|
-
/**
|
|
1430
|
-
* Gets the current date and time, formatted properly (according to RFC) for insertion into an HTTP header.
|
|
1431
|
-
**/
|
|
1432
|
-
- (NSString *)dateAsString:(NSDate *)date
|
|
1433
|
-
{
|
|
1434
|
-
// From Apple's Documentation (Data Formatting Guide -> Date Formatters -> Cache Formatters for Efficiency):
|
|
1435
|
-
//
|
|
1436
|
-
// "Creating a date formatter is not a cheap operation. If you are likely to use a formatter frequently,
|
|
1437
|
-
// it is typically more efficient to cache a single instance than to create and dispose of multiple instances.
|
|
1438
|
-
// One approach is to use a static variable."
|
|
1439
|
-
//
|
|
1440
|
-
// This was discovered to be true in massive form via issue #46:
|
|
1441
|
-
//
|
|
1442
|
-
// "Was doing some performance benchmarking using instruments and httperf. Using this single optimization
|
|
1443
|
-
// I got a 26% speed improvement - from 1000req/sec to 3800req/sec. Not insignificant.
|
|
1444
|
-
// The culprit? Why, NSDateFormatter, of course!"
|
|
1445
|
-
//
|
|
1446
|
-
// Thus, we are using a static NSDateFormatter here.
|
|
1447
|
-
|
|
1448
|
-
static NSDateFormatter *df;
|
|
1449
|
-
|
|
1450
|
-
static dispatch_once_t onceToken;
|
|
1451
|
-
dispatch_once(&onceToken, ^{
|
|
1452
|
-
|
|
1453
|
-
// Example: Sun, 06 Nov 1994 08:49:37 GMT
|
|
1454
|
-
|
|
1455
|
-
df = [[NSDateFormatter alloc] init];
|
|
1456
|
-
[df setFormatterBehavior:NSDateFormatterBehavior10_4];
|
|
1457
|
-
[df setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]];
|
|
1458
|
-
[df setDateFormat:@"EEE, dd MMM y HH:mm:ss 'GMT'"];
|
|
1459
|
-
[df setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]];
|
|
1460
|
-
|
|
1461
|
-
// For some reason, using zzz in the format string produces GMT+00:00
|
|
1462
|
-
});
|
|
1463
|
-
|
|
1464
|
-
return [df stringFromDate:date];
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
/**
|
|
1468
|
-
* This method is called immediately prior to sending the response headers.
|
|
1469
|
-
* This method adds standard header fields, and then converts the response to an NSData object.
|
|
1470
|
-
**/
|
|
1471
|
-
- (NSData *)preprocessResponse:(HTTPMessage *)response
|
|
1472
|
-
{
|
|
1473
|
-
HTTPLogTrace();
|
|
1474
|
-
|
|
1475
|
-
// Override me to customize the response headers
|
|
1476
|
-
// You'll likely want to add your own custom headers, and then return [super preprocessResponse:response]
|
|
1477
|
-
|
|
1478
|
-
// Add standard headers
|
|
1479
|
-
NSString *now = [self dateAsString:[NSDate date]];
|
|
1480
|
-
[response setHeaderField:@"Date" value:now];
|
|
1481
|
-
|
|
1482
|
-
// Add server capability headers
|
|
1483
|
-
[response setHeaderField:@"Accept-Ranges" value:@"bytes"];
|
|
1484
|
-
|
|
1485
|
-
// Add optional response headers
|
|
1486
|
-
if ([httpResponse respondsToSelector:@selector(httpHeaders)])
|
|
1487
|
-
{
|
|
1488
|
-
NSDictionary *responseHeaders = [httpResponse httpHeaders];
|
|
1489
|
-
|
|
1490
|
-
NSEnumerator *keyEnumerator = [responseHeaders keyEnumerator];
|
|
1491
|
-
NSString *key;
|
|
1492
|
-
|
|
1493
|
-
while ((key = [keyEnumerator nextObject]))
|
|
1494
|
-
{
|
|
1495
|
-
NSString *value = [responseHeaders objectForKey:key];
|
|
1496
|
-
|
|
1497
|
-
[response setHeaderField:key value:value];
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
return [response messageData];
|
|
1502
|
-
}
|
|
1503
|
-
|
|
1504
|
-
/**
|
|
1505
|
-
* This method is called immediately prior to sending the response headers (for an error).
|
|
1506
|
-
* This method adds standard header fields, and then converts the response to an NSData object.
|
|
1507
|
-
**/
|
|
1508
|
-
- (NSData *)preprocessErrorResponse:(HTTPMessage *)response
|
|
1509
|
-
{
|
|
1510
|
-
HTTPLogTrace();
|
|
1511
|
-
|
|
1512
|
-
// Override me to customize the error response headers
|
|
1513
|
-
// You'll likely want to add your own custom headers, and then return [super preprocessErrorResponse:response]
|
|
1514
|
-
//
|
|
1515
|
-
// Notes:
|
|
1516
|
-
// You can use [response statusCode] to get the type of error.
|
|
1517
|
-
// You can use [response setBody:data] to add an optional HTML body.
|
|
1518
|
-
// If you add a body, don't forget to update the Content-Length.
|
|
1519
|
-
//
|
|
1520
|
-
// if ([response statusCode] == 404)
|
|
1521
|
-
// {
|
|
1522
|
-
// NSString *msg = @"<html><body>Error 404 - Not Found</body></html>";
|
|
1523
|
-
// NSData *msgData = [msg dataUsingEncoding:NSUTF8StringEncoding];
|
|
1524
|
-
//
|
|
1525
|
-
// [response setBody:msgData];
|
|
1526
|
-
//
|
|
1527
|
-
// NSString *contentLengthStr = [NSString stringWithFormat:@"%lu", (unsigned long)[msgData length]];
|
|
1528
|
-
// [response setHeaderField:@"Content-Length" value:contentLengthStr];
|
|
1529
|
-
// }
|
|
1530
|
-
|
|
1531
|
-
// Add standard headers
|
|
1532
|
-
NSString *now = [self dateAsString:[NSDate date]];
|
|
1533
|
-
[response setHeaderField:@"Date" value:now];
|
|
1534
|
-
|
|
1535
|
-
// Add server capability headers
|
|
1536
|
-
[response setHeaderField:@"Accept-Ranges" value:@"bytes"];
|
|
1537
|
-
|
|
1538
|
-
// Add optional response headers
|
|
1539
|
-
if ([httpResponse respondsToSelector:@selector(httpHeaders)])
|
|
1540
|
-
{
|
|
1541
|
-
NSDictionary *responseHeaders = [httpResponse httpHeaders];
|
|
1542
|
-
|
|
1543
|
-
NSEnumerator *keyEnumerator = [responseHeaders keyEnumerator];
|
|
1544
|
-
NSString *key;
|
|
1545
|
-
|
|
1546
|
-
while((key = [keyEnumerator nextObject]))
|
|
1547
|
-
{
|
|
1548
|
-
NSString *value = [responseHeaders objectForKey:key];
|
|
1549
|
-
|
|
1550
|
-
[response setHeaderField:key value:value];
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
return [response messageData];
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1558
|
-
#pragma mark GCDAsyncSocket Delegate
|
|
1559
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1560
|
-
|
|
1561
|
-
/**
|
|
1562
|
-
* This method is called after the socket has successfully read data from the stream.
|
|
1563
|
-
* Remember that this method will only be called after the socket reaches a CRLF, or after it's read the proper length.
|
|
1564
|
-
**/
|
|
1565
|
-
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData*)data withTag:(long)tag
|
|
1566
|
-
{
|
|
1567
|
-
if (tag == HTTP_REQUEST_HEADER)
|
|
1568
|
-
{
|
|
1569
|
-
// Append the header line to the http message
|
|
1570
|
-
BOOL result = [request appendData:data];
|
|
1571
|
-
if (!result)
|
|
1572
|
-
{
|
|
1573
|
-
HTTPLogWarn(@"%@[%p]: Malformed request", THIS_FILE, self);
|
|
1574
|
-
|
|
1575
|
-
[self handleInvalidRequest:data];
|
|
1576
|
-
}
|
|
1577
|
-
else if (![request isHeaderComplete])
|
|
1578
|
-
{
|
|
1579
|
-
// We don't have a complete header yet
|
|
1580
|
-
// That is, we haven't yet received a CRLF on a line by itself, indicating the end of the header
|
|
1581
|
-
if (++numHeaderLines > MAX_HEADER_LINES)
|
|
1582
|
-
{
|
|
1583
|
-
// Reached the maximum amount of header lines in a single HTTP request
|
|
1584
|
-
// This could be an attempted DOS attack
|
|
1585
|
-
[asyncSocket disconnect];
|
|
1586
|
-
|
|
1587
|
-
// Explictly return to ensure we don't do anything after the socket disconnect
|
|
1588
|
-
return;
|
|
1589
|
-
}
|
|
1590
|
-
else
|
|
1591
|
-
{
|
|
1592
|
-
[asyncSocket readDataToData:[GCDAsyncSocket CRLFData]
|
|
1593
|
-
withTimeout:TIMEOUT_READ_SUBSEQUENT_HEADER_LINE
|
|
1594
|
-
maxLength:MAX_HEADER_LINE_LENGTH
|
|
1595
|
-
tag:HTTP_REQUEST_HEADER];
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
else
|
|
1599
|
-
{
|
|
1600
|
-
// We have an entire HTTP request header from the client
|
|
1601
|
-
|
|
1602
|
-
// Extract the method (such as GET, HEAD, POST, etc)
|
|
1603
|
-
NSString *method = [request method];
|
|
1604
|
-
|
|
1605
|
-
// Extract the uri (such as "/index.html")
|
|
1606
|
-
NSString *uri = [self requestURI];
|
|
1607
|
-
|
|
1608
|
-
// Check for a Transfer-Encoding field
|
|
1609
|
-
NSString *transferEncoding = [request headerField:@"Transfer-Encoding"];
|
|
1610
|
-
|
|
1611
|
-
// Check for a Content-Length field
|
|
1612
|
-
NSString *contentLength = [request headerField:@"Content-Length"];
|
|
1613
|
-
|
|
1614
|
-
// Content-Length MUST be present for upload methods (such as POST or PUT)
|
|
1615
|
-
// and MUST NOT be present for other methods.
|
|
1616
|
-
BOOL expectsUpload = [self expectsRequestBodyFromMethod:method atPath:uri];
|
|
1617
|
-
|
|
1618
|
-
if (expectsUpload)
|
|
1619
|
-
{
|
|
1620
|
-
if (transferEncoding && ![transferEncoding caseInsensitiveCompare:@"Chunked"])
|
|
1621
|
-
{
|
|
1622
|
-
requestContentLength = -1;
|
|
1623
|
-
}
|
|
1624
|
-
else
|
|
1625
|
-
{
|
|
1626
|
-
if (contentLength == nil)
|
|
1627
|
-
{
|
|
1628
|
-
HTTPLogWarn(@"%@[%p]: Method expects request body, but had no specified Content-Length",
|
|
1629
|
-
THIS_FILE, self);
|
|
1630
|
-
|
|
1631
|
-
[self handleInvalidRequest:nil];
|
|
1632
|
-
return;
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1635
|
-
if (![NSNumber parseString:(NSString *)contentLength intoUInt64:&requestContentLength])
|
|
1636
|
-
{
|
|
1637
|
-
HTTPLogWarn(@"%@[%p]: Unable to parse Content-Length header into a valid number",
|
|
1638
|
-
THIS_FILE, self);
|
|
1639
|
-
|
|
1640
|
-
[self handleInvalidRequest:nil];
|
|
1641
|
-
return;
|
|
1642
|
-
}
|
|
1643
|
-
|
|
1644
|
-
if (requestContentLength > [self maxRequestBodySize])
|
|
1645
|
-
{
|
|
1646
|
-
HTTPLogWarn(@"%@[%p]: Request body size %llu exceeds the configured limit %llu",
|
|
1647
|
-
THIS_FILE, self, requestContentLength, [self maxRequestBodySize]);
|
|
1648
|
-
|
|
1649
|
-
[self handleRequestBodyTooLarge];
|
|
1650
|
-
return;
|
|
1651
|
-
}
|
|
1652
|
-
}
|
|
1653
|
-
}
|
|
1654
|
-
else
|
|
1655
|
-
{
|
|
1656
|
-
if (contentLength != nil)
|
|
1657
|
-
{
|
|
1658
|
-
// Received Content-Length header for method not expecting an upload.
|
|
1659
|
-
// This better be zero...
|
|
1660
|
-
|
|
1661
|
-
if (![NSNumber parseString:(NSString *)contentLength intoUInt64:&requestContentLength])
|
|
1662
|
-
{
|
|
1663
|
-
HTTPLogWarn(@"%@[%p]: Unable to parse Content-Length header into a valid number",
|
|
1664
|
-
THIS_FILE, self);
|
|
1665
|
-
|
|
1666
|
-
[self handleInvalidRequest:nil];
|
|
1667
|
-
return;
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
|
-
if (requestContentLength > 0)
|
|
1671
|
-
{
|
|
1672
|
-
HTTPLogWarn(@"%@[%p]: Method not expecting request body had non-zero Content-Length",
|
|
1673
|
-
THIS_FILE, self);
|
|
1674
|
-
|
|
1675
|
-
[self handleInvalidRequest:nil];
|
|
1676
|
-
return;
|
|
1677
|
-
}
|
|
1678
|
-
}
|
|
1679
|
-
|
|
1680
|
-
requestContentLength = 0;
|
|
1681
|
-
requestContentLengthReceived = 0;
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
// Check to make sure the given method is supported
|
|
1685
|
-
if (![self supportsMethod:method atPath:uri])
|
|
1686
|
-
{
|
|
1687
|
-
// The method is unsupported - either in general, or for this specific request
|
|
1688
|
-
// Send a 405 - Method not allowed response
|
|
1689
|
-
[self handleUnknownMethod:method];
|
|
1690
|
-
return;
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
if (expectsUpload)
|
|
1694
|
-
{
|
|
1695
|
-
// Reset the total amount of data received for the upload
|
|
1696
|
-
requestContentLengthReceived = 0;
|
|
1697
|
-
|
|
1698
|
-
// Prepare for the upload
|
|
1699
|
-
[self prepareForBodyWithSize:requestContentLength];
|
|
1700
|
-
|
|
1701
|
-
if (requestContentLength > 0)
|
|
1702
|
-
{
|
|
1703
|
-
// Start reading the request body
|
|
1704
|
-
if (requestContentLength == -1)
|
|
1705
|
-
{
|
|
1706
|
-
// Chunked transfer
|
|
1707
|
-
|
|
1708
|
-
[asyncSocket readDataToData:[GCDAsyncSocket CRLFData]
|
|
1709
|
-
withTimeout:TIMEOUT_READ_BODY
|
|
1710
|
-
maxLength:MAX_CHUNK_LINE_LENGTH
|
|
1711
|
-
tag:HTTP_REQUEST_CHUNK_SIZE];
|
|
1712
|
-
}
|
|
1713
|
-
else
|
|
1714
|
-
{
|
|
1715
|
-
NSUInteger bytesToRead;
|
|
1716
|
-
if (requestContentLength < POST_CHUNKSIZE)
|
|
1717
|
-
bytesToRead = (NSUInteger)requestContentLength;
|
|
1718
|
-
else
|
|
1719
|
-
bytesToRead = POST_CHUNKSIZE;
|
|
1720
|
-
|
|
1721
|
-
[asyncSocket readDataToLength:bytesToRead
|
|
1722
|
-
withTimeout:TIMEOUT_READ_BODY
|
|
1723
|
-
tag:HTTP_REQUEST_BODY];
|
|
1724
|
-
}
|
|
1725
|
-
}
|
|
1726
|
-
else
|
|
1727
|
-
{
|
|
1728
|
-
// Empty upload
|
|
1729
|
-
[self finishBody];
|
|
1730
|
-
[self replyToHTTPRequest];
|
|
1731
|
-
}
|
|
1732
|
-
}
|
|
1733
|
-
else
|
|
1734
|
-
{
|
|
1735
|
-
// Now we need to reply to the request
|
|
1736
|
-
[self replyToHTTPRequest];
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
|
-
else
|
|
1741
|
-
{
|
|
1742
|
-
BOOL doneReadingRequest = NO;
|
|
1743
|
-
|
|
1744
|
-
// A chunked message body contains a series of chunks,
|
|
1745
|
-
// followed by a line with "0" (zero),
|
|
1746
|
-
// followed by optional footers (just like headers),
|
|
1747
|
-
// and a blank line.
|
|
1748
|
-
//
|
|
1749
|
-
// Each chunk consists of two parts:
|
|
1750
|
-
//
|
|
1751
|
-
// 1. A line with the size of the chunk data, in hex,
|
|
1752
|
-
// possibly followed by a semicolon and extra parameters you can ignore (none are currently standard),
|
|
1753
|
-
// and ending with CRLF.
|
|
1754
|
-
// 2. The data itself, followed by CRLF.
|
|
1755
|
-
//
|
|
1756
|
-
// Part 1 is represented by HTTP_REQUEST_CHUNK_SIZE
|
|
1757
|
-
// Part 2 is represented by HTTP_REQUEST_CHUNK_DATA and HTTP_REQUEST_CHUNK_TRAILER
|
|
1758
|
-
// where the trailer is the CRLF that follows the data.
|
|
1759
|
-
//
|
|
1760
|
-
// The optional footers and blank line are represented by HTTP_REQUEST_CHUNK_FOOTER.
|
|
1761
|
-
|
|
1762
|
-
if (tag == HTTP_REQUEST_CHUNK_SIZE)
|
|
1763
|
-
{
|
|
1764
|
-
// We have just read in a line with the size of the chunk data, in hex,
|
|
1765
|
-
// possibly followed by a semicolon and extra parameters that can be ignored,
|
|
1766
|
-
// and ending with CRLF.
|
|
1767
|
-
|
|
1768
|
-
NSString *sizeLine = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
|
1769
|
-
|
|
1770
|
-
errno = 0; // Reset errno before calling strtoull() to ensure it is always zero on success
|
|
1771
|
-
requestChunkSize = (UInt64)strtoull([sizeLine UTF8String], NULL, 16);
|
|
1772
|
-
requestChunkSizeReceived = 0;
|
|
1773
|
-
|
|
1774
|
-
if (errno != 0)
|
|
1775
|
-
{
|
|
1776
|
-
HTTPLogWarn(@"%@[%p]: Method expects chunk size, but received something else", THIS_FILE, self);
|
|
1777
|
-
|
|
1778
|
-
[self handleInvalidRequest:nil];
|
|
1779
|
-
return;
|
|
1780
|
-
}
|
|
1781
|
-
|
|
1782
|
-
if (requestChunkSize > 0)
|
|
1783
|
-
{
|
|
1784
|
-
UInt64 maxRequestBodySize = [self maxRequestBodySize];
|
|
1785
|
-
if (requestChunkSize > maxRequestBodySize ||
|
|
1786
|
-
requestContentLengthReceived > maxRequestBodySize - requestChunkSize)
|
|
1787
|
-
{
|
|
1788
|
-
HTTPLogWarn(@"%@[%p]: Chunked request body exceeds the configured limit %llu",
|
|
1789
|
-
THIS_FILE, self, maxRequestBodySize);
|
|
1790
|
-
|
|
1791
|
-
[self handleRequestBodyTooLarge];
|
|
1792
|
-
return;
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
|
-
NSUInteger bytesToRead;
|
|
1796
|
-
bytesToRead = (requestChunkSize < POST_CHUNKSIZE) ? (NSUInteger)requestChunkSize : POST_CHUNKSIZE;
|
|
1797
|
-
|
|
1798
|
-
[asyncSocket readDataToLength:bytesToRead
|
|
1799
|
-
withTimeout:TIMEOUT_READ_BODY
|
|
1800
|
-
tag:HTTP_REQUEST_CHUNK_DATA];
|
|
1801
|
-
}
|
|
1802
|
-
else
|
|
1803
|
-
{
|
|
1804
|
-
// This is the "0" (zero) line,
|
|
1805
|
-
// which is to be followed by optional footers (just like headers) and finally a blank line.
|
|
1806
|
-
|
|
1807
|
-
[asyncSocket readDataToData:[GCDAsyncSocket CRLFData]
|
|
1808
|
-
withTimeout:TIMEOUT_READ_BODY
|
|
1809
|
-
maxLength:MAX_HEADER_LINE_LENGTH
|
|
1810
|
-
tag:HTTP_REQUEST_CHUNK_FOOTER];
|
|
1811
|
-
}
|
|
1812
|
-
|
|
1813
|
-
return;
|
|
1814
|
-
}
|
|
1815
|
-
else if (tag == HTTP_REQUEST_CHUNK_DATA)
|
|
1816
|
-
{
|
|
1817
|
-
// We just read part of the actual data.
|
|
1818
|
-
|
|
1819
|
-
requestContentLengthReceived += [data length];
|
|
1820
|
-
requestChunkSizeReceived += [data length];
|
|
1821
|
-
|
|
1822
|
-
[self processBodyData:data];
|
|
1823
|
-
|
|
1824
|
-
UInt64 bytesLeft = requestChunkSize - requestChunkSizeReceived;
|
|
1825
|
-
if (bytesLeft > 0)
|
|
1826
|
-
{
|
|
1827
|
-
NSUInteger bytesToRead = (bytesLeft < POST_CHUNKSIZE) ? (NSUInteger)bytesLeft : POST_CHUNKSIZE;
|
|
1828
|
-
|
|
1829
|
-
[asyncSocket readDataToLength:bytesToRead
|
|
1830
|
-
withTimeout:TIMEOUT_READ_BODY
|
|
1831
|
-
tag:HTTP_REQUEST_CHUNK_DATA];
|
|
1832
|
-
}
|
|
1833
|
-
else
|
|
1834
|
-
{
|
|
1835
|
-
// We've read in all the data for this chunk.
|
|
1836
|
-
// The data is followed by a CRLF, which we need to read (and basically ignore)
|
|
1837
|
-
|
|
1838
|
-
[asyncSocket readDataToLength:2
|
|
1839
|
-
withTimeout:TIMEOUT_READ_BODY
|
|
1840
|
-
tag:HTTP_REQUEST_CHUNK_TRAILER];
|
|
1841
|
-
}
|
|
1842
|
-
|
|
1843
|
-
return;
|
|
1844
|
-
}
|
|
1845
|
-
else if (tag == HTTP_REQUEST_CHUNK_TRAILER)
|
|
1846
|
-
{
|
|
1847
|
-
// This should be the CRLF following the data.
|
|
1848
|
-
// Just ensure it's a CRLF.
|
|
1849
|
-
|
|
1850
|
-
if (![data isEqualToData:[GCDAsyncSocket CRLFData]])
|
|
1851
|
-
{
|
|
1852
|
-
HTTPLogWarn(@"%@[%p]: Method expects chunk trailer, but is missing", THIS_FILE, self);
|
|
1853
|
-
|
|
1854
|
-
[self handleInvalidRequest:nil];
|
|
1855
|
-
return;
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
|
-
// Now continue with the next chunk
|
|
1859
|
-
|
|
1860
|
-
[asyncSocket readDataToData:[GCDAsyncSocket CRLFData]
|
|
1861
|
-
withTimeout:TIMEOUT_READ_BODY
|
|
1862
|
-
maxLength:MAX_CHUNK_LINE_LENGTH
|
|
1863
|
-
tag:HTTP_REQUEST_CHUNK_SIZE];
|
|
1864
|
-
|
|
1865
|
-
}
|
|
1866
|
-
else if (tag == HTTP_REQUEST_CHUNK_FOOTER)
|
|
1867
|
-
{
|
|
1868
|
-
if (++numHeaderLines > MAX_HEADER_LINES)
|
|
1869
|
-
{
|
|
1870
|
-
// Reached the maximum amount of header lines in a single HTTP request
|
|
1871
|
-
// This could be an attempted DOS attack
|
|
1872
|
-
[asyncSocket disconnect];
|
|
1873
|
-
|
|
1874
|
-
// Explictly return to ensure we don't do anything after the socket disconnect
|
|
1875
|
-
return;
|
|
1876
|
-
}
|
|
1877
|
-
|
|
1878
|
-
if ([data length] > 2)
|
|
1879
|
-
{
|
|
1880
|
-
// We read in a footer.
|
|
1881
|
-
// In the future we may want to append these to the request.
|
|
1882
|
-
// For now we ignore, and continue reading the footers, waiting for the final blank line.
|
|
1883
|
-
|
|
1884
|
-
[asyncSocket readDataToData:[GCDAsyncSocket CRLFData]
|
|
1885
|
-
withTimeout:TIMEOUT_READ_BODY
|
|
1886
|
-
maxLength:MAX_HEADER_LINE_LENGTH
|
|
1887
|
-
tag:HTTP_REQUEST_CHUNK_FOOTER];
|
|
1888
|
-
}
|
|
1889
|
-
else
|
|
1890
|
-
{
|
|
1891
|
-
doneReadingRequest = YES;
|
|
1892
|
-
}
|
|
1893
|
-
}
|
|
1894
|
-
else // HTTP_REQUEST_BODY
|
|
1895
|
-
{
|
|
1896
|
-
// Handle a chunk of data from the POST body
|
|
1897
|
-
|
|
1898
|
-
requestContentLengthReceived += [data length];
|
|
1899
|
-
[self processBodyData:data];
|
|
1900
|
-
|
|
1901
|
-
if (requestContentLengthReceived < requestContentLength)
|
|
1902
|
-
{
|
|
1903
|
-
// We're not done reading the post body yet...
|
|
1904
|
-
|
|
1905
|
-
UInt64 bytesLeft = requestContentLength - requestContentLengthReceived;
|
|
1906
|
-
|
|
1907
|
-
NSUInteger bytesToRead = bytesLeft < POST_CHUNKSIZE ? (NSUInteger)bytesLeft : POST_CHUNKSIZE;
|
|
1908
|
-
|
|
1909
|
-
[asyncSocket readDataToLength:bytesToRead
|
|
1910
|
-
withTimeout:TIMEOUT_READ_BODY
|
|
1911
|
-
tag:HTTP_REQUEST_BODY];
|
|
1912
|
-
}
|
|
1913
|
-
else
|
|
1914
|
-
{
|
|
1915
|
-
doneReadingRequest = YES;
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
|
-
// Now that the entire body has been received, we need to reply to the request
|
|
1920
|
-
|
|
1921
|
-
if (doneReadingRequest)
|
|
1922
|
-
{
|
|
1923
|
-
[self finishBody];
|
|
1924
|
-
[self replyToHTTPRequest];
|
|
1925
|
-
}
|
|
1926
|
-
}
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
/**
|
|
1930
|
-
* This method is called after the socket has successfully written data to the stream.
|
|
1931
|
-
**/
|
|
1932
|
-
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
|
|
1933
|
-
{
|
|
1934
|
-
BOOL doneSendingResponse = NO;
|
|
1935
|
-
|
|
1936
|
-
if (tag == HTTP_PARTIAL_RESPONSE_BODY)
|
|
1937
|
-
{
|
|
1938
|
-
// Update the amount of data we have in asyncSocket's write queue
|
|
1939
|
-
if ([responseDataSizes count] > 0) {
|
|
1940
|
-
[responseDataSizes removeObjectAtIndex:0];
|
|
1941
|
-
}
|
|
1942
|
-
|
|
1943
|
-
// We only wrote a part of the response - there may be more
|
|
1944
|
-
[self continueSendingStandardResponseBody];
|
|
1945
|
-
}
|
|
1946
|
-
else if (tag == HTTP_CHUNKED_RESPONSE_BODY)
|
|
1947
|
-
{
|
|
1948
|
-
// Update the amount of data we have in asyncSocket's write queue.
|
|
1949
|
-
// This will allow asynchronous responses to continue sending more data.
|
|
1950
|
-
if ([responseDataSizes count] > 0) {
|
|
1951
|
-
[responseDataSizes removeObjectAtIndex:0];
|
|
1952
|
-
}
|
|
1953
|
-
// Don't continue sending the response yet.
|
|
1954
|
-
// The chunked footer that was sent after the body will tell us if we have more data to send.
|
|
1955
|
-
}
|
|
1956
|
-
else if (tag == HTTP_CHUNKED_RESPONSE_FOOTER)
|
|
1957
|
-
{
|
|
1958
|
-
// Normal chunked footer indicating we have more data to send (non final footer).
|
|
1959
|
-
[self continueSendingStandardResponseBody];
|
|
1960
|
-
}
|
|
1961
|
-
else if (tag == HTTP_PARTIAL_RANGE_RESPONSE_BODY)
|
|
1962
|
-
{
|
|
1963
|
-
// Update the amount of data we have in asyncSocket's write queue
|
|
1964
|
-
if ([responseDataSizes count] > 0) {
|
|
1965
|
-
[responseDataSizes removeObjectAtIndex:0];
|
|
1966
|
-
}
|
|
1967
|
-
// We only wrote a part of the range - there may be more
|
|
1968
|
-
[self continueSendingSingleRangeResponseBody];
|
|
1969
|
-
}
|
|
1970
|
-
else if (tag == HTTP_PARTIAL_RANGES_RESPONSE_BODY)
|
|
1971
|
-
{
|
|
1972
|
-
// Update the amount of data we have in asyncSocket's write queue
|
|
1973
|
-
if ([responseDataSizes count] > 0) {
|
|
1974
|
-
[responseDataSizes removeObjectAtIndex:0];
|
|
1975
|
-
}
|
|
1976
|
-
// We only wrote part of the range - there may be more, or there may be more ranges
|
|
1977
|
-
[self continueSendingMultiRangeResponseBody];
|
|
1978
|
-
}
|
|
1979
|
-
else if (tag == HTTP_RESPONSE || tag == HTTP_FINAL_RESPONSE)
|
|
1980
|
-
{
|
|
1981
|
-
// Update the amount of data we have in asyncSocket's write queue
|
|
1982
|
-
if ([responseDataSizes count] > 0)
|
|
1983
|
-
{
|
|
1984
|
-
[responseDataSizes removeObjectAtIndex:0];
|
|
1985
|
-
}
|
|
1986
|
-
|
|
1987
|
-
doneSendingResponse = YES;
|
|
1988
|
-
}
|
|
1989
|
-
|
|
1990
|
-
if (doneSendingResponse)
|
|
1991
|
-
{
|
|
1992
|
-
// Inform the http response that we're done
|
|
1993
|
-
if ([httpResponse respondsToSelector:@selector(connectionDidClose)])
|
|
1994
|
-
{
|
|
1995
|
-
[httpResponse connectionDidClose];
|
|
1996
|
-
}
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
if (tag == HTTP_FINAL_RESPONSE)
|
|
2000
|
-
{
|
|
2001
|
-
// Cleanup after the last request
|
|
2002
|
-
[self finishResponse];
|
|
2003
|
-
|
|
2004
|
-
// Terminate the connection
|
|
2005
|
-
[asyncSocket disconnect];
|
|
2006
|
-
|
|
2007
|
-
// Explictly return to ensure we don't do anything after the socket disconnects
|
|
2008
|
-
return;
|
|
2009
|
-
}
|
|
2010
|
-
else
|
|
2011
|
-
{
|
|
2012
|
-
if ([self shouldDie])
|
|
2013
|
-
{
|
|
2014
|
-
// Cleanup after the last request
|
|
2015
|
-
// Note: Don't do this before calling shouldDie, as it needs the request object still.
|
|
2016
|
-
[self finishResponse];
|
|
2017
|
-
|
|
2018
|
-
// The only time we should invoke [self die] is from socketDidDisconnect,
|
|
2019
|
-
// or if the socket gets taken over by someone else like a WebSocket.
|
|
2020
|
-
|
|
2021
|
-
[asyncSocket disconnect];
|
|
2022
|
-
}
|
|
2023
|
-
else
|
|
2024
|
-
{
|
|
2025
|
-
// Cleanup after the last request
|
|
2026
|
-
[self finishResponse];
|
|
2027
|
-
|
|
2028
|
-
// Prepare for the next request
|
|
2029
|
-
|
|
2030
|
-
// If this assertion fails, it likely means you overrode the
|
|
2031
|
-
// finishBody method and forgot to call [super finishBody].
|
|
2032
|
-
NSAssert(request == nil, @"Request not properly released in finishBody");
|
|
2033
|
-
|
|
2034
|
-
request = [[HTTPMessage alloc] initEmptyRequest];
|
|
2035
|
-
|
|
2036
|
-
numHeaderLines = 0;
|
|
2037
|
-
sentResponseHeaders = NO;
|
|
2038
|
-
|
|
2039
|
-
// And start listening for more requests
|
|
2040
|
-
[self startReadingRequest];
|
|
2041
|
-
}
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
}
|
|
2045
|
-
|
|
2046
|
-
/**
|
|
2047
|
-
* Sent after the socket has been disconnected.
|
|
2048
|
-
**/
|
|
2049
|
-
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
|
|
2050
|
-
{
|
|
2051
|
-
HTTPLogTrace();
|
|
2052
|
-
|
|
2053
|
-
asyncSocket = nil;
|
|
2054
|
-
|
|
2055
|
-
[self die];
|
|
2056
|
-
}
|
|
2057
|
-
|
|
2058
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
2059
|
-
#pragma mark HTTPResponse Notifications
|
|
2060
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
2061
|
-
|
|
2062
|
-
/**
|
|
2063
|
-
* This method may be called by asynchronous HTTPResponse objects.
|
|
2064
|
-
* That is, HTTPResponse objects that return YES in their "- (BOOL)isAsynchronous" method.
|
|
2065
|
-
*
|
|
2066
|
-
* This informs us that the response object has generated more data that we may be able to send.
|
|
2067
|
-
**/
|
|
2068
|
-
- (void)responseHasAvailableData:(NSObject<HTTPResponse> *)sender
|
|
2069
|
-
{
|
|
2070
|
-
HTTPLogTrace();
|
|
2071
|
-
|
|
2072
|
-
// We always dispatch this asynchronously onto our connectionQueue,
|
|
2073
|
-
// even if the connectionQueue is the current queue.
|
|
2074
|
-
//
|
|
2075
|
-
// We do this to give the HTTPResponse classes the flexibility to call
|
|
2076
|
-
// this method whenever they want, even from within a readDataOfLength method.
|
|
2077
|
-
|
|
2078
|
-
dispatch_async(connectionQueue, ^{ @autoreleasepool {
|
|
2079
|
-
|
|
2080
|
-
if (sender != httpResponse)
|
|
2081
|
-
{
|
|
2082
|
-
HTTPLogWarn(@"%@[%p]: %@ - Sender is not current httpResponse", THIS_FILE, self, THIS_METHOD);
|
|
2083
|
-
return;
|
|
2084
|
-
}
|
|
2085
|
-
|
|
2086
|
-
if (!sentResponseHeaders)
|
|
2087
|
-
{
|
|
2088
|
-
[self sendResponseHeadersAndBody];
|
|
2089
|
-
}
|
|
2090
|
-
else
|
|
2091
|
-
{
|
|
2092
|
-
if (ranges == nil)
|
|
2093
|
-
{
|
|
2094
|
-
[self continueSendingStandardResponseBody];
|
|
2095
|
-
}
|
|
2096
|
-
else
|
|
2097
|
-
{
|
|
2098
|
-
if ([ranges count] == 1)
|
|
2099
|
-
[self continueSendingSingleRangeResponseBody];
|
|
2100
|
-
else
|
|
2101
|
-
[self continueSendingMultiRangeResponseBody];
|
|
2102
|
-
}
|
|
2103
|
-
}
|
|
2104
|
-
}});
|
|
2105
|
-
}
|
|
2106
|
-
|
|
2107
|
-
/**
|
|
2108
|
-
* This method is called if the response encounters some critical error,
|
|
2109
|
-
* and it will be unable to fullfill the request.
|
|
2110
|
-
**/
|
|
2111
|
-
- (void)responseDidAbort:(NSObject<HTTPResponse> *)sender
|
|
2112
|
-
{
|
|
2113
|
-
HTTPLogTrace();
|
|
2114
|
-
|
|
2115
|
-
// We always dispatch this asynchronously onto our connectionQueue,
|
|
2116
|
-
// even if the connectionQueue is the current queue.
|
|
2117
|
-
//
|
|
2118
|
-
// We do this to give the HTTPResponse classes the flexibility to call
|
|
2119
|
-
// this method whenever they want, even from within a readDataOfLength method.
|
|
2120
|
-
|
|
2121
|
-
dispatch_async(connectionQueue, ^{ @autoreleasepool {
|
|
2122
|
-
|
|
2123
|
-
if (sender != httpResponse)
|
|
2124
|
-
{
|
|
2125
|
-
HTTPLogWarn(@"%@[%p]: %@ - Sender is not current httpResponse", THIS_FILE, self, THIS_METHOD);
|
|
2126
|
-
return;
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
|
-
[asyncSocket disconnectAfterWriting];
|
|
2130
|
-
}});
|
|
2131
|
-
}
|
|
2132
|
-
|
|
2133
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
2134
|
-
#pragma mark Post Request
|
|
2135
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
2136
|
-
|
|
2137
|
-
/**
|
|
2138
|
-
* This method is called after each response has been fully sent.
|
|
2139
|
-
* Since a single connection may handle multiple request/responses, this method may be called multiple times.
|
|
2140
|
-
* That is, it will be called after completion of each response.
|
|
2141
|
-
**/
|
|
2142
|
-
- (void)finishResponse
|
|
2143
|
-
{
|
|
2144
|
-
HTTPLogTrace();
|
|
2145
|
-
|
|
2146
|
-
// Override me if you want to perform any custom actions after a response has been fully sent.
|
|
2147
|
-
// This is the place to release memory or resources associated with the last request.
|
|
2148
|
-
//
|
|
2149
|
-
// If you override this method, you should take care to invoke [super finishResponse] at some point.
|
|
2150
|
-
|
|
2151
|
-
request = nil;
|
|
2152
|
-
|
|
2153
|
-
httpResponse = nil;
|
|
2154
|
-
|
|
2155
|
-
ranges = nil;
|
|
2156
|
-
ranges_headers = nil;
|
|
2157
|
-
ranges_boundry = nil;
|
|
2158
|
-
}
|
|
2159
|
-
|
|
2160
|
-
/**
|
|
2161
|
-
* This method is called after each successful response has been fully sent.
|
|
2162
|
-
* It determines whether the connection should stay open and handle another request.
|
|
2163
|
-
**/
|
|
2164
|
-
- (BOOL)shouldDie
|
|
2165
|
-
{
|
|
2166
|
-
HTTPLogTrace();
|
|
2167
|
-
|
|
2168
|
-
// Override me if you have any need to force close the connection.
|
|
2169
|
-
// You may do so by simply returning YES.
|
|
2170
|
-
//
|
|
2171
|
-
// If you override this method, you should take care to fall through with [super shouldDie]
|
|
2172
|
-
// instead of returning NO.
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
BOOL shouldDie = NO;
|
|
2176
|
-
|
|
2177
|
-
NSString *version = [request version];
|
|
2178
|
-
if ([version isEqualToString:HTTPVersion1_1])
|
|
2179
|
-
{
|
|
2180
|
-
// HTTP version 1.1
|
|
2181
|
-
// Connection should only be closed if request included "Connection: close" header
|
|
2182
|
-
|
|
2183
|
-
NSString *connection = [request headerField:@"Connection"];
|
|
2184
|
-
|
|
2185
|
-
shouldDie = (connection && ([connection caseInsensitiveCompare:@"close"] == NSOrderedSame));
|
|
2186
|
-
}
|
|
2187
|
-
else if ([version isEqualToString:HTTPVersion1_0])
|
|
2188
|
-
{
|
|
2189
|
-
// HTTP version 1.0
|
|
2190
|
-
// Connection should be closed unless request included "Connection: Keep-Alive" header
|
|
2191
|
-
|
|
2192
|
-
NSString *connection = [request headerField:@"Connection"];
|
|
2193
|
-
|
|
2194
|
-
if (connection == nil)
|
|
2195
|
-
shouldDie = YES;
|
|
2196
|
-
else
|
|
2197
|
-
shouldDie = [connection caseInsensitiveCompare:@"Keep-Alive"] != NSOrderedSame;
|
|
2198
|
-
}
|
|
2199
|
-
|
|
2200
|
-
return shouldDie;
|
|
2201
|
-
}
|
|
2202
|
-
|
|
2203
|
-
- (void)die
|
|
2204
|
-
{
|
|
2205
|
-
HTTPLogTrace();
|
|
2206
|
-
|
|
2207
|
-
// Override me if you want to perform any custom actions when a connection is closed.
|
|
2208
|
-
// Then call [super die] when you're done.
|
|
2209
|
-
//
|
|
2210
|
-
// See also the finishResponse method.
|
|
2211
|
-
//
|
|
2212
|
-
// Important: There is a rare timing condition where this method might get invoked twice.
|
|
2213
|
-
// If you override this method, you should be prepared for this situation.
|
|
2214
|
-
|
|
2215
|
-
// Inform the http response that we're done
|
|
2216
|
-
if ([httpResponse respondsToSelector:@selector(connectionDidClose)])
|
|
2217
|
-
{
|
|
2218
|
-
[httpResponse connectionDidClose];
|
|
2219
|
-
}
|
|
2220
|
-
|
|
2221
|
-
// Release the http response so we don't call it's connectionDidClose method again in our dealloc method
|
|
2222
|
-
httpResponse = nil;
|
|
2223
|
-
|
|
2224
|
-
// Post notification of dead connection
|
|
2225
|
-
// This will allow our server to release us from its array of connections
|
|
2226
|
-
[[NSNotificationCenter defaultCenter] postNotificationName:HTTPConnectionDidDieNotification object:self];
|
|
2227
|
-
}
|
|
2228
|
-
|
|
2229
|
-
@end
|
|
2230
|
-
|
|
2231
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
2232
|
-
#pragma mark -
|
|
2233
|
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
2234
|
-
|
|
2235
|
-
@implementation HTTPConfig
|
|
2236
|
-
|
|
2237
|
-
@synthesize server;
|
|
2238
|
-
@synthesize documentRoot;
|
|
2239
|
-
@synthesize queue;
|
|
2240
|
-
|
|
2241
|
-
- (id)initWithServer:(HTTPServer *)aServer documentRoot:(NSString *)aDocumentRoot
|
|
2242
|
-
{
|
|
2243
|
-
if ((self = [super init]))
|
|
2244
|
-
{
|
|
2245
|
-
server = aServer;
|
|
2246
|
-
documentRoot = aDocumentRoot;
|
|
2247
|
-
}
|
|
2248
|
-
return self;
|
|
2249
|
-
}
|
|
2250
|
-
|
|
2251
|
-
- (id)initWithServer:(HTTPServer *)aServer documentRoot:(NSString *)aDocumentRoot queue:(dispatch_queue_t)q
|
|
2252
|
-
{
|
|
2253
|
-
if ((self = [super init]))
|
|
2254
|
-
{
|
|
2255
|
-
server = aServer;
|
|
2256
|
-
|
|
2257
|
-
documentRoot = [aDocumentRoot stringByStandardizingPath];
|
|
2258
|
-
if ([documentRoot hasSuffix:@"/"])
|
|
2259
|
-
{
|
|
2260
|
-
documentRoot = [documentRoot stringByAppendingString:@"/"];
|
|
2261
|
-
}
|
|
2262
|
-
|
|
2263
|
-
if (q)
|
|
2264
|
-
{
|
|
2265
|
-
queue = q;
|
|
2266
|
-
#if !OS_OBJECT_USE_OBJC
|
|
2267
|
-
dispatch_retain(queue);
|
|
2268
|
-
#endif
|
|
2269
|
-
}
|
|
2270
|
-
}
|
|
2271
|
-
return self;
|
|
2272
|
-
}
|
|
2273
|
-
|
|
2274
|
-
- (void)dealloc
|
|
2275
|
-
{
|
|
2276
|
-
#if !OS_OBJECT_USE_OBJC
|
|
2277
|
-
if (queue) dispatch_release(queue);
|
|
2278
|
-
#endif
|
|
2279
|
-
}
|
|
2280
|
-
|
|
2281
|
-
@end
|