/*
 * $Id$
 *
    Copyright (c) 2006-2019 Chung, Hyung-Hwan. All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:
    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.

    THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "httpd.h"
#include "../cmn/mem-prv.h"
#include <qse/cmn/str.h>
#include <qse/cmn/fmt.h>

#define ETAG_LEN_MAX 127

#define PUTFILE_INIT_FAILED  (1 << 0)
#define PUTFILE_WRITE_FAILED (1 << 1)

typedef struct task_file_t task_file_t;
struct task_file_t
{
	qse_mcstr_t path;

	qse_http_version_t version;
	int                keepalive;
	int                method;

	union
	{
		struct
		{
			qse_mcstr_t mime;
			qse_mchar_t if_none_match[ETAG_LEN_MAX + 1];
			qse_ntime_t if_modified_since;
			qse_http_range_t range;
		} get;
		struct
		{
			qse_httpd_t* httpd;
			int flags;
			int status;
			qse_htre_t* req;
			qse_httpd_hnd_t handle;
		} put;
	} u;
};

/*------------------------------------------------------------------------*/

typedef struct task_getfseg_t task_getfseg_t;
struct task_getfseg_t
{
	qse_httpd_hnd_t handle;
	qse_foff_t left;
	qse_foff_t offset;
};

static int task_init_getfseg (
	qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
	task_getfseg_t* xtn = qse_httpd_gettaskxtn (httpd, task);
	QSE_MEMCPY (xtn, task->ctx, QSE_SIZEOF(*xtn));
	task->ctx = xtn;
	return 0;
}

static void task_fini_getfseg (
	qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
	task_getfseg_t* ctx = (task_getfseg_t*)task->ctx;
	httpd->opt.scb.file.close (httpd, ctx->handle);
}

static int task_main_getfseg (
	qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
	qse_ssize_t n;
	qse_size_t count;
	task_getfseg_t* ctx = (task_getfseg_t*)task->ctx;

	count = MAX_SEND_SIZE;
	if (count >= ctx->left) count = ctx->left;

	httpd->errnum = QSE_HTTPD_ENOERR;
	n = httpd->opt.scb.client.sendfile (
		httpd, client, ctx->handle, &ctx->offset, count);
	if (n <= -1) 
	{
/* HANDLE EGAIN specially??? */
		if (httpd->errnum != QSE_HTTPD_EAGAIN)
		{
			/* TODO: logging */
			return -1; 
		}

		goto more_work;
	}

	if (n == 0 && count > 0)
	{
		/* The file could be truncated when this condition is set.
		 * The content-length sent in the header can't be fulfilled. 
		 * So let's return an error here so that the main loop abort 
		 * the connection. */
/* TODO: any logging....??? */
		return -1;
	}

	ctx->left -= n;
	if (ctx->left <= 0) return 0;

more_work:
	return 1; /* more work to do */
}

static qse_httpd_task_t* entask_getfseg (
	qse_httpd_t* httpd, qse_httpd_client_t* client, 
	qse_httpd_task_t* pred,
	qse_httpd_hnd_t handle, qse_foff_t offset, qse_foff_t size)
{
	qse_httpd_task_t task;
	task_getfseg_t data;

	QSE_MEMSET (&data, 0, QSE_SIZEOF(data));
	data.handle = handle;
	data.offset = offset;
	data.left = size;

	QSE_MEMSET (&task, 0, QSE_SIZEOF(task));
	task.init = task_init_getfseg;
	task.main = task_main_getfseg;
	task.fini = task_fini_getfseg;
	task.ctx = &data;

	return qse_httpd_entask (httpd, client, pred, &task, QSE_SIZEOF(data));
}

/*------------------------------------------------------------------------*/


static int task_init_getfile (
	qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
	task_file_t* file = qse_httpd_gettaskxtn (httpd, task);
	task_file_t* arg = (task_file_t*)task->ctx;

	QSE_MEMCPY (file, arg, QSE_SIZEOF(*file));

	file->path.ptr = (qse_mchar_t*)(file + 1);
	qse_mbscpy ((qse_mchar_t*)file->path.ptr, arg->path.ptr);
	if (arg->u.get.mime.ptr)
	{
		file->u.get.mime.ptr = file->path.ptr + file->path.len + 1;
		qse_mbscpy ((qse_mchar_t*)file->u.get.mime.ptr, arg->u.get.mime.ptr);
	}

	task->ctx = file;
	return 0;
}

