You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

545 lines
22 KiB

5 years ago
5 years ago
5 years ago
  1. //
  2. // FLEXNetworkTransactionDetailTableViewController.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 2/10/15.
  6. // Copyright (c) 2015 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXColor.h"
  9. #import "FLEXNetworkTransactionDetailTableViewController.h"
  10. #import "FLEXNetworkCurlLogger.h"
  11. #import "FLEXNetworkRecorder.h"
  12. #import "FLEXNetworkTransaction.h"
  13. #import "FLEXWebViewController.h"
  14. #import "FLEXImagePreviewViewController.h"
  15. #import "FLEXMultilineTableViewCell.h"
  16. #import "FLEXUtility.h"
  17. #import "FLEXManager+Private.h"
  18. #import "FLEXTableView.h"
  19. typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
  20. @interface FLEXNetworkDetailRow : NSObject
  21. @property (nonatomic, copy) NSString *title;
  22. @property (nonatomic, copy) NSString *detailText;
  23. @property (nonatomic, copy) FLEXNetworkDetailRowSelectionFuture selectionFuture;
  24. @end
  25. @implementation FLEXNetworkDetailRow
  26. @end
  27. @interface FLEXNetworkDetailSection : NSObject
  28. @property (nonatomic, copy) NSString *title;
  29. @property (nonatomic, copy) NSArray<FLEXNetworkDetailRow *> *rows;
  30. @end
  31. @implementation FLEXNetworkDetailSection
  32. @end
  33. @interface FLEXNetworkTransactionDetailTableViewController ()
  34. @property (nonatomic, copy) NSArray<FLEXNetworkDetailSection *> *sections;
  35. @end
  36. @implementation FLEXNetworkTransactionDetailTableViewController
  37. - (instancetype)initWithStyle:(UITableViewStyle)style
  38. {
  39. // Force grouped style
  40. self = [super initWithStyle:UITableViewStyleGrouped];
  41. if (self) {
  42. [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(handleTransactionUpdatedNotification:) name:kFLEXNetworkRecorderTransactionUpdatedNotification object:nil];
  43. self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Copy curl" style:UIBarButtonItemStylePlain target:self action:@selector(copyButtonPressed:)];
  44. }
  45. return self;
  46. }
  47. - (void)viewDidLoad
  48. {
  49. [super viewDidLoad];
  50. [self.tableView registerClass:[FLEXMultilineTableViewCell class] forCellReuseIdentifier:kFLEXMultilineCell];
  51. }
  52. - (void)setTransaction:(FLEXNetworkTransaction *)transaction
  53. {
  54. if (![_transaction isEqual:transaction]) {
  55. _transaction = transaction;
  56. self.title = [transaction.request.URL lastPathComponent];
  57. [self rebuildTableSections];
  58. }
  59. }
  60. - (void)setSections:(NSArray<FLEXNetworkDetailSection *> *)sections
  61. {
  62. if (![_sections isEqual:sections]) {
  63. _sections = [sections copy];
  64. [self.tableView reloadData];
  65. }
  66. }
  67. - (void)rebuildTableSections
  68. {
  69. NSMutableArray<FLEXNetworkDetailSection *> *sections = [NSMutableArray array];
  70. FLEXNetworkDetailSection *generalSection = [[self class] generalSectionForTransaction:self.transaction];
  71. if (generalSection.rows.count > 0) {
  72. [sections addObject:generalSection];
  73. }
  74. FLEXNetworkDetailSection *requestHeadersSection = [[self class] requestHeadersSectionForTransaction:self.transaction];
  75. if (requestHeadersSection.rows.count > 0) {
  76. [sections addObject:requestHeadersSection];
  77. }
  78. FLEXNetworkDetailSection *queryParametersSection = [[self class] queryParametersSectionForTransaction:self.transaction];
  79. if (queryParametersSection.rows.count > 0) {
  80. [sections addObject:queryParametersSection];
  81. }
  82. FLEXNetworkDetailSection *postBodySection = [[self class] postBodySectionForTransaction:self.transaction];
  83. if (postBodySection.rows.count > 0) {
  84. [sections addObject:postBodySection];
  85. }
  86. FLEXNetworkDetailSection *responseHeadersSection = [[self class] responseHeadersSectionForTransaction:self.transaction];
  87. if (responseHeadersSection.rows.count > 0) {
  88. [sections addObject:responseHeadersSection];
  89. }
  90. self.sections = sections;
  91. }
  92. - (void)handleTransactionUpdatedNotification:(NSNotification *)notification
  93. {
  94. FLEXNetworkTransaction *transaction = [[notification userInfo] objectForKey:kFLEXNetworkRecorderUserInfoTransactionKey];
  95. if (transaction == self.transaction) {
  96. [self rebuildTableSections];
  97. }
  98. }
  99. - (void)copyButtonPressed:(id)sender
  100. {
  101. [UIPasteboard.generalPasteboard setString:[FLEXNetworkCurlLogger curlCommandString:_transaction.request]];
  102. }
  103. #pragma mark - Table view data source
  104. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  105. {
  106. return self.sections.count;
  107. }
  108. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  109. {
  110. FLEXNetworkDetailSection *sectionModel = self.sections[section];
  111. return sectionModel.rows.count;
  112. }
  113. - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
  114. {
  115. FLEXNetworkDetailSection *sectionModel = self.sections[section];
  116. return sectionModel.title;
  117. }
  118. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  119. {
  120. FLEXMultilineTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXMultilineCell forIndexPath:indexPath];
  121. FLEXNetworkDetailRow *rowModel = [self rowModelAtIndexPath:indexPath];
  122. cell.textLabel.attributedText = [[self class] attributedTextForRow:rowModel];
  123. cell.accessoryType = rowModel.selectionFuture ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone;
  124. cell.selectionStyle = rowModel.selectionFuture ? UITableViewCellSelectionStyleDefault : UITableViewCellSelectionStyleNone;
  125. return cell;
  126. }
  127. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  128. {
  129. FLEXNetworkDetailRow *rowModel = [self rowModelAtIndexPath:indexPath];
  130. UIViewController *viewController = nil;
  131. if (rowModel.selectionFuture) {
  132. viewController = rowModel.selectionFuture();
  133. }
  134. if ([viewController isKindOfClass:UIAlertController.class]) {
  135. [self presentViewController:viewController animated:YES completion:nil];
  136. } else if (viewController) {
  137. [self.navigationController pushViewController:viewController animated:YES];
  138. }
  139. [tableView deselectRowAtIndexPath:indexPath animated:YES];
  140. }
  141. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
  142. {
  143. FLEXNetworkDetailRow *row = [self rowModelAtIndexPath:indexPath];
  144. NSAttributedString *attributedText = [[self class] attributedTextForRow:row];
  145. BOOL showsAccessory = row.selectionFuture != nil;
  146. return [FLEXMultilineTableViewCell preferredHeightWithAttributedText:attributedText inTableViewWidth:self.tableView.bounds.size.width style:UITableViewStyleGrouped showsAccessory:showsAccessory];
  147. }
  148. - (FLEXNetworkDetailRow *)rowModelAtIndexPath:(NSIndexPath *)indexPath
  149. {
  150. FLEXNetworkDetailSection *sectionModel = self.sections[indexPath.section];
  151. return sectionModel.rows[indexPath.row];
  152. }
  153. #pragma mark - Cell Copying
  154. - (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath
  155. {
  156. return YES;
  157. }
  158. - (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
  159. {
  160. return action == @selector(copy:);
  161. }
  162. - (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
  163. {
  164. if (action == @selector(copy:)) {
  165. FLEXNetworkDetailRow *row = [self rowModelAtIndexPath:indexPath];
  166. UIPasteboard.generalPasteboard.string = row.detailText;
  167. }
  168. }
  169. #if FLEX_AT_LEAST_IOS13_SDK
  170. - (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0)
  171. {
  172. return [UIContextMenuConfiguration
  173. configurationWithIdentifier:nil
  174. previewProvider:nil
  175. actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) {
  176. UIAction *copy = [UIAction
  177. actionWithTitle:@"Copy"
  178. image:nil
  179. identifier:nil
  180. handler:^(__kindof UIAction *action) {
  181. FLEXNetworkDetailRow *row = [self rowModelAtIndexPath:indexPath];
  182. UIPasteboard.generalPasteboard.string = row.detailText;
  183. }
  184. ];
  185. return [UIMenu
  186. menuWithTitle:@"" image:nil identifier:nil
  187. options:UIMenuOptionsDisplayInline
  188. children:@[copy]
  189. ];
  190. }
  191. ];
  192. }
  193. #endif
  194. #pragma mark - View Configuration
  195. + (NSAttributedString *)attributedTextForRow:(FLEXNetworkDetailRow *)row
  196. {
  197. NSDictionary<NSString *, id> *titleAttributes = @{ NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:12.0],
  198. NSForegroundColorAttributeName : [UIColor colorWithWhite:0.5 alpha:1.0] };
  199. NSDictionary<NSString *, id> *detailAttributes = @{ NSFontAttributeName : [FLEXUtility defaultTableViewCellLabelFont],
  200. NSForegroundColorAttributeName : [FLEXColor primaryTextColor] };
  201. NSString *title = [NSString stringWithFormat:@"%@: ", row.title];
  202. NSString *detailText = row.detailText ?: @"";
  203. NSMutableAttributedString *attributedText = [NSMutableAttributedString new];
  204. [attributedText appendAttributedString:[[NSAttributedString alloc] initWithString:title attributes:titleAttributes]];
  205. [attributedText appendAttributedString:[[NSAttributedString alloc] initWithString:detailText attributes:detailAttributes]];
  206. return attributedText;
  207. }
  208. #pragma mark - Table Data Generation
  209. + (FLEXNetworkDetailSection *)generalSectionForTransaction:(FLEXNetworkTransaction *)transaction
  210. {
  211. NSMutableArray<FLEXNetworkDetailRow *> *rows = [NSMutableArray array];
  212. FLEXNetworkDetailRow *requestURLRow = [FLEXNetworkDetailRow new];
  213. requestURLRow.title = @"Request URL";
  214. NSURL *url = transaction.request.URL;
  215. requestURLRow.detailText = url.absoluteString;
  216. requestURLRow.selectionFuture = ^{
  217. UIViewController *urlWebViewController = [[FLEXWebViewController alloc] initWithURL:url];
  218. urlWebViewController.title = url.absoluteString;
  219. return urlWebViewController;
  220. };
  221. [rows addObject:requestURLRow];
  222. FLEXNetworkDetailRow *requestMethodRow = [FLEXNetworkDetailRow new];
  223. requestMethodRow.title = @"Request Method";
  224. requestMethodRow.detailText = transaction.request.HTTPMethod;
  225. [rows addObject:requestMethodRow];
  226. if (transaction.cachedRequestBody.length > 0) {
  227. FLEXNetworkDetailRow *postBodySizeRow = [FLEXNetworkDetailRow new];
  228. postBodySizeRow.title = @"Request Body Size";
  229. postBodySizeRow.detailText = [NSByteCountFormatter stringFromByteCount:transaction.cachedRequestBody.length countStyle:NSByteCountFormatterCountStyleBinary];
  230. [rows addObject:postBodySizeRow];
  231. FLEXNetworkDetailRow *postBodyRow = [FLEXNetworkDetailRow new];
  232. postBodyRow.title = @"Request Body";
  233. postBodyRow.detailText = @"tap to view";
  234. postBodyRow.selectionFuture = ^UIViewController * () {
  235. // Show the body if we can
  236. NSString *contentType = [transaction.request valueForHTTPHeaderField:@"Content-Type"];
  237. UIViewController *detailViewController = [self detailViewControllerForMIMEType:contentType data:[self postBodyDataForTransaction:transaction]];
  238. if (detailViewController) {
  239. detailViewController.title = @"Request Body";
  240. return detailViewController;
  241. }
  242. // We can't show the body, alert user
  243. return [FLEXAlert makeAlert:^(FLEXAlert *make) {
  244. make.title(@"Can't View HTTP Body Data");
  245. make.message(@"FLEX does not have a viewer for request body data with MIME type: ");
  246. make.message(contentType);
  247. make.button(@"Dismiss").cancelStyle();
  248. }];
  249. };
  250. [rows addObject:postBodyRow];
  251. }
  252. NSString *statusCodeString = [FLEXUtility statusCodeStringFromURLResponse:transaction.response];
  253. if (statusCodeString.length > 0) {
  254. FLEXNetworkDetailRow *statusCodeRow = [FLEXNetworkDetailRow new];
  255. statusCodeRow.title = @"Status Code";
  256. statusCodeRow.detailText = statusCodeString;
  257. [rows addObject:statusCodeRow];
  258. }
  259. if (transaction.error) {
  260. FLEXNetworkDetailRow *errorRow = [FLEXNetworkDetailRow new];
  261. errorRow.title = @"Error";
  262. errorRow.detailText = transaction.error.localizedDescription;
  263. [rows addObject:errorRow];
  264. }
  265. FLEXNetworkDetailRow *responseBodyRow = [FLEXNetworkDetailRow new];
  266. responseBodyRow.title = @"Response Body";
  267. NSData *responseData = [[FLEXNetworkRecorder defaultRecorder] cachedResponseBodyForTransaction:transaction];
  268. if (responseData.length > 0) {
  269. responseBodyRow.detailText = @"tap to view";
  270. // Avoid a long lived strong reference to the response data in case we need to purge it from the cache.
  271. __weak NSData *weakResponseData = responseData;
  272. responseBodyRow.selectionFuture = ^UIViewController * () {
  273. // Show the response if we can
  274. NSString *contentType = transaction.response.MIMEType;
  275. NSData *strongResponseData = weakResponseData;
  276. if (strongResponseData) {
  277. UIViewController *bodyDetailController = [self detailViewControllerForMIMEType:contentType data:strongResponseData];
  278. if (bodyDetailController) {
  279. bodyDetailController.title = @"Response";
  280. return bodyDetailController;
  281. }
  282. }
  283. // We can't show the response, alert user
  284. return [FLEXAlert makeAlert:^(FLEXAlert *make) {
  285. make.title(@"Unable to View Response");
  286. if (strongResponseData) {
  287. make.message(@"No viewer content type: ").message(contentType);
  288. } else {
  289. make.message(@"The response has been purged from the cache");
  290. }
  291. make.button(@"OK").cancelStyle();
  292. }];
  293. };
  294. } else {
  295. BOOL emptyResponse = transaction.receivedDataLength == 0;
  296. responseBodyRow.detailText = emptyResponse ? @"empty" : @"not in cache";
  297. }
  298. [rows addObject:responseBodyRow];
  299. FLEXNetworkDetailRow *responseSizeRow = [FLEXNetworkDetailRow new];
  300. responseSizeRow.title = @"Response Size";
  301. responseSizeRow.detailText = [NSByteCountFormatter stringFromByteCount:transaction.receivedDataLength countStyle:NSByteCountFormatterCountStyleBinary];
  302. [rows addObject:responseSizeRow];
  303. FLEXNetworkDetailRow *mimeTypeRow = [FLEXNetworkDetailRow new];
  304. mimeTypeRow.title = @"MIME Type";
  305. mimeTypeRow.detailText = transaction.response.MIMEType;
  306. [rows addObject:mimeTypeRow];
  307. FLEXNetworkDetailRow *mechanismRow = [FLEXNetworkDetailRow new];
  308. mechanismRow.title = @"Mechanism";
  309. mechanismRow.detailText = transaction.requestMechanism;
  310. [rows addObject:mechanismRow];
  311. NSDateFormatter *startTimeFormatter = [NSDateFormatter new];
  312. startTimeFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS";
  313. FLEXNetworkDetailRow *localStartTimeRow = [FLEXNetworkDetailRow new];
  314. localStartTimeRow.title = [NSString stringWithFormat:@"Start Time (%@)", [[NSTimeZone localTimeZone] abbreviationForDate:transaction.startTime]];
  315. localStartTimeRow.detailText = [startTimeFormatter stringFromDate:transaction.startTime];
  316. [rows addObject:localStartTimeRow];
  317. startTimeFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];
  318. FLEXNetworkDetailRow *utcStartTimeRow = [FLEXNetworkDetailRow new];
  319. utcStartTimeRow.title = @"Start Time (UTC)";
  320. utcStartTimeRow.detailText = [startTimeFormatter stringFromDate:transaction.startTime];
  321. [rows addObject:utcStartTimeRow];
  322. FLEXNetworkDetailRow *unixStartTime = [FLEXNetworkDetailRow new];
  323. unixStartTime.title = @"Unix Start Time";
  324. unixStartTime.detailText = [NSString stringWithFormat:@"%f", [transaction.startTime timeIntervalSince1970]];
  325. [rows addObject:unixStartTime];
  326. FLEXNetworkDetailRow *durationRow = [FLEXNetworkDetailRow new];
  327. durationRow.title = @"Total Duration";
  328. durationRow.detailText = [FLEXUtility stringFromRequestDuration:transaction.duration];
  329. [rows addObject:durationRow];
  330. FLEXNetworkDetailRow *latencyRow = [FLEXNetworkDetailRow new];
  331. latencyRow.title = @"Latency";
  332. latencyRow.detailText = [FLEXUtility stringFromRequestDuration:transaction.latency];
  333. [rows addObject:latencyRow];
  334. FLEXNetworkDetailSection *generalSection = [FLEXNetworkDetailSection new];
  335. generalSection.title = @"General";
  336. generalSection.rows = rows;
  337. return generalSection;
  338. }
  339. + (FLEXNetworkDetailSection *)requestHeadersSectionForTransaction:(FLEXNetworkTransaction *)transaction
  340. {
  341. FLEXNetworkDetailSection *requestHeadersSection = [FLEXNetworkDetailSection new];
  342. requestHeadersSection.title = @"Request Headers";
  343. requestHeadersSection.rows = [self networkDetailRowsFromDictionary:transaction.request.allHTTPHeaderFields];
  344. return requestHeadersSection;
  345. }
  346. + (FLEXNetworkDetailSection *)postBodySectionForTransaction:(FLEXNetworkTransaction *)transaction
  347. {
  348. FLEXNetworkDetailSection *postBodySection = [FLEXNetworkDetailSection new];
  349. postBodySection.title = @"Request Body Parameters";
  350. if (transaction.cachedRequestBody.length > 0) {
  351. NSString *contentType = [transaction.request valueForHTTPHeaderField:@"Content-Type"];
  352. if ([contentType hasPrefix:@"application/x-www-form-urlencoded"]) {
  353. NSString *bodyString = [NSString stringWithCString:[self postBodyDataForTransaction:transaction].bytes encoding:NSUTF8StringEncoding];
  354. postBodySection.rows = [self networkDetailRowsFromQueryItems:[FLEXUtility itemsFromQueryString:bodyString]];
  355. }
  356. }
  357. return postBodySection;
  358. }
  359. + (FLEXNetworkDetailSection *)queryParametersSectionForTransaction:(FLEXNetworkTransaction *)transaction
  360. {
  361. NSArray<NSURLQueryItem *> *queries = [FLEXUtility itemsFromQueryString:transaction.request.URL.query];
  362. FLEXNetworkDetailSection *querySection = [FLEXNetworkDetailSection new];
  363. querySection.title = @"Query Parameters";
  364. querySection.rows = [self networkDetailRowsFromQueryItems:queries];
  365. return querySection;
  366. }
  367. + (FLEXNetworkDetailSection *)responseHeadersSectionForTransaction:(FLEXNetworkTransaction *)transaction
  368. {
  369. FLEXNetworkDetailSection *responseHeadersSection = [FLEXNetworkDetailSection new];
  370. responseHeadersSection.title = @"Response Headers";
  371. if ([transaction.response isKindOfClass:[NSHTTPURLResponse class]]) {
  372. NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)transaction.response;
  373. responseHeadersSection.rows = [self networkDetailRowsFromDictionary:httpResponse.allHeaderFields];
  374. }
  375. return responseHeadersSection;
  376. }
  377. + (NSArray<FLEXNetworkDetailRow *> *)networkDetailRowsFromDictionary:(NSDictionary<NSString *, id> *)dictionary
  378. {
  379. NSMutableArray<FLEXNetworkDetailRow *> *rows = [NSMutableArray new];
  380. NSArray<NSString *> *sortedKeys = [dictionary.allKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
  381. for (NSString *key in sortedKeys) {
  382. id value = dictionary[key];
  383. FLEXNetworkDetailRow *row = [FLEXNetworkDetailRow new];
  384. row.title = key;
  385. row.detailText = [value description];
  386. [rows addObject:row];
  387. }
  388. return rows.copy;
  389. }
  390. + (NSArray<FLEXNetworkDetailRow *> *)networkDetailRowsFromQueryItems:(NSArray<NSURLQueryItem *> *)items
  391. {
  392. // Sort the items by name
  393. items = [items sortedArrayUsingComparator:^NSComparisonResult(NSURLQueryItem *item1, NSURLQueryItem *item2) {
  394. return [item1.name caseInsensitiveCompare:item2.name];
  395. }];
  396. NSMutableArray<FLEXNetworkDetailRow *> *rows = [NSMutableArray new];
  397. for (NSURLQueryItem *item in items) {
  398. FLEXNetworkDetailRow *row = [FLEXNetworkDetailRow new];
  399. row.title = item.name;
  400. row.detailText = item.value;
  401. [rows addObject:row];
  402. }
  403. return [rows copy];
  404. }
  405. + (UIViewController *)detailViewControllerForMIMEType:(NSString *)mimeType data:(NSData *)data
  406. {
  407. FLEXCustomContentViewerFuture makeCustomViewer = [FLEXManager sharedManager].customContentTypeViewers[mimeType.lowercaseString];
  408. if (makeCustomViewer) {
  409. UIViewController *viewer = makeCustomViewer(data);
  410. if (viewer) {
  411. return viewer;
  412. }
  413. }
  414. // FIXME (RKO): Don't rely on UTF8 string encoding
  415. UIViewController *detailViewController = nil;
  416. if ([FLEXUtility isValidJSONData:data]) {
  417. NSString *prettyJSON = [FLEXUtility prettyJSONStringFromData:data];
  418. if (prettyJSON.length > 0) {
  419. detailViewController = [[FLEXWebViewController alloc] initWithText:prettyJSON];
  420. }
  421. } else if ([mimeType hasPrefix:@"image/"]) {
  422. UIImage *image = [UIImage imageWithData:data];
  423. detailViewController = [[FLEXImagePreviewViewController alloc] initWithImage:image];
  424. } else if ([mimeType isEqual:@"application/x-plist"]) {
  425. id propertyList = [NSPropertyListSerialization propertyListWithData:data options:0 format:NULL error:NULL];
  426. detailViewController = [[FLEXWebViewController alloc] initWithText:[propertyList description]];
  427. }
  428. // Fall back to trying to show the response as text
  429. if (!detailViewController) {
  430. NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  431. if (text.length > 0) {
  432. detailViewController = [[FLEXWebViewController alloc] initWithText:text];
  433. }
  434. }
  435. return detailViewController;
  436. }
  437. + (NSData *)postBodyDataForTransaction:(FLEXNetworkTransaction *)transaction
  438. {
  439. NSData *bodyData = transaction.cachedRequestBody;
  440. if (bodyData.length > 0) {
  441. NSString *contentEncoding = [transaction.request valueForHTTPHeaderField:@"Content-Encoding"];
  442. if ([contentEncoding rangeOfString:@"deflate" options:NSCaseInsensitiveSearch].length > 0 || [contentEncoding rangeOfString:@"gzip" options:NSCaseInsensitiveSearch].length > 0) {
  443. bodyData = [FLEXUtility inflatedDataFromCompressedData:bodyData];
  444. }
  445. }
  446. return bodyData;
  447. }
  448. @end