/* ** Opens a local file usien whatever means are available on the current ** platform. If we have unix file descriptors then use that as we can use ** select on them. Otherwise use ANSI C file descriptors. ** ** returns HT_ERROR Error has occured or interrupted ** HT_OK if connected ** HT_WOULD_BLOCK if operation would have blocked */ PUBLIC int HTFileOpen (HTNet * net, char * local, HTLocalMode mode) { HTRequest * request = HTNet_request(net); HTHost * host = HTNet_host(net); #ifdef NO_UNIX_IO FILE * fp = NULL; #else SOCKET sockfd = INVSOC; #endif /* NO_UNIX_IO */ #ifndef NO_UNIX_IO int status = -1; /* JTD:5/30/96 - must init status to -1 */ if ((sockfd = open(local, mode)) == -1) { HTRequest_addSystemError(request, ERR_FATAL, errno, NO, "open"); return HT_ERROR; } /* If non-blocking protocol then change socket status ** I use fcntl() so that I can ask the status before I set it. ** See W. Richard Stevens (Advan. Prog. in UNIX env, p.364) ** Be CAREFULL with the old `O_NDELAY' - it wont work as read() ** returns 0 when blocking and NOT -1. FNDELAY is ONLY for BSD ** and does NOT work on SVR4 systems. O_NONBLOCK is POSIX. */ if (!HTNet_preemptive(net)) { #ifdef HAVE_FCNTL if ((status = fcntl(sockfd, F_GETFL, 0)) != -1) { #ifdef O_NONBLOCK status |= O_NONBLOCK;/* POSIX */ #else #ifdef F_NDELAY status |= F_NDELAY; /* BSD */ #endif /* F_NDELAY */ #endif /* O_NONBLOCK */ status = fcntl(sockfd, F_SETFL, status); } #endif /* HAVE_FCNTL */ HTTRACE(PROT_TRACE, "HTFileOpen.. `%s\' opened %d as %sblocking socket\n" _ local _ sockfd _ status == -1 ? "" : "NON-"); } /* #endif - HAVE_FCNTL <- wrong location, moved up JTD:5/30/96 */ #else /* !NO_UNIX_IO */ #ifdef VMS if ((fp = fopen(local, mode,"shr=put","shr=upd")) == NULL) { HTRequest_addSystemError(request, ERR_FATAL, errno, NO, "fopen"); return HT_ERROR; } #else if ((fp = fopen(local, mode)) == NULL) { HTRequest_addSystemError(request, ERR_FATAL, errno, NO, "fopen"); return HT_ERROR; } #endif /* VMS */ HTTRACE(PROT_TRACE, "HTDoOpen.... `%s\' opened using FILE %p\n" _ local _ fp); #endif /* !NO_UNIX_IO */ /* ** Associate the channel with the host and create ** an input and and output stream */ #ifdef NO_UNIX_IO HTHost_setChannel(host, HTChannel_new(INVSOC, fp, YES)); #else HTHost_setChannel(host, HTChannel_new(sockfd, NULL, YES)); #endif /* NO_UNIX_IO */ HTHost_getInput(host, HTNet_transport(net), NULL, 0); HTHost_getOutput(host, HTNet_transport(net), NULL, 0); return HT_OK; }
PRIVATE int FileEvent (SOCKET soc, void * pVoid, HTEventType type) { file_info *file = pVoid; /* Specific access information */ int status = HT_ERROR; HTNet * net = file->net; HTRequest * request = HTNet_request(net); HTParentAnchor * anchor = HTRequest_anchor(request); /* ** Initiate a new file structure and bind to request structure ** This is actually state FILE_BEGIN, but it can't be in the state ** machine as we need the structure first. */ if (type == HTEvent_CLOSE) { /* Interrupted */ HTRequest_addError(request, ERR_FATAL, NO, HTERR_INTERRUPTED, NULL, 0, "HTLoadFile"); FileCleanup(request, HT_INTERRUPTED); return HT_OK; } /* Now jump into the machine. We know the state from the previous run */ while (1) { switch (file->state) { case FS_BEGIN: /* We only support safe (GET, HEAD, etc) methods for the moment */ if (!HTMethod_isSafe(HTRequest_method(request))) { HTRequest_addError(request, ERR_FATAL, NO, HTERR_NOT_ALLOWED, NULL, 0, "HTLoadFile"); file->state = FS_ERROR; break; } /* Check whether we have access to local disk at all */ if (HTLib_secure()) { HTTRACE(PROT_TRACE, "LoadFile.... No access to local file system\n"); file->state = FS_TRY_FTP; break; } file->local = HTWWWToLocal(HTAnchor_physical(anchor), "", HTRequest_userProfile(request)); if (!file->local) { file->state = FS_TRY_FTP; break; } /* Create a new host object and link it to the net object */ { HTHost * host = NULL; if ((host = HTHost_new("localhost", 0)) == NULL) return HT_ERROR; HTNet_setHost(net, host); if (HTHost_addNet(host, net) == HT_PENDING) { HTTRACE(PROT_TRACE, "HTLoadFile.. Pending...\n"); /* move to the hack state */ file->state = FS_PENDING; return HT_OK; } } file->state = FS_DO_CN; break; case FS_PENDING: /* ** 2000/08/10 JK : This is a funny state. Because of the ** internal libwww stacks, when doing multiple local ** requests (e.g., while using the Robot), we need to ask ** again for the host object. If we had jumped directly to ** the FS_DO_CN state, libwww would have blocked because ** of socket starvation. ** This state is similar to FS_BEGINNING, but just requests ** the host object. ** YES. THIS IS AN UGLY HACK!! */ { HTHost * host = NULL; if ((host = HTHost_new("localhost", 0)) == NULL) return HT_ERROR; HTNet_setHost(net, host); if (HTHost_addNet(host, net) == HT_PENDING) { HTTRACE(PROT_TRACE, "HTLoadFile.. Pending...\n"); file->state = FS_PENDING; return HT_OK; } } file->state = FS_DO_CN; break; case FS_DO_CN: /* ** If we have to do content negotiation then find the object that ** fits best into either what the client has indicated in the ** accept headers or what the client has registered on its own. ** The object chosen can in fact be a directory! However, content ** negotiation only makes sense if we can read the directory! ** We stat the file in order to find the size and to see it if ** exists. */ if (HTRequest_negotiation(request) && HTMethod_isSafe(HTRequest_method(request))) { char * conneg = HTMulti(request, file->local,&file->stat_info); if (conneg) { HT_FREE(file->local); file->local = conneg; HTAnchor_setPhysical(anchor, conneg); HTTRACE(PROT_TRACE, "Load File... Found `%s\'\n" _ conneg); } else { HTTRACE(PROT_TRACE, "Load File... Not found - even tried content negotiation\n"); HTRequest_addError(request, ERR_FATAL, NO, HTERR_NOT_FOUND, NULL, 0, "HTLoadFile"); file->state = FS_ERROR; break; } } else { if (HT_STAT(file->local, &file->stat_info) == -1) { HTTRACE(PROT_TRACE, "Load File... Not found `%s\'\n" _ file->local); HTRequest_addError(request, ERR_FATAL, NO, HTERR_NOT_FOUND, NULL, 0, "HTLoadFile"); file->state = FS_ERROR; break; } } /* ** Check to see if the 'localname' is in fact a directory. ** Note that we can't do a HEAD on a directory */ if (((file->stat_info.st_mode) & S_IFMT) == S_IFDIR) { if (HTRequest_method(request) == METHOD_GET) file->state = FS_PARSE_DIR; else { HTRequest_addError(request, ERR_INFO, NO, HTERR_NO_CONTENT, NULL, 0, "HTLoadFile"); file->state = FS_NO_DATA; } break; } /* ** If empty file then only serve it if it is editable. We also get ** the bindings for the file suffixes in lack of better bindings */ { BOOL editable = HTEditable(file->local, &file->stat_info); if (file_suffix_binding) HTBind_getAnchorBindings(anchor); if (editable) HTAnchor_appendAllow(anchor, METHOD_PUT); /* Set the file size */ if (file->stat_info.st_size) HTAnchor_setLength(anchor, file->stat_info.st_size); /* Set the file last modified time stamp */ if (file->stat_info.st_mtime > 0) HTAnchor_setLastModified(anchor, file->stat_info.st_mtime); /* Check to see if we can edit it */ if (!editable && !file->stat_info.st_size) { HTRequest_addError(request, ERR_INFO, NO, HTERR_NO_CONTENT, NULL, 0, "HTLoadFile"); file->state = FS_NO_DATA; } else { file->state = (HTRequest_method(request)==METHOD_GET) ? FS_NEED_OPEN_FILE : FS_GOT_DATA; } } break; case FS_NEED_OPEN_FILE: status = HTFileOpen(net, file->local, HT_FB_RDONLY); if (status == HT_OK) { /* ** Create the stream pipe FROM the channel to the application. ** The target for the input stream pipe is set up using the ** stream stack. */ { HTStream * rstream = HTStreamStack(HTAnchor_format(anchor), HTRequest_outputFormat(request), HTRequest_outputStream(request), request, YES); HTNet_setReadStream(net, rstream); HTRequest_setOutputConnected(request, YES); } /* ** Create the stream pipe TO the channel from the application ** and hook it up to the request object */ { HTOutputStream * output = HTNet_getOutput(net, NULL, 0); HTRequest_setInputStream(request, (HTStream *) output); } /* ** Set up concurrent read/write if this request isn't the ** source for a PUT or POST. As source we don't start reading ** before all destinations are ready. If destination then ** register the input stream and get ready for read */ if (HTRequest_isSource(request) && !HTRequest_destinationsReady(request)) return HT_OK; HTRequest_addError(request, ERR_INFO, NO, HTERR_OK, NULL, 0, "HTLoadFile"); file->state = FS_NEED_BODY; /* If we are _not_ using preemptive mode and we are Unix fd's ** then return here to get the same effect as when we are ** connecting to a socket. That way, HTFile acts just like any ** other protocol module even though we are in fact doing ** blocking connect */ if (HTEvent_isCallbacksRegistered()) { if (!HTRequest_preemptive(request)) { if (!HTNet_preemptive(net)) { HTTRACE(PROT_TRACE, "HTLoadFile.. Returning\n"); HTHost_register(HTNet_host(net), net, HTEvent_READ); } else if (!file->timer) { HTTRACE(PROT_TRACE, "HTLoadFile.. Returning\n"); file->timer = HTTimer_new(NULL, ReturnEvent, file, 1, YES, NO); } return HT_OK; } } } else if (status == HT_WOULD_BLOCK || status == HT_PENDING) return HT_OK; else { HTRequest_addError(request, ERR_INFO, NO, HTERR_INTERNAL, NULL, 0, "HTLoadFile"); file->state = FS_ERROR; /* Error or interrupt */ } break; case FS_NEED_BODY: status = HTHost_read(HTNet_host(net), net); if (status == HT_WOULD_BLOCK) return HT_OK; else if (status == HT_LOADED || status == HT_CLOSED) { file->state = FS_GOT_DATA; } else { HTRequest_addError(request, ERR_INFO, NO, HTERR_FORBIDDEN, NULL, 0, "HTLoadFile"); file->state = FS_ERROR; } break; case FS_PARSE_DIR: status = HTFile_readDir(request, file); if (status == HT_LOADED) file->state = FS_GOT_DATA; else file->state = FS_ERROR; break; case FS_TRY_FTP: { char *url = HTAnchor_physical(anchor); HTAnchor *anchor; char *newname = NULL; StrAllocCopy(newname, "ftp:"); if (!strncmp(url, "file:", 5)) StrAllocCat(newname, url+5); else StrAllocCat(newname, url); anchor = HTAnchor_findAddress(newname); HTRequest_setAnchor(request, anchor); HT_FREE(newname); FileCleanup(request, HT_IGNORE); return HTLoad(request, YES); } break; case FS_GOT_DATA: FileCleanup(request, HT_LOADED); return HT_OK; break; case FS_NO_DATA: FileCleanup(request, HT_NO_DATA); return HT_OK; break; case FS_RETRY: FileCleanup(request, HT_RETRY); return HT_OK; break; case FS_ERROR: FileCleanup(request, HT_ERROR); return HT_OK; break; } } /* End of while(1) */ }