static QSE_INLINE int task_main_getfile (
	qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
	task_file_t* file;
	qse_httpd_task_t* x;
	qse_httpd_hnd_t handle;
	int fileopen = 0;
	qse_httpd_stat_t st;

	file = (task_file_t*)task->ctx;
	x = task;

/* TODO: if you should deal with files on a network-mounted drive,
         setting a trigger or non-blocking I/O are needed. */

	httpd->errnum = QSE_HTTPD_ENOERR;
	if (httpd->opt.scb.file.stat (httpd, file->path.ptr, &st) <= -1)
	{
		int http_errnum;
		http_errnum = (httpd->errnum == QSE_HTTPD_ENOENT)? 404:
		              (httpd->errnum == QSE_HTTPD_EACCES)? 403: 500;
		x = qse_httpd_entaskerrorwithmvk (
			httpd, client, x, http_errnum, 
			file->method, &file->version, file->keepalive);
		goto no_file_send;
	}

	httpd->errnum = QSE_HTTPD_ENOERR;
	if (httpd->opt.scb.file.ropen (httpd, file->path.ptr, &handle) <= -1)
	{
		int http_errnum;
		http_errnum = (httpd->errnum == QSE_HTTPD_ENOENT)? 404:
		              (httpd->errnum == QSE_HTTPD_EACCES)? 403: 500;
		x = qse_httpd_entaskerrorwithmvk (
			httpd, client, x, http_errnum, 
			file->method, &file->version, file->keepalive);
		goto no_file_send;
	}
	fileopen = 1;

	if (file->u.get.range.type != QSE_HTTP_RANGE_NONE)
	{ 
		qse_mchar_t tmp[4][64];
		qse_mchar_t etag[ETAG_LEN_MAX + 1];
		qse_size_t etag_len;

		if (file->u.get.range.type == QSE_HTTP_RANGE_SUFFIX)
		{
			if (file->u.get.range.to > st.size) file->u.get.range.to = st.size;
			file->u.get.range.from = st.size - file->u.get.range.to;
			file->u.get.range.to = file->u.get.range.to + file->u.get.range.from;
			if (st.size > 0) file->u.get.range.to--;
		}

		if (file->u.get.range.from >= st.size)
		{
			x = qse_httpd_entaskerrorwithmvk (
				httpd, client, x, 416, file->method, &file->version, file->keepalive);
			goto no_file_send;
		}

		if (file->u.get.range.to >= st.size) file->u.get.range.to = st.size - 1;

		qse_fmtuintmaxtombs (tmp[0], QSE_COUNTOF(tmp[0]), (file->u.get.range.to - file->u.get.range.from + 1), 10, -1, QSE_MT('\0'), QSE_NULL);
		qse_fmtuintmaxtombs (tmp[1], QSE_COUNTOF(tmp[1]), file->u.get.range.from, 10, -1, QSE_MT('\0'), QSE_NULL);
		qse_fmtuintmaxtombs (tmp[2], QSE_COUNTOF(tmp[2]), file->u.get.range.to, 10, -1, QSE_MT('\0'), QSE_NULL);
		qse_fmtuintmaxtombs (tmp[3], QSE_COUNTOF(tmp[3]), st.size, 10, -1, QSE_MT('\0'), QSE_NULL);

		etag_len = qse_fmtuintmaxtombs (&etag[0], QSE_COUNTOF(etag), st.mtime.sec, 16, -1, QSE_MT('\0'), QSE_NULL);
		etag[etag_len++] = QSE_MT('-');
		etag_len += qse_fmtuintmaxtombs (&etag[etag_len], QSE_COUNTOF(etag), st.mtime.nsec, 16, -1, QSE_MT('\0'), QSE_NULL);
		etag[etag_len++] = QSE_MT('-');
		etag_len += qse_fmtuintmaxtombs (&etag[etag_len], QSE_COUNTOF(etag) - etag_len, st.size, 16, -1, QSE_MT('\0'), QSE_NULL);
		etag[etag_len++] = QSE_MT('-');
		etag_len += qse_fmtuintmaxtombs (&etag[etag_len], QSE_COUNTOF(etag) - etag_len, st.ino, 16, -1, QSE_MT('\0'), QSE_NULL);
		etag[etag_len++] = QSE_MT('-');
		etag_len += qse_fmtuintmaxtombs (&etag[etag_len], QSE_COUNTOF(etag) - etag_len, st.dev, 16, -1, QSE_MT('\0'), QSE_NULL);

		x = qse_httpd_entaskformat (
			httpd, client, x,
    			QSE_MT("HTTP/%d.%d 206 Partial Content\r\nServer: %s\r\nDate: %s\r\nConnection: %s\r\n%s%s%sContent-Length: %s\r\nContent-Range: bytes %s-%s/%s\r\nAccept-Ranges: bytes\r\nLast-Modified: %s\r\nETag: %s\r\n\r\n"), 
			file->version.major, file->version.minor,
			qse_httpd_getname (httpd),
			qse_httpd_fmtgmtimetobb (httpd, QSE_NULL, 0),
			(file->keepalive? QSE_MT("keep-alive"): QSE_MT("close")),
			(file->u.get.mime.len > 0? QSE_MT("Content-Type: "): QSE_MT("")),
			(file->u.get.mime.len > 0? file->u.get.mime.ptr: QSE_MT("")),
			(file->u.get.mime.len > 0? QSE_MT("\r\n"): QSE_MT("")),
			((file->method == QSE_HTTP_HEAD)? QSE_MT("0"): tmp[0]),
			tmp[1], tmp[2], tmp[3],
			qse_httpd_fmtgmtimetobb (httpd, &st.mtime, 1),
			etag
		);
		if (x)
		{
			if (file->method == QSE_HTTP_HEAD) goto no_file_send;
			x = entask_getfseg (
				httpd, client, x, handle, 
				file->u.get.range.from, 
				(file->u.get.range.to - file->u.get.range.from + 1)
			);
		}
	}
	else
	{
		qse_mchar_t b_fsize[64];
		qse_mchar_t etag[ETAG_LEN_MAX + 1];
		qse_size_t etag_len;

		etag_len = qse_fmtuintmaxtombs (&etag[0], QSE_COUNTOF(etag), st.mtime.sec, 16, -1, QSE_MT('\0'), QSE_NULL);
		etag[etag_len++] = QSE_MT('-');
		etag_len += qse_fmtuintmaxtombs (&etag[etag_len], QSE_COUNTOF(etag), st.mtime.nsec, 16, -1, QSE_MT('\0'), QSE_NULL);
		etag[etag_len++] = QSE_MT('-');
		etag_len += qse_fmtuintmaxtombs (&etag[etag_len], QSE_COUNTOF(etag) - etag_len, st.size, 16, -1, QSE_MT('\0'), QSE_NULL);
		etag[etag_len++] = QSE_MT('-');
		etag_len += qse_fmtuintmaxtombs (&etag[etag_len], QSE_COUNTOF(etag) - etag_len, st.ino, 16, -1, QSE_MT('\0'), QSE_NULL);
		etag[etag_len++] = QSE_MT('-');
		etag_len += qse_fmtuintmaxtombs (&etag[etag_len], QSE_COUNTOF(etag) - etag_len, st.dev, 16, -1, QSE_MT('\0'), QSE_NULL);

		if ((file->u.get.if_none_match[0] != QSE_MT('\0') && qse_mbscmp (etag, file->u.get.if_none_match) == 0) ||
		    (file->u.get.if_modified_since.sec > 0 && st.mtime.sec <= file->u.get.if_modified_since.sec)) 
		{
			/* i've converted milliseconds to seconds before timestamp comparison
			 * because st.mtime has the actual milliseconds less than 1 second
			 * while if_modified_since doesn't have such small milliseconds */
			x = qse_httpd_entask_nomod (httpd, client, x, file->method, &file->version, file->keepalive);
			goto no_file_send;
		}

		qse_fmtuintmaxtombs (b_fsize, QSE_COUNTOF(b_fsize), st.size, 10, -1, QSE_MT('\0'), QSE_NULL);

		/* wget 1.8.2 set 'Connection: keep-alive' in the http 1.0 header.
		 * if the reply doesn't contain 'Connection: keep-alive', it didn't
		 * close connection.*/

		x = qse_httpd_entaskformat (
			httpd, client, x,
    			QSE_MT("HTTP/%d.%d 200 OK\r\nServer: %s\r\nDate: %s\r\nConnection: %s\r\n%s%s%sContent-Length: %s\r\nAccept-Ranges: bytes\r\nLast-Modified: %s\r\nETag: %s\r\n\r\n"), 
			file->version.major, file->version.minor,
			qse_httpd_getname (httpd),
			qse_httpd_fmtgmtimetobb (httpd, QSE_NULL, 0),
			(file->keepalive? QSE_MT("keep-alive"): QSE_MT("close")),
			(file->u.get.mime.len > 0? QSE_MT("Content-Type: "): QSE_MT("")),
			(file->u.get.mime.len > 0? file->u.get.mime.ptr: QSE_MT("")),
			(file->u.get.mime.len > 0? QSE_MT("\r\n"): QSE_MT("")),
			((file->method == QSE_HTTP_HEAD)? QSE_MT("0"): b_fsize),
			qse_httpd_fmtgmtimetobb (httpd, &st.mtime, 1),
			etag
		);
		if (x) 
		{
			if (file->method == QSE_HTTP_HEAD) goto no_file_send;
			x = entask_getfseg (httpd, client, x, handle, 0, st.size);
		}
	}

	if (x) return 0;
	httpd->opt.scb.file.close (httpd, handle);
	return -1;

no_file_send:
	if (fileopen) httpd->opt.scb.file.close (httpd, handle);
	return (x == QSE_NULL)? -1: 0;
}

