Make ResourceLoader::willSendRequestInternal asynchronous
[WebKit-https.git] / Source / WebKitLegacy / mac / Plugins / Hosted / HostedNetscapePluginStream.mm
1 /*
2  * Copyright (C) 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 #if USE(PLUGIN_HOST_PROCESS) && ENABLE(NETSCAPE_PLUGIN_API)
27
28 #import "HostedNetscapePluginStream.h"
29
30 #import "NetscapePluginHostProxy.h"
31 #import "NetscapePluginInstanceProxy.h"
32 #import "WebFrameInternal.h"
33 #import "WebHostedNetscapePluginView.h"
34 #import "WebKitErrorsPrivate.h"
35 #import "WebKitPluginHost.h"
36 #import "WebNSURLExtras.h"
37 #import "WebNSURLRequestExtras.h"
38 #import "WebResourceLoadScheduler.h"
39 #import <WebCore/Document.h>
40 #import <WebCore/DocumentLoader.h>
41 #import <WebCore/Frame.h>
42 #import <WebCore/FrameLoader.h>
43 #import <WebCore/PlatformStrategies.h>
44 #import <WebCore/SecurityOrigin.h>
45 #import <WebCore/SecurityPolicy.h>
46 #import <WebCore/WebCoreURLResponse.h>
47 #import <pal/spi/cf/CFNetworkSPI.h>
48 #import <wtf/CompletionHandler.h>
49 #import <wtf/RefCountedLeakCounter.h>
50
51 using namespace WebCore;
52
53 namespace WebKit {
54
55 DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, hostedNetscapePluginStreamCounter, ("HostedNetscapePluginStream"));
56
57 HostedNetscapePluginStream::HostedNetscapePluginStream(NetscapePluginInstanceProxy* instance, uint32_t streamID, NSURLRequest *request)
58     : m_instance(instance)
59     , m_streamID(streamID)
60     , m_request(adoptNS([request mutableCopy]))
61     , m_requestURL([request URL])
62     , m_frameLoader(0)
63 {
64     String referrer = SecurityPolicy::generateReferrerHeader(core([instance->pluginView() webFrame])->document()->referrerPolicy(), [request URL], core([instance->pluginView() webFrame])->loader().outgoingReferrer());
65     if (referrer.isEmpty())
66         [m_request.get() _web_setHTTPReferrer:nil];
67     else
68         [m_request.get() _web_setHTTPReferrer:referrer];
69
70 #ifndef NDEBUG
71     hostedNetscapePluginStreamCounter.increment();
72 #endif
73 }
74
75 HostedNetscapePluginStream::HostedNetscapePluginStream(NetscapePluginInstanceProxy* instance, WebCore::FrameLoader* frameLoader)
76     : m_instance(instance)
77     , m_streamID(1)
78     , m_frameLoader(frameLoader)
79 {
80 #ifndef NDEBUG
81     hostedNetscapePluginStreamCounter.increment();
82 #endif
83 }
84
85 HostedNetscapePluginStream::~HostedNetscapePluginStream()
86 {
87 #ifndef NDEBUG
88     hostedNetscapePluginStreamCounter.decrement();
89 #endif
90 }
91
92 void HostedNetscapePluginStream::startStreamWithResponse(NSURLResponse *response)
93 {
94     didReceiveResponse(0, response);
95 }
96     
97 void HostedNetscapePluginStream::startStream(NSURL *responseURL, long long expectedContentLength, NSDate *lastModifiedDate, NSString *mimeType, NSData *headers)
98 {
99     m_responseURL = responseURL;
100     m_mimeType = mimeType;
101
102     char* mimeTypeUTF8 = const_cast<char*>([mimeType UTF8String]);
103     int mimeTypeUTF8Length = mimeTypeUTF8 ? strlen (mimeTypeUTF8) + 1 : 0;
104     
105     const char *url = [responseURL _web_URLCString];
106     int urlLength = url ? strlen(url) + 1 : 0;
107     
108     _WKPHStartStream(m_instance->hostProxy()->port(),
109                      m_instance->pluginID(),
110                      m_streamID,
111                      const_cast<char*>(url), urlLength,
112                      expectedContentLength,
113                      [lastModifiedDate timeIntervalSince1970],
114                      mimeTypeUTF8, mimeTypeUTF8Length,
115                      const_cast<char*>(reinterpret_cast<const char*>([headers bytes])), [headers length]);
116 }
117                      
118 void HostedNetscapePluginStream::didReceiveData(WebCore::NetscapePlugInStreamLoader*, const char* bytes, int length)
119 {
120     _WKPHStreamDidReceiveData(m_instance->hostProxy()->port(),
121                               m_instance->pluginID(),
122                               m_streamID,
123                               const_cast<char*>(bytes), length);
124 }
125     
126 void HostedNetscapePluginStream::didFinishLoading(WebCore::NetscapePlugInStreamLoader*)
127 {
128     _WKPHStreamDidFinishLoading(m_instance->hostProxy()->port(),
129                                 m_instance->pluginID(),
130                                 m_streamID);
131     m_instance->disconnectStream(this);
132 }
133
134 void HostedNetscapePluginStream::willSendRequest(NetscapePlugInStreamLoader*, ResourceRequest&& request, const ResourceResponse&, CompletionHandler<void(WebCore::ResourceRequest&&)>&& callback)
135 {
136     // FIXME: We should notify the plug-in with NPP_URLRedirectNotify here.
137     callback(WTFMove(request));
138 }
139
140 void HostedNetscapePluginStream::didReceiveResponse(NetscapePlugInStreamLoader*, const ResourceResponse& response)
141 {
142     NSURLResponse *r = response.nsURLResponse();
143     
144     NSMutableData *theHeaders = nil;
145     long long expectedContentLength = [r expectedContentLength];
146     
147     if ([r isKindOfClass:[NSHTTPURLResponse class]]) {
148         NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)r;
149         theHeaders = [NSMutableData dataWithCapacity:1024];
150         
151         // FIXME: it would be nice to be able to get the raw HTTP header block.
152         // This includes the HTTP version, the real status text,
153         // all headers in their original order and including duplicates,
154         // and all original bytes verbatim, rather than sent through Unicode translation.
155         // Unfortunately NSHTTPURLResponse doesn't provide access at that low a level.
156         
157         [theHeaders appendBytes:"HTTP " length:5];
158         char statusStr[10];
159         long statusCode = [httpResponse statusCode];
160         snprintf(statusStr, sizeof(statusStr), "%ld", statusCode);
161         [theHeaders appendBytes:statusStr length:strlen(statusStr)];
162         [theHeaders appendBytes:" OK\n" length:4];
163         
164         // HACK: pass the headers through as UTF-8.
165         // This is not the intended behavior; we're supposed to pass original bytes verbatim.
166         // But we don't have the original bytes, we have NSStrings built by the URL loading system.
167         // It hopefully shouldn't matter, since RFC2616/RFC822 require ASCII-only headers,
168         // but surely someone out there is using non-ASCII characters, and hopefully UTF-8 is adequate here.
169         // It seems better than NSASCIIStringEncoding, which will lose information if non-ASCII is used.
170         
171         NSDictionary *headerDict = [httpResponse allHeaderFields];
172         NSArray *keys = [[headerDict allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
173         NSEnumerator *i = [keys objectEnumerator];
174         NSString *k;
175         while ((k = [i nextObject]) != nil) {
176             NSString *v = [headerDict objectForKey:k];
177             [theHeaders appendData:[k dataUsingEncoding:NSUTF8StringEncoding]];
178             [theHeaders appendBytes:": " length:2];
179             [theHeaders appendData:[v dataUsingEncoding:NSUTF8StringEncoding]];
180             [theHeaders appendBytes:"\n" length:1];
181         }
182         
183         // If the content is encoded (most likely compressed), then don't send its length to the plugin,
184         // which is only interested in the decoded length, not yet known at the moment.
185         // <rdar://problem/4470599> tracks a request for -[NSURLResponse expectedContentLength] to incorporate this logic.
186         NSString *contentEncoding = (NSString *)[[(NSHTTPURLResponse *)r allHeaderFields] objectForKey:@"Content-Encoding"];
187         if (contentEncoding && ![contentEncoding isEqualToString:@"identity"])
188             expectedContentLength = -1;
189
190         [theHeaders appendBytes:"\0" length:1];
191     }
192     
193     startStream([r URL], expectedContentLength, [r _lastModifiedDate], [r MIMEType], theHeaders);
194 }
195
196 NPReason HostedNetscapePluginStream::reasonForError(NSError *error)
197 {
198     if (!error)
199         return NPRES_DONE;
200
201     if ([[error domain] isEqualToString:NSURLErrorDomain] && [error code] == NSURLErrorCancelled)
202         return NPRES_USER_BREAK;
203
204     return NPRES_NETWORK_ERR;
205 }
206
207 void HostedNetscapePluginStream::didFail(WebCore::NetscapePlugInStreamLoader*, const WebCore::ResourceError& error)
208 {
209     if (NetscapePluginHostProxy* hostProxy = m_instance->hostProxy())
210         _WKPHStreamDidFail(hostProxy->port(), m_instance->pluginID(), m_streamID, reasonForError(error));
211     m_instance->disconnectStream(this);
212 }
213
214 bool HostedNetscapePluginStream::wantsAllStreams() const
215 {
216     // FIXME: Implement.
217     return false;
218 }
219
220 void HostedNetscapePluginStream::start()
221 {
222     ASSERT(m_request);
223     ASSERT(!m_frameLoader);
224     ASSERT(!m_loader);
225
226     webResourceLoadScheduler().schedulePluginStreamLoad(*core([m_instance->pluginView() webFrame]), *this, m_request.get(), [this, protectedThis = makeRef(*this)] (RefPtr<WebCore::NetscapePlugInStreamLoader>&& loader) {
227         m_loader = WTFMove(loader);
228     });
229 }
230
231 void HostedNetscapePluginStream::stop()
232 {
233     ASSERT(!m_frameLoader);
234     
235     if (!m_loader->isDone())
236         m_loader->cancel(m_loader->cancelledError());
237 }
238     
239 void HostedNetscapePluginStream::cancelLoad(NPReason reason)
240 {
241     cancelLoad(errorForReason(reason));
242 }
243
244 void HostedNetscapePluginStream::cancelLoad(NSError *error)
245 {
246     if (m_frameLoader) {
247         ASSERT(!m_loader);
248         
249         DocumentLoader* documentLoader = m_frameLoader->activeDocumentLoader();
250         if (documentLoader && documentLoader->isLoadingMainResource())
251             documentLoader->cancelMainResourceLoad(error);
252         return;
253     }
254
255     if (!m_loader->isDone()) {
256         // Cancelling the load will disconnect the stream so there's no need to do it explicitly.
257         m_loader->cancel(error);
258     } else
259         m_instance->disconnectStream(this);
260 }
261
262 NSError *HostedNetscapePluginStream::pluginCancelledConnectionError() const
263 {
264     return [[[NSError alloc] _initWithPluginErrorCode:WebKitErrorPlugInCancelledConnection
265                                            contentURL:m_responseURL ? m_responseURL.get() : m_requestURL.get()
266                                         pluginPageURL:nil
267                                            pluginName:[[m_instance->pluginView() pluginPackage] pluginInfo].name
268                                              MIMEType:m_mimeType.get()] autorelease];
269 }
270
271 NSError *HostedNetscapePluginStream::errorForReason(NPReason reason) const
272 {
273     if (reason == NPRES_DONE)
274         return nil;
275
276     if (reason == NPRES_USER_BREAK)
277         return [NSError _webKitErrorWithDomain:NSURLErrorDomain
278                                           code:NSURLErrorCancelled 
279                                            URL:m_responseURL ? m_responseURL.get() : m_requestURL.get()];
280
281     return pluginCancelledConnectionError();
282 }
283
284 } // namespace WebKit
285
286 #endif // USE(PLUGIN_HOST_PROCESS) && ENABLE(NETSCAPE_PLUGIN_API)
287