123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- /*
- * This file is part of the FreeStreamer project,
- * (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
- * See the file ''LICENSE'' for using the code.
- *
- * https://github.com/muhku/FreeStreamer
- */
- #import "FSXMLHttpRequest.h"
- #import <libxml/parser.h>
- #import <libxml/xpath.h>
- #define DATE_COMPONENTS (NSYearCalendarUnit| NSMonthCalendarUnit | NSDayCalendarUnit | NSWeekCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit | NSWeekdayCalendarUnit | NSWeekdayOrdinalCalendarUnit)
- #define CURRENT_CALENDAR [NSCalendar currentCalendar]
- @interface FSXMLHttpRequest (PrivateMethods)
- - (const char *)detectEncoding;
- - (void)parseResponseData;
- - (void)parseXMLNode:(xmlNodePtr)node xPathQuery:(NSString *)xPathQuery;
- @end
- @implementation FSXMLHttpRequest
- - (id)init
- {
- self = [super init];
- if (self) {
- _dateFormatter = [[NSDateFormatter alloc] init];
- }
- return self;
- }
- - (void)start
- {
-
- if (_task) {
- return;
- }
-
- _lastError = FSXMLHttpRequestError_NoError;
-
- NSURLRequest *request = [NSURLRequest requestWithURL:self.url
- cachePolicy:NSURLRequestUseProtocolCachePolicy
- timeoutInterval:10.0];
-
- NSURLSession *session = [NSURLSession sharedSession];
-
- __weak FSXMLHttpRequest *weakSelf = self;
- @synchronized (self) {
- _task = [session dataTaskWithRequest:request
- completionHandler:
- ^(NSData *data, NSURLResponse *response, NSError *error) {
- FSXMLHttpRequest *strongSelf = weakSelf;
-
- if(error) {
- strongSelf->_lastError = FSXMLHttpRequestError_Connection_Failed;
-
- #if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
- NSLog(@"FSXMLHttpRequest: Request failed for URL: %@, error %@", strongSelf.url, [error localizedDescription]);
- #endif
- dispatch_async(dispatch_get_main_queue(), ^(){
- strongSelf.onFailure();
- });
- } else {
- if ([response isKindOfClass:[NSHTTPURLResponse class]]){
- NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
- if (httpResponse.statusCode != 200) {
- strongSelf->_lastError = FSXMLHttpRequestError_Invalid_Http_Status;
-
- #if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
- NSLog(@"FSXMLHttpRequest: Unable to receive content for URL: %@", strongSelf.url);
- #endif
- dispatch_async(dispatch_get_main_queue(), ^(){
- strongSelf.onFailure();
- });
- return;
- }
- }
-
- const char *encoding = [self detectEncoding:data];
-
- strongSelf->_xmlDocument = xmlReadMemory([data bytes],
- (int)[data length],
- "",
- encoding,
- 0);
-
- if (!strongSelf->_xmlDocument) {
- strongSelf->_lastError = FSXMLHttpRequestError_XML_Parser_Failed;
-
- #if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
- NSLog(@"FSXMLHttpRequest: Unable to parse the content for URL: %@", strongSelf.url);
- #endif
-
- dispatch_async(dispatch_get_main_queue(), ^(){
- strongSelf.onFailure();
- });
- return;
- }
-
- [strongSelf parseResponseData];
-
- xmlFreeDoc(strongSelf->_xmlDocument);
- strongSelf->_xmlDocument = nil;
-
- dispatch_async(dispatch_get_main_queue(), ^(){
- strongSelf.onCompletion();
- });
- }
- }];
- }
- [_task resume];
-
- }
- - (void)cancel
- {
- if (!_task) {
- return;
- }
- @synchronized (self) {
- [_task cancel];
- _task = nil;
- }
- }
- /*
- * =======================================
- * XML handling
- * =======================================
- */
- - (NSArray *)performXPathQuery:(NSString *)query
- {
- NSMutableArray *resultNodes = [NSMutableArray array];
- xmlXPathContextPtr xpathCtx = NULL;
- xmlXPathObjectPtr xpathObj = NULL;
-
- xpathCtx = xmlXPathNewContext(_xmlDocument);
- if (xpathCtx == NULL) {
- goto cleanup;
- }
-
- xpathObj = xmlXPathEvalExpression((xmlChar *)[query cStringUsingEncoding:NSUTF8StringEncoding], xpathCtx);
- if (xpathObj == NULL) {
- goto cleanup;
- }
-
- xmlNodeSetPtr nodes = xpathObj->nodesetval;
- if (!nodes) {
- goto cleanup;
- }
-
- for (size_t i = 0; i < nodes->nodeNr; i++) {
- [self parseXMLNode:nodes->nodeTab[i] xPathQuery:query];
- }
-
- cleanup:
- if (xpathObj) {
- xmlXPathFreeObject(xpathObj);
- }
- if (xpathCtx) {
- xmlXPathFreeContext(xpathCtx);
- }
- return resultNodes;
- }
- - (NSString *)contentForNode:(xmlNodePtr)node
- {
- NSString *stringWithContent;
- if (!node) {
- stringWithContent = [[NSString alloc] init];
- } else {
- xmlChar *content = xmlNodeGetContent(node);
- if (!content) {
- return stringWithContent;
- }
- stringWithContent = @((const char *)content);
- xmlFree(content);
- }
- return stringWithContent;
- }
- - (NSString *)contentForNodeAttribute:(xmlNodePtr)node attribute:(const char *)attr
- {
- NSString *stringWithContent;
- if (!node) {
- stringWithContent = [[NSString alloc] init];
- } else {
- xmlChar *content = xmlGetProp(node, (const xmlChar *)attr);
- if (!content) {
- return stringWithContent;
- }
- stringWithContent = @((const char *)content);
- xmlFree(content);
- }
- return stringWithContent;
- }
- /*
- * =======================================
- * Helpers
- * =======================================
- */
- - (const char *)detectEncoding:(NSData *)receivedData
- {
- const char *encoding = 0;
- const char *header = strndup([receivedData bytes], 60);
-
- if (strstr(header, "utf-8") || strstr(header, "UTF-8")) {
- encoding = "UTF-8";
- } else if (strstr(header, "iso-8859-1") || strstr(header, "ISO-8859-1")) {
- encoding = "ISO-8859-1";
- }
-
- free((void *)header);
- return encoding;
- }
- - (NSDate *)dateFromNode:(xmlNodePtr)node
- {
- NSString *dateString = [self contentForNode:node];
-
- /*
- * For some NSDateFormatter date parsing oddities: http://www.openradar.me/9944011
- *
- * Engineering has determined that this issue behaves as intended based on the following information:
- *
- * This is an intentional change in iOS 5. The issue is this: With the short formats as specified by z (=zzz) or v (=vvv),
- * there can be a lot of ambiguity. For example, "ET" for Eastern Time" could apply to different time zones in many different regions.
- * To improve formatting and parsing reliability, the short forms are only used in a locale if the "cu" (commonly used) flag is set
- * for the locale. Otherwise, only the long forms are used (for both formatting and parsing). This is a change in
- * open-source CLDR 2.0 / ICU 4.8, which is the basis for the ICU in iOS 5, which in turn is the basis of NSDateFormatter behavior.
- *
- * For the "en" locale (= "en_US"), the cu flag is set for metazones such as Alaska, America_Central, America_Eastern, America_Mountain,
- * America_Pacific, Atlantic, Hawaii_Aleutian, and GMT. It is not set for Europe_Central.
- *
- * However, for the "en_GB" locale, the cu flag is set for Europe_Central.
- *
- * So, a formatter set for short timezone style "z" or "zzz" and locale "en" or "en_US" will not parse "CEST" or "CET", but if the
- * locale is instead set to "en_GB" it will parse those. The "GMT" style will be parsed by all.
- *
- * If the formatter is set for the long timezone style "zzzz", and the locale is any of "en", "en_US", or "en_GB", then any of the
- * following will be parsed, because they are unambiguous:
- *
- * "Pacific Daylight Time" "Central European Summer Time" "Central European Time"
- *
- */
-
- return [_dateFormatter dateFromString:dateString];
- }
- @end
|