FSXMLHttpRequest.m 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. /*
  2. * This file is part of the FreeStreamer project,
  3. * (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
  4. * See the file ''LICENSE'' for using the code.
  5. *
  6. * https://github.com/muhku/FreeStreamer
  7. */
  8. #import "FSXMLHttpRequest.h"
  9. #import <libxml/parser.h>
  10. #import <libxml/xpath.h>
  11. #define DATE_COMPONENTS (NSYearCalendarUnit| NSMonthCalendarUnit | NSDayCalendarUnit | NSWeekCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit | NSWeekdayCalendarUnit | NSWeekdayOrdinalCalendarUnit)
  12. #define CURRENT_CALENDAR [NSCalendar currentCalendar]
  13. @interface FSXMLHttpRequest (PrivateMethods)
  14. - (const char *)detectEncoding;
  15. - (void)parseResponseData;
  16. - (void)parseXMLNode:(xmlNodePtr)node xPathQuery:(NSString *)xPathQuery;
  17. @end
  18. @implementation FSXMLHttpRequest
  19. - (id)init
  20. {
  21. self = [super init];
  22. if (self) {
  23. _dateFormatter = [[NSDateFormatter alloc] init];
  24. }
  25. return self;
  26. }
  27. - (void)start
  28. {
  29. if (_task) {
  30. return;
  31. }
  32. _lastError = FSXMLHttpRequestError_NoError;
  33. NSURLRequest *request = [NSURLRequest requestWithURL:self.url
  34. cachePolicy:NSURLRequestUseProtocolCachePolicy
  35. timeoutInterval:10.0];
  36. NSURLSession *session = [NSURLSession sharedSession];
  37. __weak FSXMLHttpRequest *weakSelf = self;
  38. @synchronized (self) {
  39. _task = [session dataTaskWithRequest:request
  40. completionHandler:
  41. ^(NSData *data, NSURLResponse *response, NSError *error) {
  42. FSXMLHttpRequest *strongSelf = weakSelf;
  43. if(error) {
  44. strongSelf->_lastError = FSXMLHttpRequestError_Connection_Failed;
  45. #if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
  46. NSLog(@"FSXMLHttpRequest: Request failed for URL: %@, error %@", strongSelf.url, [error localizedDescription]);
  47. #endif
  48. dispatch_async(dispatch_get_main_queue(), ^(){
  49. strongSelf.onFailure();
  50. });
  51. } else {
  52. if ([response isKindOfClass:[NSHTTPURLResponse class]]){
  53. NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
  54. if (httpResponse.statusCode != 200) {
  55. strongSelf->_lastError = FSXMLHttpRequestError_Invalid_Http_Status;
  56. #if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
  57. NSLog(@"FSXMLHttpRequest: Unable to receive content for URL: %@", strongSelf.url);
  58. #endif
  59. dispatch_async(dispatch_get_main_queue(), ^(){
  60. strongSelf.onFailure();
  61. });
  62. return;
  63. }
  64. }
  65. const char *encoding = [self detectEncoding:data];
  66. strongSelf->_xmlDocument = xmlReadMemory([data bytes],
  67. (int)[data length],
  68. "",
  69. encoding,
  70. 0);
  71. if (!strongSelf->_xmlDocument) {
  72. strongSelf->_lastError = FSXMLHttpRequestError_XML_Parser_Failed;
  73. #if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
  74. NSLog(@"FSXMLHttpRequest: Unable to parse the content for URL: %@", strongSelf.url);
  75. #endif
  76. dispatch_async(dispatch_get_main_queue(), ^(){
  77. strongSelf.onFailure();
  78. });
  79. return;
  80. }
  81. [strongSelf parseResponseData];
  82. xmlFreeDoc(strongSelf->_xmlDocument);
  83. strongSelf->_xmlDocument = nil;
  84. dispatch_async(dispatch_get_main_queue(), ^(){
  85. strongSelf.onCompletion();
  86. });
  87. }
  88. }];
  89. }
  90. [_task resume];
  91. }
  92. - (void)cancel
  93. {
  94. if (!_task) {
  95. return;
  96. }
  97. @synchronized (self) {
  98. [_task cancel];
  99. _task = nil;
  100. }
  101. }
  102. /*
  103. * =======================================
  104. * XML handling
  105. * =======================================
  106. */
  107. - (NSArray *)performXPathQuery:(NSString *)query
  108. {
  109. NSMutableArray *resultNodes = [NSMutableArray array];
  110. xmlXPathContextPtr xpathCtx = NULL;
  111. xmlXPathObjectPtr xpathObj = NULL;
  112. xpathCtx = xmlXPathNewContext(_xmlDocument);
  113. if (xpathCtx == NULL) {
  114. goto cleanup;
  115. }
  116. xpathObj = xmlXPathEvalExpression((xmlChar *)[query cStringUsingEncoding:NSUTF8StringEncoding], xpathCtx);
  117. if (xpathObj == NULL) {
  118. goto cleanup;
  119. }
  120. xmlNodeSetPtr nodes = xpathObj->nodesetval;
  121. if (!nodes) {
  122. goto cleanup;
  123. }
  124. for (size_t i = 0; i < nodes->nodeNr; i++) {
  125. [self parseXMLNode:nodes->nodeTab[i] xPathQuery:query];
  126. }
  127. cleanup:
  128. if (xpathObj) {
  129. xmlXPathFreeObject(xpathObj);
  130. }
  131. if (xpathCtx) {
  132. xmlXPathFreeContext(xpathCtx);
  133. }
  134. return resultNodes;
  135. }
  136. - (NSString *)contentForNode:(xmlNodePtr)node
  137. {
  138. NSString *stringWithContent;
  139. if (!node) {
  140. stringWithContent = [[NSString alloc] init];
  141. } else {
  142. xmlChar *content = xmlNodeGetContent(node);
  143. if (!content) {
  144. return stringWithContent;
  145. }
  146. stringWithContent = @((const char *)content);
  147. xmlFree(content);
  148. }
  149. return stringWithContent;
  150. }
  151. - (NSString *)contentForNodeAttribute:(xmlNodePtr)node attribute:(const char *)attr
  152. {
  153. NSString *stringWithContent;
  154. if (!node) {
  155. stringWithContent = [[NSString alloc] init];
  156. } else {
  157. xmlChar *content = xmlGetProp(node, (const xmlChar *)attr);
  158. if (!content) {
  159. return stringWithContent;
  160. }
  161. stringWithContent = @((const char *)content);
  162. xmlFree(content);
  163. }
  164. return stringWithContent;
  165. }
  166. /*
  167. * =======================================
  168. * Helpers
  169. * =======================================
  170. */
  171. - (const char *)detectEncoding:(NSData *)receivedData
  172. {
  173. const char *encoding = 0;
  174. const char *header = strndup([receivedData bytes], 60);
  175. if (strstr(header, "utf-8") || strstr(header, "UTF-8")) {
  176. encoding = "UTF-8";
  177. } else if (strstr(header, "iso-8859-1") || strstr(header, "ISO-8859-1")) {
  178. encoding = "ISO-8859-1";
  179. }
  180. free((void *)header);
  181. return encoding;
  182. }
  183. - (NSDate *)dateFromNode:(xmlNodePtr)node
  184. {
  185. NSString *dateString = [self contentForNode:node];
  186. /*
  187. * For some NSDateFormatter date parsing oddities: http://www.openradar.me/9944011
  188. *
  189. * Engineering has determined that this issue behaves as intended based on the following information:
  190. *
  191. * This is an intentional change in iOS 5. The issue is this: With the short formats as specified by z (=zzz) or v (=vvv),
  192. * there can be a lot of ambiguity. For example, "ET" for Eastern Time" could apply to different time zones in many different regions.
  193. * To improve formatting and parsing reliability, the short forms are only used in a locale if the "cu" (commonly used) flag is set
  194. * for the locale. Otherwise, only the long forms are used (for both formatting and parsing). This is a change in
  195. * 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.
  196. *
  197. * For the "en" locale (= "en_US"), the cu flag is set for metazones such as Alaska, America_Central, America_Eastern, America_Mountain,
  198. * America_Pacific, Atlantic, Hawaii_Aleutian, and GMT. It is not set for Europe_Central.
  199. *
  200. * However, for the "en_GB" locale, the cu flag is set for Europe_Central.
  201. *
  202. * 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
  203. * locale is instead set to "en_GB" it will parse those. The "GMT" style will be parsed by all.
  204. *
  205. * 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
  206. * following will be parsed, because they are unambiguous:
  207. *
  208. * "Pacific Daylight Time" "Central European Summer Time" "Central European Time"
  209. *
  210. */
  211. return [_dateFormatter dateFromString:dateString];
  212. }
  213. @end