/*------------------------------------------------------------------------*/

static int write_file (qse_httpd_t* httpd, qse_httpd_hnd_t handle, const qse_mchar_t* ptr, qse_size_t len)
{
	qse_ssize_t n;
	qse_size_t pos = 0;

	/* this implementation assumes that file writing will never get 
	 * blocked in practice. so no i/o multiplexing is performed over
	 * file descriptors */
	while (pos < len)
	{
		n = httpd->opt.scb.file.write (httpd, handle, &ptr[pos], len - pos);
		if (n <= 0) return -1;
		pos += n;
	}

	return 0;
}

static int putfile_snatch_client_input (
	qse_htre_t* req, const qse_mchar_t* ptr, qse_size_t len, void* ctx)
{
	qse_httpd_task_t* task;
	task_file_t* file; 

	task = (qse_httpd_task_t*)ctx;
	file = (task_file_t*)task->ctx;

	if (ptr == QSE_NULL)
	{
		/*
		 * this callback is called with ptr of QSE_NULL 
		 * when the request is completed or discarded. 
		 * and this indicates that there's nothing more to read
		 * from the client side. this can happen when the end of
		 * a request is seen or when an error occurs 
		 */
		QSE_ASSERT (len == 0);

		/* mark the there's nothing to read form the client side */
		qse_htre_unsetconcb (file->u.put.req);
		file->u.put.req = QSE_NULL; 

		/* since there is no more to read from the client side.
		 * the trigger is not needed any more. */
		task->trigger.v[0].mask = 0;
	}
	else if (!(file->u.put.flags & PUTFILE_WRITE_FAILED))
	{
		if (write_file (file->u.put.httpd, file->u.put.handle, ptr, len) <= -1)
		{
			file->u.put.flags |= PUTFILE_WRITE_FAILED;
			file->u.put.status = 500;
		}
	}

	return 0;
}

