// Ryzom - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include extern "C" { /* Library Includes */ #include "wwwsys.h" #include "WWWUtil.h" #include "WWWCore.h" #include "WWWDir.h" #include "WWWTrans.h" #include "HTReqMan.h" #include "HTBind.h" #include "HTMulti.h" #include "HTNetMan.h" #include "HTChannl.h" #include "libwww_nel_stream.h" /* Implemented here */ } using namespace std; using namespace NLMISC; extern "C" { /* Final states have negative value */ typedef enum _FileState { FS_RETRY = -4, FS_ERROR = -3, FS_NO_DATA = -2, FS_GOT_DATA = -1, FS_BEGIN = 0, FS_PENDING, FS_DO_CN, FS_NEED_OPEN_FILE, FS_NEED_BODY, FS_PARSE_DIR, FS_TRY_FTP } FileState; /* This is the context structure for the this module */ typedef struct _file_info { FileState state; /* Current state of the connection */ char * local; /* Local representation of file name */ struct stat stat_info; /* Contains actual file chosen */ HTNet * net; HTTimer * timer; } file_info; struct _HTStream { const HTStreamClass * isa; }; struct _HTInputStream { const HTInputStreamClass * isa; HTChannel * ch; HTHost * host; char * write; /* Last byte written */ char * read; /* Last byte read */ int b_read; char data [INPUT_BUFFER_SIZE]; /* buffer */ }; PRIVATE int FileCleanup (HTRequest *req, int status) { HTNet * net = HTRequest_net(req); file_info * file = (file_info *) HTNet_context(net); HTStream * input = HTRequest_inputStream(req); /* Free stream with data TO Local file system */ if (input) { if (status == HT_INTERRUPTED) (*input->isa->abort)(input, NULL); else (*input->isa->_free)(input); HTRequest_setInputStream(req, NULL); } /* ** Remove if we have registered a timer function as a callback */ if (file->timer) { HTTimer_delete(file->timer); file->timer = NULL; } if (file) { HT_FREE(file->local); HT_FREE(file); } HTNet_delete(net, status); return YES; } PRIVATE int FileEvent (SOCKET soc, void * pVoid, HTEventType type); PUBLIC int HTLoadNeLFile (SOCKET soc, HTRequest * request) { file_info *file; /* Specific access information */ HTNet * net = HTRequest_net(request); HTParentAnchor * anchor = HTRequest_anchor(request); HTTRACE(PROT_TRACE, "HTLoadFile.. Looking for `%s\'\n" _ HTAnchor_physical(anchor)); if ((file = (file_info *) HT_CALLOC(1, sizeof(file_info))) == NULL) HT_OUTOFMEM((char*)"HTLoadFILE"); file->state = FS_BEGIN; file->net = net; HTNet_setContext(net, file); HTNet_setEventCallback(net, FileEvent); HTNet_setEventParam(net, file); /* callbacks get http* */ return FileEvent(soc, file, HTEvent_BEGIN); /* get it started - ops is ignored */ } PRIVATE int ReturnEvent (HTTimer * timer, void * param, HTEventType /* type */) { file_info * file = (file_info *) param; if (timer != file->timer) HTDEBUGBREAK((char*)"File timer %p not in sync\n" _ timer); HTTRACE(PROT_TRACE, "HTLoadFile.. Continuing %p with timer %p\n" _ file _ timer); /* ** Delete the timer */ HTTimer_delete(file->timer); file->timer = NULL; /* ** Now call the event again */ return FileEvent(INVSOC, file, HTEvent_READ); } PUBLIC int HTNeLFileOpen (HTNet * net, char * local, HTLocalMode /* mode */) { HTRequest * request = HTNet_request(net); HTHost * host = HTNet_host(net); CIFile* fp = new CIFile; if (!fp->open (local)) { HTRequest_addSystemError(request, ERR_FATAL, errno, NO, (char*)"CIFile::open"); return HT_ERROR; } HTHost_setChannel(host, HTChannel_new(INVSOC, (FILE*)fp, YES)); 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 = (file_info *)pVoid; /* Specific access information */ int status = HT_ERROR; HTNet * net = file->net; HTRequest * request = HTNet_request(net); HTParentAnchor * anchor = HTRequest_anchor(request); if (type == HTEvent_CLOSE) { /* Interrupted */ HTRequest_addError(request, ERR_FATAL, NO, HTERR_INTERRUPTED, NULL, 0, (char*)"HTLoadFile"); FileCleanup(request, HT_INTERRUPTED); return HT_OK; } /* Now jump into the machine. We know the state from the previous run */ for(;;) { 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, (char*)"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));*/ { string tmp = HTAnchor_physical(anchor); if (strlwr(tmp).find("file:/") == 0) { tmp = tmp.substr(6, tmp.size()-6); } StrAllocCopy(file->local, tmp.c_str()); } 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((char*)"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: { HTHost * host = NULL; if ((host = HTHost_new((char*)"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 (HTRequest_negotiation(request) && HTMethod_isSafe(HTRequest_method(request))) { HTAnchor_setPhysical(anchor, file->local); HTTRACE(PROT_TRACE, "Load File... Found `%s\'\n" _ file->local); } 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, (char*)"HTLoadFile"); file->state = FS_ERROR; break; } } 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, (char*)"HTLoadFile"); file->state = FS_NO_DATA; } break; } { BOOL editable = FALSE; HTBind_getAnchorBindings(anchor); if (editable) HTAnchor_appendAllow(anchor, METHOD_PUT); /* Set the file size */ CIFile nelFile; if (nelFile.open (file->local)) { file->stat_info.st_size = nelFile.getFileSize(); } nelFile.close(); 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, (char*)"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 = HTNeLFileOpen(net, file->local, HT_FB_RDONLY); if (status == HT_OK) { { HTStream * rstream = HTStreamStack(HTAnchor_format(anchor), HTRequest_outputFormat(request), HTRequest_outputStream(request), request, YES); HTNet_setReadStream(net, rstream); HTRequest_setOutputConnected(request, YES); } { HTOutputStream * output = HTNet_getOutput(net, NULL, 0); HTRequest_setInputStream(request, (HTStream *) output); } if (HTRequest_isSource(request) && !HTRequest_destinationsReady(request)) return HT_OK; HTRequest_addError(request, ERR_INFO, NO, HTERR_OK, NULL, 0, (char*)"HTLoadFile"); file->state = FS_NEED_BODY; 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, (char*)"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, (char*)"HTLoadFile"); 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; default: break; } } /* End of while(1) */ } // ************************************************************************* // HTNeLReader // ************************************************************************* size_t nel_fread (void *buffer, uint size, FILE *fp) { CIFile *file = (CIFile *)fp; int toRead = std::min ((int)(file->getFileSize () - file->getPos ()), (int)size); file->serialBuffer((uint8*)buffer, toRead); return toRead; } PRIVATE int HTNeLReader_read (HTInputStream * me) { FILE * fp = HTChannel_file(me->ch); HTNet * net = HTHost_getReadNet(me->host); int status; /* Read the file desriptor */ while (fp) { if ((me->b_read = (int)nel_fread(me->data, FILE_BUFFER_SIZE, fp)) == 0) { HTAlertCallback *cbf = HTAlert_find(HT_PROG_DONE); // HTTRACE(PROT_TRACE, "ANSI read... Finished loading file %p\n" _ fp); if (cbf) (*cbf)(net->request, HT_PROG_DONE, HT_MSG_NULL,NULL,NULL,NULL); return HT_CLOSED; } /* Remember how much we have read from the input socket */ HTTRACEDATA(me->data, me->b_read, "HTANSIReader_read me->data:"); me->write = me->data; me->read = me->data + me->b_read; { HTAlertCallback * cbf = HTAlert_find(HT_PROG_READ); HTNet_addBytesRead(net, me->b_read); if (cbf) { int tr = HTNet_bytesRead(net); (*cbf)(net->request, HT_PROG_READ, HT_MSG_NULL, NULL, &tr, NULL); } } if (!net->readStream) return HT_ERROR; /* Now push the data down the stream */ if ((status = (*net->readStream->isa->put_block) (net->readStream, me->data, me->b_read)) != HT_OK) { if (status == HT_WOULD_BLOCK) { HTTRACE(PROT_TRACE, "ANSI read... Target WOULD BLOCK\n"); return HT_WOULD_BLOCK; } else if (status == HT_PAUSE) { HTTRACE(PROT_TRACE, "ANSI read... Target PAUSED\n"); return HT_PAUSE; } else if (status > 0) { /* Stream specific return code */ HTTRACE(PROT_TRACE, "ANSI read... Target returns %d\n" _ status); me->write = me->data + me->b_read; return status; } else { /* We have a real error */ HTTRACE(PROT_TRACE, "ANSI read... Target ERROR\n"); return status; } } me->write = me->data + me->b_read; } HTTRACE(PROT_TRACE, "ANSI read... File descriptor is NULL...\n"); return HT_ERROR; } PRIVATE int HTNeLReader_close (HTInputStream * me) { CIFile *file = (CIFile *)HTChannel_file(me->ch); if (file) { file->close(); } int status = HT_OK; HTNet * net = HTHost_getReadNet(me->host); if (net && net->readStream) { if ((status = (*net->readStream->isa->_free)(net->readStream))==HT_WOULD_BLOCK) return HT_WOULD_BLOCK; net->readStream = NULL; } HTTRACE(STREAM_TRACE, "Socket read. FREEING....\n"); HT_FREE(me); return status; } PUBLIC int HTNeLReader_consumed (HTInputStream * me, size_t bytes) { me->write += bytes; me->b_read -= (int)bytes; HTHost_setRemainingRead(me->host, me->b_read); return HT_OK; } PRIVATE int HTNeLReader_flush (HTInputStream * me) { HTNet * net = HTHost_getReadNet(me->host); return net && net->readStream ? (*net->readStream->isa->flush)(net->readStream) : HT_OK; } PRIVATE int HTNeLReader_free (HTInputStream * me) { CIFile *file = (CIFile *)HTChannel_file(me->ch); if (file) { delete file; HTChannel_setFile (me->ch, NULL); } HTNet * net = HTHost_getReadNet(me->host); if (net && net->readStream) { int status = (*net->readStream->isa->_free)(net->readStream); if (status == HT_OK) net->readStream = NULL; return status; } return HT_OK; } PRIVATE int HTNeLReader_abort (HTInputStream * me, HTList * /* e */) { HTNet * net = HTHost_getReadNet(me->host); if (net && net->readStream) { int status = (*net->readStream->isa->abort)(net->readStream, NULL); if (status != HT_IGNORE) net->readStream = NULL; } return HT_ERROR; } PRIVATE const HTInputStreamClass HTNeLReader = { (char*)"SocketReader", HTNeLReader_flush, HTNeLReader_free, HTNeLReader_abort, HTNeLReader_read, HTNeLReader_close, HTNeLReader_consumed }; PUBLIC HTInputStream * HTNeLReader_new (HTHost * host, HTChannel * ch, void * /* param */, int /* mode */) { if (host && ch) { HTInputStream * me = HTChannel_input(ch); if (me == NULL) { if ((me=(HTInputStream *) HT_CALLOC(1, sizeof(HTInputStream))) == NULL) HT_OUTOFMEM((char*)"HTNeLReader_new"); me->isa = &HTNeLReader; me->ch = ch; me->host = host; HTTRACE(STREAM_TRACE, "Reader...... Created reader stream %p\n" _ me); } return me; } return NULL; } //PUBLIC unsigned int WWW_TraceFlag = 0; } // extern "C"