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