582 lines
16 KiB
C++
582 lines
16 KiB
C++
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
#include "stdpch.h"
|
|
|
|
#include <nel/misc/file.h>
|
|
|
|
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("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("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, "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, "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, "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("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("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, "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, "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, "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,
|
|
"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, "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_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) */
|
|
}
|
|
|
|
// *************************************************************************
|
|
// 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 = 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 -= 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 =
|
|
{
|
|
"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("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"
|