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