/WebServiceDemo/Required_Libs/HTTPRequest/Classes/ASIWebPageRequest/ASIWebPageRequest.m
Objective C | 722 lines | 595 code | 75 blank | 52 comment | 203 complexity | aca610403c6ff600714fed188e64ee09 MD5 | raw file
- //
- // ASIWebPageRequest.m
- // Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
- //
- // Created by Ben Copsey on 29/06/2010.
- // Copyright 2010 All-Seeing Interactive. All rights reserved.
- //
- // This is an EXPERIMENTAL class - use at your own risk!
- #import "ASIWebPageRequest.h"
- #import "ASINetworkQueue.h"
- #import <CommonCrypto/CommonHMAC.h>
- #import <libxml/HTMLparser.h>
- #import <libxml/xmlsave.h>
- #import <libxml/xpath.h>
- #import <libxml/xpathInternals.h>
- // An xPath query that controls the external resources ASIWebPageRequest will fetch
- // By default, it will fetch stylesheets, javascript files, images, frames, iframes, and html 5 video / audio
- static xmlChar *xpathExpr = (xmlChar *)"//link/@href|//a/@href|//script/@src|//img/@src|//frame/@src|//iframe/@src|//style|//*/@style|//source/@src|//video/@poster|//audio/@src";
- static NSLock *xmlParsingLock = nil;
- static NSMutableArray *requestsUsingXMLParser = nil;
- @interface ASIWebPageRequest ()
- - (void)readResourceURLs;
- - (void)updateResourceURLs;
- - (void)parseAsHTML;
- - (void)parseAsCSS;
- - (void)addURLToFetch:(NSString *)newURL;
- + (NSArray *)CSSURLsFromString:(NSString *)string;
- - (NSString *)relativePathTo:(NSString *)destinationPath fromPath:(NSString *)sourcePath;
- - (void)finishedFetchingExternalResources:(ASINetworkQueue *)queue;
- - (void)externalResourceFetchSucceeded:(ASIHTTPRequest *)externalResourceRequest;
- - (void)externalResourceFetchFailed:(ASIHTTPRequest *)externalResourceRequest;
- @property (retain, nonatomic) ASINetworkQueue *externalResourceQueue;
- @property (retain, nonatomic) NSMutableDictionary *resourceList;
- @end
- @implementation ASIWebPageRequest
- + (void)initialize
- {
- if (self == [ASIWebPageRequest class]) {
- xmlParsingLock = [[NSLock alloc] init];
- requestsUsingXMLParser = [[NSMutableArray alloc] init];
- }
- }
- - (id)initWithURL:(NSURL *)newURL
- {
- self = [super initWithURL:newURL];
- [self setShouldIgnoreExternalResourceErrors:YES];
- return self;
- }
- - (void)dealloc
- {
- [externalResourceQueue cancelAllOperations];
- [externalResourceQueue release];
- [resourceList release];
- [parentRequest release];
- [super dealloc];
- }
- // This is a bit of a hack
- // The role of this method in normal ASIHTTPRequests is to tell the queue we are done with the request, and perform some cleanup
- // We override it to stop that happening, and instead do that work in the bottom of finishedFetchingExternalResources:
- - (void)markAsFinished
- {
- if ([self error]) {
- [super markAsFinished];
- }
- }
- // This method is normally responsible for telling delegates we are done, but it happens to be the most convenient place to parse the responses
- // Again, we call the super implementation in finishedFetchingExternalResources:, or here if this download was not an HTML or CSS file
- - (void)requestFinished
- {
- complete = NO;
- if ([self mainRequest] || [self didUseCachedResponse]) {
- [super requestFinished];
- [super markAsFinished];
- return;
- }
- webContentType = ASINotParsedWebContentType;
- NSString *contentType = [[[self responseHeaders] objectForKey:@"Content-Type"] lowercaseString];
- contentType = [[contentType componentsSeparatedByString:@";"] objectAtIndex:0];
- if ([contentType isEqualToString:@"text/html"] || [contentType isEqualToString:@"text/xhtml"] || [contentType isEqualToString:@"text/xhtml+xml"] || [contentType isEqualToString:@"application/xhtml+xml"]) {
- [self parseAsHTML];
- return;
- } else if ([contentType isEqualToString:@"text/css"]) {
- [self parseAsCSS];
- return;
- }
- [super requestFinished];
- [super markAsFinished];
- }
- - (void)parseAsCSS
- {
- webContentType = ASICSSWebContentType;
- NSString *responseCSS = nil;
- NSError *err = nil;
- if ([self downloadDestinationPath]) {
- responseCSS = [NSString stringWithContentsOfFile:[self downloadDestinationPath] encoding:[self responseEncoding] error:&err];
- } else {
- responseCSS = [self responseString];
- }
- if (err) {
- [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:100 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to read HTML string from response",NSLocalizedDescriptionKey,err,NSUnderlyingErrorKey,nil]]];
- return;
- } else if (!responseCSS) {
- [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:100 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to read HTML string from response",NSLocalizedDescriptionKey,nil]]];
- return;
- }
- NSArray *urls = [[self class] CSSURLsFromString:responseCSS];
- [self setResourceList:[NSMutableDictionary dictionary]];
- for (NSString *theURL in urls) {
- [self addURLToFetch:theURL];
- }
- if (![[self resourceList] count]) {
- [super requestFinished];
- [super markAsFinished];
- return;
- }
- // Create a new request for every item in the queue
- [[self externalResourceQueue] cancelAllOperations];
- [self setExternalResourceQueue:[ASINetworkQueue queue]];
- [[self externalResourceQueue] setDelegate:self];
- [[self externalResourceQueue] setShouldCancelAllRequestsOnFailure:[self shouldIgnoreExternalResourceErrors]];
- [[self externalResourceQueue] setShowAccurateProgress:[self showAccurateProgress]];
- [[self externalResourceQueue] setQueueDidFinishSelector:@selector(finishedFetchingExternalResources:)];
- [[self externalResourceQueue] setRequestDidFinishSelector:@selector(externalResourceFetchSucceeded:)];
- [[self externalResourceQueue] setRequestDidFailSelector:@selector(externalResourceFetchFailed:)];
- for (NSString *theURL in [[self resourceList] keyEnumerator]) {
- ASIWebPageRequest *externalResourceRequest = [ASIWebPageRequest requestWithURL:[NSURL URLWithString:theURL relativeToURL:[self url]]];
- [externalResourceRequest setRequestHeaders:[self requestHeaders]];
- [externalResourceRequest setDownloadCache:[self downloadCache]];
- [externalResourceRequest setCachePolicy:[self cachePolicy]];
- [externalResourceRequest setCacheStoragePolicy:[self cacheStoragePolicy]];
- [externalResourceRequest setUserInfo:[NSDictionary dictionaryWithObject:theURL forKey:@"Path"]];
- [externalResourceRequest setParentRequest:self];
- [externalResourceRequest setUrlReplacementMode:[self urlReplacementMode]];
- [externalResourceRequest setShouldResetDownloadProgress:NO];
- [externalResourceRequest setDelegate:self];
- [externalResourceRequest setUploadProgressDelegate:self];
- [externalResourceRequest setDownloadProgressDelegate:self];
- if ([self downloadDestinationPath]) {
- [externalResourceRequest setDownloadDestinationPath:[self cachePathForRequest:externalResourceRequest]];
- }
- [[self externalResourceQueue] addOperation:externalResourceRequest];
- }
- [[self externalResourceQueue] go];
- }
- - (const char *)encodingName
- {
- xmlCharEncoding encoding = XML_CHAR_ENCODING_NONE;
- switch ([self responseEncoding])
- {
- case NSASCIIStringEncoding:
- encoding = XML_CHAR_ENCODING_ASCII;
- break;
- case NSJapaneseEUCStringEncoding:
- encoding = XML_CHAR_ENCODING_EUC_JP;
- break;
- case NSUTF8StringEncoding:
- encoding = XML_CHAR_ENCODING_UTF8;
- break;
- case NSISOLatin1StringEncoding:
- encoding = XML_CHAR_ENCODING_8859_1;
- break;
- case NSShiftJISStringEncoding:
- encoding = XML_CHAR_ENCODING_SHIFT_JIS;
- break;
- case NSISOLatin2StringEncoding:
- encoding = XML_CHAR_ENCODING_8859_2;
- break;
- case NSISO2022JPStringEncoding:
- encoding = XML_CHAR_ENCODING_2022_JP;
- break;
- case NSUTF16BigEndianStringEncoding:
- encoding = XML_CHAR_ENCODING_UTF16BE;
- break;
- case NSUTF16LittleEndianStringEncoding:
- encoding = XML_CHAR_ENCODING_UTF16LE;
- break;
- case NSUTF32BigEndianStringEncoding:
- encoding = XML_CHAR_ENCODING_UCS4BE;
- break;
- case NSUTF32LittleEndianStringEncoding:
- encoding = XML_CHAR_ENCODING_UCS4LE;
- break;
- case NSNEXTSTEPStringEncoding:
- case NSSymbolStringEncoding:
- case NSNonLossyASCIIStringEncoding:
- case NSUnicodeStringEncoding:
- case NSMacOSRomanStringEncoding:
- case NSUTF32StringEncoding:
- default:
- encoding = XML_CHAR_ENCODING_ERROR;
- break;
- }
- return xmlGetCharEncodingName(encoding);
- }
- - (void)parseAsHTML
- {
- webContentType = ASIHTMLWebContentType;
- // Only allow parsing of a single document at a time
- [xmlParsingLock lock];
- if (![requestsUsingXMLParser count]) {
- xmlInitParser();
- }
- [requestsUsingXMLParser addObject:self];
- /* Load XML document */
- if ([self downloadDestinationPath]) {
- doc = htmlReadFile([[self downloadDestinationPath] cStringUsingEncoding:NSUTF8StringEncoding], [self encodingName], HTML_PARSE_NONET | HTML_PARSE_NOWARNING | HTML_PARSE_NOERROR);
- } else {
- NSData *data = [self responseData];
- doc = htmlReadMemory([data bytes], (int)[data length], "", [self encodingName], HTML_PARSE_NONET | HTML_PARSE_NOWARNING | HTML_PARSE_NOERROR);
- }
- if (doc == NULL) {
- [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to parse reponse XML",NSLocalizedDescriptionKey,nil]]];
- return;
- }
-
- [self setResourceList:[NSMutableDictionary dictionary]];
- // Populate the list of URLS to download
- [self readResourceURLs];
- if ([self error] || ![[self resourceList] count]) {
- [requestsUsingXMLParser removeObject:self];
- xmlFreeDoc(doc);
- doc = NULL;
- }
- [xmlParsingLock unlock];
- if ([self error]) {
- return;
- } else if (![[self resourceList] count]) {
- [super requestFinished];
- [super markAsFinished];
- return;
- }
-
- // Create a new request for every item in the queue
- [[self externalResourceQueue] cancelAllOperations];
- [self setExternalResourceQueue:[ASINetworkQueue queue]];
- [[self externalResourceQueue] setDelegate:self];
- [[self externalResourceQueue] setShouldCancelAllRequestsOnFailure:[self shouldIgnoreExternalResourceErrors]];
- [[self externalResourceQueue] setShowAccurateProgress:[self showAccurateProgress]];
- [[self externalResourceQueue] setQueueDidFinishSelector:@selector(finishedFetchingExternalResources:)];
- [[self externalResourceQueue] setRequestDidFinishSelector:@selector(externalResourceFetchSucceeded:)];
- [[self externalResourceQueue] setRequestDidFailSelector:@selector(externalResourceFetchFailed:)];
- for (NSString *theURL in [[self resourceList] keyEnumerator]) {
- ASIWebPageRequest *externalResourceRequest = [ASIWebPageRequest requestWithURL:[NSURL URLWithString:theURL relativeToURL:[self url]]];
- [externalResourceRequest setRequestHeaders:[self requestHeaders]];
- [externalResourceRequest setDownloadCache:[self downloadCache]];
- [externalResourceRequest setCachePolicy:[self cachePolicy]];
- [externalResourceRequest setCacheStoragePolicy:[self cacheStoragePolicy]];
- [externalResourceRequest setUserInfo:[NSDictionary dictionaryWithObject:theURL forKey:@"Path"]];
- [externalResourceRequest setParentRequest:self];
- [externalResourceRequest setUrlReplacementMode:[self urlReplacementMode]];
- [externalResourceRequest setShouldResetDownloadProgress:NO];
- [externalResourceRequest setDelegate:self];
- [externalResourceRequest setUploadProgressDelegate:self];
- [externalResourceRequest setDownloadProgressDelegate:self];
- if ([self downloadDestinationPath]) {
- [externalResourceRequest setDownloadDestinationPath:[self cachePathForRequest:externalResourceRequest]];
- }
- [[self externalResourceQueue] addOperation:externalResourceRequest];
- }
- [[self externalResourceQueue] go];
- }
- - (void)externalResourceFetchSucceeded:(ASIHTTPRequest *)externalResourceRequest
- {
- NSString *originalPath = [[externalResourceRequest userInfo] objectForKey:@"Path"];
- NSMutableDictionary *requestResponse = [[self resourceList] objectForKey:originalPath];
- NSString *contentType = [[externalResourceRequest responseHeaders] objectForKey:@"Content-Type"];
- if (!contentType) {
- contentType = @"application/octet-stream";
- }
- [requestResponse setObject:contentType forKey:@"ContentType"];
- if ([self downloadDestinationPath]) {
- [requestResponse setObject:[externalResourceRequest downloadDestinationPath] forKey:@"DataPath"];
- } else {
- NSData *data = [externalResourceRequest responseData];
- if (data) {
- [requestResponse setObject:data forKey:@"Data"];
- }
- }
- }
- - (void)externalResourceFetchFailed:(ASIHTTPRequest *)externalResourceRequest
- {
- if ([[self externalResourceQueue] shouldCancelAllRequestsOnFailure]) {
- [self failWithError:[externalResourceRequest error]];
- }
- }
- - (void)finishedFetchingExternalResources:(ASINetworkQueue *)queue
- {
- if ([self urlReplacementMode] != ASIDontModifyURLs) {
- if (webContentType == ASICSSWebContentType) {
- NSMutableString *parsedResponse;
- NSError *err = nil;
- if ([self downloadDestinationPath]) {
- parsedResponse = [NSMutableString stringWithContentsOfFile:[self downloadDestinationPath] encoding:[self responseEncoding] error:&err];
- } else {
- parsedResponse = [[[self responseString] mutableCopy] autorelease];
- }
- if (err) {
- [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to read response CSS from disk",NSLocalizedDescriptionKey,nil]]];
- return;
- }
- if (![self error]) {
- for (NSString *resource in [[self resourceList] keyEnumerator]) {
- if ([parsedResponse rangeOfString:resource].location != NSNotFound) {
- NSString *newURL = [self contentForExternalURL:resource];
- if (newURL) {
- [parsedResponse replaceOccurrencesOfString:resource withString:newURL options:0 range:NSMakeRange(0, [parsedResponse length])];
- }
- }
- }
- }
- if ([self downloadDestinationPath]) {
- [parsedResponse writeToFile:[self downloadDestinationPath] atomically:NO encoding:[self responseEncoding] error:&err];
- if (err) {
- [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to write response CSS to disk",NSLocalizedDescriptionKey,nil]]];
- return;
- }
- } else {
- [self setRawResponseData:(id)[parsedResponse dataUsingEncoding:[self responseEncoding]]];
- }
- } else {
- [xmlParsingLock lock];
- [self updateResourceURLs];
- if (![self error]) {
- // We'll use the xmlsave API so we can strip the xml declaration
- xmlSaveCtxtPtr saveContext;
- if ([self downloadDestinationPath]) {
- // Truncate the file first
- [[[[NSFileManager alloc] init] autorelease] createFileAtPath:[self downloadDestinationPath] contents:nil attributes:nil];
- saveContext = xmlSaveToFd([[NSFileHandle fileHandleForWritingAtPath:[self downloadDestinationPath]] fileDescriptor],NULL,2); // 2 == XML_SAVE_NO_DECL, this isn't declared on Mac OS 10.5
- xmlSaveDoc(saveContext, doc);
- xmlSaveClose(saveContext);
- } else {
- #if TARGET_OS_MAC && MAC_OS_X_VERSION_MAX_ALLOWED <= __MAC_10_5
- // xmlSaveToBuffer() is not implemented in the 10.5 version of libxml
- NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
- [[[[NSFileManager alloc] init] autorelease] createFileAtPath:tempPath contents:nil attributes:nil];
- saveContext = xmlSaveToFd([[NSFileHandle fileHandleForWritingAtPath:tempPath] fileDescriptor],NULL,2); // 2 == XML_SAVE_NO_DECL, this isn't declared on Mac OS 10.5
- xmlSaveDoc(saveContext, doc);
- xmlSaveClose(saveContext);
- [self setRawResponseData:[NSMutableData dataWithContentsOfFile:tempPath]];
- #else
- xmlBufferPtr buffer = xmlBufferCreate();
- saveContext = xmlSaveToBuffer(buffer,NULL,2); // 2 == XML_SAVE_NO_DECL, this isn't declared on Mac OS 10.5
- xmlSaveDoc(saveContext, doc);
- xmlSaveClose(saveContext);
- [self setRawResponseData:[[[NSMutableData alloc] initWithBytes:buffer->content length:buffer->use] autorelease]];
- xmlBufferFree(buffer);
- #endif
- }
- // Strip the content encoding if the original response was gzipped
- if ([self isResponseCompressed]) {
- NSMutableDictionary *headers = [[[self responseHeaders] mutableCopy] autorelease];
- [headers removeObjectForKey:@"Content-Encoding"];
- [self setResponseHeaders:headers];
- }
- }
- xmlFreeDoc(doc);
- doc = nil;
- [requestsUsingXMLParser removeObject:self];
- if (![requestsUsingXMLParser count]) {
- xmlCleanupParser();
- }
- [xmlParsingLock unlock];
- }
- }
- if (![self parentRequest]) {
- [[self class] updateProgressIndicator:&downloadProgressDelegate withProgress:contentLength ofTotal:contentLength];
- }
- NSMutableDictionary *newHeaders = [[[self responseHeaders] mutableCopy] autorelease];
- [newHeaders removeObjectForKey:@"Content-Encoding"];
- [self setResponseHeaders:newHeaders];
- // Write the parsed content back to the cache
- if ([self urlReplacementMode] != ASIDontModifyURLs) {
- [[self downloadCache] storeResponseForRequest:self maxAge:[self secondsToCache]];
- }
- [super requestFinished];
- [super markAsFinished];
- }
- - (void)readResourceURLs
- {
- // Create xpath evaluation context
- xmlXPathContextPtr xpathCtx = xmlXPathNewContext(doc);
- if(xpathCtx == NULL) {
- [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to create new XPath context",NSLocalizedDescriptionKey,nil]]];
- return;
- }
- // Evaluate xpath expression
- xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx);
- if(xpathObj == NULL) {
- xmlXPathFreeContext(xpathCtx);
- [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to evaluate XPath expression!",NSLocalizedDescriptionKey,nil]]];
- return;
- }
-
- // Now loop through our matches
- xmlNodeSetPtr nodes = xpathObj->nodesetval;
- int size = (nodes) ? nodes->nodeNr : 0;
- int i;
- for(i = size - 1; i >= 0; i--) {
- assert(nodes->nodeTab[i]);
- NSString *parentName = [NSString stringWithCString:(char *)nodes->nodeTab[i]->parent->name encoding:[self responseEncoding]];
- NSString *nodeName = [NSString stringWithCString:(char *)nodes->nodeTab[i]->name encoding:[self responseEncoding]];
- xmlChar *nodeValue = xmlNodeGetContent(nodes->nodeTab[i]);
- NSString *value = [NSString stringWithCString:(char *)nodeValue encoding:[self responseEncoding]];
- xmlFree(nodeValue);
- // Our xpath query matched all <link> elements, but we're only interested in stylesheets
- // 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
- if ([[parentName lowercaseString] isEqualToString:@"link"]) {
- xmlChar *relAttribute = xmlGetNoNsProp(nodes->nodeTab[i]->parent,(xmlChar *)"rel");
- if (relAttribute) {
- NSString *rel = [NSString stringWithCString:(char *)relAttribute encoding:[self responseEncoding]];
- xmlFree(relAttribute);
- if ([[rel lowercaseString] isEqualToString:@"stylesheet"]) {
- [self addURLToFetch:value];
- }
- }
- // Parse the content of <style> tags and style attributes to find external image urls or external css files
- } else if ([[nodeName lowercaseString] isEqualToString:@"style"]) {
- NSArray *externalResources = [[self class] CSSURLsFromString:value];
- for (NSString *theURL in externalResources) {
- [self addURLToFetch:theURL];
- }
- // Parse the content of <source src=""> tags (HTML 5 audio + video)
- // 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
- } else if ([[parentName lowercaseString] isEqualToString:@"source"] || [[parentName lowercaseString] isEqualToString:@"audio"]) {
- NSString *fileExtension = [[value pathExtension] lowercaseString];
- if (![fileExtension isEqualToString:@"ogg"] && ![fileExtension isEqualToString:@"ogv"] && ![fileExtension isEqualToString:@"webm"]) {
- [self addURLToFetch:value];
- }
- // For all other elements matched by our xpath query (except hyperlinks), add the content as an external url to fetch
- } else if (![[parentName lowercaseString] isEqualToString:@"a"]) {
- [self addURLToFetch:value];
- }
- if (nodes->nodeTab[i]->type != XML_NAMESPACE_DECL) {
- nodes->nodeTab[i] = NULL;
- }
- }
-
- xmlXPathFreeObject(xpathObj);
- xmlXPathFreeContext(xpathCtx);
- }
- - (void)addURLToFetch:(NSString *)newURL
- {
- // Get rid of any surrounding whitespace
- newURL = [newURL stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
- // Don't attempt to fetch data URIs
- if ([newURL length] > 4) {
- if (![[[newURL substringToIndex:5] lowercaseString] isEqualToString:@"data:"]) {
- NSURL *theURL = [NSURL URLWithString:newURL relativeToURL:[self url]];
- if (theURL) {
- if (![[self resourceList] objectForKey:newURL]) {
- [[self resourceList] setObject:[NSMutableDictionary dictionary] forKey:newURL];
- }
- }
- }
- }
- }
- - (void)updateResourceURLs
- {
- // Create xpath evaluation context
- xmlXPathContextPtr xpathCtx = xmlXPathNewContext(doc);
- if(xpathCtx == NULL) {
- [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to create new XPath context",NSLocalizedDescriptionKey,nil]]];
- return;
- }
- // Evaluate xpath expression
- xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx);
- if(xpathObj == NULL) {
- xmlXPathFreeContext(xpathCtx);
- [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to evaluate XPath expression!",NSLocalizedDescriptionKey,nil]]];
- return;
- }
- // Loop through all the matches, replacing urls where nescessary
- xmlNodeSetPtr nodes = xpathObj->nodesetval;
- int size = (nodes) ? nodes->nodeNr : 0;
- int i;
- for(i = size - 1; i >= 0; i--) {
- assert(nodes->nodeTab[i]);
- NSString *parentName = [NSString stringWithCString:(char *)nodes->nodeTab[i]->parent->name encoding:[self responseEncoding]];
- NSString *nodeName = [NSString stringWithCString:(char *)nodes->nodeTab[i]->name encoding:[self responseEncoding]];
- xmlChar *nodeValue = xmlNodeGetContent(nodes->nodeTab[i]);
- NSString *value = [NSString stringWithCString:(char *)nodeValue encoding:[self responseEncoding]];
- xmlFree(nodeValue);
- // Replace external urls in <style> tags or in style attributes
- if ([[nodeName lowercaseString] isEqualToString:@"style"]) {
- NSArray *externalResources = [[self class] CSSURLsFromString:value];
- for (NSString *theURL in externalResources) {
- if ([value rangeOfString:theURL].location != NSNotFound) {
- NSString *newURL = [self contentForExternalURL:theURL];
- if (newURL) {
- value = [value stringByReplacingOccurrencesOfString:theURL withString:newURL];
- }
- }
- }
- xmlNodeSetContent(nodes->nodeTab[i], (xmlChar *)[value cStringUsingEncoding:[self responseEncoding]]);
- // Replace relative hyperlinks with absolute ones, since we will need to set a local baseURL when loading this in a web view
- } else if ([self urlReplacementMode] == ASIReplaceExternalResourcesWithLocalURLs && [[parentName lowercaseString] isEqualToString:@"a"]) {
- NSString *newURL = [[NSURL URLWithString:value relativeToURL:[self url]] absoluteString];
- if (newURL) {
- xmlNodeSetContent(nodes->nodeTab[i], (xmlChar *)[newURL cStringUsingEncoding:[self responseEncoding]]);
- }
- // Replace all other external resource urls
- } else {
- NSString *newURL = [self contentForExternalURL:value];
- if (newURL) {
- xmlNodeSetContent(nodes->nodeTab[i], (xmlChar *)[newURL cStringUsingEncoding:[self responseEncoding]]);
- }
- }
- if (nodes->nodeTab[i]->type != XML_NAMESPACE_DECL) {
- nodes->nodeTab[i] = NULL;
- }
- }
- xmlXPathFreeObject(xpathObj);
- xmlXPathFreeContext(xpathCtx);
- }
- // The three methods below are responsible for forwarding delegate methods we want to handle to the parent request's approdiate delegate
- // Certain delegate methods are ignored (eg setProgress: / setDoubleValue: / setMaxValue:)
- - (BOOL)respondsToSelector:(SEL)selector
- {
- if ([self parentRequest]) {
- return [[self parentRequest] respondsToSelector:selector];
- }
- //Ok, now check for selectors we want to pass on to the delegate
- 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:)) {
- return [delegate respondsToSelector:selector];
- } else if (selector == @selector(request:didReceiveBytes:) || selector == @selector(request:incrementDownloadSizeBy:)) {
- return [downloadProgressDelegate respondsToSelector:selector];
- } else if (selector == @selector(request:didSendBytes:) || selector == @selector(request:incrementUploadSizeBy:)) {
- return [uploadProgressDelegate respondsToSelector:selector];
- }
- return [super respondsToSelector:selector];
- }
- - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
- {
- if ([self parentRequest]) {
- return [[self parentRequest] methodSignatureForSelector:selector];
- }
- 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:)) {
- return [(id)delegate methodSignatureForSelector:selector];
- } else if (selector == @selector(request:didReceiveBytes:) || selector == @selector(request:incrementDownloadSizeBy:)) {
- return [(id)downloadProgressDelegate methodSignatureForSelector:selector];
- } else if (selector == @selector(request:didSendBytes:) || selector == @selector(request:incrementUploadSizeBy:)) {
- return [(id)uploadProgressDelegate methodSignatureForSelector:selector];
- }
- return nil;
- }
- - (void)forwardInvocation:(NSInvocation *)anInvocation
- {
- if ([self parentRequest]) {
- return [[self parentRequest] forwardInvocation:anInvocation];
- }
- SEL selector = [anInvocation selector];
- 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:)) {
- [anInvocation invokeWithTarget:delegate];
- } else if (selector == @selector(request:didReceiveBytes:) || selector == @selector(request:incrementDownloadSizeBy:)) {
- [anInvocation invokeWithTarget:downloadProgressDelegate];
- } else if (selector == @selector(request:didSendBytes:) || selector == @selector(request:incrementUploadSizeBy:)) {
- [anInvocation invokeWithTarget:uploadProgressDelegate];
- }
- }
- // A quick and dirty way to build a list of external resource urls from a css string
- + (NSArray *)CSSURLsFromString:(NSString *)string
- {
- NSMutableArray *urls = [NSMutableArray array];
- NSScanner *scanner = [NSScanner scannerWithString:string];
- [scanner setCaseSensitive:NO];
- while (1) {
- NSString *theURL = nil;
- [scanner scanUpToString:@"url(" intoString:NULL];
- [scanner scanString:@"url(" intoString:NULL];
- [scanner scanUpToString:@")" intoString:&theURL];
- if (!theURL) {
- break;
- }
- // Remove any quotes or whitespace around the url
- theURL = [theURL stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
- theURL = [theURL stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\"'"]];
- theURL = [theURL stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
- [urls addObject:theURL];
- }
- return urls;
- }
- // Returns a relative file path from sourcePath to destinationPath (eg ../../foo/bar.txt)
- - (NSString *)relativePathTo:(NSString *)destinationPath fromPath:(NSString *)sourcePath
- {
- NSArray *sourcePathComponents = [sourcePath pathComponents];
- NSArray *destinationPathComponents = [destinationPath pathComponents];
- NSUInteger i;
- NSString *newPath = @"";
- NSString *sourcePathComponent, *destinationPathComponent;
- for (i=0; i<[sourcePathComponents count]; i++) {
- sourcePathComponent = [sourcePathComponents objectAtIndex:i];
- if ([destinationPathComponents count] > i) {
- destinationPathComponent = [destinationPathComponents objectAtIndex:i];
- if (![sourcePathComponent isEqualToString:destinationPathComponent]) {
- NSUInteger i2;
- for (i2=i+1; i2<[sourcePathComponents count]; i2++) {
- newPath = [newPath stringByAppendingPathComponent:@".."];
- }
- newPath = [newPath stringByAppendingPathComponent:destinationPathComponent];
- for (i2=i+1; i2<[destinationPathComponents count]; i2++) {
- newPath = [newPath stringByAppendingPathComponent:[destinationPathComponents objectAtIndex:i2]];
- }
- break;
- }
- }
- }
- return newPath;
- }
- - (NSString *)contentForExternalURL:(NSString *)theURL
- {
- if ([self urlReplacementMode] == ASIReplaceExternalResourcesWithLocalURLs) {
- NSString *resourcePath = [[resourceList objectForKey:theURL] objectForKey:@"DataPath"];
- return [self relativePathTo:resourcePath fromPath:[self downloadDestinationPath]];
- }
- NSData *data;
- if ([[resourceList objectForKey:theURL] objectForKey:@"DataPath"]) {
- data = [NSData dataWithContentsOfFile:[[resourceList objectForKey:theURL] objectForKey:@"DataPath"]];
- } else {
- data = [[resourceList objectForKey:theURL] objectForKey:@"Data"];
- }
- NSString *contentType = [[resourceList objectForKey:theURL] objectForKey:@"ContentType"];
- if (data && contentType) {
- NSString *dataURI = [NSString stringWithFormat:@"data:%@;base64,",contentType];
- dataURI = [dataURI stringByAppendingString:[ASIHTTPRequest base64forData:data]];
- return dataURI;
- }
- return nil;
- }
- - (NSString *)cachePathForRequest:(ASIWebPageRequest *)theRequest
- {
- // 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
- // This ends up being quite efficient, as we download directly to the cache
- if ([self downloadCache]) {
- return [[self downloadCache] pathToStoreCachedResponseDataForRequest:theRequest];
- // 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
- } else {
- // Borrowed from: http://stackoverflow.com/questions/652300/using-md5-hash-on-a-string-in-cocoa
- const char *cStr = [[[theRequest url] absoluteString] UTF8String];
- unsigned char result[16];
- CC_MD5(cStr, (CC_LONG)strlen(cStr), result);
- 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]];
- return [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:[md5 stringByAppendingPathExtension:@"html"]];
- }
- }
- @synthesize externalResourceQueue;
- @synthesize resourceList;
- @synthesize parentRequest;
- @synthesize urlReplacementMode;
- @synthesize shouldIgnoreExternalResourceErrors;
- @end