123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542 |
- //
- // FLEXNetworkTransactionDetailController.m
- // Flipboard
- //
- // Created by Ryan Olson on 2/10/15.
- // Copyright (c) 2020 FLEX Team. All rights reserved.
- //
- #import "FLEXColor.h"
- #import "FLEXNetworkTransactionDetailController.h"
- #import "FLEXNetworkCurlLogger.h"
- #import "FLEXNetworkRecorder.h"
- #import "FLEXNetworkTransaction.h"
- #import "FLEXWebViewController.h"
- #import "FLEXImagePreviewViewController.h"
- #import "FLEXMultilineTableViewCell.h"
- #import "FLEXUtility.h"
- #import "FLEXManager+Private.h"
- #import "FLEXTableView.h"
- #import "UIBarButtonItem+FLEX.h"
- typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
- @interface FLEXNetworkDetailRow : NSObject
- @property (nonatomic, copy) NSString *title;
- @property (nonatomic, copy) NSString *detailText;
- @property (nonatomic, copy) FLEXNetworkDetailRowSelectionFuture selectionFuture;
- @end
- @implementation FLEXNetworkDetailRow
- @end
- @interface FLEXNetworkDetailSection : NSObject
- @property (nonatomic, copy) NSString *title;
- @property (nonatomic, copy) NSArray<FLEXNetworkDetailRow *> *rows;
- @end
- @implementation FLEXNetworkDetailSection
- @end
- @interface FLEXNetworkTransactionDetailController ()
- @property (nonatomic, copy) NSArray<FLEXNetworkDetailSection *> *sections;
- @end
- @implementation FLEXNetworkTransactionDetailController
- - (instancetype)initWithStyle:(UITableViewStyle)style {
- // Force grouped style
- self = [super initWithStyle:UITableViewStyleGrouped];
- if (self) {
- [NSNotificationCenter.defaultCenter addObserver:self
- selector:@selector(handleTransactionUpdatedNotification:)
- name:kFLEXNetworkRecorderTransactionUpdatedNotification
- object:nil
- ];
- #if !TARGET_OS_TV
- self.toolbarItems = @[
- UIBarButtonItem.flex_flexibleSpace,
- [UIBarButtonItem
- flex_itemWithTitle:@"Copy curl"
- target:self
- action:@selector(copyButtonPressed:)
- ]
- ];
- #endif
- }
- return self;
- }
- - (void)viewDidLoad {
- [super viewDidLoad];
- [self.tableView registerClass:[FLEXMultilineTableViewCell class] forCellReuseIdentifier:kFLEXMultilineCell];
- }
- - (void)setTransaction:(FLEXNetworkTransaction *)transaction {
- if (![_transaction isEqual:transaction]) {
- _transaction = transaction;
- self.title = [transaction.request.URL lastPathComponent];
- [self rebuildTableSections];
- }
- }
- - (void)setSections:(NSArray<FLEXNetworkDetailSection *> *)sections {
- if (![_sections isEqual:sections]) {
- _sections = [sections copy];
- [self.tableView reloadData];
- }
- }
- - (void)rebuildTableSections {
- NSMutableArray<FLEXNetworkDetailSection *> *sections = [NSMutableArray new];
- FLEXNetworkDetailSection *generalSection = [[self class] generalSectionForTransaction:self.transaction];
- if (generalSection.rows.count > 0) {
- [sections addObject:generalSection];
- }
- FLEXNetworkDetailSection *requestHeadersSection = [[self class] requestHeadersSectionForTransaction:self.transaction];
- if (requestHeadersSection.rows.count > 0) {
- [sections addObject:requestHeadersSection];
- }
- FLEXNetworkDetailSection *queryParametersSection = [[self class] queryParametersSectionForTransaction:self.transaction];
- if (queryParametersSection.rows.count > 0) {
- [sections addObject:queryParametersSection];
- }
- FLEXNetworkDetailSection *postBodySection = [[self class] postBodySectionForTransaction:self.transaction];
- if (postBodySection.rows.count > 0) {
- [sections addObject:postBodySection];
- }
- FLEXNetworkDetailSection *responseHeadersSection = [[self class] responseHeadersSectionForTransaction:self.transaction];
- if (responseHeadersSection.rows.count > 0) {
- [sections addObject:responseHeadersSection];
- }
- self.sections = sections;
- }
- - (void)handleTransactionUpdatedNotification:(NSNotification *)notification {
- FLEXNetworkTransaction *transaction = [[notification userInfo] objectForKey:kFLEXNetworkRecorderUserInfoTransactionKey];
- if (transaction == self.transaction) {
- [self rebuildTableSections];
- }
- }
- - (void)copyButtonPressed:(id)sender {
- #if !TARGET_OS_TV
- [UIPasteboard.generalPasteboard setString:[FLEXNetworkCurlLogger curlCommandString:_transaction.request]];
- #endif
- }
- #pragma mark - Table view data source
- - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
- return self.sections.count;
- }
- - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
- FLEXNetworkDetailSection *sectionModel = self.sections[section];
- return sectionModel.rows.count;
- }
- - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
- FLEXNetworkDetailSection *sectionModel = self.sections[section];
- return sectionModel.title;
- }
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
- FLEXMultilineTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXMultilineCell forIndexPath:indexPath];
- FLEXNetworkDetailRow *rowModel = [self rowModelAtIndexPath:indexPath];
- cell.textLabel.attributedText = [[self class] attributedTextForRow:rowModel];
- cell.accessoryType = rowModel.selectionFuture ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone;
- cell.selectionStyle = rowModel.selectionFuture ? UITableViewCellSelectionStyleDefault : UITableViewCellSelectionStyleNone;
- return cell;
- }
- - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
- FLEXNetworkDetailRow *rowModel = [self rowModelAtIndexPath:indexPath];
- UIViewController *viewController = nil;
- if (rowModel.selectionFuture) {
- viewController = rowModel.selectionFuture();
- }
- if ([viewController isKindOfClass:UIAlertController.class]) {
- [self presentViewController:viewController animated:YES completion:nil];
- } else if (viewController) {
- [self.navigationController pushViewController:viewController animated:YES];
- }
- [tableView deselectRowAtIndexPath:indexPath animated:YES];
- }
- - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
- FLEXNetworkDetailRow *row = [self rowModelAtIndexPath:indexPath];
- NSAttributedString *attributedText = [[self class] attributedTextForRow:row];
- BOOL showsAccessory = row.selectionFuture != nil;
- return [FLEXMultilineTableViewCell
- preferredHeightWithAttributedText:attributedText
- maxWidth:tableView.bounds.size.width
- style:tableView.style
- showsAccessory:showsAccessory
- ];
- }
- - (FLEXNetworkDetailRow *)rowModelAtIndexPath:(NSIndexPath *)indexPath {
- FLEXNetworkDetailSection *sectionModel = self.sections[indexPath.section];
- return sectionModel.rows[indexPath.row];
- }
- #pragma mark - Cell Copying
- - (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath {
- return YES;
- }
- - (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
- return action == @selector(copy:);
- }
- - (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
- if (action == @selector(copy:)) {
- FLEXNetworkDetailRow *row = [self rowModelAtIndexPath:indexPath];
- #if !TARGET_OS_TV
- UIPasteboard.generalPasteboard.string = row.detailText;
- #endif
- }
- }
- #if FLEX_AT_LEAST_IOS13_SDK
- #if !TARGET_OS_TV
- - (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0) {
- return [UIContextMenuConfiguration
- configurationWithIdentifier:nil
- previewProvider:nil
- actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) {
- UIAction *copy = [UIAction
- actionWithTitle:@"Copy"
- image:nil
- identifier:nil
- handler:^(__kindof UIAction *action) {
- FLEXNetworkDetailRow *row = [self rowModelAtIndexPath:indexPath];
- UIPasteboard.generalPasteboard.string = row.detailText;
- }
- ];
- return [UIMenu
- menuWithTitle:@"" image:nil identifier:nil
- options:UIMenuOptionsDisplayInline
- children:@[copy]
- ];
- }
- ];
- }
- #endif
- #endif
- #pragma mark - View Configuration
- + (NSAttributedString *)attributedTextForRow:(FLEXNetworkDetailRow *)row {
- NSDictionary<NSString *, id> *titleAttributes = @{ NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:12.0],
- NSForegroundColorAttributeName : [UIColor colorWithWhite:0.5 alpha:1.0] };
- NSDictionary<NSString *, id> *detailAttributes = @{ NSFontAttributeName : UIFont.flex_defaultTableCellFont,
- NSForegroundColorAttributeName : FLEXColor.primaryTextColor };
- NSString *title = [NSString stringWithFormat:@"%@: ", row.title];
- NSString *detailText = row.detailText ?: @"";
- NSMutableAttributedString *attributedText = [NSMutableAttributedString new];
- [attributedText appendAttributedString:[[NSAttributedString alloc] initWithString:title attributes:titleAttributes]];
- [attributedText appendAttributedString:[[NSAttributedString alloc] initWithString:detailText attributes:detailAttributes]];
- return attributedText;
- }
- #pragma mark - Table Data Generation
- + (FLEXNetworkDetailSection *)generalSectionForTransaction:(FLEXNetworkTransaction *)transaction {
- NSMutableArray<FLEXNetworkDetailRow *> *rows = [NSMutableArray new];
- FLEXNetworkDetailRow *requestURLRow = [FLEXNetworkDetailRow new];
- requestURLRow.title = @"Request URL";
- NSURL *url = transaction.request.URL;
- requestURLRow.detailText = url.absoluteString;
- requestURLRow.selectionFuture = ^{
- UIViewController *urlWebViewController = [[FLEXWebViewController alloc] initWithURL:url];
- urlWebViewController.title = url.absoluteString;
- return urlWebViewController;
- };
- [rows addObject:requestURLRow];
- FLEXNetworkDetailRow *requestMethodRow = [FLEXNetworkDetailRow new];
- requestMethodRow.title = @"Request Method";
- requestMethodRow.detailText = transaction.request.HTTPMethod;
- [rows addObject:requestMethodRow];
- if (transaction.cachedRequestBody.length > 0) {
- FLEXNetworkDetailRow *postBodySizeRow = [FLEXNetworkDetailRow new];
- postBodySizeRow.title = @"Request Body Size";
- postBodySizeRow.detailText = [NSByteCountFormatter stringFromByteCount:transaction.cachedRequestBody.length countStyle:NSByteCountFormatterCountStyleBinary];
- [rows addObject:postBodySizeRow];
- FLEXNetworkDetailRow *postBodyRow = [FLEXNetworkDetailRow new];
- postBodyRow.title = @"Request Body";
- postBodyRow.detailText = @"tap to view";
- postBodyRow.selectionFuture = ^UIViewController * () {
- // Show the body if we can
- NSString *contentType = [transaction.request valueForHTTPHeaderField:@"Content-Type"];
- UIViewController *detailViewController = [self detailViewControllerForMIMEType:contentType data:[self postBodyDataForTransaction:transaction]];
- if (detailViewController) {
- detailViewController.title = @"Request Body";
- return detailViewController;
- }
- // We can't show the body, alert user
- return [FLEXAlert makeAlert:^(FLEXAlert *make) {
- make.title(@"Can't View HTTP Body Data");
- make.message(@"FLEX does not have a viewer for request body data with MIME type: ");
- make.message(contentType);
- make.button(@"Dismiss").cancelStyle();
- }];
- };
- [rows addObject:postBodyRow];
- }
- NSString *statusCodeString = [FLEXUtility statusCodeStringFromURLResponse:transaction.response];
- if (statusCodeString.length > 0) {
- FLEXNetworkDetailRow *statusCodeRow = [FLEXNetworkDetailRow new];
- statusCodeRow.title = @"Status Code";
- statusCodeRow.detailText = statusCodeString;
- [rows addObject:statusCodeRow];
- }
- if (transaction.error) {
- FLEXNetworkDetailRow *errorRow = [FLEXNetworkDetailRow new];
- errorRow.title = @"Error";
- errorRow.detailText = transaction.error.localizedDescription;
- [rows addObject:errorRow];
- }
- FLEXNetworkDetailRow *responseBodyRow = [FLEXNetworkDetailRow new];
- responseBodyRow.title = @"Response Body";
- NSData *responseData = [FLEXNetworkRecorder.defaultRecorder cachedResponseBodyForTransaction:transaction];
- if (responseData.length > 0) {
- responseBodyRow.detailText = @"tap to view";
- // Avoid a long lived strong reference to the response data in case we need to purge it from the cache.
- __weak NSData *weakResponseData = responseData;
- responseBodyRow.selectionFuture = ^UIViewController * () {
- // Show the response if we can
- NSString *contentType = transaction.response.MIMEType;
- NSData *strongResponseData = weakResponseData;
- if (strongResponseData) {
- UIViewController *bodyDetailController = [self detailViewControllerForMIMEType:contentType data:strongResponseData];
- if (bodyDetailController) {
- bodyDetailController.title = @"Response";
- return bodyDetailController;
- }
- }
- // We can't show the response, alert user
- return [FLEXAlert makeAlert:^(FLEXAlert *make) {
- make.title(@"Unable to View Response");
- if (strongResponseData) {
- make.message(@"No viewer content type: ").message(contentType);
- } else {
- make.message(@"The response has been purged from the cache");
- }
- make.button(@"OK").cancelStyle();
- }];
- };
- } else {
- BOOL emptyResponse = transaction.receivedDataLength == 0;
- responseBodyRow.detailText = emptyResponse ? @"empty" : @"not in cache";
- }
- [rows addObject:responseBodyRow];
- FLEXNetworkDetailRow *responseSizeRow = [FLEXNetworkDetailRow new];
- responseSizeRow.title = @"Response Size";
- responseSizeRow.detailText = [NSByteCountFormatter stringFromByteCount:transaction.receivedDataLength countStyle:NSByteCountFormatterCountStyleBinary];
- [rows addObject:responseSizeRow];
- FLEXNetworkDetailRow *mimeTypeRow = [FLEXNetworkDetailRow new];
- mimeTypeRow.title = @"MIME Type";
- mimeTypeRow.detailText = transaction.response.MIMEType;
- [rows addObject:mimeTypeRow];
- FLEXNetworkDetailRow *mechanismRow = [FLEXNetworkDetailRow new];
- mechanismRow.title = @"Mechanism";
- mechanismRow.detailText = transaction.requestMechanism;
- [rows addObject:mechanismRow];
- NSDateFormatter *startTimeFormatter = [NSDateFormatter new];
- startTimeFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS";
- FLEXNetworkDetailRow *localStartTimeRow = [FLEXNetworkDetailRow new];
- localStartTimeRow.title = [NSString stringWithFormat:@"Start Time (%@)", [NSTimeZone.localTimeZone abbreviationForDate:transaction.startTime]];
- localStartTimeRow.detailText = [startTimeFormatter stringFromDate:transaction.startTime];
- [rows addObject:localStartTimeRow];
- startTimeFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];
- FLEXNetworkDetailRow *utcStartTimeRow = [FLEXNetworkDetailRow new];
- utcStartTimeRow.title = @"Start Time (UTC)";
- utcStartTimeRow.detailText = [startTimeFormatter stringFromDate:transaction.startTime];
- [rows addObject:utcStartTimeRow];
- FLEXNetworkDetailRow *unixStartTime = [FLEXNetworkDetailRow new];
- unixStartTime.title = @"Unix Start Time";
- unixStartTime.detailText = [NSString stringWithFormat:@"%f", [transaction.startTime timeIntervalSince1970]];
- [rows addObject:unixStartTime];
- FLEXNetworkDetailRow *durationRow = [FLEXNetworkDetailRow new];
- durationRow.title = @"Total Duration";
- durationRow.detailText = [FLEXUtility stringFromRequestDuration:transaction.duration];
- [rows addObject:durationRow];
- FLEXNetworkDetailRow *latencyRow = [FLEXNetworkDetailRow new];
- latencyRow.title = @"Latency";
- latencyRow.detailText = [FLEXUtility stringFromRequestDuration:transaction.latency];
- [rows addObject:latencyRow];
- FLEXNetworkDetailSection *generalSection = [FLEXNetworkDetailSection new];
- generalSection.title = @"General";
- generalSection.rows = rows;
- return generalSection;
- }
- + (FLEXNetworkDetailSection *)requestHeadersSectionForTransaction:(FLEXNetworkTransaction *)transaction {
- FLEXNetworkDetailSection *requestHeadersSection = [FLEXNetworkDetailSection new];
- requestHeadersSection.title = @"Request Headers";
- requestHeadersSection.rows = [self networkDetailRowsFromDictionary:transaction.request.allHTTPHeaderFields];
- return requestHeadersSection;
- }
- + (FLEXNetworkDetailSection *)postBodySectionForTransaction:(FLEXNetworkTransaction *)transaction {
- FLEXNetworkDetailSection *postBodySection = [FLEXNetworkDetailSection new];
- postBodySection.title = @"Request Body Parameters";
- if (transaction.cachedRequestBody.length > 0) {
- NSString *contentType = [transaction.request valueForHTTPHeaderField:@"Content-Type"];
- if ([contentType hasPrefix:@"application/x-www-form-urlencoded"]) {
- NSData *body = [self postBodyDataForTransaction:transaction];
- NSString *bodyString = [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding];
- postBodySection.rows = [self networkDetailRowsFromQueryItems:[FLEXUtility itemsFromQueryString:bodyString]];
- }
- }
- return postBodySection;
- }
- + (FLEXNetworkDetailSection *)queryParametersSectionForTransaction:(FLEXNetworkTransaction *)transaction {
- NSArray<NSURLQueryItem *> *queries = [FLEXUtility itemsFromQueryString:transaction.request.URL.query];
- FLEXNetworkDetailSection *querySection = [FLEXNetworkDetailSection new];
- querySection.title = @"Query Parameters";
- querySection.rows = [self networkDetailRowsFromQueryItems:queries];
- return querySection;
- }
- + (FLEXNetworkDetailSection *)responseHeadersSectionForTransaction:(FLEXNetworkTransaction *)transaction {
- FLEXNetworkDetailSection *responseHeadersSection = [FLEXNetworkDetailSection new];
- responseHeadersSection.title = @"Response Headers";
- if ([transaction.response isKindOfClass:[NSHTTPURLResponse class]]) {
- NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)transaction.response;
- responseHeadersSection.rows = [self networkDetailRowsFromDictionary:httpResponse.allHeaderFields];
- }
- return responseHeadersSection;
- }
- + (NSArray<FLEXNetworkDetailRow *> *)networkDetailRowsFromDictionary:(NSDictionary<NSString *, id> *)dictionary {
- NSMutableArray<FLEXNetworkDetailRow *> *rows = [NSMutableArray new];
- NSArray<NSString *> *sortedKeys = [dictionary.allKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
-
- for (NSString *key in sortedKeys) {
- id value = dictionary[key];
- FLEXNetworkDetailRow *row = [FLEXNetworkDetailRow new];
- row.title = key;
- row.detailText = [value description];
- [rows addObject:row];
- }
- return rows.copy;
- }
- + (NSArray<FLEXNetworkDetailRow *> *)networkDetailRowsFromQueryItems:(NSArray<NSURLQueryItem *> *)items {
- // Sort the items by name
- items = [items sortedArrayUsingComparator:^NSComparisonResult(NSURLQueryItem *item1, NSURLQueryItem *item2) {
- return [item1.name caseInsensitiveCompare:item2.name];
- }];
- NSMutableArray<FLEXNetworkDetailRow *> *rows = [NSMutableArray new];
- for (NSURLQueryItem *item in items) {
- FLEXNetworkDetailRow *row = [FLEXNetworkDetailRow new];
- row.title = item.name;
- row.detailText = item.value;
- [rows addObject:row];
- }
- return [rows copy];
- }
- + (UIViewController *)detailViewControllerForMIMEType:(NSString *)mimeType data:(NSData *)data {
- FLEXCustomContentViewerFuture makeCustomViewer = FLEXManager.sharedManager.customContentTypeViewers[mimeType.lowercaseString];
- if (makeCustomViewer) {
- UIViewController *viewer = makeCustomViewer(data);
- if (viewer) {
- return viewer;
- }
- }
- // FIXME (RKO): Don't rely on UTF8 string encoding
- UIViewController *detailViewController = nil;
- if ([FLEXUtility isValidJSONData:data]) {
- NSString *prettyJSON = [FLEXUtility prettyJSONStringFromData:data];
- if (prettyJSON.length > 0) {
- detailViewController = [[FLEXWebViewController alloc] initWithText:prettyJSON];
- }
- } else if ([mimeType hasPrefix:@"image/"]) {
- UIImage *image = [UIImage imageWithData:data];
- detailViewController = [FLEXImagePreviewViewController forImage:image];
- } else if ([mimeType isEqual:@"application/x-plist"]) {
- id propertyList = [NSPropertyListSerialization propertyListWithData:data options:0 format:NULL error:NULL];
- detailViewController = [[FLEXWebViewController alloc] initWithText:[propertyList description]];
- }
- // Fall back to trying to show the response as text
- if (!detailViewController) {
- NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
- if (text.length > 0) {
- detailViewController = [[FLEXWebViewController alloc] initWithText:text];
- }
- }
- return detailViewController;
- }
- + (NSData *)postBodyDataForTransaction:(FLEXNetworkTransaction *)transaction {
- NSData *bodyData = transaction.cachedRequestBody;
- if (bodyData.length > 0) {
- NSString *contentEncoding = [transaction.request valueForHTTPHeaderField:@"Content-Encoding"];
- if ([contentEncoding rangeOfString:@"deflate" options:NSCaseInsensitiveSearch].length > 0 || [contentEncoding rangeOfString:@"gzip" options:NSCaseInsensitiveSearch].length > 0) {
- bodyData = [FLEXUtility inflatedDataFromCompressedData:bodyData];
- }
- }
- return bodyData;
- }
- @end
|