PageRenderTime 55ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 1ms

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