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