.: Replace "Apple Computer, Inc." with "Apple Inc." in copyright headers
[WebKit-https.git] / Source / WebCore / platform / network / mac / ResourceErrorMac.mm
1 /*
2  * Copyright (C) 2006, 2008 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #import "config.h"
27 #import "ResourceError.h"
28
29 #import "BlockExceptions.h"
30 #import "URL.h"
31 #import <CoreFoundation/CFError.h>
32 #import <Foundation/Foundation.h>
33
34 #if PLATFORM(IOS)
35 #import <CFNetwork/CFSocketStreamPriv.h>
36 #import <Foundation/NSURLError.h>
37 #endif
38
39 @interface NSError (WebExtras)
40 - (NSString *)_web_localizedDescription;
41 @end
42
43 #if PLATFORM(IOS)
44
45 // This workaround code exists here because we can't call translateToCFError in Foundation. Once we
46 // have that, we can remove this code. <rdar://problem/9837415> Need SPI for translateCFError
47 // The code is mostly identical to Foundation - I changed the class name and fixed minor compile errors.
48 // We need this because client code (Safari) wants an NSError with NSURLErrorDomain as its domain.
49 // The Foundation code below does that and sets up appropriate certificate keys in the NSError.
50
51 @interface WebCustomNSURLError : NSError
52
53 @end
54
55 @implementation WebCustomNSURLError
56
57 static NSDictionary* dictionaryThatCanCode(NSDictionary* src)
58 {
59     // This function makes a copy of input dictionary, modifies it such that it "should" (as much as we can help it)
60     // not contain any objects that do not conform to NSCoding protocol, and returns it autoreleased.
61
62     NSMutableDictionary* dst = [src mutableCopy];
63
64     // Kill the known problem entries.
65     [dst removeObjectForKey:@"NSErrorPeerCertificateChainKey"]; // NSArray with SecCertificateRef objects
66     [dst removeObjectForKey:@"NSErrorClientCertificateChainKey"]; // NSArray with SecCertificateRef objects
67     [dst removeObjectForKey:NSURLErrorFailingURLPeerTrustErrorKey]; // SecTrustRef object
68     [dst removeObjectForKey:NSUnderlyingErrorKey]; // (Immutable) CFError containing kCF equivalent of the above
69     // We could reconstitute this but it's more trouble than it's worth
70
71     // Non-comprehensive safety check:  Kill top-level dictionary entries that don't conform to NSCoding.
72     // We may hit ones we just removed, but that's fine.
73     // We don't handle arbitrary objects that clients have stuffed into the dictionary, since we may not know how to
74     // get at its conents (e.g., a CFError object -- you'd have to know it had a userInfo dictionary and kill things
75     // inside it).
76     [src enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL*) {
77         if (! [obj conformsToProtocol:@protocol(NSCoding)]) {
78             [dst removeObjectForKey:key];
79         }
80         // FIXME: We could drill down into subdictionaries, but it seems more trouble than it's worth
81     }];
82
83     return [dst autorelease];
84 }
85
86 - (void)encodeWithCoder:(NSCoder *)coder
87 {
88     NSDictionary* newUserInfo = dictionaryThatCanCode([self userInfo]);
89
90     [[NSError errorWithDomain:[self domain] code:[self code] userInfo:newUserInfo] encodeWithCoder:coder];
91 }
92
93 @end
94
95
96 #if USE(CFNETWORK)
97 static NSError *NSErrorFromCFError(CFErrorRef cfError, NSURL *url)
98 {
99     CFIndex errCode = CFErrorGetCode(cfError);
100     if (CFEqual(CFErrorGetDomain(cfError), kCFErrorDomainCFNetwork) && errCode <= kCFURLErrorUnknown && errCode > -4000) {
101         // This is an URL error and needs to be translated to the NSURLErrorDomain
102         id keys[10], values[10];
103         CFDictionaryRef userInfo = CFErrorCopyUserInfo(cfError);
104         NSError *result;
105         NSInteger count = 0;
106
107         if (url) {
108             keys[count] = NSURLErrorFailingURLErrorKey;
109             values[count] = url;
110             count ++;
111
112             keys[count] = NSURLErrorFailingURLStringErrorKey;
113             values[count] = [url absoluteString];
114             count ++;
115         }
116
117         values[count] = (id)CFDictionaryGetValue(userInfo, kCFErrorLocalizedDescriptionKey);
118         if (values[count]) {
119             keys[count] = NSLocalizedDescriptionKey;
120             count ++;
121         }
122
123         values[count] = (id)CFDictionaryGetValue(userInfo, kCFErrorLocalizedFailureReasonKey);
124         if (values[count]) {
125             keys[count] = NSLocalizedFailureReasonErrorKey;
126             count ++;
127         }
128
129         values[count] = (id)CFDictionaryGetValue(userInfo, kCFErrorLocalizedRecoverySuggestionKey);
130         if (values[count]) {
131             keys[count] = NSLocalizedRecoverySuggestionErrorKey;
132             count ++;
133         }
134
135 #pragma clang diagnostic push
136 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
137         if (userInfo && (values[count] = (id)CFDictionaryGetValue(userInfo, kCFStreamPropertySSLPeerCertificates)) != nil) {
138             // Need to translate the cert
139             keys[count] = @"NSErrorPeerCertificateChainKey";
140             count ++;
141
142             values[count] = (id)CFDictionaryGetValue(userInfo, _kCFStreamPropertySSLClientCertificates);
143             if (values[count]) {
144                 keys[count] = @"NSErrorClientCertificateChainKey";
145                 count ++;
146             }
147
148             values[count] = (id)CFDictionaryGetValue(userInfo, _kCFStreamPropertySSLClientCertificateState);
149             if (values[count]) {
150                 keys[count] = @"NSErrorClientCertificateStateKey";
151                 count ++;
152             }
153         }
154 #pragma clang diagnostic pop
155
156         if (userInfo && (values[count] = (id)CFDictionaryGetValue(userInfo, kCFStreamPropertySSLPeerTrust)) != nil) {
157             keys[count] = NSURLErrorFailingURLPeerTrustErrorKey;
158             count++;
159         }
160
161         keys[count] = NSUnderlyingErrorKey;
162         values[count] = (id)cfError;
163         count ++;
164
165         result = [WebCustomNSURLError errorWithDomain:NSURLErrorDomain code:(errCode == kCFURLErrorUnknown ? (CFIndex)NSURLErrorUnknown : errCode) userInfo:[NSDictionary dictionaryWithObjects:values forKeys:keys count:count]];
166         if (userInfo)
167             CFRelease(userInfo);
168         return result;
169     }
170     return (NSError *)cfError;
171 }
172 #endif // USE(CFNETWORK)
173
174 #endif // PLATFORM(IOS)
175
176 namespace WebCore {
177
178 static RetainPtr<NSError> createNSErrorFromResourceErrorBase(const ResourceErrorBase& resourceError)
179 {
180     RetainPtr<NSMutableDictionary> userInfo = adoptNS([[NSMutableDictionary alloc] init]);
181
182     if (!resourceError.localizedDescription().isEmpty())
183         [userInfo.get() setValue:resourceError.localizedDescription() forKey:NSLocalizedDescriptionKey];
184
185     if (!resourceError.failingURL().isEmpty()) {
186         RetainPtr<NSURL> cocoaURL = adoptNS([[NSURL alloc] initWithString:resourceError.failingURL()]);
187         [userInfo.get() setValue:resourceError.failingURL() forKey:@"NSErrorFailingURLStringKey"];
188         [userInfo.get() setValue:cocoaURL.get() forKey:@"NSErrorFailingURLKey"];
189     }
190
191     return adoptNS([[NSError alloc] initWithDomain:resourceError.domain() code:resourceError.errorCode() userInfo:userInfo.get()]);
192 }
193
194 #if USE(CFNETWORK)
195
196 ResourceError::ResourceError(NSError *error)
197     : m_dataIsUpToDate(false)
198     , m_platformError(reinterpret_cast<CFErrorRef>(error))
199 {
200     m_isNull = !error;
201     if (!m_isNull)
202         m_isTimeout = [error code] == NSURLErrorTimedOut;
203 }
204
205 NSError *ResourceError::nsError() const
206 {
207     if (m_isNull) {
208         ASSERT(!m_platformError);
209         return nil;
210     }
211
212     if (m_platformNSError)
213         return m_platformNSError.get();
214
215     if (m_platformError) {
216         CFErrorRef error = m_platformError.get();
217         RetainPtr<CFDictionaryRef> userInfo = adoptCF(CFErrorCopyUserInfo(error));
218 #if PLATFORM(IOS)
219         m_platformNSError = NSErrorFromCFError(error, (NSURL *)[(NSDictionary *)userInfo.get() objectForKey:(id) kCFURLErrorFailingURLErrorKey]);
220         // If NSErrorFromCFError created a new NSError for us, return that.
221         if (m_platformNSError.get() != (NSError *)error)
222             return m_platformNSError.get();
223
224         // Otherwise fall through to create a new NSError from the CFError.
225 #endif
226         m_platformNSError = adoptNS([[NSError alloc] initWithDomain:(NSString *)CFErrorGetDomain(error) code:CFErrorGetCode(error) userInfo:(NSDictionary *)userInfo.get()]);
227         return m_platformNSError.get();
228     }
229
230     m_platformNSError = createNSErrorFromResourceErrorBase(*this);
231     return m_platformNSError.get();
232 }
233
234 ResourceError::operator NSError *() const
235 {
236     return nsError();
237 }
238
239 #else
240
241 ResourceError::ResourceError(NSError *nsError)
242     : m_dataIsUpToDate(false)
243     , m_platformError(nsError)
244 {
245     m_isNull = !nsError;
246     if (!m_isNull)
247         m_isTimeout = [m_platformError.get() code] == NSURLErrorTimedOut;
248 }
249
250 ResourceError::ResourceError(CFErrorRef cfError)
251     : m_dataIsUpToDate(false)
252     , m_platformError((NSError *)cfError)
253 {
254     m_isNull = !cfError;
255     if (!m_isNull)
256         m_isTimeout = [m_platformError.get() code] == NSURLErrorTimedOut;
257 }
258
259 void ResourceError::platformLazyInit()
260 {
261     if (m_dataIsUpToDate)
262         return;
263
264     m_domain = [m_platformError.get() domain];
265     m_errorCode = [m_platformError.get() code];
266
267     NSString* failingURLString = [[m_platformError.get() userInfo] valueForKey:@"NSErrorFailingURLStringKey"];
268     if (!failingURLString)
269         failingURLString = [[[m_platformError.get() userInfo] valueForKey:@"NSErrorFailingURLKey"] absoluteString];
270     m_failingURL = failingURLString; 
271     // Workaround for <rdar://problem/6554067>
272     m_localizedDescription = m_failingURL;
273     BEGIN_BLOCK_OBJC_EXCEPTIONS;
274     m_localizedDescription = [m_platformError.get() _web_localizedDescription];
275     END_BLOCK_OBJC_EXCEPTIONS;
276
277     m_dataIsUpToDate = true;
278 }
279
280 bool ResourceError::platformCompare(const ResourceError& a, const ResourceError& b)
281 {
282     return a.nsError() == b.nsError();
283 }
284
285 NSError *ResourceError::nsError() const
286 {
287     if (m_isNull) {
288         ASSERT(!m_platformError);
289         return nil;
290     }
291
292     if (!m_platformError)
293         m_platformError = createNSErrorFromResourceErrorBase(*this);;
294
295     return m_platformError.get();
296 }
297
298 ResourceError::operator NSError *() const
299 {
300     return nsError();
301 }
302
303 CFErrorRef ResourceError::cfError() const
304 {
305     return (CFErrorRef)nsError();
306 }
307
308 ResourceError::operator CFErrorRef() const
309 {
310     return cfError();
311 }
312
313 #endif // USE(CFNETWORK)
314
315 } // namespace WebCore