handler.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886
  1. /*
  2. * Copyright (c) 2018 Markus Hennecke <markus-hennecke@markus-hennecke.de>
  3. *
  4. * Permission to use, copy, modify, and distribute this software for any
  5. * purpose with or without fee is hereby granted, provided that the above
  6. * copyright notice and this permission notice appear in all copies.
  7. *
  8. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9. * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  10. * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  11. * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  12. * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  13. * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  14. * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. */
  16. #include <sys/mman.h>
  17. #include <sys/time.h>
  18. #include <err.h>
  19. #include <errno.h>
  20. #include <fcntl.h>
  21. #include <limits.h>
  22. #include <regex.h>
  23. #include <stdio.h>
  24. #include <stdlib.h>
  25. #include <string.h>
  26. #include <time.h>
  27. #include <unistd.h>
  28. #include "buffer.h"
  29. #include "filehelper.h"
  30. #include "handler.h"
  31. #include "helper.h"
  32. #define PAGE_URI_RX_PATTERN "/?((([a-z]{1,8}(-[a-z]{1,8})?))/)?" \
  33. "([_a-z0-9-]+)\\.html$"
  34. #define PAGE_URI_MAX_GROUPS 6
  35. #define PAGE_URI_LANG_GROUP 2
  36. #define PAGE_URI_PAGE_GROUP 5
  37. #define ACCEPT_LANGUAGE_RX_PATTERN "^ *([a-z]{1,8}(-[a-z]{1,8})?)" \
  38. " *(; *q *= *(1|0\\.[0-9]+))?$"
  39. #define ACCEPT_LANGUAGE_MAX_GROUPS 5
  40. #define HTTP_DATE_FMT "%a, %d %b %y %H:%M:%S GMT"
  41. const char *supported_request_methods[3] = {
  42. "GET",
  43. "POST",
  44. NULL
  45. };
  46. struct lang_pref *
  47. lang_pref_new(const char *_lang, const float _prio)
  48. {
  49. struct lang_pref *lp = malloc(sizeof(struct lang_pref));
  50. if (lp == NULL)
  51. err(1, NULL);
  52. strlcpy(lp->lang, _lang, sizeof(lp->lang));
  53. lp->priority = _prio;
  54. strlcpy(lp->short_lang, _lang, sizeof(lp->short_lang));
  55. strtok(lp->short_lang, "-");
  56. return lp;
  57. }
  58. void
  59. request_add_lang_pref(struct request *_req, struct lang_pref *_lang_pref)
  60. {
  61. struct lang_pref *l = TAILQ_FIRST(&_req->accept_languages);
  62. while (l) {
  63. if (_lang_pref->priority < l->priority) {
  64. l = TAILQ_NEXT(l, entries);
  65. continue;
  66. }
  67. TAILQ_INSERT_BEFORE(l, _lang_pref, entries);
  68. return;
  69. }
  70. TAILQ_INSERT_TAIL(&_req->accept_languages, _lang_pref, entries);
  71. }
  72. void
  73. request_parse_lang_pref(struct request *_req)
  74. {
  75. regex_t rx;
  76. regmatch_t pmatch[ACCEPT_LANGUAGE_MAX_GROUPS];
  77. char *env_accept_lang = getenv("HTTP_ACCEPT_LANGUAGE");
  78. if (env_accept_lang == NULL)
  79. return;
  80. char *accept_lang = strdup(env_accept_lang);
  81. // Split the string and feed the bits to request_add_lang_pref
  82. size_t len = strlen(accept_lang);
  83. char *end = accept_lang + len;
  84. char *it = accept_lang;
  85. int rc = regcomp(&rx, ACCEPT_LANGUAGE_RX_PATTERN,
  86. REG_EXTENDED | REG_ICASE);
  87. if (rc != 0) {
  88. warnx("regcomp: %s", rx_get_errormsg(rc, &rx));
  89. return;
  90. }
  91. while (it < end) {
  92. float prio = 1;
  93. size_t match_len;
  94. char *comma = strchr(it, ',');
  95. char *lang;
  96. if (comma == NULL)
  97. comma = end;
  98. else
  99. *comma = '\0';
  100. rc = regexec(&rx, it, ACCEPT_LANGUAGE_MAX_GROUPS, pmatch, 0);
  101. switch (rc) {
  102. case REG_NOMATCH:
  103. warnx("'%s' did not match language rx\n", it);
  104. goto cleanup_lang;
  105. case 0:
  106. if (pmatch[4].rm_so != -1)
  107. prio = atof(it + pmatch[4].rm_so);
  108. match_len = pmatch[1].rm_eo - pmatch[1].rm_so;
  109. lang = strndup(it + pmatch[1].rm_so, match_len);
  110. struct lang_pref *lp = lang_pref_new(lang, prio);
  111. // Only store available languages
  112. bool store = false;
  113. if (dir_entry_exists(lp->lang, _req->avail_languages)) {
  114. store = true;
  115. } else {
  116. lp->lang[0] = '\0';
  117. }
  118. if (dir_entry_exists(lp->short_lang,
  119. _req->avail_languages)) {
  120. store = true;
  121. } else {
  122. lp->short_lang[0] = '\0';
  123. }
  124. if (store) {
  125. request_add_lang_pref(_req, lp);
  126. } else {
  127. free(lp);
  128. }
  129. free(lang);
  130. break;
  131. default:
  132. warnx("regexec: %s", rx_get_errormsg(rc, &rx));
  133. break;
  134. }
  135. it = comma + 1;
  136. }
  137. cleanup_lang:
  138. free(accept_lang);
  139. regfree(&rx);
  140. }
  141. struct page_info *
  142. page_info_new(char *_path)
  143. {
  144. struct page_info *info = calloc(1, sizeof(struct page_info));
  145. if (NULL == info)
  146. err(1, NULL);
  147. info->path = _path;
  148. return info;
  149. }
  150. void
  151. page_info_free(struct page_info *_info)
  152. {
  153. if (NULL == _info)
  154. return;
  155. md_mmap_free(_info->content);
  156. md_mmap_free(_info->descr);
  157. memmap_free(_info->link);
  158. md_mmap_free(_info->login);
  159. memmap_free(_info->sort);
  160. memmap_free(_info->style);
  161. memmap_free(_info->script);
  162. memmap_free(_info->title);
  163. free(_info->path);
  164. free(_info);
  165. }
  166. struct request *
  167. request_new(char *_path_info)
  168. {
  169. size_t match_len;
  170. regmatch_t *rxmatch;
  171. struct request *req = calloc(1, sizeof(struct request));
  172. if (req == NULL)
  173. err(1, NULL);
  174. req->content_dir = open(cms_content_dir, O_DIRECTORY | O_RDONLY);
  175. if (-1 == req->content_dir)
  176. err(1, "%s", cms_content_dir);
  177. req->template_dir = open(cms_template_dir, O_DIRECTORY | O_RDONLY);
  178. if (-1 == req->template_dir)
  179. err(1, "%s", cms_template_dir);
  180. req->page_dir = -1;
  181. req->lang_dir = -1;
  182. req->path_info = strdup(_path_info);
  183. req->lang = NULL;
  184. req->page = NULL;
  185. req->path = CMS_ROOT_URL;
  186. TAILQ_INIT(&req->headers);
  187. TAILQ_INIT(&req->accept_languages);
  188. TAILQ_INIT(&req->cookies);
  189. TAILQ_INIT(&req->params);
  190. const char *req_method = getenv("REQUEST_METHOD");
  191. if (req_method) {
  192. int i = 0;
  193. const char **supported = &supported_request_methods[0];
  194. while (*supported) {
  195. if (strcmp(req_method, *supported) == 0) {
  196. req->request_method = i;
  197. break;
  198. }
  199. i++;
  200. supported++;
  201. }
  202. if (*supported == NULL)
  203. errx(1, "unsupported request method '%s'", req_method);
  204. }
  205. regex_t rx;
  206. int rc = regcomp(&rx, PAGE_URI_RX_PATTERN, REG_EXTENDED);
  207. if (rc != 0) {
  208. warnx("regcomp: %s", rx_get_errormsg(rc, &rx));
  209. request_free(req);
  210. return NULL;
  211. }
  212. regmatch_t rx_group_array[PAGE_URI_MAX_GROUPS];
  213. rc = regexec(&rx, _path_info, PAGE_URI_MAX_GROUPS,
  214. rx_group_array, 0);
  215. switch (rc) {
  216. case REG_NOMATCH:
  217. request_free(req);
  218. return NULL;
  219. case 0:
  220. rxmatch = &rx_group_array[PAGE_URI_PAGE_GROUP];
  221. match_len = rxmatch->rm_eo - rxmatch->rm_so;
  222. req->page = strndup(_path_info + rxmatch->rm_so, match_len);
  223. rxmatch = &rx_group_array[PAGE_URI_LANG_GROUP];
  224. match_len = rxmatch->rm_eo - rxmatch->rm_so;
  225. if (match_len > 0) {
  226. req->lang = strndup(_path_info + rxmatch->rm_so,
  227. match_len);
  228. }
  229. break;
  230. default:
  231. warnx("regexec: %s", rx_get_errormsg(rc, &rx));
  232. break;
  233. }
  234. regfree(&rx);
  235. req->avail_languages = get_dir_entries(cms_content_dir);
  236. request_parse_lang_pref(req);
  237. if (NULL == req->lang) {
  238. struct lang_pref *lang = TAILQ_FIRST(&req->accept_languages);
  239. if (lang == NULL) {
  240. req->lang = strndup(CMS_DEFAULT_LANGUAGE,
  241. sizeof(req->lang) - 1);
  242. } else {
  243. if (lang->lang[0] != '\0')
  244. req->lang = strdup(lang->lang);
  245. else
  246. req->lang = strdup(lang->short_lang);
  247. }
  248. }
  249. req->lang_dir = openat(req->content_dir, req->lang,
  250. O_DIRECTORY | O_RDONLY);
  251. if (req->lang_dir == -1) {
  252. request_free(req);
  253. _error("404 Not Found", NULL);
  254. }
  255. return req;
  256. }
  257. void
  258. request_free(struct request *_req)
  259. {
  260. if (_req) {
  261. if (-1 != _req->content_dir)
  262. close(_req->content_dir);
  263. if (-1 != _req->template_dir)
  264. close(_req->template_dir);
  265. if (-1 != _req->lang_dir)
  266. close(_req->lang_dir);
  267. if (-1 != _req->page_dir)
  268. close(_req->page_dir);
  269. page_info_free(_req->page_info);
  270. tmpl_data_free(_req->data);
  271. free(_req->path_info);
  272. free(_req->page);
  273. free(_req->lang);
  274. free(_req->status);
  275. struct header *h;
  276. while ((h = TAILQ_FIRST(&_req->headers))) {
  277. TAILQ_REMOVE(&_req->headers, h, entries);
  278. header_free(h);
  279. }
  280. struct lang_pref *lp;
  281. while ((lp = TAILQ_FIRST(&_req->accept_languages))) {
  282. TAILQ_REMOVE(&_req->accept_languages, lp, entries);
  283. free(lp);
  284. }
  285. struct cookie *c;
  286. while ((c = TAILQ_FIRST(&_req->cookies))) {
  287. TAILQ_REMOVE(&_req->cookies, c, entries);
  288. free(c);
  289. }
  290. struct param *p;
  291. while ((p = TAILQ_FIRST(&_req->params))) {
  292. TAILQ_REMOVE(&_req->params, p, entries);
  293. free(p);
  294. }
  295. dir_list_free(_req->avail_languages);
  296. session_free(_req->session);
  297. if (_req->session_store)
  298. session_store_cleanup(_req->session_store);
  299. session_store_free(_req->session_store);
  300. htpasswd_free(_req->htpasswd);
  301. memmap_free(_req->tmpl_file);
  302. }
  303. }
  304. struct page_info *
  305. request_fetch_page(struct request *_req)
  306. {
  307. struct dir_list *files = NULL;
  308. struct dir_entry *e;
  309. struct page_info *p = NULL;
  310. int cwd = -1;
  311. char *path;
  312. if (-1 == asprintf(&path, "%s/%s/%s", cms_content_dir, _req->lang,
  313. _req->page))
  314. err(1, NULL);
  315. cwd = open(path, O_DIRECTORY | O_RDONLY);
  316. if (-1 == cwd) {
  317. warn("%s", path);
  318. goto error_out;
  319. }
  320. p = page_info_new(path);
  321. files = get_dir_entries(path);
  322. if (files == NULL)
  323. goto error_out;
  324. TAILQ_FOREACH(e, &files->entries, entries) {
  325. if (strcmp("CONTENT", e->filename) == 0) {
  326. if (p->content == NULL)
  327. p->content = md_mmap_new_at(cwd, e->filename);
  328. } else if (strcmp("CONTENT.md", e->filename) == 0) {
  329. md_mmap_free(p->content);
  330. p->content = md_mmap_new_at(cwd, e->filename);
  331. md_mmap_parse(p->content);
  332. } else if (strcmp("DESCR", e->filename) == 0) {
  333. if (p->descr == NULL)
  334. p->descr = md_mmap_new_at(cwd, e->filename);
  335. } else if (strcmp("DESCR.md", e->filename) == 0) {
  336. md_mmap_free(p->descr);
  337. p->descr = md_mmap_new_at(cwd, e->filename);
  338. md_mmap_parse(p->descr);
  339. } else if (strcmp("LINK", e->filename) == 0) {
  340. p->link = memmap_new_at(cwd, e->filename);
  341. } else if (strcmp("LOGIN", e->filename) == 0) {
  342. p->login = md_mmap_new_at(cwd, e->filename);
  343. } else if (strcmp("SORT", e->filename) == 0) {
  344. p->sort = memmap_new_at(cwd, e->filename);
  345. } else if (strcmp("SCRIPT", e->filename) == 0) {
  346. p->script = memmap_new_at(cwd, e->filename);
  347. } else if (strcmp("SSL", e->filename) == 0) {
  348. p->ssl = true;
  349. } else if (strcmp("STYLE", e->filename) == 0) {
  350. p->style = memmap_new_at(cwd, e->filename);
  351. } else if (strcmp("SUB", e->filename) == 0) {
  352. p->sub = true;
  353. } else if (strcmp("TITLE", e->filename) == 0) {
  354. p->title = memmap_new_at(cwd, e->filename);
  355. }
  356. }
  357. // Test for all required fields, if not here we error out
  358. if (!(p->content && p->link && p->sort && p->title && p->descr))
  359. goto error_out;
  360. // Test if the If-Modified-Since header exists and the newest
  361. // file from the content directory have equal time stamps
  362. const char *if_modified_since = getenv("HTTP_IF_MODIFIED_SINCE");
  363. if (if_modified_since) {
  364. struct tm tm;
  365. if (strptime(if_modified_since, HTTP_DATE_FMT, &tm)) {
  366. if (timegm(&tm) >= files->newest)
  367. _error("304 Not Modified", NULL);
  368. }
  369. }
  370. char last_modified[30];
  371. strftime(last_modified, sizeof(last_modified),
  372. HTTP_DATE_FMT, gmtime(&files->newest));
  373. request_add_header(_req, "Last-Modified", last_modified);
  374. _req->page_info = p;
  375. _req->content = p->content;
  376. cleanup:
  377. if (files)
  378. dir_list_free(files);
  379. if (cwd != -1) {
  380. fchdir(cwd);
  381. close(cwd);
  382. }
  383. return p;
  384. error_out:
  385. page_info_free(p);
  386. p = NULL;
  387. goto cleanup;
  388. }
  389. struct tmpl_data *
  390. request_init_tmpl_data(struct request *_req)
  391. {
  392. _req->data = tmpl_data_new();
  393. tmpl_data_set_variable(_req->data, "CURRENT_PAGE", _req->path_info);
  394. if (_req->page_info->descr) {
  395. void *data;
  396. size_t size;
  397. md_mmap_content(_req->page_info->descr, &data, &size);
  398. tmpl_data_set_variablen(_req->data, "DESCR", data, size);
  399. }
  400. return _req->data;
  401. }
  402. bool
  403. request_handle_login(struct request *_req)
  404. {
  405. if (! _req->page_info->login)
  406. return true;
  407. request_parse_cookies(_req);
  408. _req->session_store = session_store_new(cms_session_db);
  409. struct cookie *c = request_cookie_get(_req, "sid");
  410. if (c != NULL)
  411. _req->session = session_load(c->value, _req->session_store);
  412. const bool new_session = (_req->session == NULL);
  413. const time_t now = time(NULL);
  414. const bool timeout = (_req->session
  415. && (_req->session->data.timeout < now));
  416. if (new_session || timeout) {
  417. struct session *session = session_new();
  418. if (timeout) {
  419. session_free(_req->session);
  420. }
  421. _req->session = session;
  422. request_cookie_remove(_req, c);
  423. cookie_free(c);
  424. c = cookie_new("sid", _req->session->sid);
  425. c->path = strdup("/");
  426. }
  427. request_cookie_set(_req, c);
  428. _req->session->data.timeout = now + (60 * 30);
  429. session_save(_req->session, _req->session_store, new_session);
  430. if (_req->request_method == POST) {
  431. request_parse_params(_req, getenv("QUERY_STRING"));
  432. char *content_type = getenv("CONTENT_TYPE");
  433. if (content_type
  434. && (strcmp(content_type,
  435. "application/x-www-form-urlencoded")
  436. == 0)) {
  437. if (request_read_post_body(_req))
  438. request_parse_params(_req, _req->req_body);
  439. }
  440. char *username = NULL;
  441. char *password = NULL;
  442. struct param *p = request_get_param(_req, "username", NULL);
  443. if (p)
  444. username = p->value;
  445. p = request_get_param(_req, "password", NULL);
  446. if (p)
  447. password = p->value;
  448. if (username && password) {
  449. if (_req->htpasswd == NULL)
  450. _req->htpasswd
  451. = htpasswd_init(cms_session_htpasswd);
  452. _req->session->data.loggedin
  453. = htpasswd_check_password(_req->htpasswd,
  454. username, password);
  455. session_save(_req->session, _req->session_store, false);
  456. }
  457. }
  458. if (!_req->session->data.loggedin) {
  459. _req->content = _req->page_info->login;
  460. }
  461. return (_req->content == _req->page_info->content);
  462. }
  463. struct buffer_list *
  464. request_render_page(struct request *_req, const char *_tmpl_filename)
  465. {
  466. struct buffer_list *cb;
  467. struct buffer_list *result = NULL;
  468. if (_req->content) {
  469. void *data;
  470. size_t size;
  471. md_mmap_content(_req->content, &data, &size);
  472. cb = tmpl_parse(data, size, _req->data);
  473. tmpl_data_set_variable(_req->data, "CONTENT",
  474. buffer_list_concat_string(cb));
  475. } else {
  476. _error("404 Not Found", NULL);
  477. }
  478. tmpl_data_set_variable(_req->data, "LANGUAGE", _req->lang);
  479. tmpl_data_set_variablen(_req->data, "TITLE",
  480. _req->page_info->title->data,
  481. memmap_chomp(_req->page_info->title));
  482. struct tmpl_loop *links = request_get_links(_req);
  483. struct tmpl_loop *lang_links = request_get_language_links(_req);
  484. if (links)
  485. tmpl_data_set_loop(_req->data, "LINK_LOOP", links);
  486. if (lang_links)
  487. tmpl_data_set_loop(_req->data, "LANGUAGE_LINKS", lang_links);
  488. _req->tmpl_file = memmap_new_at(_req->template_dir, _tmpl_filename);
  489. if (_req->tmpl_file == NULL)
  490. _error("500 Internal Server Error", NULL);
  491. result = tmpl_parse(_req->tmpl_file->data, _req->tmpl_file->size,
  492. _req->data);
  493. buffer_list_free(cb);
  494. return result;
  495. }
  496. void
  497. header_free(struct header *_header)
  498. {
  499. if (_header) {
  500. free(_header->key);
  501. free(_header->value);
  502. }
  503. }
  504. struct header *
  505. header_new(const char *_key, const char *_value)
  506. {
  507. struct header *h = malloc(sizeof(struct header));
  508. if (h == NULL)
  509. err(1, NULL);
  510. h->key = strdup(_key);
  511. h->value = strdup(_value);
  512. return h;
  513. }
  514. void
  515. request_add_header(struct request *_req, const char *_key, const char *_value)
  516. {
  517. struct header *h = header_new(_key, _value);
  518. TAILQ_INSERT_TAIL(&_req->headers, h, entries);
  519. }
  520. struct buffer_list *
  521. request_output_headers(struct request *_req)
  522. {
  523. struct header *h;
  524. struct buffer_list *bl = buffer_list_new();
  525. TAILQ_FOREACH(h, &_req->headers, entries) {
  526. char *header;
  527. if ((asprintf(&header, "%s: %s\n", h->key, h->value) == -1))
  528. err(1, NULL);
  529. buffer_list_add_string(bl, header);
  530. free(header);
  531. }
  532. return bl;
  533. }
  534. void
  535. request_cookie_mark_delete(struct request *_req, struct cookie *_cookie)
  536. {
  537. free(_cookie->value);
  538. _cookie->value = strdup("deleted");
  539. _cookie->expires = 1;
  540. request_cookie_set(_req, _cookie);
  541. }
  542. void
  543. request_cookie_set(struct request *_req, struct cookie *_cookie)
  544. {
  545. char expires[38] = { 0 };
  546. if (_cookie->expires != 0) {
  547. struct tm *tm = gmtime(&_cookie->expires);
  548. strftime(expires, sizeof(expires), "%a, %d %b %Y %T GMT", tm);
  549. }
  550. char *value;
  551. if (asprintf(&value, "%s=%s%s%s%s%s%s",
  552. _cookie->name, _cookie->value,
  553. (_cookie->path) ? "; path=" : "",
  554. (_cookie->path) ? _cookie->path : "",
  555. (_cookie->domain) ? "; domain=" : "",
  556. (_cookie->domain) ? _cookie->domain : "",
  557. (expires[0]) ? expires : "") == -1)
  558. err(1, NULL);
  559. request_add_header(_req, "Set-Cookie", value);
  560. free(value);
  561. }
  562. struct cookie *
  563. request_cookie_get(struct request *_req, const char *_name)
  564. {
  565. struct cookie *c;
  566. TAILQ_FOREACH(c, &_req->cookies, entries) {
  567. if (strcmp(c->name, _name) == 0)
  568. return c;
  569. }
  570. return NULL;
  571. }
  572. bool
  573. request_cookie_remove(struct request *_req, struct cookie *_c)
  574. {
  575. struct cookie *c = TAILQ_FIRST(&_req->cookies);
  576. while (c) {
  577. if (_c == c) {
  578. TAILQ_REMOVE(&_req->cookies, c, entries);
  579. return true;
  580. }
  581. c = TAILQ_NEXT(c, entries);
  582. }
  583. return false;
  584. }
  585. struct cookie *
  586. cookie_new(const char *_name, const char *_value)
  587. {
  588. struct cookie *c = calloc(1, sizeof(struct cookie));
  589. if (c == NULL)
  590. err(1, NULL);
  591. c->name = strdup(_name);
  592. c->value = strdup(_value);
  593. return c;
  594. }
  595. void
  596. cookie_free(struct cookie *_cookie)
  597. {
  598. if (_cookie) {
  599. free(_cookie->name);
  600. free(_cookie->value);
  601. free(_cookie->path);
  602. free(_cookie->domain);
  603. free(_cookie);
  604. }
  605. }
  606. void
  607. request_parse_cookies(struct request *_req)
  608. {
  609. const char *env_http_cookie = getenv("HTTP_COOKIE");
  610. if (env_http_cookie == NULL)
  611. return;
  612. char *cookies = strdup(env_http_cookie);
  613. if (cookies == NULL)
  614. err(1, NULL);
  615. char *key, *value;
  616. char *s = cookies;
  617. while (s && *s != '\0') {
  618. // Skip leading whitespace
  619. while (*s == ' ')
  620. s++;
  621. // Find a '='
  622. key = s;
  623. if ((s = strchr(s, '=')) != NULL) {
  624. *s++ = '\0';
  625. value = s;
  626. s = strchr(s, ';');
  627. if (s != NULL) {
  628. *s++ = '\0';
  629. }
  630. } else {
  631. // Try to find a ';', and continue after
  632. s = strchr(key, ';');
  633. if (s != NULL)
  634. s++;
  635. continue;
  636. }
  637. struct cookie * cookie = cookie_new(key, value);
  638. TAILQ_INSERT_TAIL(&_req->cookies, cookie, entries);
  639. }
  640. free(cookies);
  641. }
  642. struct param *
  643. param_new(const char *_name, const char *_value)
  644. {
  645. struct param *p = calloc(1, sizeof(struct param));
  646. if (_name)
  647. p->name = strdup(_name);
  648. if (_value) {
  649. p->value = strdup(_value);
  650. param_decode(p);
  651. }
  652. return p;
  653. }
  654. void
  655. param_decode(struct param *_param)
  656. {
  657. if (_param->value)
  658. decode_string(_param->value);
  659. }
  660. void
  661. param_free(struct param *_param)
  662. {
  663. if (_param) {
  664. free(_param->name);
  665. free(_param->value);
  666. }
  667. }
  668. void
  669. request_parse_params(struct request *_req, const char *_params)
  670. {
  671. if (_params == NULL)
  672. return;
  673. char *param_string = strdup(_params);
  674. if (param_string == NULL)
  675. err(1, NULL);
  676. char *s = param_string;
  677. char *name, *value;
  678. while (s && *s != '\0') {
  679. name = s;
  680. value = NULL;
  681. s = strchr(s, '=');
  682. if (s != NULL) {
  683. *s++ = '\0';
  684. value = s;
  685. s = strchr(s, '&');
  686. if (s != NULL) {
  687. *s++ = '\0';
  688. }
  689. }
  690. struct param *p = param_new(name, value);
  691. TAILQ_INSERT_TAIL(&_req->params, p, entries);
  692. }
  693. free(param_string);
  694. }
  695. struct param *
  696. request_get_param(struct request *_req, const char *_name, struct param *_p)
  697. {
  698. struct param *p;
  699. TAILQ_FOREACH(p, &_req->params, entries) {
  700. if (strcmp(_name, p->name) == 0) {
  701. if (p != _p)
  702. break;
  703. }
  704. }
  705. return p;
  706. }
  707. bool
  708. request_read_post_body(struct request *_req)
  709. {
  710. if (_req->request_method == POST) {
  711. const char *content_length_env = getenv("CONTENT_LENGTH");
  712. if (content_length_env == NULL)
  713. return false;
  714. errno = 0;
  715. char *ep;
  716. unsigned long content_length
  717. = strtoul(content_length_env, &ep, 10);
  718. if (content_length_env[0] == '\0' || *ep != '\0') {
  719. warnx("CONTENT_LENGTH is not a number");
  720. return false;
  721. }
  722. if (errno == ERANGE && content_length == ULONG_MAX) {
  723. warnx("CONTENT_LENGTH out of range");
  724. return false;
  725. }
  726. _req->req_body_size = content_length;
  727. _req->req_body = malloc(content_length + 1);
  728. unsigned char *buf = _req->req_body;
  729. ssize_t todo = content_length;
  730. off_t offset = 0;
  731. while (todo > 0) {
  732. ssize_t n = read(STDIN_FILENO, buf + offset,
  733. todo - offset);
  734. if (n == -1)
  735. err(1, NULL);
  736. todo -= n;
  737. offset += n;
  738. }
  739. buf[content_length] = '\0';
  740. return (todo == 0);
  741. }
  742. return false;
  743. }
  744. __dead void
  745. _error(const char *_status, const char *_content)
  746. {
  747. dprintf(STDOUT_FILENO, "Status: %s\r\n", _status);
  748. dprintf(STDOUT_FILENO, "Content-Length: %zd\r\n\r\n",
  749. _content ? strlen(_content) : 0);
  750. if (_content)
  751. dprintf(STDOUT_FILENO, "%s", _content);
  752. exit(0);
  753. }