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
545 lines
22 KiB
//
|
|
// FLEXNetworkTransactionDetailTableViewController.m
|
|
// Flipboard
|
|
//
|
|
// Created by Ryan Olson on 2/10/15.
|
|
// Copyright (c) 2015 Flipboard. All rights reserved.
|
|
//
|
|
|
|
#import "FLEXColor.h"
|
|
#import "FLEXNetworkTransactionDetailTableViewController.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"
|
|
|
|
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 FLEXNetworkTransactionDetailTableViewController ()
|
|
|
|
@property (nonatomic, copy) NSArray<FLEXNetworkDetailSection *> *sections;
|
|
|
|
@end
|
|
|
|
@implementation FLEXNetworkTransactionDetailTableViewController
|
|
|
|
- (instancetype)initWithStyle:(UITableViewStyle)style
|
|
{
|
|
// Force grouped style
|
|
self = [super initWithStyle:UITableViewStyleGrouped];
|
|
if (self) {
|
|
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(handleTransactionUpdatedNotification:) name:kFLEXNetworkRecorderTransactionUpdatedNotification object:nil];
|
|
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Copy curl" style:UIBarButtonItemStylePlain target:self action:@selector(copyButtonPressed:)];
|
|
}
|
|
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 array];
|
|
|
|
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
|
|
{
|
|
[UIPasteboard.generalPasteboard setString:[FLEXNetworkCurlLogger curlCommandString:_transaction.request]];
|
|
}
|
|
|
|
#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 inTableViewWidth:self.tableView.bounds.size.width style:UITableViewStyleGrouped 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];
|
|
UIPasteboard.generalPasteboard.string = row.detailText;
|
|
}
|
|
}
|
|
|
|
#if FLEX_AT_LEAST_IOS13_SDK
|
|
|
|
- (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
|
|
|
|
#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 : [FLEXUtility defaultTableViewCellLabelFont],
|
|
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 array];
|
|
|
|
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"]) {
|
|
NSString *bodyString = [NSString stringWithCString:[self postBodyDataForTransaction:transaction].bytes 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 alloc] initWithImage: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
|