|
1 /* |
|
2 * Copyright (C) 2005, 2006, 2007 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 * |
|
8 * 1. Redistributions of source code must retain the above copyright |
|
9 * notice, this list of conditions and the following disclaimer. |
|
10 * 2. Redistributions in binary form must reproduce the above copyright |
|
11 * notice, this list of conditions and the following disclaimer in the |
|
12 * documentation and/or other materials provided with the distribution. |
|
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
|
14 * its contributors may be used to endorse or promote products derived |
|
15 * from this software without specific prior written permission. |
|
16 * |
|
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
|
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
|
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
|
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
27 */ |
|
28 |
|
29 #if ENABLE(NETSCAPE_PLUGIN_API) |
|
30 #import "WebNetscapePluginStream.h" |
|
31 |
|
32 #import "WebNetscapePluginView.h" |
|
33 #import "WebFrameInternal.h" |
|
34 #import "WebKitErrorsPrivate.h" |
|
35 #import "WebKitLogging.h" |
|
36 #import "WebNSObjectExtras.h" |
|
37 #import "WebNSURLExtras.h" |
|
38 #import "WebNSURLRequestExtras.h" |
|
39 #import "WebNetscapePluginPackage.h" |
|
40 #import <Foundation/NSURLResponse.h> |
|
41 #import <runtime/JSLock.h> |
|
42 #import <WebCore/DocumentLoader.h> |
|
43 #import <WebCore/Frame.h> |
|
44 #import <WebCore/FrameLoader.h> |
|
45 #import <WebCore/SecurityOrigin.h> |
|
46 #import <WebCore/WebCoreObjCExtras.h> |
|
47 #import <WebCore/WebCoreURLResponse.h> |
|
48 #import <WebKitSystemInterface.h> |
|
49 #import <wtf/HashMap.h> |
|
50 #import <wtf/StdLibExtras.h> |
|
51 |
|
52 using namespace WebCore; |
|
53 using namespace std; |
|
54 |
|
55 #define WEB_REASON_NONE -1 |
|
56 |
|
57 static NSString *CarbonPathFromPOSIXPath(NSString *posixPath); |
|
58 |
|
59 class PluginStopDeferrer { |
|
60 public: |
|
61 PluginStopDeferrer(WebNetscapePluginView* pluginView) |
|
62 : m_pluginView(pluginView) |
|
63 { |
|
64 ASSERT(m_pluginView); |
|
65 |
|
66 [m_pluginView.get() willCallPlugInFunction]; |
|
67 } |
|
68 |
|
69 ~PluginStopDeferrer() |
|
70 { |
|
71 ASSERT(m_pluginView); |
|
72 [m_pluginView.get() didCallPlugInFunction]; |
|
73 } |
|
74 |
|
75 private: |
|
76 RetainPtr<WebNetscapePluginView> m_pluginView; |
|
77 }; |
|
78 |
|
79 typedef HashMap<NPStream*, NPP> StreamMap; |
|
80 static StreamMap& streams() |
|
81 { |
|
82 DEFINE_STATIC_LOCAL(StreamMap, staticStreams, ()); |
|
83 return staticStreams; |
|
84 } |
|
85 |
|
86 NPP WebNetscapePluginStream::ownerForStream(NPStream *stream) |
|
87 { |
|
88 return streams().get(stream); |
|
89 } |
|
90 |
|
91 NPReason WebNetscapePluginStream::reasonForError(NSError *error) |
|
92 { |
|
93 if (!error) |
|
94 return NPRES_DONE; |
|
95 |
|
96 if ([[error domain] isEqualToString:NSURLErrorDomain] && [error code] == NSURLErrorCancelled) |
|
97 return NPRES_USER_BREAK; |
|
98 |
|
99 return NPRES_NETWORK_ERR; |
|
100 } |
|
101 |
|
102 NSError *WebNetscapePluginStream::pluginCancelledConnectionError() const |
|
103 { |
|
104 return [[[NSError alloc] _initWithPluginErrorCode:WebKitErrorPlugInCancelledConnection |
|
105 contentURL:m_responseURL ? m_responseURL.get() : (NSURL *)m_requestURL |
|
106 pluginPageURL:nil |
|
107 pluginName:[[m_pluginView.get() pluginPackage] pluginInfo].name |
|
108 MIMEType:(NSString *)String::fromUTF8(m_mimeType.data(), m_mimeType.length())] autorelease]; |
|
109 } |
|
110 |
|
111 NSError *WebNetscapePluginStream::errorForReason(NPReason reason) const |
|
112 { |
|
113 if (reason == NPRES_DONE) |
|
114 return nil; |
|
115 |
|
116 if (reason == NPRES_USER_BREAK) |
|
117 return [NSError _webKitErrorWithDomain:NSURLErrorDomain |
|
118 code:NSURLErrorCancelled |
|
119 URL:m_responseURL ? m_responseURL.get() : (NSURL *)m_requestURL]; |
|
120 |
|
121 return pluginCancelledConnectionError(); |
|
122 } |
|
123 |
|
124 WebNetscapePluginStream::WebNetscapePluginStream(FrameLoader* frameLoader) |
|
125 : m_plugin(0) |
|
126 , m_transferMode(0) |
|
127 , m_offset(0) |
|
128 , m_fileDescriptor(-1) |
|
129 , m_sendNotification(false) |
|
130 , m_notifyData(0) |
|
131 , m_headers(0) |
|
132 , m_reason(NPRES_BASE) |
|
133 , m_isTerminated(false) |
|
134 , m_newStreamSuccessful(false) |
|
135 , m_frameLoader(frameLoader) |
|
136 , m_pluginFuncs(0) |
|
137 , m_deliverDataTimer(this, &WebNetscapePluginStream::deliverDataTimerFired) |
|
138 { |
|
139 memset(&m_stream, 0, sizeof(NPStream)); |
|
140 } |
|
141 |
|
142 WebNetscapePluginStream::WebNetscapePluginStream(NSURLRequest *request, NPP plugin, bool sendNotification, void* notifyData) |
|
143 : m_requestURL([request URL]) |
|
144 , m_plugin(0) |
|
145 , m_transferMode(0) |
|
146 , m_offset(0) |
|
147 , m_fileDescriptor(-1) |
|
148 , m_sendNotification(sendNotification) |
|
149 , m_notifyData(notifyData) |
|
150 , m_headers(0) |
|
151 , m_reason(NPRES_BASE) |
|
152 , m_isTerminated(false) |
|
153 , m_newStreamSuccessful(false) |
|
154 , m_frameLoader(0) |
|
155 , m_request(AdoptNS, [request mutableCopy]) |
|
156 , m_pluginFuncs(0) |
|
157 , m_deliverDataTimer(this, &WebNetscapePluginStream::deliverDataTimerFired) |
|
158 { |
|
159 memset(&m_stream, 0, sizeof(NPStream)); |
|
160 |
|
161 WebNetscapePluginView *view = (WebNetscapePluginView *)plugin->ndata; |
|
162 |
|
163 // This check has already been done by the plug-in view. |
|
164 ASSERT(SecurityOrigin::canLoad([request URL], String(), core([view webFrame])->document())); |
|
165 |
|
166 ASSERT([request URL]); |
|
167 ASSERT(plugin); |
|
168 |
|
169 setPlugin(plugin); |
|
170 |
|
171 streams().add(&m_stream, plugin); |
|
172 |
|
173 if (SecurityOrigin::shouldHideReferrer([request URL], core([view webFrame])->loader()->outgoingReferrer())) |
|
174 [m_request.get() _web_setHTTPReferrer:nil]; |
|
175 } |
|
176 |
|
177 WebNetscapePluginStream::~WebNetscapePluginStream() |
|
178 { |
|
179 ASSERT(!m_plugin); |
|
180 ASSERT(m_isTerminated); |
|
181 ASSERT(!m_stream.ndata); |
|
182 |
|
183 // The stream file should have been deleted, and the path freed, in -_destroyStream |
|
184 ASSERT(!m_path); |
|
185 ASSERT(m_fileDescriptor == -1); |
|
186 |
|
187 free((void *)m_stream.url); |
|
188 free(m_headers); |
|
189 |
|
190 streams().remove(&m_stream); |
|
191 } |
|
192 |
|
193 void WebNetscapePluginStream::setPlugin(NPP plugin) |
|
194 { |
|
195 if (plugin) { |
|
196 m_plugin = plugin; |
|
197 m_pluginView = static_cast<WebNetscapePluginView *>(m_plugin->ndata); |
|
198 |
|
199 WebNetscapePluginPackage *pluginPackage = [m_pluginView.get() pluginPackage]; |
|
200 |
|
201 m_pluginFuncs = [pluginPackage pluginFuncs]; |
|
202 } else { |
|
203 WebNetscapePluginView *view = m_pluginView.get(); |
|
204 m_plugin = 0; |
|
205 m_pluginFuncs = 0; |
|
206 |
|
207 [view disconnectStream:this]; |
|
208 m_pluginView = 0; |
|
209 } |
|
210 } |
|
211 |
|
212 void WebNetscapePluginStream::startStream(NSURL *url, long long expectedContentLength, NSDate *lastModifiedDate, const String& mimeType, NSData *headers) |
|
213 { |
|
214 ASSERT(!m_isTerminated); |
|
215 |
|
216 m_responseURL = url; |
|
217 m_mimeType = mimeType.utf8(); |
|
218 |
|
219 free((void *)m_stream.url); |
|
220 m_stream.url = strdup([m_responseURL.get() _web_URLCString]); |
|
221 |
|
222 m_stream.ndata = this; |
|
223 m_stream.end = expectedContentLength > 0 ? (uint32_t)expectedContentLength : 0; |
|
224 m_stream.lastmodified = (uint32_t)[lastModifiedDate timeIntervalSince1970]; |
|
225 m_stream.notifyData = m_notifyData; |
|
226 |
|
227 if (headers) { |
|
228 unsigned len = [headers length]; |
|
229 m_headers = (char*) malloc(len + 1); |
|
230 [headers getBytes:m_headers]; |
|
231 m_headers[len] = 0; |
|
232 m_stream.headers = m_headers; |
|
233 } |
|
234 |
|
235 m_transferMode = NP_NORMAL; |
|
236 m_offset = 0; |
|
237 m_reason = WEB_REASON_NONE; |
|
238 // FIXME: If WebNetscapePluginStream called our initializer we wouldn't have to do this here. |
|
239 m_fileDescriptor = -1; |
|
240 |
|
241 // FIXME: Need a way to check if stream is seekable |
|
242 |
|
243 NPError npErr; |
|
244 { |
|
245 PluginStopDeferrer deferrer(m_pluginView.get()); |
|
246 npErr = m_pluginFuncs->newstream(m_plugin, m_mimeType.mutableData(), &m_stream, NO, &m_transferMode); |
|
247 } |
|
248 |
|
249 LOG(Plugins, "NPP_NewStream URL=%@ MIME=%s error=%d", m_responseURL.get(), m_mimeType.data(), npErr); |
|
250 |
|
251 if (npErr != NPERR_NO_ERROR) { |
|
252 LOG_ERROR("NPP_NewStream failed with error: %d responseURL: %@", npErr, m_responseURL.get()); |
|
253 // Calling cancelLoadWithError: cancels the load, but doesn't call NPP_DestroyStream. |
|
254 cancelLoadWithError(pluginCancelledConnectionError()); |
|
255 return; |
|
256 } |
|
257 |
|
258 m_newStreamSuccessful = true; |
|
259 |
|
260 switch (m_transferMode) { |
|
261 case NP_NORMAL: |
|
262 LOG(Plugins, "Stream type: NP_NORMAL"); |
|
263 break; |
|
264 case NP_ASFILEONLY: |
|
265 LOG(Plugins, "Stream type: NP_ASFILEONLY"); |
|
266 break; |
|
267 case NP_ASFILE: |
|
268 LOG(Plugins, "Stream type: NP_ASFILE"); |
|
269 break; |
|
270 case NP_SEEK: |
|
271 LOG_ERROR("Stream type: NP_SEEK not yet supported"); |
|
272 cancelLoadAndDestroyStreamWithError(pluginCancelledConnectionError()); |
|
273 break; |
|
274 default: |
|
275 LOG_ERROR("unknown stream type"); |
|
276 } |
|
277 } |
|
278 |
|
279 void WebNetscapePluginStream::start() |
|
280 { |
|
281 ASSERT(m_request); |
|
282 ASSERT(!m_frameLoader); |
|
283 ASSERT(!m_loader); |
|
284 |
|
285 m_loader = NetscapePlugInStreamLoader::create(core([m_pluginView.get() webFrame]), this); |
|
286 m_loader->setShouldBufferData(false); |
|
287 |
|
288 m_loader->documentLoader()->addPlugInStreamLoader(m_loader.get()); |
|
289 m_loader->load(m_request.get()); |
|
290 } |
|
291 |
|
292 void WebNetscapePluginStream::stop() |
|
293 { |
|
294 ASSERT(!m_frameLoader); |
|
295 |
|
296 if (!m_loader->isDone()) |
|
297 cancelLoadAndDestroyStreamWithError(m_loader->cancelledError()); |
|
298 } |
|
299 |
|
300 void WebNetscapePluginStream::didReceiveResponse(NetscapePlugInStreamLoader*, const ResourceResponse& response) |
|
301 { |
|
302 NSURLResponse *r = response.nsURLResponse(); |
|
303 |
|
304 NSMutableData *theHeaders = nil; |
|
305 long long expectedContentLength = [r expectedContentLength]; |
|
306 |
|
307 if ([r isKindOfClass:[NSHTTPURLResponse class]]) { |
|
308 NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)r; |
|
309 theHeaders = [NSMutableData dataWithCapacity:1024]; |
|
310 |
|
311 // FIXME: it would be nice to be able to get the raw HTTP header block. |
|
312 // This includes the HTTP version, the real status text, |
|
313 // all headers in their original order and including duplicates, |
|
314 // and all original bytes verbatim, rather than sent through Unicode translation. |
|
315 // Unfortunately NSHTTPURLResponse doesn't provide access at that low a level. |
|
316 |
|
317 [theHeaders appendBytes:"HTTP " length:5]; |
|
318 char statusStr[10]; |
|
319 long statusCode = [httpResponse statusCode]; |
|
320 snprintf(statusStr, sizeof(statusStr), "%ld", statusCode); |
|
321 [theHeaders appendBytes:statusStr length:strlen(statusStr)]; |
|
322 [theHeaders appendBytes:" OK\n" length:4]; |
|
323 |
|
324 // HACK: pass the headers through as UTF-8. |
|
325 // This is not the intended behavior; we're supposed to pass original bytes verbatim. |
|
326 // But we don't have the original bytes, we have NSStrings built by the URL loading system. |
|
327 // It hopefully shouldn't matter, since RFC2616/RFC822 require ASCII-only headers, |
|
328 // but surely someone out there is using non-ASCII characters, and hopefully UTF-8 is adequate here. |
|
329 // It seems better than NSASCIIStringEncoding, which will lose information if non-ASCII is used. |
|
330 |
|
331 NSDictionary *headerDict = [httpResponse allHeaderFields]; |
|
332 NSArray *keys = [[headerDict allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; |
|
333 NSEnumerator *i = [keys objectEnumerator]; |
|
334 NSString *k; |
|
335 while ((k = [i nextObject]) != nil) { |
|
336 NSString *v = [headerDict objectForKey:k]; |
|
337 [theHeaders appendData:[k dataUsingEncoding:NSUTF8StringEncoding]]; |
|
338 [theHeaders appendBytes:": " length:2]; |
|
339 [theHeaders appendData:[v dataUsingEncoding:NSUTF8StringEncoding]]; |
|
340 [theHeaders appendBytes:"\n" length:1]; |
|
341 } |
|
342 |
|
343 // If the content is encoded (most likely compressed), then don't send its length to the plugin, |
|
344 // which is only interested in the decoded length, not yet known at the moment. |
|
345 // <rdar://problem/4470599> tracks a request for -[NSURLResponse expectedContentLength] to incorporate this logic. |
|
346 NSString *contentEncoding = (NSString *)[[(NSHTTPURLResponse *)r allHeaderFields] objectForKey:@"Content-Encoding"]; |
|
347 if (contentEncoding && ![contentEncoding isEqualToString:@"identity"]) |
|
348 expectedContentLength = -1; |
|
349 |
|
350 // startStreamResponseURL:... will null-terminate. |
|
351 } |
|
352 |
|
353 startStream([r URL], expectedContentLength, WKGetNSURLResponseLastModifiedDate(r), response.mimeType(), theHeaders); |
|
354 } |
|
355 |
|
356 void WebNetscapePluginStream::startStreamWithResponse(NSURLResponse *response) |
|
357 { |
|
358 didReceiveResponse(0, response); |
|
359 } |
|
360 |
|
361 bool WebNetscapePluginStream::wantsAllStreams() const |
|
362 { |
|
363 if (!m_pluginFuncs->getvalue) |
|
364 return false; |
|
365 |
|
366 void *value = 0; |
|
367 NPError error; |
|
368 { |
|
369 PluginStopDeferrer deferrer(m_pluginView.get()); |
|
370 JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); |
|
371 error = m_pluginFuncs->getvalue(m_plugin, NPPVpluginWantsAllNetworkStreams, &value); |
|
372 } |
|
373 if (error != NPERR_NO_ERROR) |
|
374 return false; |
|
375 |
|
376 return value; |
|
377 } |
|
378 |
|
379 void WebNetscapePluginStream::destroyStream() |
|
380 { |
|
381 if (m_isTerminated) |
|
382 return; |
|
383 |
|
384 RefPtr<WebNetscapePluginStream> protect(this); |
|
385 |
|
386 ASSERT(m_reason != WEB_REASON_NONE); |
|
387 ASSERT([m_deliveryData.get() length] == 0); |
|
388 |
|
389 m_deliverDataTimer.stop(); |
|
390 |
|
391 if (m_stream.ndata) { |
|
392 if (m_reason == NPRES_DONE && (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY)) { |
|
393 ASSERT(m_fileDescriptor == -1); |
|
394 ASSERT(m_path); |
|
395 NSString *carbonPath = CarbonPathFromPOSIXPath(m_path.get()); |
|
396 ASSERT(carbonPath != NULL); |
|
397 |
|
398 PluginStopDeferrer deferrer(m_pluginView.get()); |
|
399 m_pluginFuncs->asfile(m_plugin, &m_stream, [carbonPath fileSystemRepresentation]); |
|
400 LOG(Plugins, "NPP_StreamAsFile responseURL=%@ path=%s", m_responseURL.get(), carbonPath); |
|
401 } |
|
402 |
|
403 if (m_path) { |
|
404 // Delete the file after calling NPP_StreamAsFile(), instead of in -dealloc/-finalize. It should be OK |
|
405 // to delete the file here -- NPP_StreamAsFile() is always called immediately before NPP_DestroyStream() |
|
406 // (the stream destruction function), so there can be no expectation that a plugin will read the stream |
|
407 // file asynchronously after NPP_StreamAsFile() is called. |
|
408 unlink([m_path.get() fileSystemRepresentation]); |
|
409 m_path = 0; |
|
410 |
|
411 if (m_isTerminated) |
|
412 return; |
|
413 } |
|
414 |
|
415 if (m_fileDescriptor != -1) { |
|
416 // The file may still be open if we are destroying the stream before it completed loading. |
|
417 close(m_fileDescriptor); |
|
418 m_fileDescriptor = -1; |
|
419 } |
|
420 |
|
421 if (m_newStreamSuccessful) { |
|
422 PluginStopDeferrer deferrer(m_pluginView.get()); |
|
423 #if !LOG_DISABLED |
|
424 NPError npErr = |
|
425 #endif |
|
426 m_pluginFuncs->destroystream(m_plugin, &m_stream, m_reason); |
|
427 LOG(Plugins, "NPP_DestroyStream responseURL=%@ error=%d", m_responseURL.get(), npErr); |
|
428 } |
|
429 |
|
430 free(m_headers); |
|
431 m_headers = NULL; |
|
432 m_stream.headers = NULL; |
|
433 |
|
434 m_stream.ndata = 0; |
|
435 |
|
436 if (m_isTerminated) |
|
437 return; |
|
438 } |
|
439 |
|
440 if (m_sendNotification) { |
|
441 // NPP_URLNotify expects the request URL, not the response URL. |
|
442 PluginStopDeferrer deferrer(m_pluginView.get()); |
|
443 m_pluginFuncs->urlnotify(m_plugin, m_requestURL.string().utf8().data(), m_reason, m_notifyData); |
|
444 LOG(Plugins, "NPP_URLNotify requestURL=%@ reason=%d", (NSURL *)m_requestURL, m_reason); |
|
445 } |
|
446 |
|
447 m_isTerminated = true; |
|
448 |
|
449 setPlugin(0); |
|
450 } |
|
451 |
|
452 void WebNetscapePluginStream::destroyStreamWithReason(NPReason reason) |
|
453 { |
|
454 m_reason = reason; |
|
455 if (m_reason != NPRES_DONE) { |
|
456 // Stop any pending data from being streamed. |
|
457 [m_deliveryData.get() setLength:0]; |
|
458 } else if ([m_deliveryData.get() length] > 0) { |
|
459 // There is more data to be streamed, don't destroy the stream now. |
|
460 return; |
|
461 } |
|
462 |
|
463 RefPtr<WebNetscapePluginStream> protect(this); |
|
464 destroyStream(); |
|
465 ASSERT(!m_stream.ndata); |
|
466 } |
|
467 |
|
468 void WebNetscapePluginStream::cancelLoadWithError(NSError *error) |
|
469 { |
|
470 if (m_frameLoader) { |
|
471 ASSERT(!m_loader); |
|
472 |
|
473 DocumentLoader* documentLoader = m_frameLoader->activeDocumentLoader(); |
|
474 ASSERT(documentLoader); |
|
475 |
|
476 if (documentLoader->isLoadingMainResource()) |
|
477 documentLoader->cancelMainResourceLoad(error); |
|
478 return; |
|
479 } |
|
480 |
|
481 if (!m_loader->isDone()) |
|
482 m_loader->cancel(error); |
|
483 } |
|
484 |
|
485 void WebNetscapePluginStream::destroyStreamWithError(NSError *error) |
|
486 { |
|
487 destroyStreamWithReason(reasonForError(error)); |
|
488 } |
|
489 |
|
490 void WebNetscapePluginStream::didFail(WebCore::NetscapePlugInStreamLoader*, const WebCore::ResourceError& error) |
|
491 { |
|
492 destroyStreamWithError(error); |
|
493 } |
|
494 |
|
495 void WebNetscapePluginStream::cancelLoadAndDestroyStreamWithError(NSError *error) |
|
496 { |
|
497 RefPtr<WebNetscapePluginStream> protect(this); |
|
498 cancelLoadWithError(error); |
|
499 destroyStreamWithError(error); |
|
500 setPlugin(0); |
|
501 } |
|
502 |
|
503 void WebNetscapePluginStream::deliverData() |
|
504 { |
|
505 if (!m_stream.ndata || [m_deliveryData.get() length] == 0) |
|
506 return; |
|
507 |
|
508 RefPtr<WebNetscapePluginStream> protect(this); |
|
509 |
|
510 int32_t totalBytes = [m_deliveryData.get() length]; |
|
511 int32_t totalBytesDelivered = 0; |
|
512 |
|
513 while (totalBytesDelivered < totalBytes) { |
|
514 PluginStopDeferrer deferrer(m_pluginView.get()); |
|
515 int32_t deliveryBytes = m_pluginFuncs->writeready(m_plugin, &m_stream); |
|
516 LOG(Plugins, "NPP_WriteReady responseURL=%@ bytes=%d", m_responseURL.get(), deliveryBytes); |
|
517 |
|
518 if (m_isTerminated) |
|
519 return; |
|
520 |
|
521 if (deliveryBytes <= 0) { |
|
522 // Plug-in can't receive anymore data right now. Send it later. |
|
523 if (!m_deliverDataTimer.isActive()) |
|
524 m_deliverDataTimer.startOneShot(0); |
|
525 break; |
|
526 } else { |
|
527 deliveryBytes = min(deliveryBytes, totalBytes - totalBytesDelivered); |
|
528 NSData *subdata = [m_deliveryData.get() subdataWithRange:NSMakeRange(totalBytesDelivered, deliveryBytes)]; |
|
529 PluginStopDeferrer deferrer(m_pluginView.get()); |
|
530 deliveryBytes = m_pluginFuncs->write(m_plugin, &m_stream, m_offset, [subdata length], (void *)[subdata bytes]); |
|
531 if (deliveryBytes < 0) { |
|
532 // Netscape documentation says that a negative result from NPP_Write means cancel the load. |
|
533 cancelLoadAndDestroyStreamWithError(pluginCancelledConnectionError()); |
|
534 return; |
|
535 } |
|
536 deliveryBytes = min<int32_t>(deliveryBytes, [subdata length]); |
|
537 m_offset += deliveryBytes; |
|
538 totalBytesDelivered += deliveryBytes; |
|
539 LOG(Plugins, "NPP_Write responseURL=%@ bytes=%d total-delivered=%d/%d", m_responseURL.get(), deliveryBytes, m_offset, m_stream.end); |
|
540 } |
|
541 } |
|
542 |
|
543 if (totalBytesDelivered > 0) { |
|
544 if (totalBytesDelivered < totalBytes) { |
|
545 NSMutableData *newDeliveryData = [[NSMutableData alloc] initWithCapacity:totalBytes - totalBytesDelivered]; |
|
546 [newDeliveryData appendBytes:(char *)[m_deliveryData.get() bytes] + totalBytesDelivered length:totalBytes - totalBytesDelivered]; |
|
547 |
|
548 m_deliveryData.adoptNS(newDeliveryData); |
|
549 } else { |
|
550 [m_deliveryData.get() setLength:0]; |
|
551 if (m_reason != WEB_REASON_NONE) |
|
552 destroyStream(); |
|
553 } |
|
554 } |
|
555 } |
|
556 |
|
557 void WebNetscapePluginStream::deliverDataTimerFired(WebCore::Timer<WebNetscapePluginStream>* timer) |
|
558 { |
|
559 deliverData(); |
|
560 } |
|
561 |
|
562 void WebNetscapePluginStream::deliverDataToFile(NSData *data) |
|
563 { |
|
564 if (m_fileDescriptor == -1 && !m_path) { |
|
565 NSString *temporaryFileMask = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WebKitPlugInStreamXXXXXX"]; |
|
566 char *temporaryFileName = strdup([temporaryFileMask fileSystemRepresentation]); |
|
567 m_fileDescriptor = mkstemp(temporaryFileName); |
|
568 if (m_fileDescriptor == -1) { |
|
569 LOG_ERROR("Can't create a temporary file."); |
|
570 // This is not a network error, but the only error codes are "network error" and "user break". |
|
571 destroyStreamWithReason(NPRES_NETWORK_ERR); |
|
572 free(temporaryFileName); |
|
573 return; |
|
574 } |
|
575 |
|
576 m_path.adoptNS([[NSString stringWithUTF8String:temporaryFileName] retain]); |
|
577 free(temporaryFileName); |
|
578 } |
|
579 |
|
580 int dataLength = [data length]; |
|
581 if (!dataLength) |
|
582 return; |
|
583 |
|
584 int byteCount = write(m_fileDescriptor, [data bytes], dataLength); |
|
585 if (byteCount != dataLength) { |
|
586 // This happens only rarely, when we are out of disk space or have a disk I/O error. |
|
587 LOG_ERROR("error writing to temporary file, errno %d", errno); |
|
588 close(m_fileDescriptor); |
|
589 m_fileDescriptor = -1; |
|
590 |
|
591 // This is not a network error, but the only error codes are "network error" and "user break". |
|
592 destroyStreamWithReason(NPRES_NETWORK_ERR); |
|
593 m_path = 0; |
|
594 } |
|
595 } |
|
596 |
|
597 void WebNetscapePluginStream::didFinishLoading(NetscapePlugInStreamLoader*) |
|
598 { |
|
599 if (!m_stream.ndata) |
|
600 return; |
|
601 |
|
602 if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY) { |
|
603 // Fake the delivery of an empty data to ensure that the file has been created |
|
604 deliverDataToFile([NSData data]); |
|
605 if (m_fileDescriptor != -1) |
|
606 close(m_fileDescriptor); |
|
607 m_fileDescriptor = -1; |
|
608 } |
|
609 |
|
610 destroyStreamWithReason(NPRES_DONE); |
|
611 } |
|
612 |
|
613 void WebNetscapePluginStream::didReceiveData(NetscapePlugInStreamLoader*, const char* bytes, int length) |
|
614 { |
|
615 NSData *data = [[NSData alloc] initWithBytesNoCopy:(void*)bytes length:length freeWhenDone:NO]; |
|
616 |
|
617 ASSERT([data length] > 0); |
|
618 |
|
619 if (m_transferMode != NP_ASFILEONLY) { |
|
620 if (!m_deliveryData) |
|
621 m_deliveryData.adoptNS([[NSMutableData alloc] initWithCapacity:[data length]]); |
|
622 [m_deliveryData.get() appendData:data]; |
|
623 deliverData(); |
|
624 } |
|
625 if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY) |
|
626 deliverDataToFile(data); |
|
627 |
|
628 [data release]; |
|
629 } |
|
630 |
|
631 static NSString *CarbonPathFromPOSIXPath(NSString *posixPath) |
|
632 { |
|
633 // Doesn't add a trailing colon for directories; this is a problem for paths to a volume, |
|
634 // so this function would need to be revised if we ever wanted to call it with that. |
|
635 |
|
636 CFURLRef url = (CFURLRef)[NSURL fileURLWithPath:posixPath]; |
|
637 if (!url) |
|
638 return nil; |
|
639 |
|
640 return WebCFAutorelease(CFURLCopyFileSystemPath(url, kCFURLHFSPathStyle)); |
|
641 } |
|
642 |
|
643 #endif |