--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/webengine/osswebengine/WebKit/Plugins/WebBaseNetscapePluginStream.mm Mon Mar 30 12:54:55 2009 +0300
@@ -0,0 +1,591 @@
+/*
+ * 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 Computer, 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.
+ */
+
+#ifndef __LP64__
+#import "WebBaseNetscapePluginStream.h"
+
+#import "WebBaseNetscapePluginView.h"
+#import "WebKitErrorsPrivate.h"
+#import "WebKitLogging.h"
+#import "WebNSObjectExtras.h"
+#import "WebNSURLExtras.h"
+#import "WebNetscapePluginPackage.h"
+#import <Foundation/NSURLResponse.h>
+#import <WebCore/WebCoreObjCExtras.h>
+#import <WebKitSystemInterface.h>
+#import <wtf/HashMap.h>
+
+#define WEB_REASON_NONE -1
+
+static char *CarbonPathFromPOSIXPath(const char *posixPath);
+
+typedef HashMap<NPStream*, NPP> StreamMap;
+static StreamMap& streams()
+{
+ static StreamMap staticStreams;
+ return staticStreams;
+}
+
+@implementation WebBaseNetscapePluginStream
+
+#ifndef BUILDING_ON_TIGER
++ (void)initialize
+{
+ WebCoreObjCFinalizeOnMainThread(self);
+}
+#endif
+
++ (NPP)ownerForStream:(NPStream *)stream
+{
+ return streams().get(stream);
+}
+
++ (NPReason)reasonForError:(NSError *)error
+{
+ if (error == nil) {
+ return NPRES_DONE;
+ }
+ if ([[error domain] isEqualToString:NSURLErrorDomain] && [error code] == NSURLErrorCancelled) {
+ return NPRES_USER_BREAK;
+ }
+ return NPRES_NETWORK_ERR;
+}
+
+- (NSError *)_pluginCancelledConnectionError
+{
+ return [[[NSError alloc] _initWithPluginErrorCode:WebKitErrorPlugInCancelledConnection
+ contentURL:responseURL != nil ? responseURL : requestURL
+ pluginPageURL:nil
+ pluginName:[[pluginView pluginPackage] name]
+ MIMEType:MIMEType] autorelease];
+}
+
+- (NSError *)errorForReason:(NPReason)theReason
+{
+ if (theReason == NPRES_DONE) {
+ return nil;
+ }
+ if (theReason == NPRES_USER_BREAK) {
+ return [NSError _webKitErrorWithDomain:NSURLErrorDomain
+ code:NSURLErrorCancelled
+ URL:responseURL != nil ? responseURL : requestURL];
+ }
+ return [self _pluginCancelledConnectionError];
+}
+
+- (id)initWithRequestURL:(NSURL *)theRequestURL
+ plugin:(NPP)thePlugin
+ notifyData:(void *)theNotifyData
+ sendNotification:(BOOL)flag
+{
+ [super init];
+
+ // Temporarily set isTerminated to YES to avoid assertion failure in dealloc in case we are released in this method.
+ isTerminated = YES;
+
+ if (theRequestURL == nil || thePlugin == NULL) {
+ [self release];
+ return nil;
+ }
+
+ [self setRequestURL:theRequestURL];
+ [self setPlugin:thePlugin];
+ notifyData = theNotifyData;
+ sendNotification = flag;
+
+ streams().add(&stream, thePlugin);
+
+ isTerminated = NO;
+
+ return self;
+}
+
+- (void)dealloc
+{
+ ASSERT(!plugin);
+ ASSERT(isTerminated);
+ ASSERT(stream.ndata == nil);
+
+ // The stream file should have been deleted, and the path freed, in -_destroyStream
+ ASSERT(!path);
+
+ [requestURL release];
+ [responseURL release];
+ [MIMEType release];
+ [pluginView release];
+ [deliveryData release];
+
+ free((void *)stream.url);
+ free(path);
+ free(headers);
+
+ streams().remove(&stream);
+
+ [super dealloc];
+}
+
+- (void)finalize
+{
+ ASSERT_MAIN_THREAD();
+ ASSERT(isTerminated);
+ ASSERT(stream.ndata == nil);
+
+ // The stream file should have been deleted, and the path freed, in -_destroyStream
+ ASSERT(!path);
+
+ free((void *)stream.url);
+ free(path);
+ free(headers);
+
+ streams().remove(&stream);
+
+ [super finalize];
+}
+
+- (uint16)transferMode
+{
+ return transferMode;
+}
+
+- (NPP)plugin
+{
+ return plugin;
+}
+
+- (void)setRequestURL:(NSURL *)theRequestURL
+{
+ [theRequestURL retain];
+ [requestURL release];
+ requestURL = theRequestURL;
+}
+
+- (void)setResponseURL:(NSURL *)theResponseURL
+{
+ [theResponseURL retain];
+ [responseURL release];
+ responseURL = theResponseURL;
+}
+
+- (void)setPlugin:(NPP)thePlugin
+{
+ if (thePlugin) {
+ plugin = thePlugin;
+ pluginView = [(WebBaseNetscapePluginView *)plugin->ndata retain];
+ WebNetscapePluginPackage *pluginPackage = [pluginView pluginPackage];
+ NPP_NewStream = [pluginPackage NPP_NewStream];
+ NPP_WriteReady = [pluginPackage NPP_WriteReady];
+ NPP_Write = [pluginPackage NPP_Write];
+ NPP_StreamAsFile = [pluginPackage NPP_StreamAsFile];
+ NPP_DestroyStream = [pluginPackage NPP_DestroyStream];
+ NPP_URLNotify = [pluginPackage NPP_URLNotify];
+ } else {
+ WebBaseNetscapePluginView *view = pluginView;
+
+ plugin = NULL;
+ NPP_NewStream = NULL;
+ NPP_WriteReady = NULL;
+ NPP_Write = NULL;
+ NPP_StreamAsFile = NULL;
+ NPP_DestroyStream = NULL;
+ NPP_URLNotify = NULL;
+ pluginView = nil;
+
+ [view disconnectStream:self];
+ [view release];
+ }
+}
+
+- (void)setMIMEType:(NSString *)theMIMEType
+{
+ [theMIMEType retain];
+ [MIMEType release];
+ MIMEType = theMIMEType;
+}
+
+- (void)startStreamResponseURL:(NSURL *)URL
+ expectedContentLength:(long long)expectedContentLength
+ lastModifiedDate:(NSDate *)lastModifiedDate
+ MIMEType:(NSString *)theMIMEType
+ headers:(NSData *)theHeaders
+{
+ ASSERT(!isTerminated);
+
+ [self setResponseURL:URL];
+ [self setMIMEType:theMIMEType];
+
+ free((void *)stream.url);
+ stream.url = strdup([responseURL _web_URLCString]);
+
+ stream.ndata = self;
+ stream.end = expectedContentLength > 0 ? (uint32)expectedContentLength : 0;
+ stream.lastmodified = (uint32)[lastModifiedDate timeIntervalSince1970];
+ stream.notifyData = notifyData;
+
+ if (theHeaders) {
+ unsigned len = [theHeaders length];
+ headers = (char*) malloc(len + 1);
+ [theHeaders getBytes:headers];
+ headers[len] = 0;
+ stream.headers = headers;
+ }
+
+ transferMode = NP_NORMAL;
+ offset = 0;
+ reason = WEB_REASON_NONE;
+
+ // FIXME: Need a way to check if stream is seekable
+
+ WebBaseNetscapePluginView *pv = pluginView;
+ [pv willCallPlugInFunction];
+ NPError npErr = NPP_NewStream(plugin, (char *)[MIMEType UTF8String], &stream, NO, &transferMode);
+ [pv didCallPlugInFunction];
+ LOG(Plugins, "NPP_NewStream URL=%@ MIME=%@ error=%d", responseURL, MIMEType, npErr);
+
+ if (npErr != NPERR_NO_ERROR) {
+ LOG_ERROR("NPP_NewStream failed with error: %d responseURL: %@", npErr, responseURL);
+ // Calling cancelLoadWithError: cancels the load, but doesn't call NPP_DestroyStream.
+ [self cancelLoadWithError:[self _pluginCancelledConnectionError]];
+ return;
+ }
+
+ switch (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");
+ [self cancelLoadAndDestroyStreamWithError:[self _pluginCancelledConnectionError]];
+ break;
+ default:
+ LOG_ERROR("unknown stream type");
+ }
+}
+
+- (void)startStreamWithResponse:(NSURLResponse *)r
+{
+ 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.
+ }
+
+ [self startStreamResponseURL:[r URL]
+ expectedContentLength:expectedContentLength
+ lastModifiedDate:WKGetNSURLResponseLastModifiedDate(r)
+ MIMEType:[r MIMEType]
+ headers:theHeaders];
+}
+
+- (void)_destroyStream
+{
+ if (isTerminated)
+ return;
+
+ [self retain];
+
+ ASSERT(reason != WEB_REASON_NONE);
+ ASSERT([deliveryData length] == 0);
+
+ [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_deliverData) object:nil];
+
+ if (stream.ndata != nil) {
+ if (reason == NPRES_DONE && (transferMode == NP_ASFILE || transferMode == NP_ASFILEONLY)) {
+ ASSERT(path != NULL);
+ char *carbonPath = CarbonPathFromPOSIXPath(path);
+ ASSERT(carbonPath != NULL);
+ WebBaseNetscapePluginView *pv = pluginView;
+ [pv willCallPlugInFunction];
+ NPP_StreamAsFile(plugin, &stream, carbonPath);
+ [pv didCallPlugInFunction];
+
+ // 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(path);
+ free(path);
+ path = NULL;
+ LOG(Plugins, "NPP_StreamAsFile responseURL=%@ path=%s", responseURL, carbonPath);
+ free(carbonPath);
+
+ if (isTerminated)
+ goto exit;
+ }
+
+ NPError npErr;
+ WebBaseNetscapePluginView *pv = pluginView;
+ [pv willCallPlugInFunction];
+ npErr = NPP_DestroyStream(plugin, &stream, reason);
+ [pv didCallPlugInFunction];
+ LOG(Plugins, "NPP_DestroyStream responseURL=%@ error=%d", responseURL, npErr);
+
+ free(headers);
+ headers = NULL;
+ stream.headers = NULL;
+
+ stream.ndata = nil;
+
+ if (isTerminated)
+ goto exit;
+ }
+
+ if (sendNotification) {
+ // NPP_URLNotify expects the request URL, not the response URL.
+ WebBaseNetscapePluginView *pv = pluginView;
+ [pv willCallPlugInFunction];
+ NPP_URLNotify(plugin, [requestURL _web_URLCString], reason, notifyData);
+ [pv didCallPlugInFunction];
+ LOG(Plugins, "NPP_URLNotify requestURL=%@ reason=%d", requestURL, reason);
+ }
+
+ isTerminated = YES;
+
+ [self setPlugin:NULL];
+
+exit:
+ [self release];
+}
+
+- (void)_destroyStreamWithReason:(NPReason)theReason
+{
+ reason = theReason;
+ if (reason != NPRES_DONE) {
+ // Stop any pending data from being streamed.
+ [deliveryData setLength:0];
+ } else if ([deliveryData length] > 0) {
+ // There is more data to be streamed, don't destroy the stream now.
+ return;
+ }
+ [self _destroyStream];
+ ASSERT(stream.ndata == nil);
+}
+
+- (void)cancelLoadWithError:(NSError *)error
+{
+ // Overridden by subclasses.
+ ASSERT_NOT_REACHED();
+}
+
+- (void)destroyStreamWithError:(NSError *)error
+{
+ [self _destroyStreamWithReason:[[self class] reasonForError:error]];
+}
+
+- (void)cancelLoadAndDestroyStreamWithError:(NSError *)error
+{
+ [self retain];
+ [self cancelLoadWithError:error];
+ [self destroyStreamWithError:error];
+ [self setPlugin:NULL];
+ [self release];
+}
+
+- (void)finishedLoadingWithData:(NSData *)data
+{
+ if (!stream.ndata)
+ return;
+
+ if ((transferMode == NP_ASFILE || transferMode == NP_ASFILEONLY) && !path) {
+ path = strdup("/tmp/WebKitPlugInStreamXXXXXX");
+ int fd = mkstemp(path);
+ if (fd == -1) {
+ // This should almost never happen.
+ LOG_ERROR("can't make temporary file, almost certainly a problem with /tmp");
+ // This is not a network error, but the only error codes are "network error" and "user break".
+ [self _destroyStreamWithReason:NPRES_NETWORK_ERR];
+ free(path);
+ path = NULL;
+ return;
+ }
+ int dataLength = [data length];
+ if (dataLength > 0) {
+ int byteCount = write(fd, [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(fd);
+ // This is not a network error, but the only error codes are "network error" and "user break".
+ [self _destroyStreamWithReason:NPRES_NETWORK_ERR];
+ free(path);
+ path = NULL;
+ return;
+ }
+ }
+ close(fd);
+ }
+
+ [self _destroyStreamWithReason:NPRES_DONE];
+}
+
+- (void)_deliverData
+{
+ if (!stream.ndata || [deliveryData length] == 0)
+ return;
+
+ [self retain];
+
+ int32 totalBytes = [deliveryData length];
+ int32 totalBytesDelivered = 0;
+
+ while (totalBytesDelivered < totalBytes) {
+ WebBaseNetscapePluginView *pv = pluginView;
+ [pv willCallPlugInFunction];
+ int32 deliveryBytes = NPP_WriteReady(plugin, &stream);
+ [pv didCallPlugInFunction];
+ LOG(Plugins, "NPP_WriteReady responseURL=%@ bytes=%d", responseURL, deliveryBytes);
+
+ if (isTerminated)
+ goto exit;
+
+ if (deliveryBytes <= 0) {
+ // Plug-in can't receive anymore data right now. Send it later.
+ [self performSelector:@selector(_deliverData) withObject:nil afterDelay:0];
+ break;
+ } else {
+ deliveryBytes = MIN(deliveryBytes, totalBytes - totalBytesDelivered);
+ NSData *subdata = [deliveryData subdataWithRange:NSMakeRange(totalBytesDelivered, deliveryBytes)];
+ pv = pluginView;
+ [pv willCallPlugInFunction];
+ deliveryBytes = NPP_Write(plugin, &stream, offset, [subdata length], (void *)[subdata bytes]);
+ [pv didCallPlugInFunction];
+ if (deliveryBytes < 0) {
+ // Netscape documentation says that a negative result from NPP_Write means cancel the load.
+ [self cancelLoadAndDestroyStreamWithError:[self _pluginCancelledConnectionError]];
+ return;
+ }
+ deliveryBytes = MIN((unsigned)deliveryBytes, [subdata length]);
+ offset += deliveryBytes;
+ totalBytesDelivered += deliveryBytes;
+ LOG(Plugins, "NPP_Write responseURL=%@ bytes=%d total-delivered=%d/%d", responseURL, deliveryBytes, offset, stream.end);
+ }
+ }
+
+ if (totalBytesDelivered > 0) {
+ if (totalBytesDelivered < totalBytes) {
+ NSMutableData *newDeliveryData = [[NSMutableData alloc] initWithCapacity:totalBytes - totalBytesDelivered];
+ [newDeliveryData appendBytes:(char *)[deliveryData bytes] + totalBytesDelivered length:totalBytes - totalBytesDelivered];
+ [deliveryData release];
+ deliveryData = newDeliveryData;
+ } else {
+ [deliveryData setLength:0];
+ if (reason != WEB_REASON_NONE) {
+ [self _destroyStream];
+ }
+ }
+ }
+
+exit:
+ [self release];
+}
+
+- (void)receivedData:(NSData *)data
+{
+ ASSERT([data length] > 0);
+
+ if (transferMode != NP_ASFILEONLY) {
+ if (!deliveryData) {
+ deliveryData = [[NSMutableData alloc] initWithCapacity:[data length]];
+ }
+ [deliveryData appendData:data];
+ [self _deliverData];
+ }
+}
+
+@end
+
+static char *CarbonPathFromPOSIXPath(const char *posixPath)
+{
+ // Doesn't add a trailing colon for directories; this is a problem for paths to a volume,
+ // so this function would need to be revised if we ever wanted to call it with that.
+
+ CFURLRef url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *)posixPath, strlen(posixPath), false);
+ if (url) {
+ CFStringRef hfsPath = CFURLCopyFileSystemPath(url, kCFURLHFSPathStyle);
+ CFRelease(url);
+ if (hfsPath) {
+ CFIndex bufSize = CFStringGetMaximumSizeOfFileSystemRepresentation(hfsPath);
+ char* filename = static_cast<char*>(malloc(bufSize));
+ CFStringGetFileSystemRepresentation(hfsPath, filename, bufSize);
+ CFRelease(hfsPath);
+ return filename;
+ }
+ }
+
+ return NULL;
+}
+
+#endif