/* * $Id$ * Copyright 2006-2014 Chung, Hyung-Hwan. This file is part of QSE. QSE is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. QSE 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with QSE. If not, see . */ #include "httpd.h" #include #include "../cmn/mem.h" /* TODO: * many functions in this file use qse_size_t. * so the size data transfers is limited by this type. * break this barrier... */ /*------------------------------------------------------------------------*/ static int task_main_disconnect ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task) { httpd->opt.scb.client.shutdown (httpd, client); return 0; } qse_httpd_task_t* qse_httpd_entaskdisconnect ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred) { qse_httpd_task_t task; QSE_MEMSET (&task, 0, QSE_SIZEOF(task)); task.main = task_main_disconnect; return qse_httpd_entask (httpd, client, pred, &task, 0); } /*------------------------------------------------------------------------*/ /* TODO: send wide character string when QSE_CHAR_IS_WCHAR */ /*------------------------------------------------------------------------*/ typedef struct task_format_t task_format_t; struct task_format_t { qse_mchar_t* org; const qse_mchar_t* ptr; qse_size_t left; }; static int task_init_format ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task) { task_format_t* xtn = qse_httpd_gettaskxtn (httpd, task); QSE_MEMCPY (xtn, task->ctx, QSE_SIZEOF(*xtn)); task->ctx = xtn; return 0; } static void task_fini_format ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task) { task_format_t* ctx = (task_format_t*)task->ctx; qse_httpd_freemem (httpd, ctx->org); } static int task_main_format ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task) { qse_ssize_t n; qse_size_t count; task_format_t* ctx = (task_format_t*)task->ctx; count = MAX_SEND_SIZE; if (count >= ctx->left) count = ctx->left; httpd->errnum = QSE_HTTPD_ENOERR; n = httpd->opt.scb.client.send (httpd, client, ctx->ptr, count); if (n <= -1) { if (httpd->errnum != QSE_HTTPD_EAGAIN) return -1; } else if (n > 0) { ctx->left -= n; if (ctx->left <= 0) return 0; ctx->ptr += n; } return 1; /* more work to do */ } qse_httpd_task_t* qse_httpd_entaskformat ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred, const qse_mchar_t* fmt, ...) { qse_httpd_task_t task; task_format_t data; va_list ap; qse_mchar_t* buf; int bytes_req, l; va_start (ap, fmt); bytes_req = qse_mbsxvfmt (QSE_NULL, 0, fmt, ap); va_end (ap); buf = (qse_mchar_t*) qse_httpd_allocmem ( httpd, (bytes_req + 1) * QSE_SIZEOF(*buf)); if (buf == QSE_NULL) return QSE_NULL; va_start (ap, fmt); l = qse_mbsxvfmt (buf, bytes_req + 1, fmt, ap); va_end (ap); if (l != bytes_req) { /* something got wrong ... */ qse_httpd_freemem (httpd, buf); httpd->errnum = QSE_HTTPD_EINTERN; return QSE_NULL; } QSE_MEMSET (&data, 0, QSE_SIZEOF(data)); data.org = buf; data.ptr = buf; data.left = l; QSE_MEMSET (&task, 0, QSE_SIZEOF(task)); task.init = task_init_format; task.fini = task_fini_format; task.main = task_main_format; task.ctx = &data; return qse_httpd_entask ( httpd, client, pred, &task, QSE_SIZEOF(data)); } /* TODO: send wide character string when QSE_CHAR_IS_WCHAR */ /*------------------------------------------------------------------------*/ qse_httpd_task_t* qse_httpd_entask_status ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred, int code, void* extra, qse_http_method_t method, const qse_http_version_t* version, int keepalive) { const qse_mchar_t* msg; const qse_mchar_t* extrapre = QSE_MT(""); const qse_mchar_t* extrapst = QSE_MT(""); const qse_mchar_t* extraval = QSE_MT(""); qse_mchar_t text[1024]; /* TODO: make this buffer dynamic or scalable */ text[0] = QSE_MT('\0'); msg = qse_httpstatustombs (code); switch (code) { case 301: /* Moved Permanently */ case 302: /* Found */ case 303: /* See Other (since HTTP/1.1) */ case 307: /* Temporary Redirect (since HTTP/1.1) */ case 308: /* Permanent Redirect (Experimental RFC; RFC 7238) */ { qse_httpd_rsrc_reloc_t* reloc; reloc = (qse_httpd_rsrc_reloc_t*)extra; extrapre = QSE_MT("Location: "); extrapst = (reloc->flags & QSE_HTTPD_RSRC_RELOC_APPENDSLASH)? QSE_MT("/\r\n"): QSE_MT("\r\n"); extraval = reloc->target; break; } case 304: case 200: case 201: case 202: case 203: case 204: case 205: case 206: /* nothing to do */ break; default: if (method != QSE_HTTP_HEAD && httpd->opt.rcb.fmterr (httpd, client, code, text, QSE_COUNTOF(text)) <= -1) return QSE_NULL; if (code == 401) { extrapre = QSE_MT("WWW-Authenticate: Basic realm=\""); extrapst = QSE_MT("\"\r\n"); extraval = (const qse_mchar_t*)extra; } break; } return qse_httpd_entaskformat ( httpd, client, pred, QSE_MT("HTTP/%d.%d %d %s\r\nServer: %s\r\nDate: %s\r\nConnection: %s\r\nContent-Type: text/html\r\nContent-Length: %u\r\n%s%s%s\r\n%s"), version->major, version->minor, code, msg, qse_httpd_getname (httpd), qse_httpd_fmtgmtimetobb (httpd, QSE_NULL, 0), (keepalive? QSE_MT("keep-alive"): QSE_MT("close")), (unsigned int)qse_mbslen(text), /* unsigned int should be large enough since text is small */ extrapre, extraval, extrapst, text); } /*------------------------------------------------------------------------*/ qse_httpd_task_t* qse_httpd_entaskerrorwithmvk ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred, int code, qse_http_method_t method, const qse_http_version_t* version, int keepalive) { return qse_httpd_entask_status (httpd, client, pred, code, QSE_NULL, method, version, keepalive); } qse_httpd_task_t* qse_httpd_entaskerror ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred, int code, qse_htre_t* req) { return qse_httpd_entask_status ( httpd, client, pred, code, QSE_NULL, qse_htre_getqmethodtype(req), qse_htre_getversion(req), (req->flags & QSE_HTRE_ATTR_KEEPALIVE) ); } /*------------------------------------------------------------------------*/ qse_httpd_task_t* qse_httpd_entaskcontinue ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred, qse_htre_t* req) { const qse_http_version_t* version = qse_htre_getversion(req); return qse_httpd_entaskformat ( httpd, client, pred, QSE_MT("HTTP/%d.%d 100 Continue\r\n\r\n"), version->major, version->minor); } /*------------------------------------------------------------------------*/ qse_httpd_task_t* qse_httpd_entaskauth ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred, const qse_mchar_t* realm, qse_htre_t* req) { return qse_httpd_entask_status ( httpd, client, pred, 401, (void*)realm, qse_htre_getqmethodtype(req), qse_htre_getversion(req), (req->flags & QSE_HTRE_ATTR_KEEPALIVE)); } /*------------------------------------------------------------------------*/ qse_httpd_task_t* qse_httpd_entaskreloc ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred, const qse_httpd_rsrc_reloc_t* reloc, qse_htre_t* req) { int code; if (reloc->flags & QSE_HTTPD_RSRC_RELOC_KEEPMETHOD) { code = (reloc->flags & QSE_HTTPD_RSRC_RELOC_PERMANENT)? 308: 307; } else { /* NOTE: 302 can be 303 for HTTP/1.1 */ code = (reloc->flags & QSE_HTTPD_RSRC_RELOC_PERMANENT)? 301: 302; } return qse_httpd_entask_status ( httpd, client, pred, code, (void*)reloc, qse_htre_getqmethodtype(req), qse_htre_getversion(req), (req->flags & QSE_HTRE_ATTR_KEEPALIVE)); } /*------------------------------------------------------------------------*/ qse_httpd_task_t* qse_httpd_entask_nomod ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred, qse_http_method_t method, const qse_http_version_t* version, int keepalive) { return qse_httpd_entask_status ( httpd, client, pred, 304, QSE_NULL, method, version, keepalive); } qse_httpd_task_t* qse_httpd_entasknomod ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred, qse_htre_t* req) { return qse_httpd_entask_status ( httpd, client, pred, 304, QSE_NULL, qse_htre_getqmethodtype(req), qse_htre_getversion(req), (req->flags & QSE_HTRE_ATTR_KEEPALIVE)); } /*------------------------------------------------------------------------*/ qse_httpd_task_t* qse_httpd_entaskallow ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred, const qse_mchar_t* allow, qse_htre_t* req) { int code = 200; const qse_mchar_t* msg; const qse_http_version_t* version; int keepalive; msg = qse_httpstatustombs (code); version = qse_htre_getversion(req); keepalive = (req->flags & QSE_HTRE_ATTR_KEEPALIVE); return qse_httpd_entaskformat ( httpd, client, pred, QSE_MT("HTTP/%d.%d %d %s\r\nServer: %s\r\nDate: %s\r\nConnection: %s\r\nAllow: %s\r\nContent-Length: 0\r\n\r\n"), version->major, version->minor, code, msg, qse_httpd_getname (httpd), qse_httpd_fmtgmtimetobb (httpd, QSE_NULL, 0), (keepalive? QSE_MT("keep-alive"): QSE_MT("close")), allow ); } /*------------------------------------------------------------------------*/ #if 0 qse_httpd_task_t* qse_httpd_entaskconnect ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred, const qse_nwad_t* nwad, qse_htre_t* req) { return -1; } #endif qse_httpd_task_t* qse_httpd_entaskrsrc ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred, qse_httpd_rsrc_t* rsrc, qse_htre_t* req) { qse_httpd_task_t* task; switch (rsrc->type) { case QSE_HTTPD_RSRC_AUTH: task = qse_httpd_entaskauth (httpd, client, pred, rsrc->u.auth.realm, req); break; case QSE_HTTPD_RSRC_CGI: task = qse_httpd_entaskcgi (httpd, client, pred, &rsrc->u.cgi, req); break; case QSE_HTTPD_RSRC_DIR: task = qse_httpd_entaskdir (httpd, client, pred, &rsrc->u.dir, req); break; case QSE_HTTPD_RSRC_ERROR: task = qse_httpd_entaskerror (httpd, client, pred, rsrc->u.error.code, req); break; case QSE_HTTPD_RSRC_FILE: task = qse_httpd_entaskfile (httpd, client, pred, rsrc->u.file.path, rsrc->u.file.mime, req); break; case QSE_HTTPD_RSRC_PROXY: task = qse_httpd_entaskproxy (httpd, client, pred, &rsrc->u.proxy, req); break; case QSE_HTTPD_RSRC_RELOC: task = qse_httpd_entaskreloc (httpd, client, pred, &rsrc->u.reloc, req); break; case QSE_HTTPD_RSRC_TEXT: task = qse_httpd_entasktext (httpd, client, pred, rsrc->u.text.ptr, rsrc->u.text.mime, req); break; default: qse_httpd_discardcontent (httpd, req); task = QSE_NULL; httpd->errnum = QSE_HTTPD_EINVAL; break; } return task; }