iOS web browser with a focus on security and privacy
at master 218 lines 6.8 kB view raw
1/* 2 * Endless 3 * Copyright (c) 2015-2018 joshua stein <jcs@jcs.org> 4 * 5 * See LICENSE file for redistribution terms. 6 */ 7 8#import "AppDelegate.h" 9#import "SearchResultsController.h" 10#import "URLInterceptor.h" 11 12#import "NSString+DTURLEncoding.h" 13 14@implementation SearchResultsController { 15 AppDelegate *appDelegate; 16 NSString *lastQuery; 17 NSArray *lastResults; 18} 19 20- (void)viewDidLoad 21{ 22 [super viewDidLoad]; 23 24 appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; 25 26 self.title = NSLocalizedString(@"Search Results", nil); 27 28 if ([[appDelegate webViewController] darkInterface]) 29 [[self tableView] setBackgroundColor:[UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:1.0]]; 30 31 lastResults = @[]; 32} 33 34- (void)viewWillDisappear:(BOOL)animated 35{ 36 [super viewWillDisappear:animated]; 37 [[appDelegate webViewController] hideSearchResults]; 38} 39 40#pragma mark - Table view data source 41 42- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 43{ 44 return 1; 45} 46 47- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 48{ 49 return [lastResults count]; 50} 51 52- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section 53{ 54 return NSLocalizedString(@"Search Results", nil); 55} 56 57- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section 58{ 59 if ([view isKindOfClass:[UITableViewHeaderFooterView class]]) { 60 UITableViewHeaderFooterView *tableViewHeaderFooterView = (UITableViewHeaderFooterView *) view; 61 int buttonSize = tableViewHeaderFooterView.frame.size.height - 8; 62 63 UIButton *b = [[UIButton alloc] init]; 64 [b setFrame:CGRectMake(tableViewHeaderFooterView.frame.size.width - buttonSize - 6, 3, buttonSize, buttonSize)]; 65 [b setBackgroundColor:[UIColor lightGrayColor]]; 66 [b setTitle:@"X" forState:UIControlStateNormal]; 67 [[b titleLabel] setFont:[UIFont boldSystemFontOfSize:12]]; 68 [[b layer] setCornerRadius:buttonSize / 2]; 69 [b setClipsToBounds:YES]; 70 71 [b addTarget:self action:@selector(close) forControlEvents:UIControlEventTouchUpInside]; 72 73 [tableViewHeaderFooterView addSubview:b]; 74 } 75} 76 77- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 78{ 79 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"result"]; 80 if (cell == nil) 81 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"result"]; 82 83 cell.textLabel.text = [lastResults objectAtIndex:indexPath.row]; 84 85 [cell setShowsReorderControl:NO]; 86 87 if ([[appDelegate webViewController] darkInterface]) { 88 [cell setBackgroundColor:[UIColor clearColor]]; 89 [[cell textLabel] setTextColor:[UIColor whiteColor]]; 90 } 91 92 return cell; 93} 94 95- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 96{ 97 NSString *q = [lastResults objectAtIndex:indexPath.row]; 98 99 if (q) 100 [[[appDelegate webViewController] curWebViewTab] searchFor:q]; 101 102 [[appDelegate webViewController] unfocusUrlField]; 103 104 [self close]; 105} 106 107- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath 108{ 109 return YES; 110} 111 112- (void)close 113{ 114 [self removeFromParentViewController]; 115 [[self view] removeFromSuperview]; 116} 117 118- (void)updateSearchResultsForQuery:(NSString *)query 119{ 120 if (query == nil || [query isEqualToString:@""] || (lastQuery != nil && [lastQuery isEqualToString:query])) 121 return; 122 123#ifdef TRACE 124 NSLog(@"[SearchResultsController] need to autocomplete search for \"%@\"", query); 125#endif 126 127 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 128 NSDictionary *se = [[appDelegate searchEngines] objectForKey:[userDefaults stringForKey:@"search_engine"]]; 129 130 if (se == nil) 131 /* just pick the first search engine */ 132 se = [[appDelegate searchEngines] objectForKey:[[[appDelegate searchEngines] allKeys] firstObject]]; 133 134 NSDictionary *pp = [se objectForKey:@"post_params"]; 135 NSString *urls; 136 if (pp == nil) 137 urls = [NSString stringWithFormat:[se objectForKey:@"autocomplete_url"], [query stringByURLEncoding]]; 138 else 139 urls = [se objectForKey:@"autocomplete_url"]; 140 141 NSURL *url = [NSURL URLWithString:urls]; 142 NSMutableURLRequest *request; 143 if (pp == nil) { 144#ifdef TRACE 145 NSLog(@"[SearchResultsController] auto-completing %@", url); 146#endif 147 request = [[NSMutableURLRequest alloc] initWithURL:url]; 148 } 149 else { 150 /* need to send this as a POST, so build our key val pairs */ 151 NSMutableString *params = [NSMutableString stringWithFormat:@""]; 152 for (NSString *key in [pp allKeys]) { 153 if (![params isEqualToString:@""]) 154 [params appendString:@"&"]; 155 156 [params appendString:[key stringByURLEncoding]]; 157 [params appendString:@"="]; 158 159 NSString *val = [pp objectForKey:key]; 160 if ([val isEqualToString:@"%@"]) 161 val = [query stringByURLEncoding]; 162 [params appendString:val]; 163 } 164 165#ifdef TRACE 166 NSLog(@"[SearchResultsController] auto-completing via POST to %@ (with params %@)", url, params); 167#endif 168 169 request = [[NSMutableURLRequest alloc] initWithURL:url]; 170 [request setHTTPMethod:@"POST"]; 171 [request setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]]; 172 } 173 174 /* so URLInterceptor leaves us alone */ 175 [NSURLProtocol setProperty:@YES forKey:REWRITTEN_KEY inRequest:request]; 176 177 __block NSString *tquery = [query copy]; 178 NSURLSessionDataTask *t = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 179 if (error != nil) { 180 NSLog(@"[SearchResultsController] failed auto-completing: %@", error); 181 return; 182 } 183 184 if (![self->lastQuery isEqualToString:tquery]) { 185 NSLog(@"[SearchResultsController] stale query results, ignoring"); 186 return; 187 } 188 189 NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; 190 if ([httpResponse statusCode] != 200) { 191 NSLog(@"[SearchResultsController] failed auto-completing, status %ld", [httpResponse statusCode]); 192 return; 193 } 194 195 @try { 196 NSString *ct = [[httpResponse allHeaderFields] objectForKey:@"Content-Type"]; 197 if (ct != nil && ([ct containsString:@"javascript"] || [ct containsString:@"json"])) { 198 NSArray *res = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error]; 199 self->lastResults = [res objectAtIndex:1]; 200 } else { 201 self->lastResults = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] componentsSeparatedByString:@"\n"]; 202 } 203#ifdef TRACE 204 NSLog(@"[SearchResultsController] auto-complete results: %@", self->lastResults); 205#endif 206 dispatch_sync(dispatch_get_main_queue(), ^{ 207 [[self tableView] reloadData]; 208 }); 209 } 210 @catch(NSException *e) { 211 NSLog(@"[SearchResultsController] failed parsing JSON: %@", e); 212 } 213 }]; 214 lastQuery = tquery; 215 [t resume]; 216} 217 218@end