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
57static 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)
97static 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
176namespace WebCore {
177
178static 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
196ResourceError::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
205NSError *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
234ResourceError::operator NSError *() const
235{
236    return nsError();
237}
238
239#else
240
241ResourceError::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
250ResourceError::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
259void 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
280bool ResourceError::platformCompare(const ResourceError& a, const ResourceError& b)
281{
282    return a.nsError() == b.nsError();
283}
284
285NSError *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
298ResourceError::operator NSError *() const
299{
300    return nsError();
301}
302
303CFErrorRef ResourceError::cfError() const
304{
305    return (CFErrorRef)nsError();
306}
307
308ResourceError::operator CFErrorRef() const
309{
310    return cfError();
311}
312
313#endif // USE(CFNETWORK)
314
315} // namespace WebCore
316