2 WebBaseNetscapePluginStream.m
3 Copyright (c) 2002, Apple, Inc. All rights reserved.
6 #import <WebKit/WebBaseNetscapePluginStream.h>
8 #import <WebKit/WebBaseNetscapePluginView.h>
9 #import <WebKit/WebKitErrorsPrivate.h>
10 #import <WebKit/WebKitLogging.h>
11 #import <WebKit/WebNetscapePluginPackage.h>
12 #import <WebKit/WebNSObjectExtras.h>
13 #import <WebKit/WebNSURLExtras.h>
15 #import <Foundation/NSURLResponse.h>
16 #import <Foundation/NSURLResponsePrivate.h>
18 static const char *CarbonPathFromPOSIXPath(const char *posixPath);
20 #define WEB_REASON_NONE -1
22 @implementation WebBaseNetscapePluginStream
24 + (NPReason)reasonForError:(NSError *)error
29 if ([[error domain] isEqualToString:NSURLErrorDomain] && [error code] == NSURLErrorCancelled) {
30 return NPRES_USER_BREAK;
32 return NPRES_NETWORK_ERR;
35 - (NSError *)_pluginCancelledConnectionError
37 return [[[NSError alloc] _initWithPluginErrorCode:WebKitErrorPlugInCancelledConnection
38 contentURL:responseURL != nil ? responseURL : requestURL
40 pluginName:[plugin name]
41 MIMEType:MIMEType] autorelease];
44 - (NSError *)errorForReason:(NPReason)theReason
46 if (theReason == NPRES_DONE) {
49 if (theReason == NPRES_USER_BREAK) {
50 return [NSError _webKitErrorWithDomain:NSURLErrorDomain
51 code:NSURLErrorCancelled
52 URL:responseURL != nil ? responseURL : requestURL];
54 return [self _pluginCancelledConnectionError];
57 - (id)initWithRequestURL:(NSURL *)theRequestURL
58 pluginPointer:(NPP)thePluginPointer
59 notifyData:(void *)theNotifyData
60 sendNotification:(BOOL)flag
64 // Temporarily set isTerminated to YES to avoid assertion failure in dealloc in case we are released in this method.
67 if (theRequestURL == nil || thePluginPointer == NULL) {
72 [self setRequestURL:theRequestURL];
73 [self setPluginPointer:thePluginPointer];
74 notifyData = theNotifyData;
75 sendNotification = flag;
85 ASSERT(stream.ndata == nil);
87 // FIXME: It's generally considered bad style to do work, like deleting a file,
88 // at dealloc time. We should change things around so that this is done at a
89 // more well-defined time rather than when the last release happens.
95 [responseURL release];
98 [deliveryData release];
100 free((void *)stream.url);
108 ASSERT(isTerminated);
109 ASSERT(stream.ndata == nil);
111 // FIXME: Bad for all the reasons mentioned above, but even worse for GC.
116 free((void *)stream.url);
122 - (uint16)transferMode
127 - (void)setRequestURL:(NSURL *)theRequestURL
129 [theRequestURL retain];
130 [requestURL release];
131 requestURL = theRequestURL;
134 - (void)setResponseURL:(NSURL *)theResponseURL
136 [theResponseURL retain];
137 [responseURL release];
138 responseURL = theResponseURL;
141 - (void)setPluginPointer:(NPP)pluginPointer
143 instance = pluginPointer;
145 plugin = [[(WebBaseNetscapePluginView *)instance->ndata plugin] retain];
147 NPP_NewStream = [plugin NPP_NewStream];
148 NPP_WriteReady = [plugin NPP_WriteReady];
149 NPP_Write = [plugin NPP_Write];
150 NPP_StreamAsFile = [plugin NPP_StreamAsFile];
151 NPP_DestroyStream = [plugin NPP_DestroyStream];
152 NPP_URLNotify = [plugin NPP_URLNotify];
155 - (void)setMIMEType:(NSString *)theMIMEType
157 [theMIMEType retain];
159 MIMEType = theMIMEType;
162 - (void)startStreamResponseURL:(NSURL *)URL
163 expectedContentLength:(long long)expectedContentLength
164 lastModifiedDate:(NSDate *)lastModifiedDate
165 MIMEType:(NSString *)theMIMEType
167 ASSERT(!isTerminated);
169 if (![plugin isLoaded]) {
173 [self setResponseURL:URL];
174 [self setMIMEType:theMIMEType];
176 free((void *)stream.url);
177 stream.url = strdup([responseURL _web_URLCString]);
180 stream.end = expectedContentLength > 0 ? expectedContentLength : 0;
181 stream.lastmodified = [lastModifiedDate timeIntervalSince1970];
182 stream.notifyData = notifyData;
184 transferMode = NP_NORMAL;
186 reason = WEB_REASON_NONE;
188 // FIXME: Need a way to check if stream is seekable
190 NPError npErr = NPP_NewStream(instance, (char *)[MIMEType UTF8String], &stream, NO, &transferMode);
191 LOG(Plugins, "NPP_NewStream URL=%@ MIME=%@ error=%d", responseURL, MIMEType, npErr);
193 if (npErr != NPERR_NO_ERROR) {
194 ERROR("NPP_NewStream failed with error: %d responseURL: %@", npErr, responseURL);
195 // Calling cancelLoadWithError: cancels the load, but doesn't call NPP_DestroyStream.
196 [self cancelLoadWithError:[self _pluginCancelledConnectionError]];
200 switch (transferMode) {
202 LOG(Plugins, "Stream type: NP_NORMAL");
205 LOG(Plugins, "Stream type: NP_ASFILEONLY");
208 LOG(Plugins, "Stream type: NP_ASFILE");
211 ERROR("Stream type: NP_SEEK not yet supported");
212 [self cancelLoadAndDestroyStreamWithError:[self _pluginCancelledConnectionError]];
215 ERROR("unknown stream type");
219 - (void)startStreamWithResponse:(NSURLResponse *)r
221 [self startStreamResponseURL:[r URL]
222 expectedContentLength:[r expectedContentLength]
223 lastModifiedDate:[r _lastModifiedDate]
224 MIMEType:[r MIMEType]];
227 - (void)_destroyStream
229 if (isTerminated || ![plugin isLoaded]) {
233 ASSERT(reason != WEB_REASON_NONE);
234 ASSERT([deliveryData length] == 0);
236 if (stream.ndata != NULL) {
237 if (reason == NPRES_DONE && (transferMode == NP_ASFILE || transferMode == NP_ASFILEONLY)) {
238 ASSERT(path != NULL);
239 const char *carbonPath = CarbonPathFromPOSIXPath(path);
240 ASSERT(carbonPath != NULL);
241 NPP_StreamAsFile(instance, &stream, carbonPath);
242 LOG(Plugins, "NPP_StreamAsFile responseURL=%@ path=%s", responseURL, carbonPath);
246 npErr = NPP_DestroyStream(instance, &stream, reason);
247 LOG(Plugins, "NPP_DestroyStream responseURL=%@ error=%d", responseURL, npErr);
252 if (sendNotification) {
253 // NPP_URLNotify expects the request URL, not the response URL.
254 NPP_URLNotify(instance, [requestURL _web_URLCString], reason, notifyData);
255 LOG(Plugins, "NPP_URLNotify requestURL=%@ reason=%d", requestURL, reason);
261 - (void)_destroyStreamWithReason:(NPReason)theReason
264 if (reason != NPRES_DONE) {
265 // Stop any pending data from being streamed.
266 [deliveryData setLength:0];
267 } else if ([deliveryData length] > 0) {
268 // There is more data to be streamed, don't destroy the stream now.
271 [self _destroyStream];
272 ASSERT(stream.ndata == nil);
275 - (void)cancelLoadWithError:(NSError *)error
277 // Overridden by subclasses.
278 ASSERT_NOT_REACHED();
281 - (void)destroyStreamWithError:(NSError *)error
283 [self _destroyStreamWithReason:[[self class] reasonForError:error]];
286 - (void)cancelLoadAndDestroyStreamWithError:(NSError *)error
288 [self cancelLoadWithError:error];
289 [self destroyStreamWithError:error];
292 - (void)finishedLoadingWithData:(NSData *)data
294 if (![plugin isLoaded] || !stream.ndata) {
298 if ((transferMode == NP_ASFILE || transferMode == NP_ASFILEONLY) && !path) {
299 path = strdup("/tmp/WebKitPlugInStreamXXXXXX");
300 int fd = mkstemp(path);
302 // This should almost never happen.
303 ERROR("can't make temporary file, almost certainly a problem with /tmp");
304 // This is not a network error, but the only error codes are "network error" and "user break".
305 [self _destroyStreamWithReason:NPRES_NETWORK_ERR];
310 int dataLength = [data length];
311 if (dataLength > 0) {
312 int byteCount = write(fd, [data bytes], dataLength);
313 if (byteCount != dataLength) {
314 // This happens only rarely, when we are out of disk space or have a disk I/O error.
315 ERROR("error writing to temporary file, errno %d", errno);
317 // This is not a network error, but the only error codes are "network error" and "user break".
318 [self _destroyStreamWithReason:NPRES_NETWORK_ERR];
327 [self _destroyStreamWithReason:NPRES_DONE];
332 if (![plugin isLoaded] || !stream.ndata || [deliveryData length] == 0) {
336 int32 totalBytes = [deliveryData length];
337 int32 totalBytesDelivered = 0;
339 while (totalBytesDelivered < totalBytes) {
340 int32 deliveryBytes = NPP_WriteReady(instance, &stream);
341 LOG(Plugins, "NPP_WriteReady responseURL=%@ bytes=%d", responseURL, deliveryBytes);
343 if (deliveryBytes <= 0) {
344 // Plug-in can't receive anymore data right now. Send it later.
345 [self performSelector:@selector(_deliverData) withObject:nil afterDelay:0];
348 deliveryBytes = MIN(deliveryBytes, totalBytes - totalBytesDelivered);
349 NSData *subdata = [deliveryData subdataWithRange:NSMakeRange(totalBytesDelivered, deliveryBytes)];
350 deliveryBytes = NPP_Write(instance, &stream, offset, [subdata length], (void *)[subdata bytes]);
351 if (deliveryBytes < 0) {
352 // Netscape documentation says that a negative result from NPP_Write means cancel the load.
353 [self cancelLoadAndDestroyStreamWithError:[self _pluginCancelledConnectionError]];
356 deliveryBytes = MIN((unsigned)deliveryBytes, [subdata length]);
357 offset += deliveryBytes;
358 totalBytesDelivered += deliveryBytes;
359 LOG(Plugins, "NPP_Write responseURL=%@ bytes=%d total-delivered=%d/%d", responseURL, deliveryBytes, offset, stream.end);
363 if (totalBytesDelivered > 0) {
364 if (totalBytesDelivered < totalBytes) {
365 NSMutableData *newDeliveryData = [[NSMutableData alloc] initWithCapacity:totalBytes - totalBytesDelivered];
366 [newDeliveryData appendBytes:(char *)[deliveryData bytes] + totalBytesDelivered length:totalBytes - totalBytesDelivered];
367 [deliveryData release];
368 deliveryData = newDeliveryData;
370 [deliveryData setLength:0];
371 if (reason != WEB_REASON_NONE) {
372 [self _destroyStream];
378 - (void)receivedData:(NSData *)data
380 ASSERT([data length] > 0);
382 if (transferMode != NP_ASFILEONLY) {
384 deliveryData = [[NSMutableData alloc] initWithCapacity:[data length]];
386 [deliveryData appendData:data];
393 static const char *CarbonPathFromPOSIXPath(const char *posixPath)
395 // Returns NULL if path is to file that does not exist.
396 // Doesn't add a trailing colon for directories; this is a problem for paths to a volume,
397 // so this function would need to be revised if we ever wanted to call it with that.
404 error = FSPathMakeRef((const UInt8 *)posixPath, &ref, NULL);
405 if (error != noErr) {
409 // Get volume refNum.
410 error = FSGetCatalogInfo(&ref, kFSCatInfoVolume, &info, NULL, NULL, NULL);
411 if (error != noErr) {
415 // Get root directory FSRef.
417 error = FSGetVolumeInfo(info.volume, 0, NULL, kFSVolInfoNone, NULL, NULL, &rootRef);
418 if (error != noErr) {
422 // Get the pieces of the path.
423 NSMutableData *carbonPath = [NSMutableData dataWithBytes:"" length:1];
428 error = FSGetCatalogInfo(&ref, kFSCatInfoNone, NULL, NULL, &spec, &parentRef);
429 if (error != noErr) {
433 [carbonPath replaceBytesInRange:NSMakeRange(0, 0) withBytes:":" length:1];
435 [carbonPath replaceBytesInRange:NSMakeRange(0, 0) withBytes:&spec.name[1] length:spec.name[0]];
437 if (FSCompareFSRefs(&ref, &rootRef) == noErr) {
443 return (const char *)[carbonPath bytes];