webengine/osswebengine/WebKit/Plugins/WebBaseNetscapePluginStream.mm
changeset 0 dd21522fd290
equal deleted inserted replaced
-1:000000000000 0:dd21522fd290
       
     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 #ifndef __LP64__
       
    30 #import "WebBaseNetscapePluginStream.h"
       
    31 
       
    32 #import "WebBaseNetscapePluginView.h"
       
    33 #import "WebKitErrorsPrivate.h"
       
    34 #import "WebKitLogging.h"
       
    35 #import "WebNSObjectExtras.h"
       
    36 #import "WebNSURLExtras.h"
       
    37 #import "WebNetscapePluginPackage.h"
       
    38 #import <Foundation/NSURLResponse.h>
       
    39 #import <WebCore/WebCoreObjCExtras.h>
       
    40 #import <WebKitSystemInterface.h>
       
    41 #import <wtf/HashMap.h>
       
    42 
       
    43 #define WEB_REASON_NONE -1
       
    44 
       
    45 static char *CarbonPathFromPOSIXPath(const char *posixPath);
       
    46 
       
    47 typedef HashMap<NPStream*, NPP> StreamMap;
       
    48 static StreamMap& streams()
       
    49 {
       
    50     static StreamMap staticStreams;
       
    51     return staticStreams;
       
    52 }
       
    53 
       
    54 @implementation WebBaseNetscapePluginStream
       
    55 
       
    56 #ifndef BUILDING_ON_TIGER
       
    57 + (void)initialize
       
    58 {
       
    59     WebCoreObjCFinalizeOnMainThread(self);
       
    60 }
       
    61 #endif
       
    62 
       
    63 + (NPP)ownerForStream:(NPStream *)stream
       
    64 {
       
    65     return streams().get(stream);
       
    66 }
       
    67 
       
    68 + (NPReason)reasonForError:(NSError *)error
       
    69 {
       
    70     if (error == nil) {
       
    71         return NPRES_DONE;
       
    72     }
       
    73     if ([[error domain] isEqualToString:NSURLErrorDomain] && [error code] == NSURLErrorCancelled) {
       
    74         return NPRES_USER_BREAK;
       
    75     }
       
    76     return NPRES_NETWORK_ERR;
       
    77 }
       
    78 
       
    79 - (NSError *)_pluginCancelledConnectionError
       
    80 {
       
    81     return [[[NSError alloc] _initWithPluginErrorCode:WebKitErrorPlugInCancelledConnection
       
    82                                            contentURL:responseURL != nil ? responseURL : requestURL
       
    83                                         pluginPageURL:nil
       
    84                                            pluginName:[[pluginView pluginPackage] name]
       
    85                                              MIMEType:MIMEType] autorelease];
       
    86 }
       
    87 
       
    88 - (NSError *)errorForReason:(NPReason)theReason
       
    89 {
       
    90     if (theReason == NPRES_DONE) {
       
    91         return nil;
       
    92     }
       
    93     if (theReason == NPRES_USER_BREAK) {
       
    94         return [NSError _webKitErrorWithDomain:NSURLErrorDomain
       
    95                                           code:NSURLErrorCancelled 
       
    96                                            URL:responseURL != nil ? responseURL : requestURL];
       
    97     }
       
    98     return [self _pluginCancelledConnectionError];
       
    99 }
       
   100 
       
   101 - (id)initWithRequestURL:(NSURL *)theRequestURL
       
   102                   plugin:(NPP)thePlugin
       
   103               notifyData:(void *)theNotifyData
       
   104         sendNotification:(BOOL)flag
       
   105 {
       
   106     [super init];
       
   107  
       
   108     // Temporarily set isTerminated to YES to avoid assertion failure in dealloc in case we are released in this method.
       
   109     isTerminated = YES;
       
   110 
       
   111     if (theRequestURL == nil || thePlugin == NULL) {
       
   112         [self release];
       
   113         return nil;
       
   114     }
       
   115     
       
   116     [self setRequestURL:theRequestURL];
       
   117     [self setPlugin:thePlugin];
       
   118     notifyData = theNotifyData;
       
   119     sendNotification = flag;
       
   120 
       
   121     streams().add(&stream, thePlugin);
       
   122     
       
   123     isTerminated = NO;
       
   124     
       
   125     return self;
       
   126 }
       
   127 
       
   128 - (void)dealloc
       
   129 {
       
   130     ASSERT(!plugin);
       
   131     ASSERT(isTerminated);
       
   132     ASSERT(stream.ndata == nil);
       
   133 
       
   134     // The stream file should have been deleted, and the path freed, in -_destroyStream
       
   135     ASSERT(!path);
       
   136 
       
   137     [requestURL release];
       
   138     [responseURL release];
       
   139     [MIMEType release];
       
   140     [pluginView release];
       
   141     [deliveryData release];
       
   142     
       
   143     free((void *)stream.url);
       
   144     free(path);
       
   145     free(headers);
       
   146 
       
   147     streams().remove(&stream);
       
   148 
       
   149     [super dealloc];
       
   150 }
       
   151 
       
   152 - (void)finalize
       
   153 {
       
   154     ASSERT_MAIN_THREAD();
       
   155     ASSERT(isTerminated);
       
   156     ASSERT(stream.ndata == nil);
       
   157 
       
   158     // The stream file should have been deleted, and the path freed, in -_destroyStream
       
   159     ASSERT(!path);
       
   160 
       
   161     free((void *)stream.url);
       
   162     free(path);
       
   163     free(headers);
       
   164 
       
   165     streams().remove(&stream);
       
   166 
       
   167     [super finalize];
       
   168 }
       
   169 
       
   170 - (uint16)transferMode
       
   171 {
       
   172     return transferMode;
       
   173 }
       
   174 
       
   175 - (NPP)plugin
       
   176 {
       
   177     return plugin;
       
   178 }
       
   179 
       
   180 - (void)setRequestURL:(NSURL *)theRequestURL
       
   181 {
       
   182     [theRequestURL retain];
       
   183     [requestURL release];
       
   184     requestURL = theRequestURL;
       
   185 }
       
   186 
       
   187 - (void)setResponseURL:(NSURL *)theResponseURL
       
   188 {
       
   189     [theResponseURL retain];
       
   190     [responseURL release];
       
   191     responseURL = theResponseURL;
       
   192 }
       
   193 
       
   194 - (void)setPlugin:(NPP)thePlugin
       
   195 {
       
   196     if (thePlugin) {
       
   197         plugin = thePlugin;
       
   198         pluginView = [(WebBaseNetscapePluginView *)plugin->ndata retain];
       
   199         WebNetscapePluginPackage *pluginPackage = [pluginView pluginPackage];
       
   200         NPP_NewStream = [pluginPackage NPP_NewStream];
       
   201         NPP_WriteReady = [pluginPackage NPP_WriteReady];
       
   202         NPP_Write = [pluginPackage NPP_Write];
       
   203         NPP_StreamAsFile = [pluginPackage NPP_StreamAsFile];
       
   204         NPP_DestroyStream = [pluginPackage NPP_DestroyStream];
       
   205         NPP_URLNotify = [pluginPackage NPP_URLNotify];
       
   206     } else {
       
   207         WebBaseNetscapePluginView *view = pluginView;
       
   208 
       
   209         plugin = NULL;
       
   210         NPP_NewStream = NULL;
       
   211         NPP_WriteReady = NULL;
       
   212         NPP_Write = NULL;
       
   213         NPP_StreamAsFile = NULL;
       
   214         NPP_DestroyStream = NULL;
       
   215         NPP_URLNotify = NULL;
       
   216         pluginView = nil;
       
   217 
       
   218         [view disconnectStream:self];
       
   219         [view release];
       
   220     }
       
   221 }
       
   222 
       
   223 - (void)setMIMEType:(NSString *)theMIMEType
       
   224 {
       
   225     [theMIMEType retain];
       
   226     [MIMEType release];
       
   227     MIMEType = theMIMEType;
       
   228 }
       
   229 
       
   230 - (void)startStreamResponseURL:(NSURL *)URL
       
   231          expectedContentLength:(long long)expectedContentLength
       
   232               lastModifiedDate:(NSDate *)lastModifiedDate
       
   233                       MIMEType:(NSString *)theMIMEType
       
   234                        headers:(NSData *)theHeaders
       
   235 {
       
   236     ASSERT(!isTerminated);
       
   237     
       
   238     [self setResponseURL:URL];
       
   239     [self setMIMEType:theMIMEType];
       
   240     
       
   241     free((void *)stream.url);
       
   242     stream.url = strdup([responseURL _web_URLCString]);
       
   243 
       
   244     stream.ndata = self;
       
   245     stream.end = expectedContentLength > 0 ? (uint32)expectedContentLength : 0;
       
   246     stream.lastmodified = (uint32)[lastModifiedDate timeIntervalSince1970];
       
   247     stream.notifyData = notifyData;
       
   248 
       
   249     if (theHeaders) {
       
   250         unsigned len = [theHeaders length];
       
   251         headers = (char*) malloc(len + 1);
       
   252         [theHeaders getBytes:headers];
       
   253         headers[len] = 0;
       
   254         stream.headers = headers;
       
   255     }
       
   256     
       
   257     transferMode = NP_NORMAL;
       
   258     offset = 0;
       
   259     reason = WEB_REASON_NONE;
       
   260 
       
   261     // FIXME: Need a way to check if stream is seekable
       
   262 
       
   263     WebBaseNetscapePluginView *pv = pluginView;
       
   264     [pv willCallPlugInFunction];
       
   265     NPError npErr = NPP_NewStream(plugin, (char *)[MIMEType UTF8String], &stream, NO, &transferMode);
       
   266     [pv didCallPlugInFunction];
       
   267     LOG(Plugins, "NPP_NewStream URL=%@ MIME=%@ error=%d", responseURL, MIMEType, npErr);
       
   268 
       
   269     if (npErr != NPERR_NO_ERROR) {
       
   270         LOG_ERROR("NPP_NewStream failed with error: %d responseURL: %@", npErr, responseURL);
       
   271         // Calling cancelLoadWithError: cancels the load, but doesn't call NPP_DestroyStream.
       
   272         [self cancelLoadWithError:[self _pluginCancelledConnectionError]];
       
   273         return;
       
   274     }
       
   275 
       
   276     switch (transferMode) {
       
   277         case NP_NORMAL:
       
   278             LOG(Plugins, "Stream type: NP_NORMAL");
       
   279             break;
       
   280         case NP_ASFILEONLY:
       
   281             LOG(Plugins, "Stream type: NP_ASFILEONLY");
       
   282             break;
       
   283         case NP_ASFILE:
       
   284             LOG(Plugins, "Stream type: NP_ASFILE");
       
   285             break;
       
   286         case NP_SEEK:
       
   287             LOG_ERROR("Stream type: NP_SEEK not yet supported");
       
   288             [self cancelLoadAndDestroyStreamWithError:[self _pluginCancelledConnectionError]];
       
   289             break;
       
   290         default:
       
   291             LOG_ERROR("unknown stream type");
       
   292     }
       
   293 }
       
   294 
       
   295 - (void)startStreamWithResponse:(NSURLResponse *)r
       
   296 {
       
   297     NSMutableData *theHeaders = nil;
       
   298     long long expectedContentLength = [r expectedContentLength];
       
   299 
       
   300     if ([r isKindOfClass:[NSHTTPURLResponse class]]) {
       
   301         NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)r;
       
   302         theHeaders = [NSMutableData dataWithCapacity:1024];
       
   303         
       
   304         // FIXME: it would be nice to be able to get the raw HTTP header block.
       
   305         // This includes the HTTP version, the real status text,
       
   306         // all headers in their original order and including duplicates,
       
   307         // and all original bytes verbatim, rather than sent through Unicode translation.
       
   308         // Unfortunately NSHTTPURLResponse doesn't provide access at that low a level.
       
   309         
       
   310         [theHeaders appendBytes:"HTTP " length:5];
       
   311         char statusStr[10];
       
   312         long statusCode = [httpResponse statusCode];
       
   313         snprintf(statusStr, sizeof(statusStr), "%ld", statusCode);
       
   314         [theHeaders appendBytes:statusStr length:strlen(statusStr)];
       
   315         [theHeaders appendBytes:" OK\n" length:4];
       
   316 
       
   317         // HACK: pass the headers through as UTF-8.
       
   318         // This is not the intended behavior; we're supposed to pass original bytes verbatim.
       
   319         // But we don't have the original bytes, we have NSStrings built by the URL loading system.
       
   320         // It hopefully shouldn't matter, since RFC2616/RFC822 require ASCII-only headers,
       
   321         // but surely someone out there is using non-ASCII characters, and hopefully UTF-8 is adequate here.
       
   322         // It seems better than NSASCIIStringEncoding, which will lose information if non-ASCII is used.
       
   323 
       
   324         NSDictionary *headerDict = [httpResponse allHeaderFields];
       
   325         NSArray *keys = [[headerDict allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
       
   326         NSEnumerator *i = [keys objectEnumerator];
       
   327         NSString *k;
       
   328         while ((k = [i nextObject]) != nil) {
       
   329             NSString *v = [headerDict objectForKey:k];
       
   330             [theHeaders appendData:[k dataUsingEncoding:NSUTF8StringEncoding]];
       
   331             [theHeaders appendBytes:": " length:2];
       
   332             [theHeaders appendData:[v dataUsingEncoding:NSUTF8StringEncoding]];
       
   333             [theHeaders appendBytes:"\n" length:1];
       
   334         }
       
   335 
       
   336         // If the content is encoded (most likely compressed), then don't send its length to the plugin,
       
   337         // which is only interested in the decoded length, not yet known at the moment.
       
   338         // <rdar://problem/4470599> tracks a request for -[NSURLResponse expectedContentLength] to incorporate this logic.
       
   339         NSString *contentEncoding = (NSString *)[[(NSHTTPURLResponse *)r allHeaderFields] objectForKey:@"Content-Encoding"];
       
   340         if (contentEncoding && ![contentEncoding isEqualToString:@"identity"])
       
   341             expectedContentLength = -1;
       
   342 
       
   343         // startStreamResponseURL:... will null-terminate.
       
   344     }
       
   345 
       
   346     [self startStreamResponseURL:[r URL]
       
   347            expectedContentLength:expectedContentLength
       
   348                 lastModifiedDate:WKGetNSURLResponseLastModifiedDate(r)
       
   349                         MIMEType:[r MIMEType]
       
   350                          headers:theHeaders];
       
   351 }
       
   352 
       
   353 - (void)_destroyStream
       
   354 {
       
   355     if (isTerminated)
       
   356         return;
       
   357 
       
   358     [self retain];
       
   359 
       
   360     ASSERT(reason != WEB_REASON_NONE);
       
   361     ASSERT([deliveryData length] == 0);
       
   362     
       
   363     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_deliverData) object:nil];
       
   364 
       
   365     if (stream.ndata != nil) {
       
   366         if (reason == NPRES_DONE && (transferMode == NP_ASFILE || transferMode == NP_ASFILEONLY)) {
       
   367             ASSERT(path != NULL);
       
   368             char *carbonPath = CarbonPathFromPOSIXPath(path);
       
   369             ASSERT(carbonPath != NULL);
       
   370             WebBaseNetscapePluginView *pv = pluginView;
       
   371             [pv willCallPlugInFunction];
       
   372             NPP_StreamAsFile(plugin, &stream, carbonPath);
       
   373             [pv didCallPlugInFunction];
       
   374 
       
   375             // Delete the file after calling NPP_StreamAsFile(), instead of in -dealloc/-finalize.  It should be OK
       
   376             // to delete the file here -- NPP_StreamAsFile() is always called immediately before NPP_DestroyStream()
       
   377             // (the stream destruction function), so there can be no expectation that a plugin will read the stream
       
   378             // file asynchronously after NPP_StreamAsFile() is called.
       
   379             unlink(path);
       
   380             free(path);
       
   381             path = NULL;
       
   382             LOG(Plugins, "NPP_StreamAsFile responseURL=%@ path=%s", responseURL, carbonPath);
       
   383             free(carbonPath);
       
   384 
       
   385             if (isTerminated)
       
   386                 goto exit;
       
   387         }
       
   388 
       
   389         NPError npErr;
       
   390         WebBaseNetscapePluginView *pv = pluginView;
       
   391         [pv willCallPlugInFunction];
       
   392         npErr = NPP_DestroyStream(plugin, &stream, reason);
       
   393         [pv didCallPlugInFunction];
       
   394         LOG(Plugins, "NPP_DestroyStream responseURL=%@ error=%d", responseURL, npErr);
       
   395 
       
   396         free(headers);
       
   397         headers = NULL;
       
   398         stream.headers = NULL;
       
   399 
       
   400         stream.ndata = nil;
       
   401 
       
   402         if (isTerminated)
       
   403             goto exit;
       
   404     }
       
   405 
       
   406     if (sendNotification) {
       
   407         // NPP_URLNotify expects the request URL, not the response URL.
       
   408         WebBaseNetscapePluginView *pv = pluginView;
       
   409         [pv willCallPlugInFunction];
       
   410         NPP_URLNotify(plugin, [requestURL _web_URLCString], reason, notifyData);
       
   411         [pv didCallPlugInFunction];
       
   412         LOG(Plugins, "NPP_URLNotify requestURL=%@ reason=%d", requestURL, reason);
       
   413     }
       
   414 
       
   415     isTerminated = YES;
       
   416 
       
   417     [self setPlugin:NULL];
       
   418 
       
   419 exit:
       
   420     [self release];
       
   421 }
       
   422 
       
   423 - (void)_destroyStreamWithReason:(NPReason)theReason
       
   424 {
       
   425     reason = theReason;
       
   426     if (reason != NPRES_DONE) {
       
   427         // Stop any pending data from being streamed.
       
   428         [deliveryData setLength:0];
       
   429     } else if ([deliveryData length] > 0) {
       
   430         // There is more data to be streamed, don't destroy the stream now.
       
   431         return;
       
   432     }
       
   433     [self _destroyStream];
       
   434     ASSERT(stream.ndata == nil);
       
   435 }
       
   436 
       
   437 - (void)cancelLoadWithError:(NSError *)error
       
   438 {
       
   439     // Overridden by subclasses.
       
   440     ASSERT_NOT_REACHED();
       
   441 }
       
   442 
       
   443 - (void)destroyStreamWithError:(NSError *)error
       
   444 {
       
   445     [self _destroyStreamWithReason:[[self class] reasonForError:error]];
       
   446 }
       
   447 
       
   448 - (void)cancelLoadAndDestroyStreamWithError:(NSError *)error
       
   449 {
       
   450     [self retain];
       
   451     [self cancelLoadWithError:error];
       
   452     [self destroyStreamWithError:error];
       
   453     [self setPlugin:NULL];
       
   454     [self release];
       
   455 }
       
   456 
       
   457 - (void)finishedLoadingWithData:(NSData *)data
       
   458 {
       
   459     if (!stream.ndata)
       
   460         return;
       
   461     
       
   462     if ((transferMode == NP_ASFILE || transferMode == NP_ASFILEONLY) && !path) {
       
   463         path = strdup("/tmp/WebKitPlugInStreamXXXXXX");
       
   464         int fd = mkstemp(path);
       
   465         if (fd == -1) {
       
   466             // This should almost never happen.
       
   467             LOG_ERROR("can't make temporary file, almost certainly a problem with /tmp");
       
   468             // This is not a network error, but the only error codes are "network error" and "user break".
       
   469             [self _destroyStreamWithReason:NPRES_NETWORK_ERR];
       
   470             free(path);
       
   471             path = NULL;
       
   472             return;
       
   473         }
       
   474         int dataLength = [data length];
       
   475         if (dataLength > 0) {
       
   476             int byteCount = write(fd, [data bytes], dataLength);
       
   477             if (byteCount != dataLength) {
       
   478                 // This happens only rarely, when we are out of disk space or have a disk I/O error.
       
   479                 LOG_ERROR("error writing to temporary file, errno %d", errno);
       
   480                 close(fd);
       
   481                 // This is not a network error, but the only error codes are "network error" and "user break".
       
   482                 [self _destroyStreamWithReason:NPRES_NETWORK_ERR];
       
   483                 free(path);
       
   484                 path = NULL;
       
   485                 return;
       
   486             }
       
   487         }
       
   488         close(fd);
       
   489     }
       
   490 
       
   491     [self _destroyStreamWithReason:NPRES_DONE];
       
   492 }
       
   493 
       
   494 - (void)_deliverData
       
   495 {
       
   496     if (!stream.ndata || [deliveryData length] == 0)
       
   497         return;
       
   498 
       
   499     [self retain];
       
   500 
       
   501     int32 totalBytes = [deliveryData length];
       
   502     int32 totalBytesDelivered = 0;
       
   503 
       
   504     while (totalBytesDelivered < totalBytes) {
       
   505         WebBaseNetscapePluginView *pv = pluginView;
       
   506         [pv willCallPlugInFunction];
       
   507         int32 deliveryBytes = NPP_WriteReady(plugin, &stream);
       
   508         [pv didCallPlugInFunction];
       
   509         LOG(Plugins, "NPP_WriteReady responseURL=%@ bytes=%d", responseURL, deliveryBytes);
       
   510 
       
   511         if (isTerminated)
       
   512             goto exit;
       
   513 
       
   514         if (deliveryBytes <= 0) {
       
   515             // Plug-in can't receive anymore data right now. Send it later.
       
   516             [self performSelector:@selector(_deliverData) withObject:nil afterDelay:0];
       
   517             break;
       
   518         } else {
       
   519             deliveryBytes = MIN(deliveryBytes, totalBytes - totalBytesDelivered);
       
   520             NSData *subdata = [deliveryData subdataWithRange:NSMakeRange(totalBytesDelivered, deliveryBytes)];
       
   521             pv = pluginView;
       
   522             [pv willCallPlugInFunction];
       
   523             deliveryBytes = NPP_Write(plugin, &stream, offset, [subdata length], (void *)[subdata bytes]);
       
   524             [pv didCallPlugInFunction];
       
   525             if (deliveryBytes < 0) {
       
   526                 // Netscape documentation says that a negative result from NPP_Write means cancel the load.
       
   527                 [self cancelLoadAndDestroyStreamWithError:[self _pluginCancelledConnectionError]];
       
   528                 return;
       
   529             }
       
   530             deliveryBytes = MIN((unsigned)deliveryBytes, [subdata length]);
       
   531             offset += deliveryBytes;
       
   532             totalBytesDelivered += deliveryBytes;
       
   533             LOG(Plugins, "NPP_Write responseURL=%@ bytes=%d total-delivered=%d/%d", responseURL, deliveryBytes, offset, stream.end);
       
   534         }
       
   535     }
       
   536 
       
   537     if (totalBytesDelivered > 0) {
       
   538         if (totalBytesDelivered < totalBytes) {
       
   539             NSMutableData *newDeliveryData = [[NSMutableData alloc] initWithCapacity:totalBytes - totalBytesDelivered];
       
   540             [newDeliveryData appendBytes:(char *)[deliveryData bytes] + totalBytesDelivered length:totalBytes - totalBytesDelivered];
       
   541             [deliveryData release];
       
   542             deliveryData = newDeliveryData;
       
   543         } else {
       
   544             [deliveryData setLength:0];
       
   545             if (reason != WEB_REASON_NONE) {
       
   546                 [self _destroyStream];
       
   547             }
       
   548         }
       
   549     }
       
   550 
       
   551 exit:
       
   552     [self release];
       
   553 }
       
   554 
       
   555 - (void)receivedData:(NSData *)data
       
   556 {
       
   557     ASSERT([data length] > 0);
       
   558     
       
   559     if (transferMode != NP_ASFILEONLY) {
       
   560         if (!deliveryData) {
       
   561             deliveryData = [[NSMutableData alloc] initWithCapacity:[data length]];
       
   562         }
       
   563         [deliveryData appendData:data];
       
   564         [self _deliverData];
       
   565     }
       
   566 }
       
   567 
       
   568 @end
       
   569 
       
   570 static char *CarbonPathFromPOSIXPath(const char *posixPath)
       
   571 {
       
   572     // Doesn't add a trailing colon for directories; this is a problem for paths to a volume,
       
   573     // so this function would need to be revised if we ever wanted to call it with that.
       
   574 
       
   575     CFURLRef url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *)posixPath, strlen(posixPath), false);
       
   576     if (url) {
       
   577         CFStringRef hfsPath = CFURLCopyFileSystemPath(url, kCFURLHFSPathStyle);
       
   578         CFRelease(url);
       
   579         if (hfsPath) {
       
   580             CFIndex bufSize = CFStringGetMaximumSizeOfFileSystemRepresentation(hfsPath);
       
   581             char* filename = static_cast<char*>(malloc(bufSize));
       
   582             CFStringGetFileSystemRepresentation(hfsPath, filename, bufSize);
       
   583             CFRelease(hfsPath);
       
   584             return filename;
       
   585         }
       
   586     }
       
   587 
       
   588     return NULL;
       
   589 }
       
   590 
       
   591 #endif