diff --git a/modules/web/http_server_simple/httpio.md b/modules/web/http_server_simple/httpio.md new file mode 100644 index 000000000..33ef95935 --- /dev/null +++ b/modules/web/http_server_simple/httpio.md @@ -0,0 +1,3 @@ +https://github.com/fetisov/httpio + +8c56b550316e064842360eeddf2abb626df9123f \ No newline at end of file diff --git a/modules/web/http_server_simple/httpio/.gitignore b/modules/web/http_server_simple/httpio/.gitignore new file mode 100644 index 000000000..612cb8c3a --- /dev/null +++ b/modules/web/http_server_simple/httpio/.gitignore @@ -0,0 +1 @@ +/tests/* diff --git a/modules/web/http_server_simple/httpio/LICENSE b/modules/web/http_server_simple/httpio/LICENSE new file mode 100644 index 000000000..9857f7f93 --- /dev/null +++ b/modules/web/http_server_simple/httpio/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Fetisov Sergey + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/modules/web/http_server_simple/httpio/README.md b/modules/web/http_server_simple/httpio/README.md new file mode 100644 index 000000000..96b7fbee6 --- /dev/null +++ b/modules/web/http_server_simple/httpio/README.md @@ -0,0 +1,120 @@ +# httpio +Cross Platform parser and response generator for the HTTP protocol. + +Library has the simple API to embed to the custom systems. + +Parser functions: +- Parses the HTTP stream on the fly +- Does not use the dynamic allocation (uses predefined connection buffer as the simple pool) +- Not used space of the connection buffer can be used as the network receiving buffer +- Supports the keep-alive multiple requests and various types of POST request + +Generator functions: +- As well as the parser it does not use the dynamic allocation +- Organized by a ring buffer +- Supports the chunked transfer encoding + +# Parser example (stdin instead the socket) +```c +#include +#include "httpio.h" + +static char in_pool[1024]; + +int main(int argc, const char **argv) +{ + FILE *f; + httpi_t *in; + f = argc == 2 ? fopen(argv[1], "rb") : stdin; + in = httpi_init(in_pool, 1024); + while (1) + { + int size; + char *data; + int status = httpi_pull(in); + switch (status) + { + case HTTP_IN_NODATA: + /* data input */ + httpi_get_state(in, &data, &size); + size = fread(data, 1, size, f); + httpi_push(in, data, size); + if (size == 0) return 0; + continue; + case HTTP_IN_REQUEST: + printf("page %s requested\n", httpi_request(in)->uri); + continue; + case HTTP_IN_POSTDATA: + printf("new chunk of POST data \"%s\"\n", httpi_posted(in)->name); + continue; + case HTTP_IN_FINISHED: + printf("request finished\n"); + continue; + default: + printf("invalid request!\n"); + return 0; + } + } +} +``` +Example input: +```http +POST /cgi-bin/login.cgi HTTP/1.1 +Host: www.tutorialspoint.com +Content-Type: application/x-www-form-urlencoded +Content-Length: 35 +Accept-Language: en-us +Accept-Encoding: gzip, deflate +Connection: Keep-Alive + +login=Petya%20Vasechkin&password=qq +``` +Example output: +``` +page /cgi-bin/login.cgi requested +new chunk of POST data "login" +new chunk of POST data "password" +request finished +``` + +# Response generator example (stdout instead the socket) +```c +#include +#include "httpio.h" + +static char out_pool[1024]; + +int main(int argc, const char **argv) +{ + FILE *f; + httpo_t *out; + http_resp_t resp; + char *data; + int size; + out = httpo_init(out_pool, 1024); + http_resp_init(&resp, 200, MIME_TEXT_HTML, RESPF_KEEPALIVE | RESPF_CHUNKED | RESPF_NOCACH); + httpo_write_resp(out, &resp); + httpo_write_data(out, "first data chunk", 16); + httpo_write_data(out, "the second data chunk", 21); + httpo_finished(out); + httpo_state(out, &data, &size); + fwrite(data, 1, size, stdout); + return 0; +} +``` + +Example output: +```http +HTTP/1.1 200 OK +Content-Language: en +Content-Type: text/html +Connection: keep-alive +Cache-Control: no-store, no-cache +Transfer-Encoding: chunked + +10 +first data chunk +15 +the second data chunk +0 +``` diff --git a/modules/web/http_server_simple/httpio/http.c b/modules/web/http_server_simple/httpio/http.c new file mode 100644 index 000000000..1e47860e8 --- /dev/null +++ b/modules/web/http_server_simple/httpio/http.c @@ -0,0 +1,298 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 by Sergey Fetisov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "http.h" + +#ifdef __cplusplus +extern "C" { +#endif + +const char MIME_APP_ATOM[] = "application/atom+xml"; +const char MIME_APP_JSON[] = "application/json"; +const char MIME_APP_JS[] = "application/javascript"; +const char MIME_APP_OCTSTR[] = "application/octet-stream"; +const char MIME_APP_PDF[] = "application/pdf"; +const char MIME_APP_PS[] = "application/postscript"; +const char MIME_APP_XHTML[] = "application/xhtml+xml"; +const char MIME_APP_XML[] = "application/xml-dtd"; +const char MIME_APP_ZIP[] = "application/zip"; +const char MIME_APP_GZIP[] = "application/x-gzip"; +const char MIME_APP_BTOR[] = "application/x-bittorrent"; +const char MIME_APP_TEX[] = "application/x-tex"; +const char MIME_URLENCODED[] = "application/x-www-form-urlencoded"; +const char MIME_TEXT_HTML[] = "text/html"; +const char MIME_TEXT_JS[] = "text/javascript"; +const char MIME_TEXT_PLAIN[] = "text/plain"; +const char MIME_TEXT_XML[] = "text/xml"; +const char MIME_TEXT_CSS[] = "text/css"; +const char MIME_IMAGE_GIF[] = "image/gif"; +const char MIME_IMAGE_JPEG[] = "image/jpeg"; +const char MIME_IMAGE_PJPEG[] = "image/pjpeg"; +const char MIME_IMAGE_PNG[] = "image/png"; +const char MIME_IMAGE_SVG[] = "image/svg+xml"; +const char MIME_IMAGE_TIFF[] = "image/tiff"; +const char MIME_IMAGE_ICON[] = "image/vnd.microsoft.icon"; +const char MIME_IMAGE_WBMP[] = "image/vnd.wap.wbmp"; +const char MIME_MPART_MIXED[] = "multipart/mixed"; +const char MIME_MPART_ALT[] = "multipart/alternative"; +const char MIME_MPART_REL[] = "multipart/related"; +const char MIME_MPART_FORM[] = "multipart/form-data"; +const char MIME_MPART_SIGN[] = "multipart/signed"; +const char MIME_MPART_ENCR[] = "multipart/encrypted"; + +const char *mime_list[] = +{ + MIME_TEXT_HTML, + MIME_TEXT_PLAIN, + MIME_URLENCODED, + MIME_APP_XHTML, + MIME_APP_XML, + MIME_TEXT_CSS, + MIME_TEXT_JS, + MIME_TEXT_XML, + MIME_IMAGE_JPEG, + MIME_IMAGE_PNG, + MIME_IMAGE_GIF, + MIME_IMAGE_SVG, + MIME_MPART_FORM, + MIME_MPART_MIXED, + MIME_APP_JS, + MIME_APP_JSON, + MIME_APP_ATOM, + MIME_APP_OCTSTR, + MIME_APP_PDF, + MIME_APP_PS, + MIME_APP_ZIP, + MIME_APP_GZIP, + MIME_APP_BTOR, + MIME_APP_TEX, + MIME_IMAGE_PJPEG, + MIME_IMAGE_TIFF, + MIME_IMAGE_ICON, + MIME_IMAGE_WBMP, + MIME_MPART_ALT, + MIME_MPART_REL, + MIME_MPART_SIGN, + MIME_MPART_ENCR, + NULL +}; + +struct http_code +{ + int code; + const char *str; +}; + +static struct http_code http_code_list[] = +{ + { 100, "Continue" }, + { 101, "Switching Protocols" }, + { 102, "Processing" }, + { 200, "OK" }, + { 201, "Created" }, + { 202, "Accepted" }, + { 203, "Non-Authoritative Information" }, + { 204, "No Content" }, + { 205, "Reset Content" }, + { 206, "Partial Content" }, + { 207, "Multi Status" }, + { 300, "Multiple Choices" }, + { 301, "Moved Permanently" }, + { 302, "Moved Temporarily" }, + { 303, "See Other" }, + { 304, "Not Modified" }, + { 305, "Use Proxy" }, + { 306, "Switch Proxy" }, + { 307, "Temporary Redirect" }, + { 400, "Bad Request" }, + { 401, "Unauthorized" }, + { 402, "Payment Required" }, + { 403, "Forbidden" }, + { 404, "Not Found" }, + { 405, "Method Not Allowed" }, + { 406, "Not Acceptable" }, + { 407, "Proxy Authentication Required" }, + { 408, "Request Time-out" }, + { 409, "Conflict" }, + { 410, "Gone" }, + { 411, "Length Required" }, + { 412, "Precondition Failed" }, + { 413, "Request Entity Too Large" }, + { 414, "Request-URI Too Large" }, + { 415, "Unsupported Media Type" }, + { 416, "Requested Range Not Satisfiable" }, + { 417, "Expectation Failed" }, + { 422, "Unprocessable Entity" }, + { 423, "Locked" }, + { 424, "Failed Dependency" }, + { 425, "Unordered Collection" }, + { 426, "Upgrade Required" }, + { 444, "No Response" }, + { 449, "Retry With" }, + { 450, "Blocked by Windows Parental Controls" }, + { 451, "Unavailable For Legal Reasons" }, + { 500, "Internal Server Error" }, + { 501, "Not Implemented" }, + { 502, "Bad Gateway" }, + { 503, "Service Unavailable" }, + { 504, "Gateway Time-out" }, + { 505, "HTTP Version not supported" }, + { 506, "Variant Also Negotiates" }, + { 507, "Insufficient Storage" }, + { 508, "Unknown" }, + { 509, "Bandwidth Limit Exceeded" }, + { 510, "Not Extended" } +}; + +static const char *CONN_TYPE_STR[] = +{ + "", "close", "keep-alive" +}; + +const char *http_req_val(const http_req_t *req, const char *param, const char *def) +{ + int i; + for (i = 0; i < req->nparams; i++) + if (strcmp(req->params[i], param) == 0) + return req->values[i]; + return def; +} + +void http_resp_init(http_resp_t *resp, int code, const char *mime, int flags) +{ + memset(resp, 0, sizeof(http_resp_t)); + resp->code = code; + resp->connection = flags & RESPF_KEEPALIVE ? CT_KEEP_ALIVE : CT_CLOSE; + resp->content.type = mime; + resp->content.length = -1; + resp->content.language = "en"; + resp->cache_control = flags & RESPF_NOCACH ? "no-store, no-cache" : NULL; + if (flags & RESPF_DEFLATE) resp->content.encoding = "deflate"; else + if (flags & RESPF_GZIP) resp->content.encoding = "gzip"; + resp->transfer_encoding = flags & RESPF_CHUNKED ? TENC_CHUNKED : TENC_NONE; +} + +const char *http_dict_mime(const char *mime) +{ + int i; + if (mime == NULL) return NULL; + for (i = 0; mime_list[i] != NULL; i++) + if (strcmp(mime_list[i], mime) == 0) + return mime_list[i]; + return NULL; +} + +int http_resp_len(const http_resp_t *resp) +{ + int tmp; + return http_resp_str(resp, (char *)&tmp, sizeof(tmp)); +} + +#if defined(__WINNT__) || defined(_WIN32) || defined(_WIN64) +int correct_snprintf(char *buff, int buff_size, const char *fmt, ...) +{ + int res; + va_list args; + va_start(args, fmt); + res = vsnprintf(buff, buff_size, fmt, args); + if (res < 0) res = _vscprintf(fmt, args); + va_end(args); + return res; +} +#else +#define correct_snprintf snprintf +#endif + +int http_resp_str(const http_resp_t *resp, char *str, int size) +{ +#define PRN(fmt, arg) \ + { \ + n = correct_snprintf(str, size, fmt, arg); \ + str += n; \ + size -= n; \ + res += n; \ + if (size < 0) size = 0; \ + } + +#define PRNSTR(prop, field) if (field != NULL) PRN(prop ": %s\r\n", field) + + int res, n; + res = 0; + + PRN("HTTP/1.1 %d ", resp->code); + PRN("%s\r\n", http_code_str(resp->code)); + if (resp->content.disp.disp != NULL) + { + PRN("Content-Disposition %s", resp->content.disp.disp); + if (resp->content.disp.disp_name != NULL) + PRN(" name=\"%s\"", resp->content.disp.disp_name); + if (resp->content.disp.disp_file != NULL) + PRN(" filename=\"%s\"", resp->content.disp.disp_file); + PRN("%s", "\r\n"); + } + PRNSTR("Content-Encoding", resp->content.encoding); + PRNSTR("Content-Language", resp->content.language); + if (resp->content.length > 0) + PRN("Content-Length: %d\r\n", resp->content.length); + PRNSTR("Content-Location", resp->content.location); + PRNSTR("Content-MD5", resp->content.md5); + /* TODO: http_rng_t range; */ + PRNSTR("Content-Type", resp->content.type); + PRNSTR("Content-Version", resp->content.ver); + PRNSTR("Expires", resp->content.expires); + PRNSTR("Last-Modified", resp->content.modified); + PRNSTR("Link", resp->content.link); + PRNSTR("Title", resp->content.title); + PRN("Connection: %s\r\n", CONN_TYPE_STR[resp->connection]); + PRNSTR("Cache-Control", resp->cache_control); + PRNSTR("ETag", resp->etag); + PRNSTR("Location", resp->location); + PRNSTR("Proxy-Authenticate", resp->proxy_authenticate); + PRNSTR("Public", resp->Public); + if (resp->retry_after > 0) + PRN("Retry-After: %d\r\n", resp->retry_after); + PRNSTR("Server", resp->server); + if (resp->transfer_encoding == TENC_CHUNKED) + PRN("Transfer-Encoding: %s\r\n", "chunked"); + PRNSTR("Vary", resp->vary); + PRNSTR("WWW-Authenticate", resp->www_authenticate); + if (resp->exthdr != NULL) + PRN("%s", resp->exthdr); + PRN("%s", "\r\n"); + return res; +} + +const char *http_code_str(int code) +{ + int i, n; + n = sizeof(http_code_list) / sizeof(struct http_code); + for (i = 0; i < n; i++) + if (http_code_list[i].code == code) + return http_code_list[i].str; + return ""; +} + +#ifdef __cplusplus +} +#endif diff --git a/modules/web/http_server_simple/httpio/http.h b/modules/web/http_server_simple/httpio/http.h new file mode 100644 index 000000000..706bb0bc4 --- /dev/null +++ b/modules/web/http_server_simple/httpio/http.h @@ -0,0 +1,237 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 by Sergey Fetisov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef HTTP_H +#define HTTP_H + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef HTTP_REQ_MAX_PARAMS +#define HTTP_REQ_MAX_PARAMS 16 +#endif + +#ifndef HTTP_REQ_MAX_ACCEPT +#define HTTP_REQ_MAX_ACCEPT 8 +#endif + +extern const char MIME_APP_ATOM[]; /* application/atom+xml */ +extern const char MIME_APP_JSON[]; /* application/json */ +extern const char MIME_APP_JS[]; /* application/javascript */ +extern const char MIME_APP_OCTSTR[]; /* application/octet-stream */ +extern const char MIME_APP_PDF[]; /* application/pdf */ +extern const char MIME_APP_PS[]; /* application/postscript */ +extern const char MIME_APP_XHTML[]; /* application/xhtml+xml */ +extern const char MIME_APP_XML[]; /* application/xml-dtd */ +extern const char MIME_APP_ZIP[]; /* application/zip */ +extern const char MIME_APP_GZIP[]; /* application/x-gzip */ +extern const char MIME_APP_BTOR[]; /* application/x-bittorrent */ +extern const char MIME_APP_TEX[]; /* application/x-tex */ +extern const char MIME_URLENCODED[]; /* application/x-www-form-urlencoded */ +extern const char MIME_TEXT_HTML[]; /* text/html */ +extern const char MIME_TEXT_JS[]; /* text/javascript */ +extern const char MIME_TEXT_PLAIN[]; /* text/plain */ +extern const char MIME_TEXT_XML[]; /* text/xml */ +extern const char MIME_TEXT_CSS[]; /* text/css */ +extern const char MIME_IMAGE_GIF[]; /* image/gif */ +extern const char MIME_IMAGE_JPEG[]; /* image/jpeg */ +extern const char MIME_IMAGE_PJPEG[]; /* image/pjpeg */ +extern const char MIME_IMAGE_PNG[]; /* image/png */ +extern const char MIME_IMAGE_SVG[]; /* image/svg+xml */ +extern const char MIME_IMAGE_TIFF[]; /* image/tiff */ +extern const char MIME_IMAGE_ICON[]; /* image/vnd.microsoft.icon */ +extern const char MIME_IMAGE_WBMP[]; /* image/vnd.wap.wbmp */ +extern const char MIME_MPART_MIXED[]; /* multipart/mixed */ +extern const char MIME_MPART_ALT[]; /* multipart/alternative */ +extern const char MIME_MPART_REL[]; /* multipart/related */ +extern const char MIME_MPART_FORM[]; /* multipart/form-data */ +extern const char MIME_MPART_SIGN[]; /* multipart/signed */ +extern const char MIME_MPART_ENCR[]; /* multipart/encrypted */ + +typedef enum http_method +{ + METHOD_NONE, + METHOD_GET, + METHOD_POST, + METHOD_HEAD, + METHOD_PUT, + METHOD_CONNECT, + METHOD_OPTIONS, + METHOD_DELETE, + METHOD_TRACE, + METHOD_PATCH +} http_mt_t; + +typedef enum http_conn_type +{ + CT_NONE, + CT_CLOSE, + CT_KEEP_ALIVE +} http_ct_t; + +/* http content disposition */ +typedef struct http_cdh +{ + const char *disp; /* Content-Disposition: [form-data]; name="File1"; filename="photo.jpg" */ + const char *disp_name; /* Content-Disposition: form-data; name="[File1]"; filename="photo.jpg" */ + const char *disp_file; /* Content-Disposition: form-data; name="File1"; filename="[photo.jpg]" */ +} http_cdh_t; + +typedef struct http_range +{ + uint32_t range_size; + uint32_t range_from; + uint32_t range_to; +} http_rng_t; + +/* http response/request entity */ + +typedef struct http_cont +{ + http_cdh_t disp; /* Content-Disposition: [form-data; name="File1"; filename="photo.jpg"] */ + const char *encoding; /* Content-Encoding: [...] */ + const char *language; /* Content-Language: [en, ase, ru] */ + int length; /* Content-Length: [123] */ + const char *location; /* Content-Location: [...] */ + const char *md5; /* Content-MD5: [Q2hlY2sgSW50ZWdyaXR5IQ==] */ + http_rng_t range; /* Content-Range: [64397516-80496894/160993792] */ + const char *type; /* Content-Type: [multipart/form-data] */ + const char *boundary; /* Content-Type: multipart/form-data; boundary="[Asrf456BGe4h]" */ + const char *charset; /* Content-Type: Content-Type: text/html; charset=[UTF-8] */ + const char *ver; /* Content-Version: [...] */ + const char *expires; /* Expires: [Tue, 31 Jan 2012 15:02:53 GMT] */ + const char *modified; /* Last-Modified: [...] */ + const char *link; /* link: [...] */ + const char *title; /* Title: [...] */ +} http_cont_t; + +/* http transfer encoding */ +typedef enum http_tenc +{ + TENC_NONE, /* - */ + TENC_CHUNKED /* Transfer-Encoding: chunked */ +} http_tenc_t; + +/* http transfer encoding */ +typedef struct httpaccept +{ + int count; + const char *type[HTTP_REQ_MAX_ACCEPT]; /* text/html */ + int level[HTTP_REQ_MAX_ACCEPT]; /* level=[1] */ + float q[HTTP_REQ_MAX_ACCEPT]; /* q=[0.8] */ +} httpaccept_t; + +typedef struct http_req +{ + /* request line */ + http_mt_t method; /* GET */ + const char *uri; /* /path */ + int nparams; /* */ + const char *params[HTTP_REQ_MAX_PARAMS]; /* param1, param2 */ + const char *values[HTTP_REQ_MAX_PARAMS]; /* value1, value2 */ + const char *ver; /* HTTP/1.1 */ + + /* general header */ + http_ct_t connection; /* Connection: [keep-alive] */ + char *via; /* Via: [1.0 fred, 1.1 example.com] */ + + /* request header */ + httpaccept_t accept; + const char *accept_charset; /* Accept-Charset: [utf-8] */ + const char *accept_encoding; /* Accept-Encoding: [gzip, deflate] */ + const char *accept_language; /* Accept-Language: [en-US;q=0.5,en;q=0.3] */ + const char *authorization; /* Authorization: [Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==] */ + const char *expect; /* Expect: [100-continue] */ + const char *from; /* From: [user@example.com] */ + const char *host; /* Host: [wikipedia.org] */ + const char *if_match; /* If-Match: "[737060cd8c284d8af7ad3082f209582d]" */ + const char *if_modified_since; /* If-Modified-Since: [Sat, 29 Oct 1994 19:43:31 GMT] */ + const char *if_none_match; /* If-None-Match: "[737060cd8c284d8af7ad3082f209582d]" */ + const char *if_range; /* If-Range: "[737060cd8c284d8af7ad3082f209582d]" */ + const char *if_unmodified_since; /* If-Unmodified-Since: [Sat, 29 Oct 1994 19:43:31 GMT] */ + int keep_alive; /* Keep-Alive: [300] */ + int max_forwards; /* Max-Forwards: [10] */ + const char *proxy_authorization; /* Proxy-Authorization: [Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==] */ + http_rng_t range; /* Range: bytes=[50000-99999],250000-399999,500000- */ + const char *referer; /* Referer: [http://en.wikipedia.org/wiki/Main_Page] */ + const char *te; /* TE: [trailers, deflate] */ + const char *user_agent; /* User-Agent: [Mozilla/5.0] */ + + /* content and cookies */ + http_cont_t content; /**/ + const char *cookie; /* Cookie: [Cookie data] */ +} http_req_t; + +typedef enum http_resp_flag +{ + RESPF_NONE = 0, + RESPF_KEEPALIVE = 1, + RESPF_CHUNKED = 2, + RESPF_NOCACH = 4, + RESPF_DEFLATE = 8, + RESPF_GZIP = 16 +} http_respf_t; + +typedef struct http_resp +{ + + char *mime_ver; /* MIME-Version: 1.0 */ + char *pragma; /* Pragma: [no-cache] */ + int code; /* HTTP/1.1 [200] OK */ + http_cont_t content; /**/ + http_ct_t connection; /* Connection: [keep-alive] */ + const char *cache_control; /* Cache-Control: [no-cache] */ + const char *etag; /* ETag: "[56d-9989200-1132c580]" */ + const char *location; /* Location: [http://example.com/about.html#contacts] */ + const char *proxy_authenticate; /* Proxy-Authenticate: [...] */ + const char *Public; /* Public: [...] */ + int retry_after; /* Retry-After: [123] */ + const char *server; /* Server: [Name] */ + http_tenc_t transfer_encoding; /* Transfer-Encoding: [chunked] */ + const char *vary; /* Vary: [...] */ + const char *www_authenticate; /* WWW-Authenticate: [...] */ + char *upgrade; /* Upgrade: [HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11] */ + const char *exthdr; /* extended header options */ +} http_resp_t; + +const char *http_req_val(const http_req_t *req, const char *param, const char *def); +void http_resp_init(http_resp_t *resp, int code, const char *mime, int flags); +const char *http_dict_mime(const char *mime); +const char *http_code_str(int code); +int http_resp_len(const http_resp_t *resp); +int http_resp_str(const http_resp_t *resp, char *str, int size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/modules/web/http_server_simple/httpio/httpio.c b/modules/web/http_server_simple/httpio/httpio.c new file mode 100644 index 000000000..039346b4d --- /dev/null +++ b/modules/web/http_server_simple/httpio/httpio.c @@ -0,0 +1,1330 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 by Sergey Fetisov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "httpio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef MEM_ALIGNMENT +#define MEM_ALIGNMENT 4 +#endif + +#define CODE_DONE 0 +#define CODE_IN_EOF 1 +#define CODE_OUT_EOF 2 +#define CODE_CNT_LIM 3 +#define CODE_ERROR 4 +#define CODE_MEM_ERROR 5 + +#define STATE0_ERROR -1 +#define STATE0_RQLINE 0 +#define STATE0_RQHDR 1 +#define STATE0_URLENC 2 +#define STATE0_MPART_DATA 3 +#define STATE0_MPART_INIT 4 +#define STATE0_MPART_HDR 5 +#define STATE0_DONE 6 +#define STATE0_RESET 7 + +#define STATE_RQLINE_METHOD 0 +#define STATE_RQLINE_URI 1 +#define STATE_RQLINE_KEY 2 +#define STATE_RQLINE_VAL 3 +#define STATE_RQLINE_VER 4 +#define STATE_RQLINE_SKIPN 5 + +#define STATE_HDRRD_INIT 0 +#define STATE_HDRRD_KEYRD 1 +#define STATE_HDRRD_KEY 2 +#define STATE_HDRRD_SKIPWS 3 +#define STATE_HDRRD_VALRD 4 +#define STATE_HDRRD_VAL 5 +#define STATE_HDRRD_CSKIPWS 6 +#define STATE_HDRRD_CVALRD 7 +#define STATE_HDRRD_CVAL 8 +#define STATE_HDRRD_SKIPN 9 +#define STATE_HDRRD_LASTN 10 +#define STATE_HDRRD_FINISHED 11 + +#define STATE_MPART_DATARD 0 +#define STATE_MPART_NOTBNDRY 1 +#define STATE_MPART_ENDCHECK 2 +#define STATE_MPART_SKIPN 3 +#define STATE_MPART_SKIPMINUS 4 +#define STATE_MPART_SKIPR 5 +#define STATE_MPART_LASTN 6 +#define STATE_MPART_BNDRY 7 +#define STATE_MPART_FINISHED 8 + +#define STATE_URLENC_INIT 0 +#define STATE_URLENC_KEY 1 +#define STATE_URLENC_CHUNK 2 +#define STATE_URLENC_END 3 +#define STATE_URLENC_FINISHED 4 +#define STATE_URLENC_KEYRD 5 +#define STATE_URLENC_VALRD 6 + +typedef struct field_info fld_t; + +struct http_input +{ + struct + { + char *data; + int pos; + int size; + int tot; + } in; + struct + { + char *data; + int pos; + int size; + } out; + struct + { + char *data; + int size; + int used; + } pool; + struct + { + char stop; + } lexrd; + struct + { + char state; + } rqline; + struct + { + int state; + } hdrrd; + struct + { + int state; + const fld_t *field; + } rqhdr; + struct + { + int state; + } setter; + struct + { + int state; + int bndry_cntr; + int bndry_pos; + const char *bndry; + int bndry_size; + } mpart; + struct + { + int state; + char extra[4]; + } urlenc; + http_req_t req; + http_post_t post; + int used; + int state; +}; + +static const char *METHODS_STR[] = +{ + "", + "GET", + "POST", + "HEAD", + "PUT", + "CONNECT", + "OPTIONS", + "DELETE", + "TRACE", + "PATCH" +}; + +static http_mt_t str_to_method(const char *str) +{ + int res; + for (res = METHOD_GET; res <= METHOD_PATCH; res++) + if (strcmp(METHODS_STR[res], str) == 0) + return (http_mt_t)res; + return METHOD_NONE; +} + +bool str2hex(const char *str, uint8_t *hex) +{ + int d; + + d = str[0]; + if (d >= 'a') d -= 32; /* to upper case */ + if (d >= '0' && d <= '9') + d -= '0'; else + if (d >= 'A' && d <= 'F') + d += 10 - 'A'; else + return false; + *hex = d << 4; + + d = str[1]; + if (d >= 'a') d -= 32; /* to upper case */ + if (d >= '0' && d <= '9') + d -= '0'; else + if (d >= 'A' && d <= 'F') + d += 10 - 'A'; else + return false; + *hex |= d; + + return true; +} + +int http_unescape(char *str, int len, int null) +{ + int res = 0; + char *c = str; + (void)len; + while (*c != 0 && len > 0) + { + if (*c == '%' && len > 2) + { + uint8_t hex; + if (str2hex(c + 1, &hex)) + { + *str = hex; + str++; + res++; + c += 3; + len -= 3; + continue; + } + } + *str = *c == '+' ? ' ' : *c; + str++; + res++; + c++; + len--; + } + if (null) *str = 0; + return res; +} + +static http_ct_t str_to_ct(const char *str) +{ + if (strcmp(str, "close") == 0) return CT_CLOSE; + if (strcmp(str, "keep-alive") == 0) return CT_KEEP_ALIVE; + return CT_NONE; +} + +static void httpi_acc_field_setter(httpi_t *httpi, int arg); +static void httpi_str_field_setter(httpi_t *httpi, int arg); +static void httpi_ct_field_setter(httpi_t *httpi, int arg); +static void httpi_cntt_field_setter(httpi_t *httpi, int arg); +static void httpi_int_field_setter(httpi_t *httpi, int arg); +static void httpi_post_cdh_setter(httpi_t *httpi, int arg); + +#define OFFSET_REQ offsetof(httpi_t, req) +#define OFFSET_FLD(name) offsetof(http_req_t, name) +#define OFFSET_CONT_FLD(name) offsetof(http_cont_t, name) +#define OFFSET_POST offsetof(httpi_t, post) +#define OFFSET_POST_FLD(name) offsetof(http_post_t, name) + +struct field_info +{ + const char *name; + int offset; + void (*setter)(httpi_t *httpi, int arg); + int composite; +}; + +struct field_info hdr_fields[] = +{ + /* hdr field name */ + { "Accept", OFFSET_REQ + OFFSET_FLD(accept), httpi_acc_field_setter, 1 }, + { "Accept-Charset", OFFSET_REQ + OFFSET_FLD(accept_charset), httpi_str_field_setter, 0 }, + { "Accept-Encoding", OFFSET_REQ + OFFSET_FLD(accept_encoding), httpi_str_field_setter, 0 }, + { "Accept-Language", OFFSET_REQ + OFFSET_FLD(accept_language), httpi_str_field_setter, 0 }, + { "Authorization", OFFSET_REQ + OFFSET_FLD(authorization), httpi_str_field_setter, 0 }, + { "Connection", OFFSET_REQ + OFFSET_FLD(connection), httpi_ct_field_setter, 0 }, + { "Expect", OFFSET_REQ + OFFSET_FLD(expect), httpi_str_field_setter, 0 }, + { "From", OFFSET_REQ + OFFSET_FLD(from), httpi_str_field_setter, 0 }, + { "Host", OFFSET_REQ + OFFSET_FLD(host), httpi_str_field_setter, 0 }, + { "If-Match", OFFSET_REQ + OFFSET_FLD(if_match), httpi_str_field_setter, 0 }, + { "If-Modified-Since", OFFSET_REQ + OFFSET_FLD(if_modified_since), httpi_str_field_setter, 0 }, + { "If-None-Match", OFFSET_REQ + OFFSET_FLD(if_none_match), httpi_str_field_setter, 0 }, + { "If-Range", OFFSET_REQ + OFFSET_FLD(if_range), httpi_str_field_setter, 0 }, + { "If-Unmodified-Since", OFFSET_REQ + OFFSET_FLD(if_unmodified_since), httpi_str_field_setter, 0 }, + { "Max-Forwards", OFFSET_REQ + OFFSET_FLD(max_forwards), httpi_int_field_setter, 0 }, + { "Proxy-Authorization", OFFSET_REQ + OFFSET_FLD(proxy_authorization), httpi_str_field_setter, 0 }, + { "Referer", OFFSET_REQ + OFFSET_FLD(referer), httpi_str_field_setter, 0 }, + { "TE", OFFSET_REQ + OFFSET_FLD(te), httpi_str_field_setter, 0 }, + { "User-Agent", OFFSET_REQ + OFFSET_FLD(user_agent), httpi_str_field_setter, 0 }, + { "Cookie", OFFSET_REQ + OFFSET_FLD(cookie), httpi_str_field_setter, 0 }, + { "Keep-Alive", OFFSET_REQ + OFFSET_FLD(keep_alive), httpi_int_field_setter, 0 }, + { "Content-Type", OFFSET_REQ + OFFSET_FLD(content), httpi_cntt_field_setter, 1 }, + { "Content-Encoding", OFFSET_REQ + OFFSET_FLD(content) + OFFSET_CONT_FLD(encoding), httpi_str_field_setter, 0 }, + { "Content-Language", OFFSET_REQ + OFFSET_FLD(content) + OFFSET_CONT_FLD(language), httpi_str_field_setter, 0 }, + { "Content-Length", OFFSET_REQ + OFFSET_FLD(content) + OFFSET_CONT_FLD(length), httpi_int_field_setter, 0 }, + { "Content-Location", OFFSET_REQ + OFFSET_FLD(content) + OFFSET_CONT_FLD(location), httpi_str_field_setter, 0 }, + { "Content-MD5", OFFSET_REQ + OFFSET_FLD(content) + OFFSET_CONT_FLD(md5), httpi_str_field_setter, 0 }, + { "Content-Version", OFFSET_REQ + OFFSET_FLD(content) + OFFSET_CONT_FLD(ver), httpi_str_field_setter, 0 }, + { "Expires", OFFSET_REQ + OFFSET_FLD(content) + OFFSET_CONT_FLD(expires), httpi_str_field_setter, 0 }, + { "Last-Modified", OFFSET_REQ + OFFSET_FLD(content) + OFFSET_CONT_FLD(modified), httpi_str_field_setter, 0 }, + { "Link", OFFSET_REQ + OFFSET_FLD(content) + OFFSET_CONT_FLD(link), httpi_str_field_setter, 0 }, + { "Title", OFFSET_REQ + OFFSET_FLD(content) + OFFSET_CONT_FLD(title), httpi_str_field_setter, 0 }, + { NULL } +}; + +struct field_info mpart_fields[] = +{ + { "Content-Type", OFFSET_POST + OFFSET_POST_FLD(content_type), httpi_str_field_setter, 1 }, + { "Content-Disposition", OFFSET_POST, httpi_post_cdh_setter, 1 }, + { NULL } +}; + +int httpi_readlex(httpi_t *httpi, int *stack, const char *stop, int skipstop, int cntlim) +{ + while (true) + { + char c; + if (httpi->in.pos == httpi->in.size) return CODE_IN_EOF; + c = httpi->in.data[httpi->in.pos]; + if (httpi->out.pos >= httpi->out.size - 1) return CODE_OUT_EOF; + if (c == stop[0] || c == stop[1] || c == stop[2] || c == stop[3]) + { + if (skipstop) httpi->in.pos++; + httpi->out.data[httpi->out.pos] = 0; + stack[1] = c; + return CODE_DONE; + } + httpi->out.data[httpi->out.pos] = c; + httpi->out.pos++; + httpi->in.pos++; + httpi->in.tot++; + *stack += 1; + if (*stack == cntlim) return CODE_CNT_LIM; + } +} + +int httpi_skip_char(httpi_t *httpi, int char1, int char2) +{ + char c; + if (httpi->in.pos == httpi->in.size) return CODE_IN_EOF; + c = httpi->in.data[httpi->in.pos]; + if (c != char1 && c != char2) return CODE_ERROR; + httpi->in.pos++; + httpi->in.tot++; + return CODE_DONE; +} + +static inline void httpi_inc_used(httpi_t *httpi, int size) +{ + httpi->pool.used += size; + httpi->out.data = httpi->pool.data + httpi->pool.used; + httpi->out.size = httpi->pool.size - httpi->pool.used; + httpi->out.pos = 0; +} + +static inline void httpi_set_used(httpi_t *httpi, int used) +{ + httpi->pool.used = used; + httpi->out.data = httpi->pool.data + httpi->pool.used; + httpi->out.size = httpi->pool.size - httpi->pool.used; + httpi->out.pos = 0; +} + +#define FIELD ((char *)httpi + httpi->rqhdr.field->offset) + +static void httpi_str_field_setter(httpi_t *httpi, int arg) +{ + *(const char **)FIELD = httpi->out.data; + httpi_inc_used(httpi, http_unescape(httpi->out.data, httpi->out.pos, 1) + 1); +} + +static void httpi_int_field_setter(httpi_t *httpi, int arg) +{ + *(int *)FIELD = strtol(httpi->out.data, NULL, 10); + httpi->out.pos = 0; +} + +static void httpi_ct_field_setter(httpi_t *httpi, int arg) +{ + *(http_ct_t *)FIELD = str_to_ct(httpi->out.data); + httpi->out.pos = 0; +} + +static void httpi_acc_field_setter(httpi_t *httpi, int arg) +{ + httpaccept_t *acc = (httpaccept_t *)FIELD; + /* text/html,application/xhtml+xml,application/xml;q=0.9,*;q=0.8 */ + if (acc->count >= HTTP_REQ_MAX_ACCEPT) return; + switch (httpi->setter.state) + { + case 0: /* wait type name */ + acc->type[acc->count] = httpi->out.data; + acc->q[acc->count] = 1; + acc->count++; + httpi_inc_used(httpi, http_unescape(httpi->out.data, httpi->out.pos, 1) + 1); + if (arg == ';') httpi->setter.state = 1; + break; + case 1: /* wait param name */ + httpi->out.pos = 0; + if (arg != '=') + { + httpi->setter.state = 0; + break; + } + if (strcmp(httpi->out.data, "level") == 0) + httpi->setter.state = 2; + else + if (strcmp(httpi->out.data, "q") == 0) + httpi->setter.state = 3; + break; + case 2: /* wait level value */ + acc->level[acc->count - 1] = strtol(httpi->out.data, NULL, 10); + if (arg == ',') + httpi->setter.state = 1; else + httpi->setter.state = 0; + httpi->out.pos = 0; + break; + case 3: /* wait q value */ + acc->q[acc->count - 1] = strtof(httpi->out.data, NULL); + if (arg == ';') + httpi->setter.state = 1; else + httpi->setter.state = 0; + httpi->out.pos = 0; + break; + } +} + +static void httpi_cntt_field_setter(httpi_t *httpi, int arg) +{ + http_cont_t *cont = (http_cont_t *)FIELD; + /* multipart/form-data; boundary=Asrf456BGe4h */ + switch (httpi->setter.state) + { + case 0: /* wait type name */ + cont->type = httpi->out.data; + httpi_inc_used(httpi, http_unescape(httpi->out.data, httpi->out.pos, 1) + 1); + if (arg == ';') + httpi->setter.state = 1; else + httpi->setter.state = 10; + break; + case 1: /* wait param name */ + httpi->out.pos = 0; + if (arg != '=') + { + httpi->setter.state = 10; + break; + } + if (strcmp(httpi->out.data, "boundary") == 0) + { + httpi->out.data[0] = '\r'; + httpi->out.data[1] = '\n'; + httpi->out.data[2] = '-'; + httpi->out.data[3] = '-'; + httpi->mpart.bndry = httpi->out.data + 2; + httpi->mpart.bndry_size = 2; + httpi_inc_used(httpi, 4); + httpi->setter.state = 2; + } + else + httpi->setter.state = 10; + break; + case 2: /* boundary */ + httpi->mpart.bndry_size += httpi->out.pos; + cont->boundary = httpi->out.data; + httpi_inc_used(httpi, httpi->out.pos + 1); + httpi->setter.state = 10; + break; + case 10: + break; + } +} + +static void httpi_post_cdh_setter(httpi_t *httpi, int arg) +{ + http_post_t *post = (http_post_t *)FIELD; + /* form-data; name="AttachedFile1"; filename="photo-1.jpg" */ + switch (httpi->setter.state) + { + case 0: /* wait type name */ + post->disp_type = httpi->out.data; + httpi_inc_used(httpi, http_unescape(httpi->out.data, httpi->out.pos, 1) + 1); + if (arg == ';') + httpi->setter.state = 1; else + httpi->setter.state = 10; + break; + case 1: /* wait param name */ + httpi->out.pos = 0; + httpi->setter.state = 10; + if (arg != '=') break; + if (strcmp(httpi->out.data, "name") == 0) + httpi->setter.state = 2; else + if (strcmp(httpi->out.data, "filename") == 0) + httpi->setter.state = 3; + break; + case 2: /* wait name */ + if (arg == ',') + httpi->setter.state = 0; else + httpi->setter.state = 1; + post->name = httpi->out.data; + httpi_inc_used(httpi, http_unescape(httpi->out.data, httpi->out.pos, 1) + 1); + break; + case 3: /* wait filename */ + if (arg == ',') + httpi->setter.state = 0; else + httpi->setter.state = 1; + post->file = httpi->out.data; + httpi_inc_used(httpi, http_unescape(httpi->out.data, httpi->out.pos, 1) + 1); + break; + case 10: + break; + } +} + +#undef FIELD + +int httpi_lex_reader(httpi_t *httpi, const char *stop, int skipstop, int cntlim) +{ + while (true) + { + char c; + if (httpi->out.pos >= httpi->out.size - 1) return CODE_OUT_EOF; + if (httpi->in.tot == cntlim && cntlim >= 0) + { + httpi->out.data[httpi->out.pos] = 0; + return CODE_CNT_LIM; + } + if (httpi->in.pos == httpi->in.size) return CODE_IN_EOF; + c = httpi->in.data[httpi->in.pos]; + if (c == stop[0] || c == stop[1] || c == stop[2] || c == stop[3]) + { + if (skipstop) + { + httpi->in.pos++; + httpi->in.tot++; + } + httpi->out.data[httpi->out.pos] = 0; + httpi->lexrd.stop = c; + return CODE_DONE; + } + httpi->out.data[httpi->out.pos] = c; + httpi->out.pos++; + httpi->in.pos++; + httpi->in.tot++; + } +} + +int httpi_rqline(httpi_t *httpi) +{ + int code; +start: + switch (httpi->rqline.state) + { + case STATE_RQLINE_METHOD: + code = httpi_lex_reader(httpi, "\r \0\0", 1, -1); + if (code != CODE_DONE) return code; + if (httpi->lexrd.stop == '\r') return CODE_ERROR; + httpi->req.method = str_to_method(httpi->out.data); + httpi->out.pos = 0; + httpi->rqline.state = STATE_RQLINE_URI; + goto start; + case STATE_RQLINE_URI: + code = httpi_lex_reader(httpi, "\r ?\0", 1, -1); + if (code != CODE_DONE) return code; + if (httpi->lexrd.stop == '\r') return CODE_ERROR; + if (httpi->lexrd.stop == '?') + httpi->rqline.state = STATE_RQLINE_KEY; else + httpi->rqline.state = STATE_RQLINE_VER; + httpi->req.uri = httpi->out.data; + httpi_inc_used(httpi, http_unescape(httpi->out.data, httpi->out.pos, 1) + 1); + goto start; + case STATE_RQLINE_KEY: + case STATE_RQLINE_VAL: + code = httpi_lex_reader(httpi, "\r &=", 1, -1); + if (code != CODE_DONE) return code; + if (httpi->req.nparams >= HTTP_REQ_MAX_PARAMS) return CODE_MEM_ERROR; + if (httpi->rqline.state == STATE_RQLINE_KEY) + { + httpi->req.params[httpi->req.nparams] = httpi->out.data; + httpi->req.values[httpi->req.nparams] = NULL; + httpi->req.nparams++; + } + else + { + httpi->req.values[httpi->req.nparams - 1] = httpi->out.data; + } + httpi_inc_used(httpi, http_unescape(httpi->out.data, httpi->out.pos, 1) + 1); + switch (httpi->lexrd.stop) + { + case ' ': httpi->rqline.state = STATE_RQLINE_VER; break; + case '&': httpi->rqline.state = STATE_RQLINE_KEY; break; + case '=': httpi->rqline.state = STATE_RQLINE_VAL; break; + case '\r': return CODE_ERROR; + } + goto start; + case STATE_RQLINE_VER: + code = httpi_lex_reader(httpi, "\r\0\0\0", 1, -1); + if (code != CODE_DONE) return code; + httpi->req.ver = httpi->out.data; + httpi_inc_used(httpi, http_unescape(httpi->out.data, httpi->out.pos, 1) + 1); + httpi->rqline.state = STATE_RQLINE_SKIPN; + goto start; + case STATE_RQLINE_SKIPN: + code = httpi_skip_char(httpi, '\n', '\0'); + return code; + default: + return CODE_ERROR; + } +} + +const fld_t *find_field(const fld_t *fields, const char *name) +{ + int i; + char c = name[0]; + for (i = 0; fields[i].name != NULL; i++) + if (c == fields[i].name[0]) + if (strcmp(fields[i].name, name) == 0) + return fields + i; + return NULL; +} + +int httpi_hdr_reader(httpi_t *httpi, int composite_val) +{ + int code; +start: + switch (httpi->hdrrd.state) + { + case STATE_HDRRD_INIT: + httpi->hdrrd.state = STATE_HDRRD_KEYRD; + httpi->out.pos = 0; + goto start; + case STATE_HDRRD_KEYRD: + code = httpi_lex_reader(httpi, "\r:\0\0", 1, -1); + if (code != CODE_DONE) return code; + if (httpi->lexrd.stop == '\r') + { + if (httpi->out.pos != 0) return CODE_ERROR; + httpi->hdrrd.state = STATE_HDRRD_LASTN; + goto start; + } + httpi->hdrrd.state = STATE_HDRRD_KEY; + return CODE_DONE; + case STATE_HDRRD_KEY: + httpi->hdrrd.state = composite_val ? STATE_HDRRD_CSKIPWS : STATE_HDRRD_SKIPWS; + goto start; + case STATE_HDRRD_FINISHED: + return CODE_DONE; + case STATE_HDRRD_SKIPWS: + case STATE_HDRRD_CSKIPWS: + if (httpi->in.pos >= httpi->in.size) return CODE_IN_EOF; + if (httpi->in.data[httpi->in.pos] == ' ') + { + httpi->in.pos++; + httpi->in.tot++; + goto start; + } + httpi->hdrrd.state++; + goto start; + case STATE_HDRRD_VALRD: + code = httpi_lex_reader(httpi, "\r\0\0\0", 1, -1); + if (code != CODE_DONE) return code; + httpi->hdrrd.state = STATE_HDRRD_VAL; + return CODE_DONE; + case STATE_HDRRD_CVALRD: + code = httpi_lex_reader(httpi, "\r;,=", 1, -1); + if (code != CODE_DONE) return code; + httpi->hdrrd.state = STATE_HDRRD_CVAL; + return CODE_DONE; + case STATE_HDRRD_VAL: + httpi->hdrrd.state = STATE_HDRRD_SKIPN; + goto start; + case STATE_HDRRD_CVAL: + httpi->hdrrd.state = httpi->lexrd.stop == '\r' ? + STATE_HDRRD_SKIPN : STATE_HDRRD_CSKIPWS; + goto start; + case STATE_HDRRD_SKIPN: + case STATE_HDRRD_LASTN: + if (httpi->in.pos >= httpi->in.size) return CODE_IN_EOF; + if (httpi->in.data[httpi->in.pos] != '\n') return CODE_ERROR; + httpi->in.pos++; + httpi->in.tot++; + httpi->hdrrd.state = httpi->hdrrd.state == STATE_HDRRD_LASTN ? + STATE_HDRRD_FINISHED : STATE_HDRRD_KEYRD; + goto start; + default: + return CODE_ERROR; + } +} + +int httpi_request_hdr_reader(httpi_t *httpi, const fld_t *fields) +{ + int code; +start: + switch (httpi->rqhdr.state) + { + case 0: /* initial */ + httpi->rqhdr.field = NULL; + httpi->rqhdr.state = 1; + httpi->hdrrd.state = 0; + case 1: /* wait key */ + code = httpi_hdr_reader(httpi, 0); + if (code != CODE_DONE) return code; + if (httpi->hdrrd.state == STATE_HDRRD_FINISHED) return CODE_DONE; + httpi->rqhdr.field = find_field(fields, httpi->out.data); + httpi->out.pos = 0; + httpi->setter.state = 0; + httpi->rqhdr.state = 2; + case 2: /* read value or composite value */ + code = 0; + if (httpi->rqhdr.field != NULL) + code = httpi->rqhdr.field->composite; + code = httpi_hdr_reader(httpi, code); + if (code != CODE_DONE) return code; + if (httpi->rqhdr.field != NULL) + httpi->rqhdr.field->setter(httpi, httpi->lexrd.stop); else + httpi->out.pos = 0; + httpi->rqhdr.state = httpi->lexrd.stop == '\r' ? 1 : 2; + goto start; + } + return CODE_ERROR; +} + +#define MPART_STATE httpi->mpart.state +#define MPART_CNTR httpi->mpart.bndry_cntr +#define MPART_POS httpi->mpart.bndry_pos + +int httpi_mpart(httpi_t *httpi) +{ + int avail; + avail = httpi->out.size - httpi->out.pos; + if (avail <= 1) return CODE_OUT_EOF; + +start: + switch (MPART_STATE) + { + case STATE_MPART_DATARD: + while (true) + { + char c; + if (httpi->in.pos >= httpi->in.size) return CODE_IN_EOF; + c = httpi->in.data[httpi->in.pos]; + if (httpi->mpart.bndry[MPART_CNTR] == c) + { + httpi->in.pos++; + httpi->in.tot++; + MPART_CNTR++; + if (MPART_CNTR != httpi->mpart.bndry_size) continue; + MPART_CNTR = 0; + MPART_STATE = STATE_MPART_ENDCHECK; + goto start; + } + /* not a boundary */ + if (MPART_CNTR > 0) + { + MPART_POS = 0; + MPART_STATE = STATE_MPART_NOTBNDRY; + goto start; + } + if (httpi->out.pos >= httpi->out.size - 1) + { + httpi->out.data[httpi->out.pos] = 0; + return CODE_OUT_EOF; + } + httpi->out.data[httpi->out.pos] = c; + httpi->out.pos++; + httpi->in.pos++; + httpi->in.tot++; + } + case STATE_MPART_NOTBNDRY: + while (true) + { + if (httpi->out.pos >= httpi->out.size - 1) + { + httpi->out.data[httpi->out.pos] = 0; + return CODE_OUT_EOF; + } + httpi->out.data[httpi->out.pos] = httpi->mpart.bndry[MPART_POS]; + MPART_POS++; + httpi->out.pos++; + MPART_CNTR--; + if (MPART_CNTR != 0) continue; + MPART_STATE = STATE_MPART_DATARD; + goto start; + } + case STATE_MPART_ENDCHECK: /* skip '-' or '\r' */ + if (httpi->in.pos >= httpi->in.size) return CODE_IN_EOF; + switch (httpi->in.data[httpi->in.pos]) + { + case '\r': MPART_STATE = STATE_MPART_SKIPN; break; + case '-': MPART_STATE = STATE_MPART_SKIPMINUS; break; + default: return CODE_ERROR; + } + httpi->in.pos++; + httpi->in.tot++; + goto start; + case STATE_MPART_SKIPMINUS: /* skip '-' */ + if (httpi->in.pos >= httpi->in.size) return CODE_IN_EOF; + if (httpi->in.data[httpi->in.pos] != '-') return CODE_ERROR; + MPART_STATE = STATE_MPART_SKIPR; + httpi->in.pos++; + httpi->in.tot++; + goto start; + case STATE_MPART_SKIPR: /* skip 'r' */ + if (httpi->in.pos >= httpi->in.size) return CODE_IN_EOF; + if (httpi->in.data[httpi->in.pos] != '\r') return CODE_ERROR; + MPART_STATE = STATE_MPART_LASTN; + httpi->in.pos++; + httpi->in.tot++; + goto start; + case STATE_MPART_LASTN: + case STATE_MPART_SKIPN: /* skip 'n' */ + if (httpi->in.pos >= httpi->in.size) return CODE_IN_EOF; + if (httpi->in.data[httpi->in.pos] != '\n') return CODE_ERROR; + httpi->in.pos++; + httpi->in.tot++; + MPART_STATE = MPART_STATE == STATE_MPART_LASTN ? + STATE_MPART_FINISHED : STATE_MPART_BNDRY; + return CODE_DONE; + case STATE_MPART_BNDRY: + MPART_STATE = STATE_MPART_DATARD; + goto start; + default: + return CODE_ERROR; + } +} + +/* +returns: + CODE_DONE + in states: + STATE_URLENC_KEY - key readed + STATE_URLENC_CHUNK - value chunk readed + STATE_URLENC_END - value readed + STATE_URLENC_FINISHED - all done + CODE_IN_EOF + in any state + not an error + CODE_MEM_ERROR + no memory in pool +*/ + +int httpi_urlenc(httpi_t *httpi) +{ + char c; + int code; +start: + switch (httpi->urlenc.state) + { + case STATE_URLENC_INIT: + httpi->in.tot = 0; + httpi->out.pos = 0; + httpi->urlenc.state = STATE_URLENC_KEYRD; + httpi->used = httpi->pool.used; + case STATE_URLENC_KEYRD: + code = httpi_lex_reader(httpi, "=&\0\0", 0, httpi->req.content.length); + switch (code) + { + case CODE_OUT_EOF: + return CODE_MEM_ERROR; + case CODE_CNT_LIM: + if (httpi->out.pos <= 0) + { + httpi->urlenc.state = STATE_URLENC_END; + return CODE_DONE; + } + case CODE_DONE: + httpi->out.pos = http_unescape(httpi->out.data, httpi->out.pos, 1); + httpi->urlenc.state = STATE_URLENC_KEY; + return CODE_DONE; + default: + return code; + } + case STATE_URLENC_KEY: + if (httpi->in.tot >= httpi->req.content.length) /* CODE_CNT_LIM! */ + goto return_empty_val; + c = httpi->in.data[httpi->in.pos]; + httpi->in.pos++; + httpi->in.tot++; + if (c == '&') goto return_empty_val; + /* read value now! */ + httpi->urlenc.state = STATE_URLENC_VALRD; + httpi->out.pos = 0; + case STATE_URLENC_VALRD: + code = httpi_lex_reader(httpi, "&\0\0\0", 0, httpi->req.content.length); + switch (code) + { + case CODE_OUT_EOF: + if (httpi->out.pos < 2) return CODE_MEM_ERROR; + *(uint32_t *)httpi->urlenc.extra = 0; + if (httpi->out.data[httpi->out.pos - 1] == '%') + { + httpi->urlenc.extra[0] = '%'; + httpi->out.pos--; + } + else + { + if (httpi->out.data[httpi->out.pos - 2] == '%') + { + httpi->urlenc.extra[0] = '%'; + httpi->urlenc.extra[1] = httpi->out.data[httpi->out.pos - 1]; + httpi->out.pos -= 2; + } + } + httpi->out.pos = http_unescape(httpi->out.data, httpi->out.pos, 1); + httpi->urlenc.state = STATE_URLENC_CHUNK; + return CODE_DONE; + case CODE_DONE: /* & */ + httpi->in.pos++; + httpi->in.tot++; + case CODE_CNT_LIM: + httpi->out.pos = http_unescape(httpi->out.data, httpi->out.pos, 1); + httpi->urlenc.state = STATE_URLENC_END; + return CODE_DONE; + default: + return code; + } + case STATE_URLENC_CHUNK: + for (httpi->out.pos = 0; httpi->urlenc.extra[httpi->out.pos] != 0; httpi->out.pos++) + httpi->out.data[httpi->out.pos] = httpi->urlenc.extra[httpi->out.pos]; + httpi->urlenc.state = STATE_URLENC_VALRD; + goto start; + case STATE_URLENC_END: + httpi_set_used(httpi, httpi->used); + if (httpi->in.tot >= httpi->req.content.length) /* CODE_CNT_LIM! */ + httpi->urlenc.state = STATE_URLENC_FINISHED; else + httpi->urlenc.state = STATE_URLENC_KEYRD; + goto start; + case STATE_URLENC_FINISHED: + return CODE_DONE; + } + return CODE_ERROR; /* paranoya */ + +return_empty_val: + if (httpi->in.pos >= httpi->in.size) return CODE_MEM_ERROR; + httpi->out.data[httpi->out.pos] = 0; + httpi->out.pos = 0; + httpi->urlenc.state = STATE_URLENC_END; + return CODE_DONE; +} + +static void httpi_reset(httpi_t *httpi) +{ + memset(&httpi->req, 0, sizeof(http_req_t)); + memset(&httpi->post, 0, sizeof(http_post_t)); + httpi->pool.used = 0; + httpi->in.tot = 0; + httpi->out.data = httpi->pool.data; + httpi->out.size = httpi->pool.size; + httpi->out.pos = 0; + httpi->state = 0; + httpi->rqline.state = 0; +} + +httpi_t *httpi_init(const void *pool, int size) +{ + httpi_t *httpi; + if (size - (int)sizeof(httpi_t) < 64) return NULL; + httpi = (httpi_t *)(((size_t)pool + size - sizeof(httpi_t)) & ~(MEM_ALIGNMENT - 1)); + memset(httpi, 0, sizeof(httpi_t)); + httpi->pool.data = (char *)pool; + httpi->pool.size = (char *)httpi - (char *)pool; + httpi->pool.used = 0; + httpi->in.data = NULL; + httpi->in.pos = 0; + httpi->in.tot = 0; + httpi->in.size = 0; + httpi_reset(httpi); + return httpi; +} + +void httpi_push(httpi_t *httpi, const void *data, int size) +{ + httpi->in.data = (char *)data; + httpi->in.size = size; + httpi->in.pos = 0; +} + +void httpi_get_state(httpi_t *httpi, char **pool, int *avail) +{ + *pool = httpi->pool.data + httpi->pool.used + httpi->out.pos + 1; + *avail = httpi->pool.size - httpi->pool.used - httpi->out.pos - 1; + if (*avail < 0) *avail = 0; +} + +const http_req_t *httpi_request(httpi_t *httpi) +{ + return httpi->state > STATE0_RQHDR ? &httpi->req : NULL; +} + +const http_post_t *httpi_posted(httpi_t *httpi) +{ + return httpi->post.name == NULL ? NULL : &httpi->post; +} + +#define ERROR(code) \ + { \ + httpi->state = STATE0_ERROR; \ + return code; \ + } + +int httpi_pull(httpi_t *httpi) +{ + httpi->post.first = 0; +start: + switch (httpi->state) + { + case STATE0_RQLINE: + switch (httpi_rqline(httpi)) + { + case CODE_IN_EOF: return HTTP_IN_NODATA; + case CODE_OUT_EOF: ERROR(HTTP_IN_ERR_NOMEM); + case CODE_ERROR: ERROR(HTTP_IN_ERR_SYNT); + case CODE_DONE: + httpi->state = STATE0_RQHDR; + httpi->rqhdr.state = 0; + goto start; + } + case STATE0_RQHDR: + switch (httpi_request_hdr_reader(httpi, hdr_fields)) + { + case CODE_IN_EOF: return HTTP_IN_NODATA; + case CODE_OUT_EOF: ERROR(HTTP_IN_ERR_NOMEM); + case CODE_ERROR: ERROR(HTTP_IN_ERR_SYNT); + case CODE_DONE: + httpi->state = STATE0_DONE; + if (httpi->req.content.type != NULL) + { + if (strcmp(httpi->req.content.type, MIME_URLENCODED) == 0) + { + httpi->state = STATE0_URLENC; + httpi->urlenc.state = 0; + memset(&httpi->post, 0, sizeof(http_post_t)); + } + else + if (strcmp(httpi->req.content.type, MIME_MPART_FORM) == 0) + { + if (httpi->req.content.boundary == NULL) ERROR(HTTP_IN_ERR_SYNT); + if (*httpi->req.content.boundary == 0) ERROR(HTTP_IN_ERR_SYNT); + memset(&httpi->post, 0, sizeof(http_post_t)); + httpi->state = STATE0_MPART_DATA; + httpi->mpart.state = 0; + httpi->mpart.bndry_cntr = 0; + httpi->mpart.bndry_pos = 0; + httpi->used = httpi->pool.used; + } + } + return HTTP_IN_REQUEST; + } + case STATE0_URLENC: + switch (httpi_urlenc(httpi)) + { + case CODE_IN_EOF: return HTTP_IN_NODATA; + case CODE_ERROR: ERROR(HTTP_IN_ERR_SYNT); + case CODE_DONE: + switch (httpi->urlenc.state) + { + case STATE_URLENC_KEY: + memset(&httpi->post, 0, sizeof(http_post_t)); + httpi->post.name = httpi->out.data; + httpi->post.first = 1; + httpi_inc_used(httpi, httpi->out.pos + 1); + goto start; + case STATE_URLENC_END: + httpi->post.last = 1; + case STATE_URLENC_CHUNK: + httpi->post.data = httpi->out.data; + httpi->post.size = httpi->out.pos; + return HTTP_IN_POSTDATA; + case STATE_URLENC_FINISHED: + httpi->state = STATE0_DONE; + goto start; + } + default: /* not possible */ + ERROR(HTTP_IN_ERR_SYNT); + } + + case STATE0_MPART_DATA: + switch (httpi_mpart(httpi)) + { + case CODE_IN_EOF: return HTTP_IN_NODATA; + case CODE_ERROR: ERROR(HTTP_IN_ERR_SYNT); + case CODE_OUT_EOF: + if (httpi->out.pos <= 0) + ERROR(HTTP_IN_ERR_NOMEM); + if (httpi->post.name == NULL) + ERROR(HTTP_IN_ERR_SYNT); + httpi->post.data = httpi->out.data; + httpi->post.size = httpi->out.pos; + httpi->post.last = 0; + httpi->out.pos = 0; + return HTTP_IN_POSTDATA; + case CODE_DONE: + switch (httpi->mpart.state) + { + case STATE_MPART_FINISHED: httpi->state = STATE0_DONE; break; + case STATE_MPART_BNDRY: httpi->state = STATE0_MPART_INIT; break; + } + if (*httpi->mpart.bndry == '-') + { + httpi->mpart.bndry_size += 2; + httpi->mpart.bndry -= 2; + } + if (httpi->post.name == NULL && httpi->out.pos > 0) + ERROR(HTTP_IN_ERR_SYNT); + if (httpi->post.name == NULL) goto start; + httpi->post.data = httpi->out.data; + httpi->post.size = httpi->out.pos; + httpi->post.last = 1; + httpi->out.pos = 0; + return HTTP_IN_POSTDATA; + } + + case STATE0_MPART_INIT: + memset(&httpi->post, 0, sizeof(http_post_t)); + httpi->post.first = 1; + httpi->state = STATE0_MPART_HDR; + httpi->rqhdr.state = 0; + httpi->pool.used = httpi->used; + httpi->out.data = httpi->pool.data; + httpi->out.size = httpi->pool.size - httpi->pool.used; + httpi->out.pos = 0; + + case STATE0_MPART_HDR: + switch (httpi_request_hdr_reader(httpi, mpart_fields)) + { + case CODE_IN_EOF: return HTTP_IN_NODATA; + case CODE_OUT_EOF: ERROR(HTTP_IN_ERR_NOMEM); + case CODE_ERROR: ERROR(HTTP_IN_ERR_SYNT); + case CODE_DONE: + httpi->state = STATE0_MPART_DATA; + httpi->mpart.state = STATE_MPART_DATARD; + goto start; + } + + case STATE0_DONE: + httpi->state = STATE0_RESET; + return HTTP_IN_FINISHED; + + case STATE0_RESET: + httpi_reset(httpi); + goto start; + + default: + ERROR(HTTP_IN_ERR_SYNT); + } + +} +#undef ERROR + +#define MIN_OUTPUT_SIZE 256 +/* bytes: 8*hex + 2*crlf + (DATA) + 2*crlf; space to ending: 1*'0' + 2*crlf + 2*crlf */ +#define CHUNKED_RESERVE 17 +#define OUT_FLAG_CHUNKED 1 +#define OUT_FLAG_FINISHED 2 + +struct http_output +{ + char *buff; + int buffsz; + int outpos; + int outsz; + int flags; +}; + +#define ALIGNED(addr) (((size_t)addr + MEM_ALIGNMENT - 1) & ~(MEM_ALIGNMENT - 1)) + +static int write_str(httpo_t *out, const char *str) +{ + while (*str != 0) + { + if (out->outsz == out->buffsz) return 0; + out->buff[(out->outpos + out->outsz) % out->buffsz] = *str; + out->outsz++; + str++; + } + return 1; +} + +static inline int write_uint(httpo_t *out, uint32_t value, int radix) +{ + char s[16]; + sprintf(s, radix == 16 ? "%X" : "%u", value); + return write_str(out, s); +} + +static const char *CONN_TYPE_STR[] = +{ + "", "close", "keep-alive" +}; + +httpo_t *httpo_init(void *pool, int poolsz) +{ + httpo_t *res; + char *apool = (char *)ALIGNED(pool); + poolsz -= (apool - (char *)pool); + poolsz -= sizeof(struct http_output); + if (poolsz < MIN_OUTPUT_SIZE) return NULL; + res = (httpo_t *)apool; + res->buff = apool + sizeof(struct http_output); + res->buffsz = poolsz; + res->outpos = 0; + res->outsz = 0; + res->flags = 0; + return res; +} + +void httpo_state(httpo_t *out, char **data, int *size) +{ + *data = out->buff + out->outpos; + *size = out->buffsz - out->outpos; + if (*size > out->outsz) *size = out->outsz; +} + +void httpo_sended(httpo_t *out, int count) +{ + out->outpos = (out->outpos + count) % out->buffsz; + out->outsz -= count; +} + +int httpo_write_resp(httpo_t *out, const http_resp_t *resp) +{ + int done; + int outsz; + +#define PRNS(s) done &= write_str(out, s) +#define PRNU(v, r) done &= write_uint(out, v, r) +#define PRNH(s, var) { PRNS(s); PRNS(": "); PRNS(var); PRNS("\r\n"); } +#define PRNHU(s, var, r) { PRNS(s); PRNS(": "); PRNU(var, r); PRNS("\r\n"); } +#define PRNHS(s, var) { if (var != NULL) PRNH(s, var); } + + done = 1; + outsz = out->outsz; + PRNS("HTTP/1.1 "); PRNU(resp->code, 10); PRNS(" "); PRNS(http_code_str(resp->code)); PRNS("\r\n"); + PRNHS("Content-Encoding", resp->content.encoding); + PRNHS("Content-Language", resp->content.language); + if (resp->content.length >= 0) + PRNHU("Content-Length", resp->content.length, 10); + PRNHS("Content-Location", resp->content.location); + PRNHS("Content-MD5", resp->content.md5); + /* TODO: http_rng_t range; */ + PRNHS("Content-Type", resp->content.type); + PRNHS("Content-Version", resp->content.ver); + PRNHS("Expires", resp->content.expires); + PRNHS("Last-Modified", resp->content.modified); + PRNHS("Link", resp->content.link); + PRNHS("Title", resp->content.title); + PRNHS("Connection", CONN_TYPE_STR[resp->connection]); + PRNHS("Cache-Control", resp->cache_control); + PRNHS("ETag", resp->etag); + PRNHS("Location", resp->location); + PRNHS("Proxy-Authenticate", resp->proxy_authenticate); + PRNHS("Public", resp->Public); + if (resp->retry_after > 0) + PRNHU("Retry-After", resp->retry_after, 10); + PRNHS("Server", resp->server); + if (resp->transfer_encoding == TENC_CHUNKED) + PRNHS("Transfer-Encoding", "chunked"); + PRNHS("Vary", resp->vary); + PRNHS("WWW-Authenticate", resp->www_authenticate); + if (resp->exthdr != NULL) PRNS(resp->exthdr); + PRNS("\r\n"); + if (!done) + { + out->outsz = outsz; + return 0; + } + if (resp->transfer_encoding == TENC_CHUNKED) + out->flags |= OUT_FLAG_CHUNKED; + return 1; +} + +int httpo_write_avail(httpo_t *out) +{ + int size = out->buffsz - out->outsz; + if (out->flags & OUT_FLAG_CHUNKED) + size -= CHUNKED_RESERVE; + return size > 0 ? size : 0; +} + +const char hex_digits[] = "0123456789ABCDEF"; + +static inline void out_write(httpo_t *out, const void *mem, int size) +{ + int i; + for (i = 0; i < size; i++) + { + out->buff[(out->outpos + out->outsz) % out->buffsz] = ((const char *)mem)[i]; + out->outsz++; + } +} + +void write_chunk_size(httpo_t *out, uint32_t val) +{ + int i; + char buff[16]; + int len = 0; + for (i = 0; i < 8; i++) + { + int v = val >> 28; + if (len > 0 || v != 0) + buff[len++] = hex_digits[v]; + val = val << 4; + } + if (len == 0) + buff[len++] = '0'; + buff[len++] = '\r'; + buff[len++] = '\n'; + out_write(out, buff, len); +} + +int httpo_write_data(httpo_t *out, const void *data, int size) +{ + int avail; + if (size <= 0) return 0; + avail = out->buffsz - out->outsz; + if (out->flags & OUT_FLAG_CHUNKED) + { + avail -= CHUNKED_RESERVE; + if (avail <= 0) return 0; + if (size > avail) size = avail; + write_chunk_size(out, size); + out_write(out, data, size); + out_write(out, "\r\n", 2); + return size; + } + if (size > avail) size = avail; + out_write(out, data, size); + return size; +} + +void httpo_finished(httpo_t *out) +{ + out->flags |= OUT_FLAG_FINISHED; + if (out->flags & OUT_FLAG_CHUNKED) + out_write(out, "0\r\n\r\n", 5); +} + +#ifdef __cplusplus +} +#endif diff --git a/modules/web/http_server_simple/httpio/httpio.h b/modules/web/http_server_simple/httpio/httpio.h new file mode 100644 index 000000000..a6cf56788 --- /dev/null +++ b/modules/web/http_server_simple/httpio/httpio.h @@ -0,0 +1,185 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 by Sergey Fetisov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef HTTPIO_H +#define HTTPIO_H + +#include +#include +#include +#include +#include +#include +#include "http.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct http_output httpo_t; +typedef struct http_input httpi_t; +typedef struct http_post_info http_post_t; + +struct http_post_info +{ + const char *name; + const char *file; + const char *disp_type; + const char *content_type; + const char *data; /**< pointer to current post data chunk */ + int size; /**< size of current post data chunk */ + int first; /**< is it first post data chunk */ + int last; /**< is it last post data chunk */ +}; + +/* next codes returns httpi_pull function: */ +#define HTTP_IN_NODATA 0 /* have no more input data */ +#define HTTP_IN_REQUEST 2 /* http request parsed */ +#define HTTP_IN_POSTDATA 32 /* has POST-request data */ +#define HTTP_IN_FINISHED 64 /* request parsing is finished */ +#define HTTP_IN_ERR_SYNT 256 /* request syntax error */ +#define HTTP_IN_ERR_NOMEM 512 /* have no memory in pool */ +#define HTTP_IN_ERR_UMETH 1024 /* unknown request method */ +#define HTTP_IN_ERR_PARLIM 2048 /* too many request parameters */ + +/** + * @brief Initialize new http-input driver + * @param pool Memory pool buffer + * @param size Memory pool buffer size + * @return Http-input driver instance or NULL if \a size value is too small + * @warning You shouldn't change any data in pool after this call + * + * Instance structure will be allocated from a pool. + * Library has no any dynamic allocation call. + */ +httpi_t *httpi_init(const void *pool, int size); + +/** + * @brief Get the next parsed element + * @param httpi Http-input driver instance + * @return One of HTTP_IN_xxx value + */ +int httpi_pull(httpi_t *httpi); + +/** + * @brief Push the received data to a driver instance for it parsing + * @param httpi Http-input driver instance + * @param data Pointer to a data + * @param size Data size + * @warning You shouldn't change data until the httpi_pull() call not returns HTTP_IN_NODATA value + */ +void httpi_push(httpi_t *httpi, const void *data, int size); + +/** + * @brief Allows to know the pool buffer state + * @param httpi Http-input driver instance + * @param pool Output pointer to unused pool space + * @param avail Output value of unused pool space + * + * You can use the unused space to receive the data from a network + * before the httpi_push() call like this: + * int size; + * char *data; + * httpi_get_state(httpi, &pool, &size); + * size = recv(client, data, size, 0); + * httpi_push(httpi, data, size); + */ +void httpi_get_state(httpi_t *httpi, char **pool, int *avail); + +/** + * @brief Get the HTTP-request structure + * @param httpi Http-input driver instance + * @return Pointer to the request structure + * + * You can do this call if httpi_pull() returned HTTP_IN_REQUEST + */ +const http_req_t *httpi_request(httpi_t *httpi); + +/** + * @brief Get the next HTTP POST data + * @param httpi Http-input driver instance + * @return Pointer to the http_post_t structure + * + * You can do this call if httpi_pull() returned HTTP_IN_POSTDATA + */ +const http_post_t *httpi_posted(httpi_t *httpi); + +/** + * @brief Initialize the HTTP-output driver instance + * @param pool Memory pool buffer + * @param poolsz Memory pool buffer size + * @return HTTP-output driver instance + */ +httpo_t *httpo_init(void *pool, int poolsz); + +/** + * @brief Returns the prepared HTTP output data + * @param out HTTP-output driver instance + * @param data Output pointer to prepared data + * @param size Output value of prepared data size + */ +void httpo_state(httpo_t *out, char **data, int *size); + +/** + * @brief Increments the tail pointer of output buffer + * @param out HTTP-output driver instance + * @param count The amount of data that has been sent + */ +void httpo_sended(httpo_t *out, int count); + +/** + * @brief Writes the HTTP response to the output buffer + * @param out HTTP-output driver instance + * @param resp Responce structure + * @return 1 if successful, or 0 if not enough memory in the output buffer + */ +int httpo_write_resp(httpo_t *out, const http_resp_t *resp); + +/** + * @brief Writes the response data to the output buffer + * @param out HTTP-output driver instance + * @param data Response data + * @param size Response data size + * @return 1 if successful, or 0 if not enough memory in the output buffer + */ +int httpo_write_data(httpo_t *out, const void *data, int size); + +/** + * @brief Returns the size of the data available for writing by httpo_write_data + * @param out HTTP-output driver instance + * @return Size of the data available for writing + */ +int httpo_write_avail(httpo_t *out); + +/** + * @brief Correctly ends the output stream + * @param out HTTP-output driver instance + */ +void httpo_finished(httpo_t *out); + +#ifdef __cplusplus +} +#endif + +#endif