static int task_init_putfile (
	qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
	task_file_t* file = qse_httpd_gettaskxtn (httpd, task);
	task_file_t* arg = (task_file_t*)task->ctx;
	qse_httpd_stat_t st;

	/* zero out the task's extension area */
	QSE_MEMCPY (file, arg, QSE_SIZEOF(*file));
	file->u.put.req = QSE_NULL;
	
	/* copy in the path name to the area */
	file->path.ptr = (qse_mchar_t*)(file + 1);
	qse_mbscpy ((qse_mchar_t*)file->path.ptr, arg->path.ptr);
	file->u.put.status = 204; /* 200 should also be ok to indicate success. */
	task->ctx = file; /* switch the task context to the extension area */

	httpd->errnum = QSE_HTTPD_ENOERR;
	if (httpd->opt.scb.file.stat (httpd, file->path.ptr, &st) <= -1)
	{
		if (httpd->errnum == QSE_HTTPD_ENOENT) 
		{
			/* stat found no such file. so if the request is achived
			 * successfully, it should send '201 Created' */
			file->u.put.status = 201;
		}
	}

	httpd->errnum = QSE_HTTPD_ENOERR;
	if (httpd->opt.scb.file.wopen (httpd, file->path.ptr, &file->u.put.handle) <= -1) 
	{
		file->u.put.status = (httpd->errnum == QSE_HTTPD_EACCES)? 403: 500;
		goto oops;
	}

	if (write_file (httpd, file->u.put.handle, qse_htre_getcontentptr(arg->u.put.req), qse_htre_getcontentlen(arg->u.put.req)) <= -1) 
	{
		httpd->opt.scb.file.close (httpd, file->u.put.handle);
		file->u.put.status = 500;
		goto oops;
	}

	if (!(arg->u.put.req->state & QSE_HTRE_DISCARDED) &&
	    !(arg->u.put.req->state & QSE_HTRE_COMPLETED))
	{
		/* set up a callback to be called when the request content
		 * is fed to the htrd reader. qse_htre_addcontent() that 
		 * htrd calls invokes this callback. */
		file->u.put.req = arg->u.put.req;
		qse_htre_setconcb (file->u.put.req, putfile_snatch_client_input, task);
	}

	/* no triggers yet since the main loop doesn't allow me to set 
	 * triggers in the task initializer. however the main task handler
	 * will be invoked so long as the client handle is writable by
	 * the main loop. */
	return 0;

oops:
	/* since a new task can't be added in the initializer,
	 * i mark that initialization failed and let task_main_putfile()
	 * add an error task */
	qse_htre_discardcontent (arg->u.put.req);
	file->u.put.flags |= PUTFILE_INIT_FAILED;
	return 0;
}

