PageRenderTime 54ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/WebServiceDemo/Required_Libs/HTTPRequest/Classes/ASIWebPageRequest/ASIWebPageRequest.m

https://gitlab.com/praveenvelanati/ios-demo
Objective C | 722 lines | 595 code | 75 blank | 52 comment | 203 complexity | aca610403c6ff600714fed188e64ee09 MD5 | raw file
  1. //
  2. // ASIWebPageRequest.m
  3. // Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
  4. //
  5. // Created by Ben Copsey on 29/06/2010.
  6. // Copyright 2010 All-Seeing Interactive. All rights reserved.
  7. //
  8. // This is an EXPERIMENTAL class - use at your own risk!
  9. #import "ASIWebPageRequest.h"
  10. #import "ASINetworkQueue.h"
  11. #import <CommonCrypto/CommonHMAC.h>
  12. #import <libxml/HTMLparser.h>
  13. #import <libxml/xmlsave.h>
  14. #import <libxml/xpath.h>
  15. #import <libxml/xpathInternals.h>
  16. // An xPath query that controls the external resources ASIWebPageRequest will fetch
  17. // By default, it will fetch stylesheets, javascript files, images, frames, iframes, and html 5 video / audio
  18. static xmlChar *xpathExpr = (xmlChar *)"//link/@href|//a/@href|//script/@src|//img/@src|//frame/@src|//iframe/@src|//style|//*/@style|//source/@src|//video/@poster|//audio/@src";
  19. static NSLock *xmlParsingLock = nil;
  20. static NSMutableArray *requestsUsingXMLParser = nil;
  21. @interface ASIWebPageRequest ()
  22. - (void)readResourceURLs;
  23. - (void)updateResourceURLs;
  24. - (void)parseAsHTML;
  25. - (void)parseAsCSS;
  26. - (void)addURLToFetch:(NSString *)newURL;
  27. + (NSArray *)CSSURLsFromString:(NSString *)string;
  28. - (NSString *)relativePathTo:(NSString *)destinationPath fromPath:(NSString *)sourcePath;
  29. - (void)finishedFetchingExternalResources:(ASINetworkQueue *)queue;
  30. - (void)externalResourceFetchSucceeded:(ASIHTTPRequest *)externalResourceRequest;
  31. - (void)externalResourceFetchFailed:(ASIHTTPRequest *)externalResourceRequest;
  32. @property (retain, nonatomic) ASINetworkQueue *externalResourceQueue;
  33. @property (retain, nonatomic) NSMutableDictionary *resourceList;
  34. @end
  35. @implementation ASIWebPageRequest
  36. + (void)initialize
  37. {
  38. if (self == [ASIWebPageRequest class]) {
  39. xmlParsingLock = [[NSLock alloc] init];
  40. requestsUsingXMLParser = [[NSMutableArray alloc] init];
  41. }
  42. }
  43. - (id)initWithURL:(NSURL *)newURL
  44. {
  45. self = [super initWithURL:newURL];
  46. [self setShouldIgnoreExternalResourceErrors:YES];
  47. return self;
  48. }
  49. - (void)dealloc
  50. {
  51. [externalResourceQueue cancelAllOperations];
  52. [externalResourceQueue release];
  53. [resourceList release];
  54. [parentRequest release];
  55. [super dealloc];
  56. }
  57. // This is a bit of a hack
  58. // The role of this method in normal ASIHTTPRequests is to tell the queue we are done with the request, and perform some cleanup
  59. // We override it to stop that happening, and instead do that work in the bottom of finishedFetchingExternalResources:
  60. - (void)markAsFinished
  61. {
  62. if ([self error]) {
  63. [super markAsFinished];
  64. }
  65. }
  66. // This method is normally responsible for telling delegates we are done, but it happens to be the most convenient place to parse the responses
  67. // Again, we call the super implementation in finishedFetchingExternalResources:, or here if this download was not an HTML or CSS file
  68. - (void)requestFinished
  69. {
  70. complete = NO;
  71. if ([self mainRequest] || [self didUseCachedResponse]) {
  72. [super requestFinished];
  73. [super markAsFinished];
  74. return;
  75. }
  76. webContentType = ASINotParsedWebContentType;
  77. NSString *contentType = [[[self responseHeaders] objectForKey:@"Content-Type"] lowercaseString];
  78. contentType = [[contentType componentsSeparatedByString:@";"] objectAtIndex:0];
  79. if ([contentType isEqualToString:@"text/html"] || [contentType isEqualToString:@"text/xhtml"] || [contentType isEqualToString:@"text/xhtml+xml"] || [contentType isEqualToString:@"application/xhtml+xml"]) {
  80. [self parseAsHTML];
  81. return;
  82. } else if ([contentType isEqualToString:@"text/css"]) {
  83. [self parseAsCSS];
  84. return;
  85. }
  86. [super requestFinished];
  87. [super markAsFinished];
  88. }
  89. - (void)parseAsCSS
  90. {
  91. webContentType = ASICSSWebContentType;
  92. NSString *responseCSS = nil;
  93. NSError *err = nil;
  94. if ([self downloadDestinationPath]) {
  95. responseCSS = [NSString stringWithContentsOfFile:[self downloadDestinationPath] encoding:[self responseEncoding] error:&err];
  96. } else {
  97. responseCSS = [self responseString];
  98. }
  99. if (err) {
  100. [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:100 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to read HTML string from response",NSLocalizedDescriptionKey,err,NSUnderlyingErrorKey,nil]]];
  101. return;
  102. } else if (!responseCSS) {
  103. [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:100 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to read HTML string from response",NSLocalizedDescriptionKey,nil]]];
  104. return;
  105. }
  106. NSArray *urls = [[self class] CSSURLsFromString:responseCSS];
  107. [self setResourceList:[NSMutableDictionary dictionary]];
  108. for (NSString *theURL in urls) {
  109. [self addURLToFetch:theURL];
  110. }
  111. if (![[self resourceList] count]) {
  112. [super requestFinished];
  113. [super markAsFinished];
  114. return;
  115. }
  116. // Create a new request for every item in the queue
  117. [[self externalResourceQueue] cancelAllOperations];
  118. [self setExternalResourceQueue:[ASINetworkQueue queue]];
  119. [[self externalResourceQueue] setDelegate:self];
  120. [[self externalResourceQueue] setShouldCancelAllRequestsOnFailure:[self shouldIgnoreExternalResourceErrors]];
  121. [[self externalResourceQueue] setShowAccurateProgress:[self showAccurateProgress]];
  122. [[self externalResourceQueue] setQueueDidFinishSelector:@selector(finishedFetchingExternalResources:)];
  123. [[self externalResourceQueue] setRequestDidFinishSelector:@selector(externalResourceFetchSucceeded:)];
  124. [[self externalResourceQueue] setRequestDidFailSelector:@selector(externalResourceFetchFailed:)];
  125. for (NSString *theURL in [[self resourceList] keyEnumerator]) {
  126. ASIWebPageRequest *externalResourceRequest = [ASIWebPageRequest requestWithURL:[NSURL URLWithString:theURL relativeToURL:[self url]]];
  127. [externalResourceRequest setRequestHeaders:[self requestHeaders]];
  128. [externalResourceRequest setDownloadCache:[self downloadCache]];
  129. [externalResourceRequest setCachePolicy:[self cachePolicy]];
  130. [externalResourceRequest setCacheStoragePolicy:[self cacheStoragePolicy]];
  131. [externalResourceRequest setUserInfo:[NSDictionary dictionaryWithObject:theURL forKey:@"Path"]];
  132. [externalResourceRequest setParentRequest:self];
  133. [externalResourceRequest setUrlReplacementMode:[self urlReplacementMode]];
  134. [externalResourceRequest setShouldResetDownloadProgress:NO];
  135. [externalResourceRequest setDelegate:self];
  136. [externalResourceRequest setUploadProgressDelegate:self];
  137. [externalResourceRequest setDownloadProgressDelegate:self];
  138. if ([self downloadDestinationPath]) {
  139. [externalResourceRequest setDownloadDestinationPath:[self cachePathForRequest:externalResourceRequest]];
  140. }
  141. [[self externalResourceQueue] addOperation:externalResourceRequest];
  142. }
  143. [[self externalResourceQueue] go];
  144. }
  145. - (const char *)encodingName
  146. {
  147. xmlCharEncoding encoding = XML_CHAR_ENCODING_NONE;
  148. switch ([self responseEncoding])
  149. {
  150. case NSASCIIStringEncoding:
  151. encoding = XML_CHAR_ENCODING_ASCII;
  152. break;
  153. case NSJapaneseEUCStringEncoding:
  154. encoding = XML_CHAR_ENCODING_EUC_JP;
  155. break;
  156. case NSUTF8StringEncoding:
  157. encoding = XML_CHAR_ENCODING_UTF8;
  158. break;
  159. case NSISOLatin1StringEncoding:
  160. encoding = XML_CHAR_ENCODING_8859_1;
  161. break;
  162. case NSShiftJISStringEncoding:
  163. encoding = XML_CHAR_ENCODING_SHIFT_JIS;
  164. break;
  165. case NSISOLatin2StringEncoding:
  166. encoding = XML_CHAR_ENCODING_8859_2;
  167. break;
  168. case NSISO2022JPStringEncoding:
  169. encoding = XML_CHAR_ENCODING_2022_JP;
  170. break;
  171. case NSUTF16BigEndianStringEncoding:
  172. encoding = XML_CHAR_ENCODING_UTF16BE;
  173. break;
  174. case NSUTF16LittleEndianStringEncoding:
  175. encoding = XML_CHAR_ENCODING_UTF16LE;
  176. break;
  177. case NSUTF32BigEndianStringEncoding:
  178. encoding = XML_CHAR_ENCODING_UCS4BE;
  179. break;
  180. case NSUTF32LittleEndianStringEncoding:
  181. encoding = XML_CHAR_ENCODING_UCS4LE;
  182. break;
  183. case NSNEXTSTEPStringEncoding:
  184. case NSSymbolStringEncoding:
  185. case NSNonLossyASCIIStringEncoding:
  186. case NSUnicodeStringEncoding:
  187. case NSMacOSRomanStringEncoding:
  188. case NSUTF32StringEncoding:
  189. default:
  190. encoding = XML_CHAR_ENCODING_ERROR;
  191. break;
  192. }
  193. return xmlGetCharEncodingName(encoding);
  194. }
  195. - (void)parseAsHTML
  196. {
  197. webContentType = ASIHTMLWebContentType;
  198. // Only allow parsing of a single document at a time
  199. [xmlParsingLock lock];
  200. if (![requestsUsingXMLParser count]) {
  201. xmlInitParser();
  202. }
  203. [requestsUsingXMLParser addObject:self];
  204. /* Load XML document */
  205. if ([self downloadDestinationPath]) {
  206. doc = htmlReadFile([[self downloadDestinationPath] cStringUsingEncoding:NSUTF8StringEncoding], [self encodingName], HTML_PARSE_NONET | HTML_PARSE_NOWARNING | HTML_PARSE_NOERROR);
  207. } else {
  208. NSData *data = [self responseData];
  209. doc = htmlReadMemory([data bytes], (int)[data length], "", [self encodingName], HTML_PARSE_NONET | HTML_PARSE_NOWARNING | HTML_PARSE_NOERROR);
  210. }
  211. if (doc == NULL) {
  212. [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to parse reponse XML",NSLocalizedDescriptionKey,nil]]];
  213. return;
  214. }
  215. [self setResourceList:[NSMutableDictionary dictionary]];
  216. // Populate the list of URLS to download
  217. [self readResourceURLs];
  218. if ([self error] || ![[self resourceList] count]) {
  219. [requestsUsingXMLParser removeObject:self];
  220. xmlFreeDoc(doc);
  221. doc = NULL;
  222. }
  223. [xmlParsingLock unlock];
  224. if ([self error]) {
  225. return;
  226. } else if (![[self resourceList] count]) {
  227. [super requestFinished];
  228. [super markAsFinished];
  229. return;
  230. }
  231. // Create a new request for every item in the queue
  232. [[self externalResourceQueue] cancelAllOperations];
  233. [self setExternalResourceQueue:[ASINetworkQueue queue]];
  234. [[self externalResourceQueue] setDelegate:self];
  235. [[self externalResourceQueue] setShouldCancelAllRequestsOnFailure:[self shouldIgnoreExternalResourceErrors]];
  236. [[self externalResourceQueue] setShowAccurateProgress:[self showAccurateProgress]];
  237. [[self externalResourceQueue] setQueueDidFinishSelector:@selector(finishedFetchingExternalResources:)];
  238. [[self externalResourceQueue] setRequestDidFinishSelector:@selector(externalResourceFetchSucceeded:)];
  239. [[self externalResourceQueue] setRequestDidFailSelector:@selector(externalResourceFetchFailed:)];
  240. for (NSString *theURL in [[self resourceList] keyEnumerator]) {
  241. ASIWebPageRequest *externalResourceRequest = [ASIWebPageRequest requestWithURL:[NSURL URLWithString:theURL relativeToURL:[self url]]];
  242. [externalResourceRequest setRequestHeaders:[self requestHeaders]];
  243. [externalResourceRequest setDownloadCache:[self downloadCache]];
  244. [externalResourceRequest setCachePolicy:[self cachePolicy]];
  245. [externalResourceRequest setCacheStoragePolicy:[self cacheStoragePolicy]];
  246. [externalResourceRequest setUserInfo:[NSDictionary dictionaryWithObject:theURL forKey:@"Path"]];
  247. [externalResourceRequest setParentRequest:self];
  248. [externalResourceRequest setUrlReplacementMode:[self urlReplacementMode]];
  249. [externalResourceRequest setShouldResetDownloadProgress:NO];
  250. [externalResourceRequest setDelegate:self];
  251. [externalResourceRequest setUploadProgressDelegate:self];
  252. [externalResourceRequest setDownloadProgressDelegate:self];
  253. if ([self downloadDestinationPath]) {
  254. [externalResourceRequest setDownloadDestinationPath:[self cachePathForRequest:externalResourceRequest]];
  255. }
  256. [[self externalResourceQueue] addOperation:externalResourceRequest];
  257. }
  258. [[self externalResourceQueue] go];
  259. }
  260. - (void)externalResourceFetchSucceeded:(ASIHTTPRequest *)externalResourceRequest
  261. {
  262. NSString *originalPath = [[externalResourceRequest userInfo] objectForKey:@"Path"];
  263. NSMutableDictionary *requestResponse = [[self resourceList] objectForKey:originalPath];
  264. NSString *contentType = [[externalResourceRequest responseHeaders] objectForKey:@"Content-Type"];
  265. if (!contentType) {
  266. contentType = @"application/octet-stream";
  267. }
  268. [requestResponse setObject:contentType forKey:@"ContentType"];
  269. if ([self downloadDestinationPath]) {
  270. [requestResponse setObject:[externalResourceRequest downloadDestinationPath] forKey:@"DataPath"];
  271. } else {
  272. NSData *data = [externalResourceRequest responseData];
  273. if (data) {
  274. [requestResponse setObject:data forKey:@"Data"];
  275. }
  276. }
  277. }
  278. - (void)externalResourceFetchFailed:(ASIHTTPRequest *)externalResourceRequest
  279. {
  280. if ([[self externalResourceQueue] shouldCancelAllRequestsOnFailure]) {
  281. [self failWithError:[externalResourceRequest error]];
  282. }
  283. }
  284. - (void)finishedFetchingExternalResources:(ASINetworkQueue *)queue
  285. {
  286. if ([self urlReplacementMode] != ASIDontModifyURLs) {
  287. if (webContentType == ASICSSWebContentType) {
  288. NSMutableString *parsedResponse;
  289. NSError *err = nil;
  290. if ([self downloadDestinationPath]) {
  291. parsedResponse = [NSMutableString stringWithContentsOfFile:[self downloadDestinationPath] encoding:[self responseEncoding] error:&err];
  292. } else {
  293. parsedResponse = [[[self responseString] mutableCopy] autorelease];
  294. }
  295. if (err) {
  296. [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to read response CSS from disk",NSLocalizedDescriptionKey,nil]]];
  297. return;
  298. }
  299. if (![self error]) {
  300. for (NSString *resource in [[self resourceList] keyEnumerator]) {
  301. if ([parsedResponse rangeOfString:resource].location != NSNotFound) {
  302. NSString *newURL = [self contentForExternalURL:resource];
  303. if (newURL) {
  304. [parsedResponse replaceOccurrencesOfString:resource withString:newURL options:0 range:NSMakeRange(0, [parsedResponse length])];
  305. }
  306. }
  307. }
  308. }
  309. if ([self downloadDestinationPath]) {
  310. [parsedResponse writeToFile:[self downloadDestinationPath] atomically:NO encoding:[self responseEncoding] error:&err];
  311. if (err) {
  312. [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to write response CSS to disk",NSLocalizedDescriptionKey,nil]]];
  313. return;
  314. }
  315. } else {
  316. [self setRawResponseData:(id)[parsedResponse dataUsingEncoding:[self responseEncoding]]];
  317. }
  318. } else {
  319. [xmlParsingLock lock];
  320. [self updateResourceURLs];
  321. if (![self error]) {
  322. // We'll use the xmlsave API so we can strip the xml declaration
  323. xmlSaveCtxtPtr saveContext;
  324. if ([self downloadDestinationPath]) {
  325. // Truncate the file first
  326. [[[[NSFileManager alloc] init] autorelease] createFileAtPath:[self downloadDestinationPath] contents:nil attributes:nil];
  327. saveContext = xmlSaveToFd([[NSFileHandle fileHandleForWritingAtPath:[self downloadDestinationPath]] fileDescriptor],NULL,2); // 2 == XML_SAVE_NO_DECL, this isn't declared on Mac OS 10.5
  328. xmlSaveDoc(saveContext, doc);
  329. xmlSaveClose(saveContext);
  330. } else {
  331. #if TARGET_OS_MAC && MAC_OS_X_VERSION_MAX_ALLOWED <= __MAC_10_5
  332. // xmlSaveToBuffer() is not implemented in the 10.5 version of libxml
  333. NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
  334. [[[[NSFileManager alloc] init] autorelease] createFileAtPath:tempPath contents:nil attributes:nil];
  335. saveContext = xmlSaveToFd([[NSFileHandle fileHandleForWritingAtPath:tempPath] fileDescriptor],NULL,2); // 2 == XML_SAVE_NO_DECL, this isn't declared on Mac OS 10.5
  336. xmlSaveDoc(saveContext, doc);
  337. xmlSaveClose(saveContext);
  338. [self setRawResponseData:[NSMutableData dataWithContentsOfFile:tempPath]];
  339. #else
  340. xmlBufferPtr buffer = xmlBufferCreate();
  341. saveContext = xmlSaveToBuffer(buffer,NULL,2); // 2 == XML_SAVE_NO_DECL, this isn't declared on Mac OS 10.5
  342. xmlSaveDoc(saveContext, doc);
  343. xmlSaveClose(saveContext);
  344. [self setRawResponseData:[[[NSMutableData alloc] initWithBytes:buffer->content length:buffer->use] autorelease]];
  345. xmlBufferFree(buffer);
  346. #endif
  347. }
  348. // Strip the content encoding if the original response was gzipped
  349. if ([self isResponseCompressed]) {
  350. NSMutableDictionary *headers = [[[self responseHeaders] mutableCopy] autorelease];
  351. [headers removeObjectForKey:@"Content-Encoding"];
  352. [self setResponseHeaders:headers];
  353. }
  354. }
  355. xmlFreeDoc(doc);
  356. doc = nil;
  357. [requestsUsingXMLParser removeObject:self];
  358. if (![requestsUsingXMLParser count]) {
  359. xmlCleanupParser();
  360. }
  361. [xmlParsingLock unlock];
  362. }
  363. }
  364. if (![self parentRequest]) {
  365. [[self class] updateProgressIndicator:&downloadProgressDelegate withProgress:contentLength ofTotal:contentLength];
  366. }
  367. NSMutableDictionary *newHeaders = [[[self responseHeaders] mutableCopy] autorelease];
  368. [newHeaders removeObjectForKey:@"Content-Encoding"];
  369. [self setResponseHeaders:newHeaders];
  370. // Write the parsed content back to the cache
  371. if ([self urlReplacementMode] != ASIDontModifyURLs) {
  372. [[self downloadCache] storeResponseForRequest:self maxAge:[self secondsToCache]];
  373. }
  374. [super requestFinished];
  375. [super markAsFinished];
  376. }
  377. - (void)readResourceURLs
  378. {
  379. // Create xpath evaluation context
  380. xmlXPathContextPtr xpathCtx = xmlXPathNewContext(doc);
  381. if(xpathCtx == NULL) {
  382. [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to create new XPath context",NSLocalizedDescriptionKey,nil]]];
  383. return;
  384. }
  385. // Evaluate xpath expression
  386. xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx);
  387. if(xpathObj == NULL) {
  388. xmlXPathFreeContext(xpathCtx);
  389. [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to evaluate XPath expression!",NSLocalizedDescriptionKey,nil]]];
  390. return;
  391. }
  392. // Now loop through our matches
  393. xmlNodeSetPtr nodes = xpathObj->nodesetval;
  394. int size = (nodes) ? nodes->nodeNr : 0;
  395. int i;
  396. for(i = size - 1; i >= 0; i--) {
  397. assert(nodes->nodeTab[i]);
  398. NSString *parentName = [NSString stringWithCString:(char *)nodes->nodeTab[i]->parent->name encoding:[self responseEncoding]];
  399. NSString *nodeName = [NSString stringWithCString:(char *)nodes->nodeTab[i]->name encoding:[self responseEncoding]];
  400. xmlChar *nodeValue = xmlNodeGetContent(nodes->nodeTab[i]);
  401. NSString *value = [NSString stringWithCString:(char *)nodeValue encoding:[self responseEncoding]];
  402. xmlFree(nodeValue);
  403. // Our xpath query matched all <link> elements, but we're only interested in stylesheets
  404. // We do the work here rather than in the xPath query because the query is case-sensitive, and we want to match on 'stylesheet', 'StyleSHEEt' etc
  405. if ([[parentName lowercaseString] isEqualToString:@"link"]) {
  406. xmlChar *relAttribute = xmlGetNoNsProp(nodes->nodeTab[i]->parent,(xmlChar *)"rel");
  407. if (relAttribute) {
  408. NSString *rel = [NSString stringWithCString:(char *)relAttribute encoding:[self responseEncoding]];
  409. xmlFree(relAttribute);
  410. if ([[rel lowercaseString] isEqualToString:@"stylesheet"]) {
  411. [self addURLToFetch:value];
  412. }
  413. }
  414. // Parse the content of <style> tags and style attributes to find external image urls or external css files
  415. } else if ([[nodeName lowercaseString] isEqualToString:@"style"]) {
  416. NSArray *externalResources = [[self class] CSSURLsFromString:value];
  417. for (NSString *theURL in externalResources) {
  418. [self addURLToFetch:theURL];
  419. }
  420. // Parse the content of <source src=""> tags (HTML 5 audio + video)
  421. // We explictly disable the download of files with .webm, .ogv and .ogg extensions, since it's highly likely they won't be useful to us
  422. } else if ([[parentName lowercaseString] isEqualToString:@"source"] || [[parentName lowercaseString] isEqualToString:@"audio"]) {
  423. NSString *fileExtension = [[value pathExtension] lowercaseString];
  424. if (![fileExtension isEqualToString:@"ogg"] && ![fileExtension isEqualToString:@"ogv"] && ![fileExtension isEqualToString:@"webm"]) {
  425. [self addURLToFetch:value];
  426. }
  427. // For all other elements matched by our xpath query (except hyperlinks), add the content as an external url to fetch
  428. } else if (![[parentName lowercaseString] isEqualToString:@"a"]) {
  429. [self addURLToFetch:value];
  430. }
  431. if (nodes->nodeTab[i]->type != XML_NAMESPACE_DECL) {
  432. nodes->nodeTab[i] = NULL;
  433. }
  434. }
  435. xmlXPathFreeObject(xpathObj);
  436. xmlXPathFreeContext(xpathCtx);
  437. }
  438. - (void)addURLToFetch:(NSString *)newURL
  439. {
  440. // Get rid of any surrounding whitespace
  441. newURL = [newURL stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  442. // Don't attempt to fetch data URIs
  443. if ([newURL length] > 4) {
  444. if (![[[newURL substringToIndex:5] lowercaseString] isEqualToString:@"data:"]) {
  445. NSURL *theURL = [NSURL URLWithString:newURL relativeToURL:[self url]];
  446. if (theURL) {
  447. if (![[self resourceList] objectForKey:newURL]) {
  448. [[self resourceList] setObject:[NSMutableDictionary dictionary] forKey:newURL];
  449. }
  450. }
  451. }
  452. }
  453. }
  454. - (void)updateResourceURLs
  455. {
  456. // Create xpath evaluation context
  457. xmlXPathContextPtr xpathCtx = xmlXPathNewContext(doc);
  458. if(xpathCtx == NULL) {
  459. [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to create new XPath context",NSLocalizedDescriptionKey,nil]]];
  460. return;
  461. }
  462. // Evaluate xpath expression
  463. xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx);
  464. if(xpathObj == NULL) {
  465. xmlXPathFreeContext(xpathCtx);
  466. [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to evaluate XPath expression!",NSLocalizedDescriptionKey,nil]]];
  467. return;
  468. }
  469. // Loop through all the matches, replacing urls where nescessary
  470. xmlNodeSetPtr nodes = xpathObj->nodesetval;
  471. int size = (nodes) ? nodes->nodeNr : 0;
  472. int i;
  473. for(i = size - 1; i >= 0; i--) {
  474. assert(nodes->nodeTab[i]);
  475. NSString *parentName = [NSString stringWithCString:(char *)nodes->nodeTab[i]->parent->name encoding:[self responseEncoding]];
  476. NSString *nodeName = [NSString stringWithCString:(char *)nodes->nodeTab[i]->name encoding:[self responseEncoding]];
  477. xmlChar *nodeValue = xmlNodeGetContent(nodes->nodeTab[i]);
  478. NSString *value = [NSString stringWithCString:(char *)nodeValue encoding:[self responseEncoding]];
  479. xmlFree(nodeValue);
  480. // Replace external urls in <style> tags or in style attributes
  481. if ([[nodeName lowercaseString] isEqualToString:@"style"]) {
  482. NSArray *externalResources = [[self class] CSSURLsFromString:value];
  483. for (NSString *theURL in externalResources) {
  484. if ([value rangeOfString:theURL].location != NSNotFound) {
  485. NSString *newURL = [self contentForExternalURL:theURL];
  486. if (newURL) {
  487. value = [value stringByReplacingOccurrencesOfString:theURL withString:newURL];
  488. }
  489. }
  490. }
  491. xmlNodeSetContent(nodes->nodeTab[i], (xmlChar *)[value cStringUsingEncoding:[self responseEncoding]]);
  492. // Replace relative hyperlinks with absolute ones, since we will need to set a local baseURL when loading this in a web view
  493. } else if ([self urlReplacementMode] == ASIReplaceExternalResourcesWithLocalURLs && [[parentName lowercaseString] isEqualToString:@"a"]) {
  494. NSString *newURL = [[NSURL URLWithString:value relativeToURL:[self url]] absoluteString];
  495. if (newURL) {
  496. xmlNodeSetContent(nodes->nodeTab[i], (xmlChar *)[newURL cStringUsingEncoding:[self responseEncoding]]);
  497. }
  498. // Replace all other external resource urls
  499. } else {
  500. NSString *newURL = [self contentForExternalURL:value];
  501. if (newURL) {
  502. xmlNodeSetContent(nodes->nodeTab[i], (xmlChar *)[newURL cStringUsingEncoding:[self responseEncoding]]);
  503. }
  504. }
  505. if (nodes->nodeTab[i]->type != XML_NAMESPACE_DECL) {
  506. nodes->nodeTab[i] = NULL;
  507. }
  508. }
  509. xmlXPathFreeObject(xpathObj);
  510. xmlXPathFreeContext(xpathCtx);
  511. }
  512. // The three methods below are responsible for forwarding delegate methods we want to handle to the parent request's approdiate delegate
  513. // Certain delegate methods are ignored (eg setProgress: / setDoubleValue: / setMaxValue:)
  514. - (BOOL)respondsToSelector:(SEL)selector
  515. {
  516. if ([self parentRequest]) {
  517. return [[self parentRequest] respondsToSelector:selector];
  518. }
  519. //Ok, now check for selectors we want to pass on to the delegate
  520. if (selector == @selector(requestStarted:) || selector == @selector(request:didReceiveResponseHeaders:) || selector == @selector(request:willRedirectToURL:) || selector == @selector(requestFinished:) || selector == @selector(requestFailed:) || selector == @selector(request:didReceiveData:) || selector == @selector(authenticationNeededForRequest:) || selector == @selector(proxyAuthenticationNeededForRequest:)) {
  521. return [delegate respondsToSelector:selector];
  522. } else if (selector == @selector(request:didReceiveBytes:) || selector == @selector(request:incrementDownloadSizeBy:)) {
  523. return [downloadProgressDelegate respondsToSelector:selector];
  524. } else if (selector == @selector(request:didSendBytes:) || selector == @selector(request:incrementUploadSizeBy:)) {
  525. return [uploadProgressDelegate respondsToSelector:selector];
  526. }
  527. return [super respondsToSelector:selector];
  528. }
  529. - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
  530. {
  531. if ([self parentRequest]) {
  532. return [[self parentRequest] methodSignatureForSelector:selector];
  533. }
  534. if (selector == @selector(requestStarted:) || selector == @selector(request:didReceiveResponseHeaders:) || selector == @selector(request:willRedirectToURL:) || selector == @selector(requestFinished:) || selector == @selector(requestFailed:) || selector == @selector(request:didReceiveData:) || selector == @selector(authenticationNeededForRequest:) || selector == @selector(proxyAuthenticationNeededForRequest:)) {
  535. return [(id)delegate methodSignatureForSelector:selector];
  536. } else if (selector == @selector(request:didReceiveBytes:) || selector == @selector(request:incrementDownloadSizeBy:)) {
  537. return [(id)downloadProgressDelegate methodSignatureForSelector:selector];
  538. } else if (selector == @selector(request:didSendBytes:) || selector == @selector(request:incrementUploadSizeBy:)) {
  539. return [(id)uploadProgressDelegate methodSignatureForSelector:selector];
  540. }
  541. return nil;
  542. }
  543. - (void)forwardInvocation:(NSInvocation *)anInvocation
  544. {
  545. if ([self parentRequest]) {
  546. return [[self parentRequest] forwardInvocation:anInvocation];
  547. }
  548. SEL selector = [anInvocation selector];
  549. if (selector == @selector(requestStarted:) || selector == @selector(request:didReceiveResponseHeaders:) || selector == @selector(request:willRedirectToURL:) || selector == @selector(requestFinished:) || selector == @selector(requestFailed:) || selector == @selector(request:didReceiveData:) || selector == @selector(authenticationNeededForRequest:) || selector == @selector(proxyAuthenticationNeededForRequest:)) {
  550. [anInvocation invokeWithTarget:delegate];
  551. } else if (selector == @selector(request:didReceiveBytes:) || selector == @selector(request:incrementDownloadSizeBy:)) {
  552. [anInvocation invokeWithTarget:downloadProgressDelegate];
  553. } else if (selector == @selector(request:didSendBytes:) || selector == @selector(request:incrementUploadSizeBy:)) {
  554. [anInvocation invokeWithTarget:uploadProgressDelegate];
  555. }
  556. }
  557. // A quick and dirty way to build a list of external resource urls from a css string
  558. + (NSArray *)CSSURLsFromString:(NSString *)string
  559. {
  560. NSMutableArray *urls = [NSMutableArray array];
  561. NSScanner *scanner = [NSScanner scannerWithString:string];
  562. [scanner setCaseSensitive:NO];
  563. while (1) {
  564. NSString *theURL = nil;
  565. [scanner scanUpToString:@"url(" intoString:NULL];
  566. [scanner scanString:@"url(" intoString:NULL];
  567. [scanner scanUpToString:@")" intoString:&theURL];
  568. if (!theURL) {
  569. break;
  570. }
  571. // Remove any quotes or whitespace around the url
  572. theURL = [theURL stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  573. theURL = [theURL stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\"'"]];
  574. theURL = [theURL stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  575. [urls addObject:theURL];
  576. }
  577. return urls;
  578. }
  579. // Returns a relative file path from sourcePath to destinationPath (eg ../../foo/bar.txt)
  580. - (NSString *)relativePathTo:(NSString *)destinationPath fromPath:(NSString *)sourcePath
  581. {
  582. NSArray *sourcePathComponents = [sourcePath pathComponents];
  583. NSArray *destinationPathComponents = [destinationPath pathComponents];
  584. NSUInteger i;
  585. NSString *newPath = @"";
  586. NSString *sourcePathComponent, *destinationPathComponent;
  587. for (i=0; i<[sourcePathComponents count]; i++) {
  588. sourcePathComponent = [sourcePathComponents objectAtIndex:i];
  589. if ([destinationPathComponents count] > i) {
  590. destinationPathComponent = [destinationPathComponents objectAtIndex:i];
  591. if (![sourcePathComponent isEqualToString:destinationPathComponent]) {
  592. NSUInteger i2;
  593. for (i2=i+1; i2<[sourcePathComponents count]; i2++) {
  594. newPath = [newPath stringByAppendingPathComponent:@".."];
  595. }
  596. newPath = [newPath stringByAppendingPathComponent:destinationPathComponent];
  597. for (i2=i+1; i2<[destinationPathComponents count]; i2++) {
  598. newPath = [newPath stringByAppendingPathComponent:[destinationPathComponents objectAtIndex:i2]];
  599. }
  600. break;
  601. }
  602. }
  603. }
  604. return newPath;
  605. }
  606. - (NSString *)contentForExternalURL:(NSString *)theURL
  607. {
  608. if ([self urlReplacementMode] == ASIReplaceExternalResourcesWithLocalURLs) {
  609. NSString *resourcePath = [[resourceList objectForKey:theURL] objectForKey:@"DataPath"];
  610. return [self relativePathTo:resourcePath fromPath:[self downloadDestinationPath]];
  611. }
  612. NSData *data;
  613. if ([[resourceList objectForKey:theURL] objectForKey:@"DataPath"]) {
  614. data = [NSData dataWithContentsOfFile:[[resourceList objectForKey:theURL] objectForKey:@"DataPath"]];
  615. } else {
  616. data = [[resourceList objectForKey:theURL] objectForKey:@"Data"];
  617. }
  618. NSString *contentType = [[resourceList objectForKey:theURL] objectForKey:@"ContentType"];
  619. if (data && contentType) {
  620. NSString *dataURI = [NSString stringWithFormat:@"data:%@;base64,",contentType];
  621. dataURI = [dataURI stringByAppendingString:[ASIHTTPRequest base64forData:data]];
  622. return dataURI;
  623. }
  624. return nil;
  625. }
  626. - (NSString *)cachePathForRequest:(ASIWebPageRequest *)theRequest
  627. {
  628. // If we're using a download cache (and its a good idea to do so when using ASIWebPageRequest), ask it for the location to store this file
  629. // This ends up being quite efficient, as we download directly to the cache
  630. if ([self downloadCache]) {
  631. return [[self downloadCache] pathToStoreCachedResponseDataForRequest:theRequest];
  632. // This is a fallback for when we don't have a download cache - we store the external resource in a file in the temporary directory
  633. } else {
  634. // Borrowed from: http://stackoverflow.com/questions/652300/using-md5-hash-on-a-string-in-cocoa
  635. const char *cStr = [[[theRequest url] absoluteString] UTF8String];
  636. unsigned char result[16];
  637. CC_MD5(cStr, (CC_LONG)strlen(cStr), result);
  638. NSString *md5 = [NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7],result[8], result[9], result[10], result[11],result[12], result[13], result[14], result[15]];
  639. return [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:[md5 stringByAppendingPathExtension:@"html"]];
  640. }
  641. }
  642. @synthesize externalResourceQueue;
  643. @synthesize resourceList;
  644. @synthesize parentRequest;
  645. @synthesize urlReplacementMode;
  646. @synthesize shouldIgnoreExternalResourceErrors;
  647. @end