PageRenderTime 72ms CodeModel.GetById 2ms app.highlight 66ms RepoModel.GetById 1ms app.codeStats 0ms

/core/externals/update-engine/externals/gdata-objectivec-client/Source/HTTPFetcher/Tests/GTMHTTPFetcherTestServer.m

http://macfuse.googlecode.com/
Objective C | 369 lines | 250 code | 40 blank | 79 comment | 67 complexity | 18b9b5649cdba921a060b2d9b2eff38c MD5 | raw file
  1/* Copyright (c) 2010 Google Inc.
  2 *
  3 * Licensed under the Apache License, Version 2.0 (the "License");
  4 * you may not use this file except in compliance with the License.
  5 * You may obtain a copy of the License at
  6 *
  7 *     http://www.apache.org/licenses/LICENSE-2.0
  8 *
  9 * Unless required by applicable law or agreed to in writing, software
 10 * distributed under the License is distributed on an "AS IS" BASIS,
 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 * See the License for the specific language governing permissions and
 13 * limitations under the License.
 14 */
 15
 16//
 17//  GTMHTTPFetcherTestServer.m
 18//
 19
 20#import "GTMHTTPFetcherTestServer.h"
 21
 22@interface GTMHTTPFetcherTestServer ()
 23- (NSString *)valueForParameter:(NSString *)paramName query:(NSString *)query;
 24@end
 25
 26@interface GTMSBJSON
 27- (id)objectWithString:(NSString*)jsonrep error:(NSError**)error;
 28@end
 29
 30@interface GTMNSJSONSerialization : NSObject
 31+ (id)JSONObjectWithData:(NSData *)data options:(NSUInteger)opt error:(NSError **)error;
 32@end
 33
 34@implementation GTMHTTPFetcherTestServer
 35
 36- (id)initWithDocRoot:(NSString *)docRoot {
 37  self = [super init];
 38  if (self) {
 39    docRoot_ = [docRoot copy];
 40    server_ = [[GTMHTTPServer alloc] initWithDelegate:self];
 41    NSError *error = nil;
 42    if ((docRoot == nil) || (![server_ start:&error])) {
 43      NSLog(@"Failed to start up the GTMHTTPFetcherTestServer "
 44            "(docRoot='%@', error=%@)", docRoot_, error);
 45      [self release];
 46      return nil;
 47    } else {
 48      NSLog(@"Started GTMHTTPFetcherTestServer on port %d (docRoot='%@')",
 49            [server_ port], docRoot_);
 50    }
 51  }
 52  return self;
 53}
 54
 55- (void)stopServer {
 56  if (server_) {
 57    NSLog(@"Stopped GTMHTTPFetcherTestServer on port %d (docRoot='%@')",
 58          [server_ port], docRoot_);
 59    [server_ release];
 60    server_ = nil;
 61
 62    [docRoot_ release];
 63    docRoot_ = nil;
 64  }
 65}
 66
 67- (void)finalize {
 68  [self stopServer];
 69  [super finalize];
 70}
 71
 72- (void)dealloc {
 73  [self stopServer];
 74  [super dealloc];
 75}
 76
 77- (uint16_t)port {
 78  return [server_ port];
 79}
 80
 81- (id)JSONFromData:(NSData *)data {
 82  NSError *error = nil;
 83  const NSUInteger kOpts = NSJSONReadingMutableContainers;
 84  id obj = [NSJSONSerialization JSONObjectWithData:data
 85                                           options:kOpts
 86                                             error:&error];
 87  if (obj == nil) {
 88    NSString *jsonStr = [[[NSString alloc] initWithData:data
 89                                               encoding:NSUTF8StringEncoding] autorelease];
 90    NSLog(@"JSON parse error: %@\n  for JSON string: %@",
 91          error, jsonStr);
 92  }
 93  return obj;
 94}
 95
 96- (GTMHTTPResponseMessage *)httpServer:(GTMHTTPServer *)server
 97                         handleRequest:(GTMHTTPRequestMessage *)request {
 98  NSAssert(server == server_, @"how'd we get a different server?!");
 99
100  GTMHTTPResponseMessage *response;
101  int resultStatus = 0;
102  NSData *data = nil;
103  NSMutableDictionary *responseHeaders = [NSMutableDictionary dictionary];
104
105  NSString *etag = @"GoodETag";
106
107  NSDictionary *requestHeaders = [request allHeaderFieldValues];
108  NSString *ifMatch = [requestHeaders objectForKey:@"If-Match"];
109  NSString *ifNoneMatch = [requestHeaders objectForKey:@"If-None-Match"];
110  NSString *authorization = [requestHeaders objectForKey:@"Authorization"];
111  NSString *path = [[request URL] path];
112  NSString *query = [[request URL] query];
113
114  NSString *method = [request method];
115
116  // Code for future testing of the chunked-upload protocol
117  //
118  // This code is not yet tested
119  NSString *host = [requestHeaders objectForKey:@"Host"];
120  NSString *contentRange = [requestHeaders objectForKey:@"Content-Range"];
121
122  // chunked (resumable) upload testing
123  if ([[path pathExtension] isEqual:@"location"]) {
124
125    // return a location header containing the request path with
126    // the ".location" suffix changed to ".upload"
127    NSString *pathWithoutLoc = [path stringByDeletingPathExtension];
128    NSString *fullLocation = [NSString stringWithFormat:@"http://%@%@.upload",
129                              host, pathWithoutLoc];
130
131    [responseHeaders setValue:fullLocation forKey:@"Location"];
132    resultStatus = 200;
133    goto SendResponse;
134  } else if ([[path pathExtension] isEqual:@"upload"]) {
135    // chunked (resumable) upload testing
136
137    // if the contentRange indicates this is a middle chunk,
138    // return status 308 with a Range header; otherwise, strip
139    // the ".upload" and continue to return the file
140    //
141    // contentRange is like
142    //  Content-Range: bytes 0-49999/135681
143    // or
144    //  Content-Range: bytes * /135681
145    NSScanner *crScanner = [NSScanner scannerWithString:contentRange];
146    long long totalToUpload = 0;
147    if ([crScanner scanString:@"bytes */" intoString:NULL]
148        && [crScanner scanLongLong:&totalToUpload]) {
149      if (totalToUpload > 0) {
150        // this is a query for where to resume; we'll arbitrarily resume at
151        // half the total length of the upload
152        long long resumeLocation = totalToUpload / 2;
153        NSString *range = [NSString stringWithFormat:@"bytes=0-%lld",
154                           resumeLocation];
155        [responseHeaders setValue:range forKey:@"Range"];
156        resultStatus = 308;
157        goto SendResponse;
158      } else {
159        // the upload is empty so this is the final chunk; remove the ".upload" at the end and
160        // fall through to return the requested resource at the path
161        path = [path stringByDeletingPathExtension];
162      }
163    } else {
164      long long rangeLow = 0;
165      long long rangeHigh = 0;
166      if ([crScanner scanString:@"bytes " intoString:nil]
167          && [crScanner scanLongLong:&rangeLow]
168          && [crScanner scanString:@"-" intoString:NULL]
169          && [crScanner scanLongLong:&rangeHigh]
170          && [crScanner scanString:@"/" intoString:NULL]
171          && [crScanner scanLongLong:&totalToUpload]) {
172        // a chunk request
173        if ((rangeHigh + 1) < totalToUpload) {
174          // this is a middle chunk, so send a 308 status to ask for more chunks
175          NSString *range = [NSString stringWithFormat:@"bytes=0-%lld",
176                             rangeHigh];
177          [responseHeaders setValue:range forKey:@"Range"];
178          resultStatus = 308;
179          goto SendResponse;
180        } else {
181          // this is the final chunk; remove the ".upload" at the end and
182          // fall through to return the requested resource at the path
183          path = [path stringByDeletingPathExtension];
184        }
185      }
186    }
187  }
188
189  // if there's an "auth=foo" query parameter, then the value of the
190  // Authorization header should be "foo"
191  NSString *authStr = [self valueForParameter:@"oauth2" query:query];
192  if (authStr) {
193    NSString *bearerStr = [@"Bearer " stringByAppendingString:authStr];
194    if (authorization == nil
195        || ![authorization isEqualToString:bearerStr]) {
196      // return status 401 Unauthorized
197      NSString *errStr = [NSString stringWithFormat:@"Authorization \"%@\" should be \"%@\"",
198                          authorization, bearerStr];
199      NSData *errData = [errStr dataUsingEncoding:NSUTF8StringEncoding];
200      GTMHTTPResponseMessage *response;
201      response = [GTMHTTPResponseMessage responseWithBody:errData
202                                              contentType:@"text/plain"
203                                               statusCode:401];
204      return response;
205    }
206  }
207
208  NSString *statusStr = [self valueForParameter:@"status" query:query];
209  if (statusStr) {
210    // queries that have something like "?status=456" should fail with the
211    // status code
212    resultStatus = [statusStr intValue];
213
214    NSString *const template = @"{ \"error\" : { \"message\" : \"Server Status %u\","
215                         @" \"code\" : %u } }";
216    NSString *errorStr = [NSString stringWithFormat:template,
217                          resultStatus, resultStatus];
218    data = [errorStr dataUsingEncoding:NSUTF8StringEncoding];
219  } else {
220    NSString *sleepStr = [self valueForParameter:@"sleep" query:query];
221    if (sleepStr) {
222      NSTimeInterval interval = [sleepStr doubleValue];
223      [NSThread sleepForTimeInterval:interval];
224      resultStatus = 408; // request timeout
225    } else if (ifMatch != nil && ![ifMatch isEqual:etag]) {
226      // there is no match, hence this is an inconsistent PUT or DELETE
227      resultStatus = 412; // precondition failed
228    } else if (ifNoneMatch != nil && [ifNoneMatch isEqual:etag]) {
229      // there is a match, hence this is a repetitive request
230      if ([method isEqual:@"GET"] || [method isEqual:@"HEAD"]) {
231        resultStatus = 304; // not modified
232      } else {
233        resultStatus = 412; // precondition failed
234      }
235    } else if ([method isEqualToString:@"DELETE"]) {
236      // it's an object delete; return empty data
237      resultStatus = 200;
238    } else {
239      if ([path hasSuffix:@".rpc"]) {
240        // JSON-RPC tests
241        //
242        // the fetch file name is like Foo.rpc; there should be local files
243        // with the expected JSON request body, and the response body
244        //
245        // replace the .rpc suffix with .request.txt and .response.txt
246        NSString *withoutRpcExtn = [path stringByDeletingPathExtension];
247        NSString *requestName = [withoutRpcExtn stringByAppendingPathExtension:@"request.txt"];
248        NSString *responseName = [withoutRpcExtn stringByAppendingPathExtension:@"response.txt"];
249
250        // read the expected request body from disk
251        NSString *requestPath = [docRoot_ stringByAppendingPathComponent:requestName];
252        NSData *requestData = [NSData dataWithContentsOfFile:requestPath];
253        if (!requestData) {
254          // we need a query request file for rpc tests
255          NSLog(@"Cannot find query request file \"%@\"", requestPath);
256        } else {
257          // verify that the RPC request body is as expected
258          NSDictionary *expectedJSON = [self JSONFromData:requestData];
259          NSDictionary *requestJSON = [self JSONFromData:[request body]];
260
261          if (expectedJSON && requestJSON
262              && [requestJSON isEqual:expectedJSON]) {
263            // the request body matches
264            //
265            // for rpc, the response file ought to be here;
266            // 404s shouldn't happen
267            NSString *responsePath = [docRoot_ stringByAppendingPathComponent:responseName];
268            if ([[NSFileManager defaultManager] fileExistsAtPath:responsePath]) {
269              path = responseName;
270            } else {
271              NSLog(@"Cannot find query response file \"%@\"", responsePath);
272            }
273          } else {
274            // the actual request did not match the expected request
275            //
276            // note that the requests may be dictionaries or arrays
277            NSLog(@"Mismatched request body for \"%@\"", path);
278            NSLog(@"\n--------\nExpected request:\n%@", expectedJSON);
279            NSLog(@"\n--------\nActual request:\n%@", requestJSON);
280          }
281        }
282      }
283
284      // read and return the document from the path, or status 404 for not found
285      NSString *docPath = [docRoot_ stringByAppendingPathComponent:path];
286      data = [NSData dataWithContentsOfFile:docPath];
287      if (data) {
288        resultStatus = 200;
289      } else {
290        resultStatus = 404;
291      }
292    }
293  }
294
295  if ([method isEqual:@"GET"]) {
296    [responseHeaders setValue:etag forKey:@"Etag"];
297  }
298
299  NSString *cookie = [NSString stringWithFormat:@"TestCookie=%@",
300                      [path lastPathComponent]];
301  [responseHeaders setValue:cookie forKey:@"Set-Cookie"];
302
303  //
304  // Finally, package up the response, and return it to the client
305  //
306SendResponse:
307  response = [GTMHTTPResponseMessage responseWithBody:data
308                                          contentType:@"application/json"
309                                           statusCode:resultStatus];
310
311  [response setHeaderValuesFromDictionary:responseHeaders];
312
313  return response;
314}
315
316- (NSString *)valueForParameter:(NSString *)paramName query:(NSString *)query {
317  if (!query) return nil;
318
319  // search the query for a parameter beginning with "paramName=" and
320  // ending with & or the end-of-string
321  NSString *result = nil;
322  NSString *paramWithEquals = [paramName stringByAppendingString:@"="];
323  NSRange paramNameRange = [query rangeOfString:paramWithEquals];
324  if (paramNameRange.location != NSNotFound) {
325    // we found the param name; find the end of the parameter
326    NSCharacterSet *endSet = [NSCharacterSet characterSetWithCharactersInString:@"&\n"];
327    NSUInteger startOfParam = paramNameRange.location + paramNameRange.length;
328    NSRange endSearchRange = NSMakeRange(startOfParam,
329                                         [query length] - startOfParam);
330    NSRange endRange = [query rangeOfCharacterFromSet:endSet
331                                              options:0
332                                                range:endSearchRange];
333    if (endRange.location == NSNotFound) {
334      // param goes to end of string
335      result = [query substringFromIndex:startOfParam];
336    } else {
337      // found and end marker
338      NSUInteger paramLen = endRange.location - startOfParam;
339      NSRange foundRange = NSMakeRange(startOfParam, paramLen);
340      result = [query substringWithRange:foundRange];
341    }
342  } else {
343    // param not found
344  }
345  return result;
346}
347
348// utilities for users
349- (NSURL *)localURLForFile:(NSString *)name {
350  // we need to create http URLs referring to the desired
351  // resource to be found by the http server running locally
352
353  // return a localhost:port URL for the test file
354  NSString *urlString = [NSString stringWithFormat:@"http://localhost:%d/%@",
355                         [self port], name];
356  return [NSURL URLWithString:urlString];
357}
358
359- (NSString *)localPathForFile:(NSString *)name {
360  // we exclude parameters
361  NSRange range = [name rangeOfString:@"?"];
362  if (range.location != NSNotFound) {
363    name = [name substringToIndex:range.location];
364  }
365  NSString *filePath = [docRoot_ stringByAppendingPathComponent:name];
366  return filePath;
367}
368
369@end