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