static void task_fini_putfile (
	qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
	task_file_t* file = (task_file_t*)task->ctx;

	if (!(file->u.put.flags & PUTFILE_INIT_FAILED))
		httpd->opt.scb.file.close (httpd, file->u.put.handle);
}

static int task_main_putfile_2 (
	qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
	task_file_t* file = (task_file_t*)task->ctx;

	if (file->u.put.req)
	{
		/* file->u.put.req is set to a non-NULL value if snatching
		 * is needed in the task_init_putfile(). and it's reset to
		 * QSE_NULL when snatching is over in putfile_snatch_client_input().
		 * i set a trigger so that the task is executed
		 * while there is input from the client side  */
		/*task->trigger.v[0].mask = QSE_HTTPD_TASK_TRIGGER_READ;
		task->trigger.v[0].handle = client->handle;*/
		task->trigger.cmask = QSE_HTTPD_TASK_TRIGGER_READ;
		return 1; /* trigger me when a client sends data  */
	}

	/* snatching is over. writing error may have occurred as well.
	 * file->u.put.status should hold a proper status code */
	if (qse_httpd_entaskerrorwithmvk (
		httpd, client, task, file->u.put.status,
		file->method, &file->version, file->keepalive) == QSE_NULL) return -1;
	return 0; /* no more data to read. task over */
}

static int task_main_putfile (
	qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
	task_file_t* file = (task_file_t*)task->ctx;

	if (!(file->u.put.flags & PUTFILE_INIT_FAILED) && file->u.put.req)
	{
		/* initialization was successful and snatching is required. 
		 * switch to the next phase. */
		task->main = task_main_putfile_2;
		return task_main_putfile_2 (httpd, client, task);
	}

	/* snatching is not required or initialization error has occurred.
	 * file->u.put.status should hold a proper status code.
	 *
	 * note: if initialization error occurred and there is contents for the
	 * client to send, this reply may get to the client before it finishes 
	 * sending the contents. */
	if (qse_httpd_entaskerrorwithmvk (
		httpd, client, task, file->u.put.status,
		file->method, &file->version, file->keepalive) == QSE_NULL) return -1;
	return 0; /* task over */
}

/*------------------------------------------------------------------------*/

