2 * Copyright (C) 2006, 2008 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #import "ResourceError.h"
30 #import <CoreFoundation/CFError.h>
31 #import <Foundation/Foundation.h>
32 #import <wtf/BlockObjCExceptions.h>
34 #if PLATFORM(IOS) && USE(CFURLCONNECTION) && USE(APPLE_INTERNAL_SDK)
35 #import <CFNetwork/CFSocketStreamPriv.h>
38 #if USE(CFURLCONNECTION)
40 const CFStringRef _kCFStreamPropertySSLClientCertificates;
41 const CFStringRef _kCFStreamPropertySSLClientCertificateState;
45 @interface NSError (WebExtras)
46 - (NSString *)_web_localizedDescription;
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.
57 @interface WebCustomNSURLError : NSError
61 @implementation WebCustomNSURLError
63 static NSDictionary* dictionaryThatCanCode(NSDictionary* src)
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.
68 NSMutableDictionary* dst = [src mutableCopy];
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
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
82 [src enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL*) {
83 if (! [obj conformsToProtocol:@protocol(NSCoding)]) {
84 [dst removeObjectForKey:key];
86 // FIXME: We could drill down into subdictionaries, but it seems more trouble than it's worth
89 return [dst autorelease];
92 - (void)encodeWithCoder:(NSCoder *)coder
94 NSDictionary* newUserInfo = dictionaryThatCanCode([self userInfo]);
96 [[NSError errorWithDomain:[self domain] code:[self code] userInfo:newUserInfo] encodeWithCoder:coder];
102 #if USE(CFURLCONNECTION)
103 static NSError *NSErrorFromCFError(CFErrorRef cfError, NSURL *url)
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);
114 keys[count] = NSURLErrorFailingURLErrorKey;
118 keys[count] = NSURLErrorFailingURLStringErrorKey;
119 values[count] = [url absoluteString];
123 values[count] = (id)CFDictionaryGetValue(userInfo, kCFErrorLocalizedDescriptionKey);
125 keys[count] = NSLocalizedDescriptionKey;
129 values[count] = (id)CFDictionaryGetValue(userInfo, kCFErrorLocalizedFailureReasonKey);
131 keys[count] = NSLocalizedFailureReasonErrorKey;
135 values[count] = (id)CFDictionaryGetValue(userInfo, kCFErrorLocalizedRecoverySuggestionKey);
137 keys[count] = NSLocalizedRecoverySuggestionErrorKey;
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";
148 values[count] = (id)CFDictionaryGetValue(userInfo, _kCFStreamPropertySSLClientCertificates);
150 keys[count] = @"NSErrorClientCertificateChainKey";
154 values[count] = (id)CFDictionaryGetValue(userInfo, _kCFStreamPropertySSLClientCertificateState);
156 keys[count] = @"NSErrorClientCertificateStateKey";
160 #pragma clang diagnostic pop
162 if (userInfo && (values[count] = (id)CFDictionaryGetValue(userInfo, kCFStreamPropertySSLPeerTrust)) != nil) {
163 keys[count] = NSURLErrorFailingURLPeerTrustErrorKey;
167 keys[count] = NSUnderlyingErrorKey;
168 values[count] = (id)cfError;
171 result = [WebCustomNSURLError errorWithDomain:NSURLErrorDomain code:(errCode == kCFURLErrorUnknown ? (CFIndex)NSURLErrorUnknown : errCode) userInfo:[NSDictionary dictionaryWithObjects:values forKeys:keys count:count]];
176 return (NSError *)cfError;
178 #endif // USE(CFURLCONNECTION)
180 #endif // PLATFORM(IOS)
184 static RetainPtr<NSError> createNSErrorFromResourceErrorBase(const ResourceErrorBase& resourceError)
186 RetainPtr<NSMutableDictionary> userInfo = adoptNS([[NSMutableDictionary alloc] init]);
188 if (!resourceError.localizedDescription().isEmpty())
189 [userInfo.get() setValue:resourceError.localizedDescription() forKey:NSLocalizedDescriptionKey];
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"];
197 return adoptNS([[NSError alloc] initWithDomain:resourceError.domain() code:resourceError.errorCode() userInfo:userInfo.get()]);
200 #if USE(CFURLCONNECTION)
202 ResourceError::ResourceError(NSError *error)
203 : ResourceErrorBase(Type::Null)
204 , m_dataIsUpToDate(false)
205 , m_platformError(reinterpret_cast<CFErrorRef>(error))
208 setType(([error code] == NSURLErrorTimedOut) ? Type::Timeout : Type::General);
211 NSError *ResourceError::nsError() const
214 ASSERT(!m_platformError);
218 if (m_platformNSError)
219 return m_platformNSError.get();
221 if (m_platformError) {
222 CFErrorRef error = m_platformError.get();
223 RetainPtr<CFDictionaryRef> userInfo = adoptCF(CFErrorCopyUserInfo(error));
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();
230 // Otherwise fall through to create a new NSError from the CFError.
232 m_platformNSError = adoptNS([[NSError alloc] initWithDomain:(NSString *)CFErrorGetDomain(error) code:CFErrorGetCode(error) userInfo:(NSDictionary *)userInfo.get()]);
233 return m_platformNSError.get();
236 m_platformNSError = createNSErrorFromResourceErrorBase(*this);
237 return m_platformNSError.get();
240 ResourceError::operator NSError *() const
247 ResourceError::ResourceError(NSError *nsError)
248 : ResourceErrorBase(Type::Null)
249 , m_dataIsUpToDate(false)
250 , m_platformError(nsError)
253 setType(([m_platformError.get() code] == NSURLErrorTimedOut) ? Type::Timeout : Type::General);
256 ResourceError::ResourceError(CFErrorRef cfError)
257 : ResourceError((NSError *)cfError)
261 void ResourceError::platformLazyInit()
263 if (m_dataIsUpToDate)
266 m_domain = [m_platformError.get() domain];
267 m_errorCode = [m_platformError.get() code];
269 if (NSString* failingURLString = [[m_platformError.get() userInfo] valueForKey:@"NSErrorFailingURLStringKey"])
270 m_failingURL = URL(URL(), failingURLString);
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;
279 m_dataIsUpToDate = true;
282 bool ResourceError::platformCompare(const ResourceError& a, const ResourceError& b)
284 return a.nsError() == b.nsError();
287 void ResourceError::doPlatformIsolatedCopy(const ResourceError&)
291 NSError *ResourceError::nsError() const
294 ASSERT(!m_platformError);
298 if (!m_platformError)
299 m_platformError = createNSErrorFromResourceErrorBase(*this);
301 return m_platformError.get();
304 ResourceError::operator NSError *() const
309 CFErrorRef ResourceError::cfError() const
311 return (CFErrorRef)nsError();
314 ResourceError::operator CFErrorRef() const
319 #endif // USE(CFURLCONNECTION)
321 } // namespace WebCore