/* * Copyright (C) 2004-2018 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "WebCoreResourceHandleAsOperationQueueDelegate.h" #import "AuthenticationChallenge.h" #import "AuthenticationMac.h" #import "Logging.h" #import "NetworkingContext.h" #import "ResourceHandle.h" #import "ResourceHandleClient.h" #import "ResourceRequest.h" #import "ResourceResponse.h" #import "SecurityOrigin.h" #import "SharedBuffer.h" #import "SynchronousLoaderClient.h" #import "WebCoreURLResponse.h" #import #import #import #import using namespace WebCore; static bool scheduledWithCustomRunLoopMode(const std::optional& pairs) { if (!pairs) return false; for (auto& pair : *pairs) { auto mode = pair->mode(); if (mode != kCFRunLoopCommonModes && mode != kCFRunLoopDefaultMode) return true; } return false; } @implementation WebCoreResourceHandleAsOperationQueueDelegate - (void)callFunctionOnMainThread:(Function&&)function { // Sync xhr uses the message queue. if (m_messageQueue) return m_messageQueue->append(makeUnique>(WTFMove(function))); // This is the common case. if (!scheduledWithCustomRunLoopMode(m_scheduledPairs)) return callOnMainThread(WTFMove(function)); // If we have been scheduled in a custom run loop mode, schedule a block in that mode. auto block = makeBlockPtr([alreadyCalled = false, function = WTFMove(function)] () mutable { if (alreadyCalled) return; alreadyCalled = true; function(); function = nullptr; }); for (auto& pair : *m_scheduledPairs) CFRunLoopPerformBlock(pair->runLoop(), pair->mode(), block.get()); } - (id)initWithHandle:(WebCore::ResourceHandle*)handle messageQueue:(RefPtr&&)messageQueue { self = [self init]; if (!self) return nil; m_handle = handle; if (m_handle && m_handle->context()) { if (auto* pairs = m_handle->context()->scheduledRunLoopPairs()) m_scheduledPairs = *pairs; } m_messageQueue = WTFMove(messageQueue); return self; } - (void)detachHandle { Locker locker { m_lock }; m_handle = nullptr; m_messageQueue = nullptr; m_requestResult = nullptr; m_cachedResponseResult = nullptr; m_boolResult = NO; m_semaphore.signal(); // OK to signal even if we are not waiting. } - (void)dealloc { [super dealloc]; } - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)newRequest redirectResponse:(NSURLResponse *)redirectResponse { ASSERT(!isMainThread()); UNUSED_PARAM(connection); redirectResponse = synthesizeRedirectResponseIfNecessary([connection currentRequest], newRequest, redirectResponse); // See . This is a workaround for a behavior change in CFNetwork where willSendRequest gets called more often. if (!redirectResponse) return newRequest; #if !LOG_DISABLED if ([redirectResponse isKindOfClass:[NSHTTPURLResponse class]]) LOG(Network, "Handle %p delegate connection:%p willSendRequest:%@ redirectResponse:%d, Location:<%@>", m_handle, connection, [newRequest description], static_cast([(id)redirectResponse statusCode]), [[(id)redirectResponse allHeaderFields] objectForKey:@"Location"]); else LOG(Network, "Handle %p delegate connection:%p willSendRequest:%@ redirectResponse:non-HTTP", m_handle, connection, [newRequest description]); #endif auto protectedSelf = retainPtr(self); auto work = [self, protectedSelf, newRequest = retainPtr(newRequest), redirectResponse = retainPtr(redirectResponse)] () mutable { if (!m_handle) { m_requestResult = nullptr; m_semaphore.signal(); return; } ResourceResponse response(redirectResponse.get()); ResourceRequest redirectRequest = newRequest.get(); if ([newRequest HTTPBodyStream]) { ASSERT(m_handle->firstRequest().httpBody()); redirectRequest.setHTTPBody(m_handle->firstRequest().httpBody()); } if (m_handle->firstRequest().httpContentType().isEmpty()) redirectRequest.clearHTTPContentType(); // Check if the redirected url is allowed to access the redirecting url's timing information. if (!m_handle->hasCrossOriginRedirect() && !WebCore::SecurityOrigin::create(redirectRequest.url())->canRequest(redirectResponse.get().URL)) m_handle->markAsHavingCrossOriginRedirect(); m_handle->checkTAO(response); m_handle->incrementRedirectCount(); m_handle->willSendRequest(WTFMove(redirectRequest), WTFMove(response), [self, protectedSelf = WTFMove(protectedSelf)](ResourceRequest&& request) { m_requestResult = request.nsURLRequest(HTTPBodyUpdatePolicy::UpdateHTTPBody); m_semaphore.signal(); }); }; [self callFunctionOnMainThread:WTFMove(work)]; m_semaphore.wait(); Locker locker { m_lock }; if (!m_handle) return nil; RetainPtr requestResult = m_requestResult; // Make sure protectedSelf gets destroyed on the main thread in case this is the last strong reference to self // as we do not want to get destroyed on a non-main thread. [self callFunctionOnMainThread:[protectedSelf = WTFMove(protectedSelf)] { }]; return requestResult.autorelease(); } ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge ALLOW_DEPRECATED_IMPLEMENTATIONS_END { ASSERT(!isMainThread()); UNUSED_PARAM(connection); LOG(Network, "Handle %p delegate connection:%p didReceiveAuthenticationChallenge:%p", m_handle, connection, challenge); auto work = [self, protectedSelf = retainPtr(self), challenge = retainPtr(challenge)] () mutable { if (!m_handle) { [[challenge sender] cancelAuthenticationChallenge:challenge.get()]; return; } m_handle->didReceiveAuthenticationChallenge(core(challenge.get())); }; [self callFunctionOnMainThread:WTFMove(work)]; } ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace ALLOW_DEPRECATED_IMPLEMENTATIONS_END { ASSERT(!isMainThread()); UNUSED_PARAM(connection); LOG(Network, "Handle %p delegate connection:%p canAuthenticateAgainstProtectionSpace:%@://%@:%u realm:%@ method:%@ %@%@", m_handle, connection, [protectionSpace protocol], [protectionSpace host], [protectionSpace port], [protectionSpace realm], [protectionSpace authenticationMethod], [protectionSpace isProxy] ? @"proxy:" : @"", [protectionSpace isProxy] ? [protectionSpace proxyType] : @""); auto protectedSelf = retainPtr(self); auto work = [self, protectedSelf, protectionSpace = retainPtr(protectionSpace)] () mutable { if (!m_handle) { m_boolResult = NO; m_semaphore.signal(); return; } m_handle->canAuthenticateAgainstProtectionSpace(ProtectionSpace(protectionSpace.get()), [self, protectedSelf = WTFMove(protectedSelf)] (bool result) mutable { m_boolResult = result; m_semaphore.signal(); }); }; [self callFunctionOnMainThread:WTFMove(work)]; m_semaphore.wait(); Locker locker { m_lock }; if (!m_handle) return NO; auto boolResult = m_boolResult; // Make sure protectedSelf gets destroyed on the main thread in case this is the last strong reference to self // as we do not want to get destroyed on a non-main thread. [self callFunctionOnMainThread:[protectedSelf = WTFMove(protectedSelf)] { }]; return boolResult; } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)r { ASSERT(!isMainThread()); LOG(Network, "Handle %p delegate connection:%p didReceiveResponse:%p (HTTP status %d, reported MIMEType '%s')", m_handle, connection, r, [r respondsToSelector:@selector(statusCode)] ? [(id)r statusCode] : 0, [[r MIMEType] UTF8String]); auto protectedSelf = retainPtr(self); auto work = [self, protectedSelf, r = retainPtr(r), connection = retainPtr(connection)] () mutable { RefPtr protectedHandle(m_handle); if (!m_handle || !m_handle->client()) { m_semaphore.signal(); return; } // Avoid MIME type sniffing if the response comes back as 304 Not Modified. int statusCode = [r respondsToSelector:@selector(statusCode)] ? [(id)r statusCode] : 0; if (statusCode != 304) { bool isMainResourceLoad = m_handle->firstRequest().requester() == ResourceRequest::Requester::Main; adjustMIMETypeIfNecessary([r _CFURLResponse], isMainResourceLoad); } if ([m_handle->firstRequest().nsURLRequest(HTTPBodyUpdatePolicy::DoNotUpdateHTTPBody) _propertyForKey:@"ForceHTMLMIMEType"]) [r _setMIMEType:@"text/html"]; ResourceResponse resourceResponse(r.get()); m_handle->checkTAO(resourceResponse); auto metrics = copyTimingData(connection.get(), *m_handle); resourceResponse.setSource(ResourceResponse::Source::Network); resourceResponse.setDeprecatedNetworkLoadMetrics(Box { metrics }); m_handle->setNetworkLoadMetrics(WTFMove(metrics)); m_handle->didReceiveResponse(WTFMove(resourceResponse), [self, protectedSelf = WTFMove(protectedSelf)] { m_semaphore.signal(); }); }; [self callFunctionOnMainThread:WTFMove(work)]; m_semaphore.wait(); // Make sure we get destroyed on the main thread. [self callFunctionOnMainThread:[protectedSelf = WTFMove(protectedSelf)] { }]; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data lengthReceived:(long long)lengthReceived { ASSERT(!isMainThread()); UNUSED_PARAM(connection); UNUSED_PARAM(lengthReceived); LOG(Network, "Handle %p delegate connection:%p didReceiveData:%p lengthReceived:%lld", m_handle, connection, data, lengthReceived); auto work = [self = self, protectedSelf = retainPtr(self), data = retainPtr(data)] () mutable { if (!m_handle || !m_handle->client()) return; // FIXME: If we get more than 2B bytes in a single chunk, this code won't do the right thing. // However, with today's computers and networking speeds, this won't happen in practice. // Could be an issue with a giant local file. // FIXME: https://bugs.webkit.org/show_bug.cgi?id=19793 // -1 means we do not provide any data about transfer size to inspector so it would use // Content-Length headers or content size to show transfer size. m_handle->client()->didReceiveData(m_handle, SharedBuffer::create(data.get()), -1); }; [self callFunctionOnMainThread:WTFMove(work)]; } - (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite { ASSERT(!isMainThread()); UNUSED_PARAM(connection); UNUSED_PARAM(bytesWritten); LOG(Network, "Handle %p delegate connection:%p didSendBodyData:%d totalBytesWritten:%d totalBytesExpectedToWrite:%d", m_handle, connection, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); auto work = [self = self, protectedSelf = retainPtr(self), totalBytesWritten = totalBytesWritten, totalBytesExpectedToWrite = totalBytesExpectedToWrite] () mutable { if (!m_handle || !m_handle->client()) return; m_handle->client()->didSendData(m_handle, totalBytesWritten, totalBytesExpectedToWrite); }; [self callFunctionOnMainThread:WTFMove(work)]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { ASSERT(!isMainThread()); UNUSED_PARAM(connection); LOG(Network, "Handle %p delegate connectionDidFinishLoading:%p", m_handle, connection); auto work = [self = self, protectedSelf = retainPtr(self), connection = retainPtr(connection), timingData = retainPtr([connection _timingData])] () mutable { if (!m_handle || !m_handle->client()) return; if (auto metrics = m_handle->networkLoadMetrics()) { if (double responseEndTime = [[timingData objectForKey:@"_kCFNTimingDataResponseEnd"] doubleValue]) metrics->responseEnd = WallTime::fromRawSeconds(adoptNS([[NSDate alloc] initWithTimeIntervalSinceReferenceDate:responseEndTime]).get().timeIntervalSince1970).approximateMonotonicTime(); else metrics->responseEnd = metrics->responseStart; metrics->protocol = (NSString *)[timingData objectForKey:@"_kCFNTimingDataNetworkProtocolName"]; metrics->responseBodyBytesReceived = [[timingData objectForKey:@"_kCFNTimingDataResponseBodyBytesReceived"] unsignedLongLongValue]; metrics->responseBodyDecodedSize = [[timingData objectForKey:@"_kCFNTimingDataResponseBodyBytesDecoded"] unsignedLongLongValue]; metrics->markComplete(); m_handle->client()->didFinishLoading(m_handle, *metrics); } else { NetworkLoadMetrics emptyMetrics; emptyMetrics.markComplete(); m_handle->client()->didFinishLoading(m_handle, emptyMetrics); } if (m_messageQueue) { m_messageQueue->kill(); m_messageQueue = nullptr; } }; [self callFunctionOnMainThread:WTFMove(work)]; } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { ASSERT(!isMainThread()); UNUSED_PARAM(connection); LOG(Network, "Handle %p delegate connection:%p didFailWithError:%@", m_handle, connection, error); auto work = [self = self, protectedSelf = retainPtr(self), error = retainPtr(error)] () mutable { if (!m_handle || !m_handle->client()) return; m_handle->client()->didFail(m_handle, error.get()); if (m_messageQueue) { m_messageQueue->kill(); m_messageQueue = nullptr; } }; [self callFunctionOnMainThread:WTFMove(work)]; } - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { ASSERT(!isMainThread()); UNUSED_PARAM(connection); LOG(Network, "Handle %p delegate connection:%p willCacheResponse:%p", m_handle, connection, cachedResponse); auto protectedSelf = retainPtr(self); auto work = [self, protectedSelf, cachedResponse = retainPtr(cachedResponse)] () mutable { if (!m_handle || !m_handle->client()) { m_cachedResponseResult = nullptr; m_semaphore.signal(); return; } m_handle->client()->willCacheResponseAsync(m_handle, cachedResponse.get(), [self, protectedSelf = WTFMove(protectedSelf)] (NSCachedURLResponse * response) mutable { m_cachedResponseResult = response; m_semaphore.signal(); }); }; [self callFunctionOnMainThread:WTFMove(work)]; m_semaphore.wait(); Locker locker { m_lock }; if (!m_handle) return nil; RetainPtr cachedResponseResult = m_cachedResponseResult; // Make sure protectedSelf gets destroyed on the main thread in case this is the last strong reference to self // as we do not want to get destroyed on a non-main thread. [self callFunctionOnMainThread:[protectedSelf = WTFMove(protectedSelf)] { }]; return cachedResponseResult.autorelease(); } @end @implementation WebCoreResourceHandleWithCredentialStorageAsOperationQueueDelegate - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection { ASSERT(!isMainThread()); UNUSED_PARAM(connection); return NO; } @end