Revert r207151
[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 "URL.h"
30 #import <CoreFoundation/CFError.h>
31 #import <Foundation/Foundation.h>
32 #import <wtf/BlockObjCExceptions.h>
33
34 #if PLATFORM(IOS) && USE(CFURLCONNECTION)
35 #import <CFNetwork/CFSocketStreamPriv.h>
36 #endif
37
38 @interface NSError (WebExtras)
39 - (NSString *)_web_localizedDescription;
40 @end
41
42 #if PLATFORM(IOS)
43
44 // This workaround code exists here because we can't call translateToCFError in Foundation. Once we
45 // have that, we can remove this code. <rdar://problem/9837415> Need SPI for translateCFError
46 // The code is mostly identical to Foundation - I changed the class name and fixed minor compile errors.
47 // We need this because client code (Safari) wants an NSError with NSURLErrorDomain as its domain.
48 // The Foundation code below does that and sets up appropriate certificate keys in the NSError.
49
50 @interface WebCustomNSURLError : NSError
51
52 @end
53
54 @implementation WebCustomNSURLError
55
56 static NSDictionary* dictionaryThatCanCode(NSDictionary* src)
57 {
58     // This function makes a copy of input dictionary, modifies it such that it "should" (as much as we can help it)
59     // not contain any objects that do not conform to NSCoding protocol, and returns it autoreleased.
60
61     NSMutableDictionary* dst = [src mutableCopy];
62
63     // Kill the known problem entries.
64     [dst removeObjectForKey:@"NSErrorPeerCertificateChainKey"]; // NSArray with SecCertificateRef objects
65     [dst removeObjectForKey:@"NSErrorClientCertificateChainKey"]; // NSArray with SecCertificateRef objects
66     [dst removeObjectForKey:NSURLErrorFailingURLPeerTrustErrorKey]; // SecTrustRef object
67     [dst removeObjectForKey:NSUnderlyingErrorKey]; // (Immutable) CFError containing kCF equivalent of the above
68     // We could reconstitute this but it's more trouble than it's worth
69
70     // Non-comprehensive safety check:  Kill top-level dictionary entries that don't conform to NSCoding.
71     // We may hit ones we just removed, but that's fine.
72     // We don't handle arbitrary objects that clients have stuffed into the dictionary, since we may not know how to
73     // get at its conents (e.g., a CFError object -- you'd have to know it had a userInfo dictionary and kill things
74     // inside it).
75     [src enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL*) {
76         if (! [obj conformsToProtocol:@protocol(NSCoding)]) {
77             [dst removeObjectForKey:key];
78         }
79         // FIXME: We could drill down into subdictionaries, but it seems more trouble than it's worth
80     }];
81
82     return [dst autorelease];
83 }
84
85 - (void)encodeWithCoder:(NSCoder *)coder
86 {
87     NSDictionary* newUserInfo = dictionaryThatCanCode([self userInfo]);
88
89     [[NSError errorWithDomain:[self domain] code:[self code] userInfo:newUserInfo] encodeWithCoder:coder];
90 }
91
92 @end
93
94
95 #if USE(CFURLCONNECTION)
96 static NSError *NSErrorFromCFError(CFErrorRef cfError, NSURL *url)
97 {
98     CFIndex errCode = CFErrorGetCode(cfError);
99     if (CFEqual(CFErrorGetDomain(cfError), kCFErrorDomainCFNetwork) && errCode <= kCFURLErrorUnknown && errCode > -4000) {
100         // This is an URL error and needs to be translated to the NSURLErrorDomain
101         id keys[10], values[10];
102         CFDictionaryRef userInfo = CFErrorCopyUserInfo(cfError);
103         NSError *result;
104         NSInteger count = 0;
105
106         if (url) {
107             keys[count] = NSURLErrorFailingURLErrorKey;
108             values[count] = url;
109             count++;
110
111             keys[count] = NSURLErrorFailingURLStringErrorKey;
112             values[count] = [url absoluteString];
113             count++;
114         }
115
116         values[count] = (id)CFDictionaryGetValue(userInfo, kCFErrorLocalizedDescriptionKey);
117         if (values[count]) {
118             keys[count] = NSLocalizedDescriptionKey;
119             count++;
120         }
121
122         values[count] = (id)CFDictionaryGetValue(userInfo, kCFErrorLocalizedFailureReasonKey);
123         if (values[count]) {
124             keys[count] = NSLocalizedFailureReasonErrorKey;
125             count++;
126         }
127
128         values[count] = (id)CFDictionaryGetValue(userInfo, kCFErrorLocalizedRecoverySuggestionKey);
129         if (values[count]) {
130             keys[count] = NSLocalizedRecoverySuggestionErrorKey;
131             count++;
132         }
133
134 #pragma clang diagnostic push
135 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
136         if (userInfo && (values[count] = (id)CFDictionaryGetValue(userInfo, kCFStreamPropertySSLPeerCertificates)) != nil) {
137             // Need to translate the cert
138             keys[count] = @"NSErrorPeerCertificateChainKey";
139             count++;
140
141             values[count] = (id)CFDictionaryGetValue(userInfo, _kCFStreamPropertySSLClientCertificates);
142             if (values[count]) {
143                 keys[count] = @"NSErrorClientCertificateChainKey";
144                 count++;
145             }
146
147             values[count] = (id)CFDictionaryGetValue(userInfo, _kCFStreamPropertySSLClientCertificateState);
148             if (values[count]) {
149                 keys[count] = @"NSErrorClientCertificateStateKey";
150                 count++;
151             }
152         }
153 #pragma clang diagnostic pop
154
155         if (userInfo && (values[count] = (id)CFDictionaryGetValue(userInfo, kCFStreamPropertySSLPeerTrust)) != nil) {
156             keys[count] = NSURLErrorFailingURLPeerTrustErrorKey;
157             count++;
158         }
159
160         keys[count] = NSUnderlyingErrorKey;
161         values[count] = (id)cfError;
162         count++;
163
164         result = [WebCustomNSURLError errorWithDomain:NSURLErrorDomain code:(errCode == kCFURLErrorUnknown ? (CFIndex)NSURLErrorUnknown : errCode) userInfo:[NSDictionary dictionaryWithObjects:values forKeys:keys count:count]];
165         if (userInfo)
166             CFRelease(userInfo);
167         return result;
168     }
169     return (NSError *)cfError;
170 }
171 #endif // USE(CFURLCONNECTION)
172
173 #endif // PLATFORM(IOS)
174
175 namespace WebCore {
176
177 static RetainPtr<NSError> createNSErrorFromResourceErrorBase(const ResourceErrorBase& resourceError)
178 {
179     RetainPtr<NSMutableDictionary> userInfo = adoptNS([[NSMutableDictionary alloc] init]);
180
181     if (!resourceError.localizedDescription().isEmpty())
182         [userInfo.get() setValue:resourceError.localizedDescription() forKey:NSLocalizedDescriptionKey];
183
184     if (!resourceError.failingURL().isEmpty()) {
185         [userInfo.get() setValue:(NSString *)resourceError.failingURL().string() forKey:@"NSErrorFailingURLStringKey"];
186         if (NSURL *cocoaURL = (NSURL *)resourceError.failingURL())
187             [userInfo.get() setValue:cocoaURL forKey:@"NSErrorFailingURLKey"];
188     }
189
190     return adoptNS([[NSError alloc] initWithDomain:resourceError.domain() code:resourceError.errorCode() userInfo:userInfo.get()]);
191 }
192
193 #if USE(CFURLCONNECTION)
194
195 ResourceError::ResourceError(NSError *error)
196     : ResourceErrorBase(Type::Null)
197     , m_dataIsUpToDate(false)
198     , m_platformError(reinterpret_cast<CFErrorRef>(error))
199 {
200     if (error)
201         setType(([error code] == NSURLErrorTimedOut) ? Type::Timeout : Type::General);
202 }
203
204 NSError *ResourceError::nsError() const
205 {
206     if (isNull()) {
207         ASSERT(!m_platformError);
208         return nil;
209     }
210
211     if (m_platformNSError)
212         return m_platformNSError.get();
213
214     if (m_platformError) {
215         CFErrorRef error = m_platformError.get();
216         RetainPtr<CFDictionaryRef> userInfo = adoptCF(CFErrorCopyUserInfo(error));
217 #if PLATFORM(IOS)
218         m_platformNSError = NSErrorFromCFError(error, (NSURL *)[(NSDictionary *)userInfo.get() objectForKey:(id) kCFURLErrorFailingURLErrorKey]);
219         // If NSErrorFromCFError created a new NSError for us, return that.
220         if (m_platformNSError.get() != (NSError *)error)
221             return m_platformNSError.get();
222
223         // Otherwise fall through to create a new NSError from the CFError.
224 #endif
225         m_platformNSError = adoptNS([[NSError alloc] initWithDomain:(NSString *)CFErrorGetDomain(error) code:CFErrorGetCode(error) userInfo:(NSDictionary *)userInfo.get()]);
226         return m_platformNSError.get();
227     }
228
229     m_platformNSError = createNSErrorFromResourceErrorBase(*this);
230     return m_platformNSError.get();
231 }
232
233 ResourceError::operator NSError *() const
234 {
235     return nsError();
236 }
237
238 #else
239
240 ResourceError::ResourceError(NSError *nsError)
241     : ResourceErrorBase(Type::Null)
242     , m_dataIsUpToDate(false)
243     , m_platformError(nsError)
244 {
245     if (nsError)
246         setType(([m_platformError.get() code] == NSURLErrorTimedOut) ? Type::Timeout : Type::General);
247 }
248
249 ResourceError::ResourceError(CFErrorRef cfError)
250     : ResourceError((NSError *)cfError)
251 {
252 }
253
254 void ResourceError::platformLazyInit()
255 {
256     if (m_dataIsUpToDate)
257         return;
258
259     m_domain = [m_platformError.get() domain];
260     m_errorCode = [m_platformError.get() code];
261
262     if (NSString* failingURLString = [[m_platformError.get() userInfo] valueForKey:@"NSErrorFailingURLStringKey"])
263         m_failingURL = URL(URL(), failingURLString);
264     else
265         m_failingURL = URL((NSURL *)[[m_platformError.get() userInfo] valueForKey:@"NSErrorFailingURLKey"]);
266     // Workaround for <rdar://problem/6554067>
267     m_localizedDescription = m_failingURL;
268     BEGIN_BLOCK_OBJC_EXCEPTIONS;
269     m_localizedDescription = [m_platformError.get() _web_localizedDescription];
270     END_BLOCK_OBJC_EXCEPTIONS;
271
272     m_dataIsUpToDate = true;
273 }
274
275 bool ResourceError::platformCompare(const ResourceError& a, const ResourceError& b)
276 {
277     return a.nsError() == b.nsError();
278 }
279
280 void ResourceError::doPlatformIsolatedCopy(const ResourceError&)
281 {
282 }
283
284 NSError *ResourceError::nsError() const
285 {
286     if (isNull()) {
287         ASSERT(!m_platformError);
288         return nil;
289     }
290
291     if (!m_platformError)
292         m_platformError = createNSErrorFromResourceErrorBase(*this);
293
294     return m_platformError.get();
295 }
296
297 ResourceError::operator NSError *() const
298 {
299     return nsError();
300 }
301
302 CFErrorRef ResourceError::cfError() const
303 {
304     return (CFErrorRef)nsError();
305 }
306
307 ResourceError::operator CFErrorRef() const
308 {
309     return cfError();
310 }
311
312 #endif // USE(CFURLCONNECTION)
313
314 } // namespace WebCore