iOS web browser with a focus on security and privacy
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