| /* |
| * Copyright (C) 2005, 2006, 2007 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. |
| * 3. Neither the name of Apple Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. |
| */ |
| |
| #if ENABLE(NETSCAPE_PLUGIN_API) |
| #import "WebNetscapePluginStream.h" |
| |
| #import "WebFrameInternal.h" |
| #import "WebKitErrorsPrivate.h" |
| #import "WebKitLogging.h" |
| #import "WebNSObjectExtras.h" |
| #import "WebNSURLExtras.h" |
| #import "WebNSURLRequestExtras.h" |
| #import "WebNetscapePluginPackage.h" |
| #import "WebNetscapePluginView.h" |
| #import "WebResourceLoadScheduler.h" |
| #import <Foundation/NSURLResponse.h> |
| #import <JavaScriptCore/JSLock.h> |
| #import <WebCore/CommonVM.h> |
| #import <WebCore/Document.h> |
| #import <WebCore/DocumentLoader.h> |
| #import <WebCore/Frame.h> |
| #import <WebCore/FrameLoader.h> |
| #import <WebCore/JSDOMWindowBase.h> |
| #import <WebCore/LoaderStrategy.h> |
| #import <WebCore/PlatformStrategies.h> |
| #import <WebCore/SecurityOrigin.h> |
| #import <WebCore/SecurityPolicy.h> |
| #import <WebCore/WebCoreURLResponse.h> |
| #import <pal/spi/cf/CFNetworkSPI.h> |
| #import <wtf/CompletionHandler.h> |
| #import <wtf/HashMap.h> |
| #import <wtf/NeverDestroyed.h> |
| #import <wtf/StdLibExtras.h> |
| |
| using namespace WebCore; |
| |
| #define WEB_REASON_NONE -1 |
| |
| class PluginStopDeferrer { |
| public: |
| PluginStopDeferrer(WebNetscapePluginView* pluginView) |
| : m_pluginView(pluginView) |
| { |
| ASSERT(m_pluginView); |
| |
| [m_pluginView.get() willCallPlugInFunction]; |
| } |
| |
| ~PluginStopDeferrer() |
| { |
| ASSERT(m_pluginView); |
| [m_pluginView.get() didCallPlugInFunction]; |
| } |
| |
| private: |
| RetainPtr<WebNetscapePluginView> m_pluginView; |
| }; |
| |
| typedef HashMap<NPStream*, NPP> StreamMap; |
| static StreamMap& streams() |
| { |
| static NeverDestroyed<StreamMap> staticStreams; |
| return staticStreams; |
| } |
| |
| NPP WebNetscapePluginStream::ownerForStream(NPStream *stream) |
| { |
| return streams().get(stream); |
| } |
| |
| NPReason WebNetscapePluginStream::reasonForError(NSError *error) |
| { |
| if (!error) |
| return NPRES_DONE; |
| |
| if ([[error domain] isEqualToString:NSURLErrorDomain] && [error code] == NSURLErrorCancelled) |
| return NPRES_USER_BREAK; |
| |
| return NPRES_NETWORK_ERR; |
| } |
| |
| NSError *WebNetscapePluginStream::pluginCancelledConnectionError() const |
| { |
| return adoptNS([[NSError alloc] _initWithPluginErrorCode:WebKitErrorPlugInCancelledConnection |
| contentURL:m_responseURL ? m_responseURL.get() : (NSURL *)m_requestURL |
| pluginPageURL:nil |
| pluginName:[[m_pluginView.get() pluginPackage] pluginInfo].name |
| MIMEType:(NSString *)String::fromUTF8(m_mimeType.data(), m_mimeType.length())]).autorelease(); |
| } |
| |
| NSError *WebNetscapePluginStream::errorForReason(NPReason reason) const |
| { |
| if (reason == NPRES_DONE) |
| return nil; |
| |
| if (reason == NPRES_USER_BREAK) |
| return [NSError _webKitErrorWithDomain:NSURLErrorDomain |
| code:NSURLErrorCancelled |
| URL:m_responseURL ? m_responseURL.get() : (NSURL *)m_requestURL]; |
| |
| return pluginCancelledConnectionError(); |
| } |
| |
| WebNetscapePluginStream::WebNetscapePluginStream(FrameLoader* frameLoader) |
| : m_plugin(0) |
| , m_transferMode(0) |
| , m_offset(0) |
| , m_fileDescriptor(-1) |
| , m_sendNotification(false) |
| , m_notifyData(0) |
| , m_headers(0) |
| , m_reason(NPRES_BASE) |
| , m_isTerminated(false) |
| , m_newStreamSuccessful(false) |
| , m_frameLoader(frameLoader) |
| , m_pluginFuncs(0) |
| , m_deliverDataTimer(*this, &WebNetscapePluginStream::deliverData) |
| { |
| memset(&m_stream, 0, sizeof(NPStream)); |
| } |
| |
| WebNetscapePluginStream::WebNetscapePluginStream(NSURLRequest *request, NPP plugin, bool sendNotification, void* notifyData) |
| : m_requestURL([request URL]) |
| , m_plugin(0) |
| , m_transferMode(0) |
| , m_offset(0) |
| , m_fileDescriptor(-1) |
| , m_sendNotification(sendNotification) |
| , m_notifyData(notifyData) |
| , m_headers(0) |
| , m_reason(NPRES_BASE) |
| , m_isTerminated(false) |
| , m_newStreamSuccessful(false) |
| , m_frameLoader(0) |
| , m_request(adoptNS([request mutableCopy])) |
| , m_pluginFuncs(0) |
| , m_deliverDataTimer(*this, &WebNetscapePluginStream::deliverData) |
| { |
| memset(&m_stream, 0, sizeof(NPStream)); |
| |
| WebNetscapePluginView *view = (WebNetscapePluginView *)plugin->ndata; |
| |
| // This check has already been done by the plug-in view. |
| ASSERT(core([view webFrame])->document()->securityOrigin().canDisplay([request URL])); |
| |
| ASSERT([request URL]); |
| ASSERT(plugin); |
| |
| setPlugin(plugin); |
| |
| streams().add(&m_stream, plugin); |
| |
| String referrer = SecurityPolicy::generateReferrerHeader(core([view webFrame])->document()->referrerPolicy(), [request URL], core([view webFrame])->loader().outgoingReferrer()); |
| if (referrer.isEmpty()) |
| [m_request.get() _web_setHTTPReferrer:nil]; |
| else |
| [m_request.get() _web_setHTTPReferrer:referrer]; |
| } |
| |
| WebNetscapePluginStream::~WebNetscapePluginStream() |
| { |
| ASSERT(!m_plugin); |
| ASSERT(m_isTerminated); |
| ASSERT(!m_stream.ndata); |
| |
| // The stream file should have been deleted, and the path freed, in -_destroyStream |
| ASSERT(!m_path); |
| ASSERT(m_fileDescriptor == -1); |
| |
| free((void *)m_stream.url); |
| free(m_headers); |
| |
| streams().remove(&m_stream); |
| } |
| |
| void WebNetscapePluginStream::setPlugin(NPP plugin) |
| { |
| if (plugin) { |
| m_plugin = plugin; |
| m_pluginView = static_cast<WebNetscapePluginView *>(m_plugin->ndata); |
| |
| WebNetscapePluginPackage *pluginPackage = [m_pluginView.get() pluginPackage]; |
| |
| m_pluginFuncs = [pluginPackage pluginFuncs]; |
| } else { |
| WebNetscapePluginView *view = m_pluginView.get(); |
| m_plugin = 0; |
| m_pluginFuncs = 0; |
| |
| [view disconnectStream:this]; |
| m_pluginView = 0; |
| } |
| } |
| |
| void WebNetscapePluginStream::startStream(NSURL *url, long long expectedContentLength, NSDate *lastModifiedDate, const String& mimeType, NSData *headers) |
| { |
| ASSERT(!m_isTerminated); |
| |
| m_responseURL = url; |
| m_mimeType = mimeType.utf8(); |
| |
| free((void *)m_stream.url); |
| m_stream.url = strdup([m_responseURL.get() _web_URLCString]); |
| |
| m_stream.ndata = this; |
| m_stream.end = expectedContentLength > 0 ? (uint32_t)expectedContentLength : 0; |
| m_stream.lastmodified = (uint32_t)[lastModifiedDate timeIntervalSince1970]; |
| m_stream.notifyData = m_notifyData; |
| |
| if (headers) { |
| unsigned len = [headers length]; |
| m_headers = (char*) malloc(len + 1); |
| [headers getBytes:m_headers length:len]; |
| m_headers[len] = 0; |
| m_stream.headers = m_headers; |
| } |
| |
| m_transferMode = NP_NORMAL; |
| m_offset = 0; |
| m_reason = WEB_REASON_NONE; |
| // FIXME: If WebNetscapePluginStream called our initializer we wouldn't have to do this here. |
| m_fileDescriptor = -1; |
| |
| // FIXME: Need a way to check if stream is seekable |
| |
| NPError npErr; |
| { |
| PluginStopDeferrer deferrer(m_pluginView.get()); |
| npErr = m_pluginFuncs->newstream(m_plugin, m_mimeType.mutableData(), &m_stream, NO, &m_transferMode); |
| } |
| |
| LOG(Plugins, "NPP_NewStream URL=%@ MIME=%s error=%d", m_responseURL.get(), m_mimeType.data(), npErr); |
| |
| if (npErr != NPERR_NO_ERROR) { |
| LOG_ERROR("NPP_NewStream failed with error: %d responseURL: %@", npErr, m_responseURL.get()); |
| // Calling cancelLoadWithError: cancels the load, but doesn't call NPP_DestroyStream. |
| cancelLoadWithError(pluginCancelledConnectionError()); |
| return; |
| } |
| |
| m_newStreamSuccessful = true; |
| |
| switch (m_transferMode) { |
| case NP_NORMAL: |
| LOG(Plugins, "Stream type: NP_NORMAL"); |
| break; |
| case NP_ASFILEONLY: |
| LOG(Plugins, "Stream type: NP_ASFILEONLY"); |
| break; |
| case NP_ASFILE: |
| LOG(Plugins, "Stream type: NP_ASFILE"); |
| break; |
| case NP_SEEK: |
| LOG_ERROR("Stream type: NP_SEEK not yet supported"); |
| cancelLoadAndDestroyStreamWithError(pluginCancelledConnectionError()); |
| break; |
| default: |
| LOG_ERROR("unknown stream type"); |
| } |
| } |
| |
| void WebNetscapePluginStream::start() |
| { |
| ASSERT(m_request); |
| ASSERT(!m_frameLoader); |
| ASSERT(!m_loader); |
| |
| webResourceLoadScheduler().schedulePluginStreamLoad(*core([m_pluginView.get() webFrame]), *this, m_request.get(), [this, protectedThis = makeRef(*this)] (RefPtr<WebCore::NetscapePlugInStreamLoader>&& loader) { |
| m_loader = WTFMove(loader); |
| }); |
| } |
| |
| void WebNetscapePluginStream::stop() |
| { |
| ASSERT(!m_frameLoader); |
| |
| if (!m_loader->isDone()) |
| cancelLoadAndDestroyStreamWithError(m_loader->cancelledError()); |
| } |
| |
| void WebNetscapePluginStream::willSendRequest(NetscapePlugInStreamLoader*, ResourceRequest&& request, const ResourceResponse&, CompletionHandler<void(WebCore::ResourceRequest&&)>&& callback) |
| { |
| // FIXME: We should notify the plug-in with NPP_URLRedirectNotify here. |
| callback(WTFMove(request)); |
| } |
| |
| void WebNetscapePluginStream::didReceiveResponse(NetscapePlugInStreamLoader*, const ResourceResponse& response) |
| { |
| NSURLResponse *r = response.nsURLResponse(); |
| |
| NSMutableData *theHeaders = nil; |
| long long expectedContentLength = [r expectedContentLength]; |
| |
| if ([r isKindOfClass:[NSHTTPURLResponse class]]) { |
| NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)r; |
| theHeaders = [NSMutableData dataWithCapacity:1024]; |
| |
| // FIXME: it would be nice to be able to get the raw HTTP header block. |
| // This includes the HTTP version, the real status text, |
| // all headers in their original order and including duplicates, |
| // and all original bytes verbatim, rather than sent through Unicode translation. |
| // Unfortunately NSHTTPURLResponse doesn't provide access at that low a level. |
| |
| [theHeaders appendBytes:"HTTP " length:5]; |
| char statusStr[10]; |
| long statusCode = [httpResponse statusCode]; |
| snprintf(statusStr, sizeof(statusStr), "%ld", statusCode); |
| [theHeaders appendBytes:statusStr length:strlen(statusStr)]; |
| [theHeaders appendBytes:" OK\n" length:4]; |
| |
| // HACK: pass the headers through as UTF-8. |
| // This is not the intended behavior; we're supposed to pass original bytes verbatim. |
| // But we don't have the original bytes, we have NSStrings built by the URL loading system. |
| // It hopefully shouldn't matter, since RFC2616/RFC822 require ASCII-only headers, |
| // but surely someone out there is using non-ASCII characters, and hopefully UTF-8 is adequate here. |
| // It seems better than NSASCIIStringEncoding, which will lose information if non-ASCII is used. |
| |
| NSDictionary *headerDict = [httpResponse allHeaderFields]; |
| NSArray *keys = [[headerDict allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; |
| NSEnumerator *i = [keys objectEnumerator]; |
| NSString *k; |
| while ((k = [i nextObject]) != nil) { |
| NSString *v = [headerDict objectForKey:k]; |
| [theHeaders appendData:[k dataUsingEncoding:NSUTF8StringEncoding]]; |
| [theHeaders appendBytes:": " length:2]; |
| [theHeaders appendData:[v dataUsingEncoding:NSUTF8StringEncoding]]; |
| [theHeaders appendBytes:"\n" length:1]; |
| } |
| |
| // If the content is encoded (most likely compressed), then don't send its length to the plugin, |
| // which is only interested in the decoded length, not yet known at the moment. |
| // <rdar://problem/4470599> tracks a request for -[NSURLResponse expectedContentLength] to incorporate this logic. |
| NSString *contentEncoding = (NSString *)[[(NSHTTPURLResponse *)r allHeaderFields] objectForKey:@"Content-Encoding"]; |
| if (contentEncoding && ![contentEncoding isEqualToString:@"identity"]) |
| expectedContentLength = -1; |
| |
| // startStreamResponseURL:... will null-terminate. |
| } |
| |
| startStream([r URL], expectedContentLength, [r _lastModifiedDate], response.mimeType(), theHeaders); |
| } |
| |
| void WebNetscapePluginStream::startStreamWithResponse(NSURLResponse *response) |
| { |
| didReceiveResponse(0, response); |
| } |
| |
| bool WebNetscapePluginStream::wantsAllStreams() const |
| { |
| if (!m_pluginFuncs->getvalue) |
| return false; |
| |
| void *value = 0; |
| NPError error; |
| { |
| PluginStopDeferrer deferrer(m_pluginView.get()); |
| JSC::JSLock::DropAllLocks dropAllLocks(commonVM()); |
| error = m_pluginFuncs->getvalue(m_plugin, NPPVpluginWantsAllNetworkStreams, &value); |
| } |
| if (error != NPERR_NO_ERROR) |
| return false; |
| |
| return value; |
| } |
| |
| void WebNetscapePluginStream::destroyStream() |
| { |
| if (m_isTerminated) |
| return; |
| |
| Ref<WebNetscapePluginStream> protect(*this); |
| |
| ASSERT(m_reason != WEB_REASON_NONE); |
| ASSERT([m_deliveryData.get() length] == 0); |
| |
| m_deliverDataTimer.stop(); |
| |
| if (m_stream.ndata) { |
| if (m_reason == NPRES_DONE && (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY)) { |
| ASSERT(m_fileDescriptor == -1); |
| ASSERT(m_path); |
| |
| PluginStopDeferrer deferrer(m_pluginView.get()); |
| m_pluginFuncs->asfile(m_plugin, &m_stream, [m_path.get() fileSystemRepresentation]); |
| LOG(Plugins, "NPP_StreamAsFile responseURL=%@ path=%s", m_responseURL.get(), m_path.get()); |
| } |
| |
| if (m_path) { |
| // Delete the file after calling NPP_StreamAsFile(), instead of in -dealloc/-finalize. It should be OK |
| // to delete the file here -- NPP_StreamAsFile() is always called immediately before NPP_DestroyStream() |
| // (the stream destruction function), so there can be no expectation that a plugin will read the stream |
| // file asynchronously after NPP_StreamAsFile() is called. |
| unlink([m_path.get() fileSystemRepresentation]); |
| m_path = 0; |
| |
| if (m_isTerminated) |
| return; |
| } |
| |
| if (m_fileDescriptor != -1) { |
| // The file may still be open if we are destroying the stream before it completed loading. |
| close(m_fileDescriptor); |
| m_fileDescriptor = -1; |
| } |
| |
| if (m_newStreamSuccessful) { |
| PluginStopDeferrer deferrer(m_pluginView.get()); |
| #if !LOG_DISABLED |
| NPError npErr = |
| #endif |
| m_pluginFuncs->destroystream(m_plugin, &m_stream, m_reason); |
| LOG(Plugins, "NPP_DestroyStream responseURL=%@ error=%d", m_responseURL.get(), npErr); |
| } |
| |
| free(m_headers); |
| m_headers = NULL; |
| m_stream.headers = NULL; |
| |
| m_stream.ndata = 0; |
| |
| if (m_isTerminated) |
| return; |
| } |
| |
| if (m_sendNotification) { |
| // NPP_URLNotify expects the request URL, not the response URL. |
| PluginStopDeferrer deferrer(m_pluginView.get()); |
| m_pluginFuncs->urlnotify(m_plugin, m_requestURL.string().utf8().data(), m_reason, m_notifyData); |
| LOG(Plugins, "NPP_URLNotify requestURL=%@ reason=%d", (NSURL *)m_requestURL, m_reason); |
| } |
| |
| m_isTerminated = true; |
| |
| setPlugin(0); |
| } |
| |
| void WebNetscapePluginStream::destroyStreamWithReason(NPReason reason) |
| { |
| m_reason = reason; |
| if (m_reason != NPRES_DONE) { |
| // Stop any pending data from being streamed. |
| [m_deliveryData.get() setLength:0]; |
| } else if ([m_deliveryData.get() length] > 0) { |
| // There is more data to be streamed, don't destroy the stream now. |
| return; |
| } |
| |
| Ref<WebNetscapePluginStream> protect(*this); |
| destroyStream(); |
| ASSERT(!m_stream.ndata); |
| } |
| |
| void WebNetscapePluginStream::cancelLoadWithError(NSError *error) |
| { |
| if (m_frameLoader) { |
| ASSERT(!m_loader); |
| auto documentLoader = m_frameLoader->activeDocumentLoader(); |
| if (documentLoader && documentLoader->isLoadingMainResource()) |
| documentLoader->cancelMainResourceLoad(error); |
| return; |
| } |
| if (!m_loader->isDone()) |
| m_loader->cancel(error); |
| } |
| |
| void WebNetscapePluginStream::destroyStreamWithError(NSError *error) |
| { |
| destroyStreamWithReason(reasonForError(error)); |
| } |
| |
| void WebNetscapePluginStream::didFail(WebCore::NetscapePlugInStreamLoader*, const WebCore::ResourceError& error) |
| { |
| destroyStreamWithError(error); |
| } |
| |
| void WebNetscapePluginStream::cancelLoadAndDestroyStreamWithError(NSError *error) |
| { |
| Ref<WebNetscapePluginStream> protect(*this); |
| cancelLoadWithError(error); |
| destroyStreamWithError(error); |
| setPlugin(0); |
| } |
| |
| void WebNetscapePluginStream::deliverData() |
| { |
| if (!m_stream.ndata || [m_deliveryData.get() length] == 0) |
| return; |
| |
| Ref<WebNetscapePluginStream> protect(*this); |
| |
| int32_t totalBytes = [m_deliveryData.get() length]; |
| int32_t totalBytesDelivered = 0; |
| |
| while (totalBytesDelivered < totalBytes) { |
| PluginStopDeferrer deferrer(m_pluginView.get()); |
| int32_t deliveryBytes = m_pluginFuncs->writeready(m_plugin, &m_stream); |
| LOG(Plugins, "NPP_WriteReady responseURL=%@ bytes=%d", m_responseURL.get(), deliveryBytes); |
| |
| if (m_isTerminated) |
| return; |
| |
| if (deliveryBytes <= 0) { |
| // Plug-in can't receive anymore data right now. Send it later. |
| if (!m_deliverDataTimer.isActive()) |
| m_deliverDataTimer.startOneShot(0_s); |
| break; |
| } else { |
| deliveryBytes = std::min(deliveryBytes, totalBytes - totalBytesDelivered); |
| NSData *subdata = [m_deliveryData.get() subdataWithRange:NSMakeRange(totalBytesDelivered, deliveryBytes)]; |
| PluginStopDeferrer deferrer(m_pluginView.get()); |
| deliveryBytes = m_pluginFuncs->write(m_plugin, &m_stream, m_offset, [subdata length], const_cast<void*>([subdata bytes])); |
| if (deliveryBytes < 0) { |
| // Netscape documentation says that a negative result from NPP_Write means cancel the load. |
| cancelLoadAndDestroyStreamWithError(pluginCancelledConnectionError()); |
| return; |
| } |
| deliveryBytes = std::min<int32_t>(deliveryBytes, [subdata length]); |
| m_offset += deliveryBytes; |
| totalBytesDelivered += deliveryBytes; |
| LOG(Plugins, "NPP_Write responseURL=%@ bytes=%d total-delivered=%d/%d", m_responseURL.get(), deliveryBytes, m_offset, m_stream.end); |
| } |
| } |
| |
| if (totalBytesDelivered > 0) { |
| if (totalBytesDelivered < totalBytes) { |
| auto newDeliveryData = adoptNS([[NSMutableData alloc] initWithCapacity:totalBytes - totalBytesDelivered]); |
| [newDeliveryData appendBytes:static_cast<char*>(const_cast<void*>([m_deliveryData.get() bytes])) + totalBytesDelivered length:totalBytes - totalBytesDelivered]; |
| |
| m_deliveryData = WTFMove(newDeliveryData); |
| } else { |
| [m_deliveryData.get() setLength:0]; |
| if (m_reason != WEB_REASON_NONE) |
| destroyStream(); |
| } |
| } |
| } |
| |
| void WebNetscapePluginStream::deliverDataToFile(NSData *data) |
| { |
| if (m_fileDescriptor == -1 && !m_path) { |
| NSString *temporaryFileMask = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WebKitPlugInStreamXXXXXX"]; |
| char *temporaryFileName = strdup([temporaryFileMask fileSystemRepresentation]); |
| m_fileDescriptor = mkstemp(temporaryFileName); |
| if (m_fileDescriptor == -1) { |
| LOG_ERROR("Can't create a temporary file."); |
| // This is not a network error, but the only error codes are "network error" and "user break". |
| destroyStreamWithReason(NPRES_NETWORK_ERR); |
| free(temporaryFileName); |
| return; |
| } |
| |
| m_path = [NSString stringWithUTF8String:temporaryFileName]; |
| free(temporaryFileName); |
| } |
| |
| int dataLength = [data length]; |
| if (!dataLength) |
| return; |
| |
| int byteCount = write(m_fileDescriptor, [data bytes], dataLength); |
| if (byteCount != dataLength) { |
| // This happens only rarely, when we are out of disk space or have a disk I/O error. |
| LOG_ERROR("error writing to temporary file, errno %d", errno); |
| close(m_fileDescriptor); |
| m_fileDescriptor = -1; |
| |
| // This is not a network error, but the only error codes are "network error" and "user break". |
| destroyStreamWithReason(NPRES_NETWORK_ERR); |
| m_path = 0; |
| } |
| } |
| |
| void WebNetscapePluginStream::didFinishLoading(NetscapePlugInStreamLoader*) |
| { |
| if (!m_stream.ndata) |
| return; |
| |
| if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY) { |
| // Fake the delivery of an empty data to ensure that the file has been created |
| deliverDataToFile([NSData data]); |
| if (m_fileDescriptor != -1) |
| close(m_fileDescriptor); |
| m_fileDescriptor = -1; |
| } |
| |
| destroyStreamWithReason(NPRES_DONE); |
| } |
| |
| void WebNetscapePluginStream::didReceiveData(NetscapePlugInStreamLoader*, const uint8_t* bytes, int length) |
| { |
| auto data = adoptNS([[NSData alloc] initWithBytesNoCopy:(void*)bytes length:length freeWhenDone:NO]); |
| |
| ASSERT([data length] > 0); |
| |
| if (m_transferMode != NP_ASFILEONLY) { |
| if (!m_deliveryData) |
| m_deliveryData = adoptNS([[NSMutableData alloc] initWithCapacity:[data length]]); |
| [m_deliveryData.get() appendData:data.get()]; |
| deliverData(); |
| } |
| if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY) |
| deliverDataToFile(data.get()); |
| } |
| |
| #endif |