qse_httpd_task_t* qse_httpd_entaskfile (
	qse_httpd_t* httpd,
	qse_httpd_client_t* client, 
	qse_httpd_task_t* pred,
	const qse_mchar_t* path,
	const qse_mchar_t* mime,
	qse_htre_t* req)
{
	qse_httpd_task_t task;
	task_file_t data;
	const qse_htre_hdrval_t* tmp;
	qse_size_t xtnsize;

	QSE_MEMSET (&data, 0, QSE_SIZEOF(data));
	data.path.ptr = (qse_mchar_t*)path;
	data.path.len = qse_mbslen(path);
	data.version = *qse_htre_getversion(req);
	data.keepalive = (req->flags & QSE_HTRE_ATTR_KEEPALIVE);
	data.method = qse_htre_getqmethodtype(req);

	xtnsize = QSE_SIZEOF(task_file_t) + data.path.len + 1;

	QSE_MEMSET (&task, 0, QSE_SIZEOF(task));

	switch (data.method)
	{
		case QSE_HTTP_OPTIONS:
			qse_htre_discardcontent (req);
			return qse_httpd_entaskallow (httpd, client, pred, 
				QSE_MT("OPTIONS,GET,HEAD,POST,PUT,DELETE"), req);

		case QSE_HTTP_HEAD:
		case QSE_HTTP_GET:
		case QSE_HTTP_POST:
			qse_htre_discardcontent (req);

			if (mime)
			{
				data.u.get.mime.ptr = (qse_mchar_t*)mime;
				data.u.get.mime.len = qse_mbslen(mime);
				xtnsize += data.u.get.mime.len + 1;
			}

			tmp = qse_htre_getheaderval(req, QSE_MT("If-None-Match"));
			if (tmp)
			{
				/*while (tmp->next) tmp = tmp->next;*/ /* get the last value */
				qse_mbsxcpy (data.u.get.if_none_match, QSE_COUNTOF(data.u.get.if_none_match), tmp->ptr);
			}
			if (data.u.get.if_none_match[0] == QSE_MT('\0'))
			{
				/* Both ETag and Last-Modified are included in the reply.
				 * If the client understand ETag, it can choose to include 
				 * If-None-Match in the request. If it understands Last-Modified,
				 * it can choose to include If-Modified-Since. I don't care
				 * the client understands both and include both of them
				 * in the request.
				 *
				 * I check If-None-Match if it's included.
				 * I check If-Modified-Since if If-None-Match is not included.
				 */
				tmp = qse_htre_getheaderval(req, QSE_MT("If-Modified-Since"));
				if (tmp)
				{
					/*while (tmp->next) tmp = tmp->next;*/ /* get the last value */
					if (qse_parsehttptime (tmp->ptr, &data.u.get.if_modified_since) <= -1)
					{
						data.u.get.if_modified_since.sec = 0;
						data.u.get.if_modified_since.nsec = 0;
					}
				}
			}

			tmp = qse_htre_getheaderval(req, QSE_MT("Range"));
			if (tmp) 
			{
				/*while (tmp->next) tmp = tmp->next;*/ /* get the last value */
				if (qse_parsehttprange (tmp->ptr, &data.u.get.range) <= -1)
				{
					return qse_httpd_entaskerror (httpd, client, pred, 416, req);
				}
			}
			else 
			{
				data.u.get.range.type = QSE_HTTP_RANGE_NONE;
			}

			task.init = task_init_getfile;
			task.main = task_main_getfile;

			task.ctx = &data;
			return qse_httpd_entask (httpd, client, pred, &task, xtnsize);

		case QSE_HTTP_PUT:
			/* note that no partial update is supported for PUT */
			data.u.put.httpd = httpd;
			data.u.put.req = req;
			task.init = task_init_putfile;
			task.main = task_main_putfile;
			task.fini = task_fini_putfile;
			task.ctx = &data;
			return qse_httpd_entask (httpd, client, pred, &task, xtnsize);

		case QSE_HTTP_DELETE:
		{
			int status = 200;

			qse_htre_discardcontent (req);
			if (httpd->opt.scb.file.purge (httpd, path) <= -1)
			{
				status = (httpd->errnum == QSE_HTTPD_ENOENT)? 404:
				         (httpd->errnum == QSE_HTTPD_EACCES)? 403: 500;
			}
				
			return qse_httpd_entaskerror (httpd, client, pred, status, req);
		}

		default:
			/* Method not allowed */
			qse_htre_discardcontent (req);
			return qse_httpd_entaskerror (httpd, client, pred, 405, req);
	}
}

