]> git.saurik.com Git - apple/security.git/blob - trust/trustd/TrustURLSessionDelegate.m
Security-59754.80.3.tar.gz
[apple/security.git] / trust / trustd / TrustURLSessionDelegate.m
1 /*
2 * Copyright (c) 2018 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 *
23 */
24
25 #import <AssertMacros.h>
26 #import <Foundation/Foundation.h>
27 #include <mach/mach_time.h>
28 #include <utilities/SecCFWrappers.h>
29 #include <Security/SecInternalReleasePriv.h>
30 #include "TrustURLSessionDelegate.h"
31
32 #define MAX_TASKS 3
33
34 /* There has got to be an easier way to do this. For now we based this code
35 on CFNetwork/Connection/URLResponse.cpp. */
36 static CFStringRef copyParseMaxAge(CFStringRef cacheControlHeader) {
37 if (!cacheControlHeader) { return NULL; }
38
39 /* The format of the cache control header is a comma-separated list, but
40 each list element could be a key-value pair, with the value quoted and
41 possibly containing a comma. */
42 CFStringInlineBuffer inlineBuf = {};
43 CFRange componentRange;
44 CFIndex length = CFStringGetLength(cacheControlHeader);
45 bool done = false;
46 CFCharacterSetRef whitespaceSet = CFCharacterSetGetPredefined(kCFCharacterSetWhitespace);
47 CFStringRef maxAgeValue = NULL;
48
49 CFStringInitInlineBuffer(cacheControlHeader, &inlineBuf, CFRangeMake(0, length));
50 componentRange.location = 0;
51
52 while (!done) {
53 bool inQuotes = false;
54 bool foundComponentStart = false;
55 CFIndex charIndex = componentRange.location;
56 CFIndex componentEnd = -1;
57 CFRange maxAgeRg;
58 componentRange.length = 0;
59
60 while (charIndex < length) {
61 UniChar ch = CFStringGetCharacterFromInlineBuffer(&inlineBuf, charIndex);
62 if (!inQuotes && ch == ',') {
63 componentRange.length = charIndex - componentRange.location;
64 break;
65 }
66 if (!CFCharacterSetIsCharacterMember(whitespaceSet, ch)) {
67 if (!foundComponentStart) {
68 foundComponentStart = true;
69 componentRange.location = charIndex;
70 } else {
71 componentEnd = charIndex;
72 }
73 if (ch == '\"') {
74 inQuotes = (inQuotes == false);
75 }
76 }
77 charIndex ++;
78 }
79
80 if (componentEnd == -1) {
81 componentRange.length = charIndex - componentRange.location;
82 } else {
83 componentRange.length = componentEnd - componentRange.location + 1;
84 }
85
86 if (charIndex == length) {
87 /* Fell off the end; this is the last component. */
88 done = true;
89 }
90
91 /* componentRange should now contain the range of the current
92 component; trimmed of any whitespace. */
93
94 /* We want to look for a max-age value. */
95 if (!maxAgeValue && CFStringFindWithOptions(cacheControlHeader, CFSTR("max-age"), componentRange, kCFCompareCaseInsensitive | kCFCompareAnchored, &maxAgeRg)) {
96 CFIndex equalIdx;
97 CFIndex maxCompRg = componentRange.location + componentRange.length;
98 for (equalIdx = maxAgeRg.location + maxAgeRg.length; equalIdx < maxCompRg; equalIdx ++) {
99 UniChar equalCh = CFStringGetCharacterFromInlineBuffer(&inlineBuf, equalIdx);
100 if (equalCh == '=') {
101 // Parse out max-age value
102 equalIdx ++;
103 while (equalIdx < maxCompRg && CFCharacterSetIsCharacterMember(whitespaceSet, CFStringGetCharacterAtIndex(cacheControlHeader, equalIdx))) {
104 equalIdx ++;
105 }
106 if (equalIdx < maxCompRg) {
107 CFReleaseNull(maxAgeValue);
108 maxAgeValue = CFStringCreateWithSubstring(kCFAllocatorDefault, cacheControlHeader, CFRangeMake(equalIdx, maxCompRg-equalIdx));
109 }
110 } else if (!CFCharacterSetIsCharacterMember(whitespaceSet, equalCh)) {
111 // Not a valid max-age header; break out doing nothing
112 break;
113 }
114 }
115 }
116
117 if (!done && maxAgeValue) {
118 done = true;
119 }
120 if (!done) {
121 /* Advance to the next component; + 1 to get past the comma. */
122 componentRange.location = charIndex + 1;
123 }
124 }
125
126 return maxAgeValue;
127 }
128
129 @implementation TrustURLSessionDelegate
130 - (id)init {
131 /* Protect future developers from themselves */
132 if ([self class] == [TrustURLSessionDelegate class]) {
133 NSException *e = [NSException exceptionWithName:@"AbstractClassException"
134 reason:@"This is an abstract class. To use it, please subclass."
135 userInfo:nil];
136 @throw e;
137 } else {
138 return [super init];
139 }
140 }
141
142 - (NSURLRequest *)createNextRequest:(NSURL *)uri {
143 return [NSURLRequest requestWithURL:uri];
144 }
145
146 - (BOOL)fetchNext:(NSURLSession *)session {
147 if (self.numTasks >= MAX_TASKS) {
148 secnotice("http", "Too many fetch %@ requests for this cert", [self class]);
149 return true;
150 }
151
152 for (NSUInteger ix = self.URIix; ix < [self.URIs count]; ix++) {
153 NSURL *uri = self.URIs[ix];
154 if ([[uri scheme] isEqualToString:@"http"]) {
155 self.URIix = ix + 1; // Next time we'll start with the next index
156 self.numTasks++;
157 NSURLSessionTask *task = [session dataTaskWithRequest:[self createNextRequest:uri]];
158 [task resume];
159 secinfo("http", "request for uri: %@", uri);
160 return false; // we scheduled a job
161 } else {
162 secnotice("http", "skipping unsupported scheme %@", [uri scheme]);
163 }
164 }
165
166 /* No more issuers left to try, we're done. Report that no async jobs were started. */
167 secdebug("http", "no request issued");
168 return true;
169 }
170
171 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
172 /* Append the data to the response data*/
173 if (!_response) {
174 _response = [NSMutableData data];
175 }
176 [_response appendData:data];
177 }
178
179 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
180 /* Protect future developers from themselves */
181 if ([self class] == [TrustURLSessionDelegate class]) {
182 NSException *e = [NSException exceptionWithName:@"AbstractClassException"
183 reason:@"This is an abstract class. To use it, please subclass and override didCompleteWithError."
184 userInfo:nil];
185 @throw e;
186 } else {
187 _expiration = 60.0 * 60.0 * 24.0 * 7; /* Default is 7 days */
188 if ([_response length] > 0 && [[task response] isKindOfClass:[NSHTTPURLResponse class]]) {
189 NSString *cacheControl = [[(NSHTTPURLResponse *)[task response] allHeaderFields] objectForKey:@"cache-control"];
190 NSString *maxAge = CFBridgingRelease(copyParseMaxAge((__bridge CFStringRef)cacheControl));
191 if (maxAge && [maxAge doubleValue] > _expiration) {
192 _expiration = [maxAge doubleValue];
193 }
194 }
195 }
196 }
197
198 - (void)URLSession:(NSURLSession *)session
199 task:(NSURLSessionTask *)task
200 willPerformHTTPRedirection:(NSHTTPURLResponse *)redirectResponse
201 newRequest:(NSURLRequest *)request
202 completionHandler:(void (^)(NSURLRequest *))completionHandler {
203 /* The old code didn't allow re-direction, so we won't either. */
204 secnotice("http", "failed redirection for %@", task.originalRequest.URL);
205 [task cancel];
206 }
207 @end
208
209 NSTimeInterval TrustURLSessionGetResourceTimeout(void) {
210 return (NSTimeInterval)3.0;
211 }