You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

http_router.c 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. /*-
  2. * Copyright 2019 Vsevolod Stakhov
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #include "http_router.h"
  17. #include "http_connection.h"
  18. #include "http_private.h"
  19. #include "libutil/regexp.h"
  20. #include "libutil/printf.h"
  21. #include "libserver/logger.h"
  22. #include "utlist.h"
  23. #include "unix-std.h"
  24. enum http_magic_type {
  25. HTTP_MAGIC_PLAIN = 0,
  26. HTTP_MAGIC_HTML,
  27. HTTP_MAGIC_CSS,
  28. HTTP_MAGIC_JS,
  29. HTTP_MAGIC_ICO,
  30. HTTP_MAGIC_PNG,
  31. HTTP_MAGIC_JPG,
  32. HTTP_MAGIC_SVG
  33. };
  34. static const struct _rspamd_http_magic {
  35. const char *ext;
  36. const char *ct;
  37. } http_file_types[] = {
  38. [HTTP_MAGIC_PLAIN] = {"txt", "text/plain"},
  39. [HTTP_MAGIC_HTML] = {"html", "text/html"},
  40. [HTTP_MAGIC_CSS] = {"css", "text/css"},
  41. [HTTP_MAGIC_JS] = {"js", "application/javascript"},
  42. [HTTP_MAGIC_ICO] = {"ico", "image/x-icon"},
  43. [HTTP_MAGIC_PNG] = {"png", "image/png"},
  44. [HTTP_MAGIC_JPG] = {"jpg", "image/jpeg"},
  45. [HTTP_MAGIC_SVG] = {"svg", "image/svg+xml"},
  46. };
  47. /*
  48. * HTTP router functions
  49. */
  50. static void
  51. rspamd_http_entry_free(struct rspamd_http_connection_entry *entry)
  52. {
  53. if (entry != NULL) {
  54. close(entry->conn->fd);
  55. rspamd_http_connection_unref(entry->conn);
  56. if (entry->rt->finish_handler) {
  57. entry->rt->finish_handler(entry);
  58. }
  59. DL_DELETE(entry->rt->conns, entry);
  60. g_free(entry);
  61. }
  62. }
  63. static void
  64. rspamd_http_router_error_handler(struct rspamd_http_connection *conn,
  65. GError *err)
  66. {
  67. struct rspamd_http_connection_entry *entry = conn->ud;
  68. struct rspamd_http_message *msg;
  69. if (entry->is_reply) {
  70. /* At this point we need to finish this session and close owned socket */
  71. if (entry->rt->error_handler != NULL) {
  72. entry->rt->error_handler(entry, err);
  73. }
  74. rspamd_http_entry_free(entry);
  75. }
  76. else {
  77. /* Here we can write a reply to a client */
  78. if (entry->rt->error_handler != NULL) {
  79. entry->rt->error_handler(entry, err);
  80. }
  81. msg = rspamd_http_new_message(HTTP_RESPONSE);
  82. msg->date = time(NULL);
  83. msg->code = err->code;
  84. rspamd_http_message_set_body(msg, err->message, strlen(err->message));
  85. rspamd_http_connection_reset(entry->conn);
  86. rspamd_http_connection_write_message(entry->conn,
  87. msg,
  88. NULL,
  89. "text/plain",
  90. entry,
  91. entry->rt->timeout);
  92. entry->is_reply = TRUE;
  93. }
  94. }
  95. static const char *
  96. rspamd_http_router_detect_ct(const char *path)
  97. {
  98. const char *dot;
  99. unsigned int i;
  100. dot = strrchr(path, '.');
  101. if (dot == NULL) {
  102. return http_file_types[HTTP_MAGIC_PLAIN].ct;
  103. }
  104. dot++;
  105. for (i = 0; i < G_N_ELEMENTS(http_file_types); i++) {
  106. if (strcmp(http_file_types[i].ext, dot) == 0) {
  107. return http_file_types[i].ct;
  108. }
  109. }
  110. return http_file_types[HTTP_MAGIC_PLAIN].ct;
  111. }
  112. static gboolean
  113. rspamd_http_router_is_subdir(const char *parent, const char *sub)
  114. {
  115. if (parent == NULL || sub == NULL || *parent == '\0') {
  116. return FALSE;
  117. }
  118. while (*parent != '\0') {
  119. if (*sub != *parent) {
  120. return FALSE;
  121. }
  122. parent++;
  123. sub++;
  124. }
  125. parent--;
  126. if (*parent == G_DIR_SEPARATOR) {
  127. return TRUE;
  128. }
  129. return (*sub == G_DIR_SEPARATOR || *sub == '\0');
  130. }
  131. static gboolean
  132. rspamd_http_router_try_file(struct rspamd_http_connection_entry *entry,
  133. rspamd_ftok_t *lookup, gboolean expand_path)
  134. {
  135. struct stat st;
  136. int fd;
  137. char filebuf[PATH_MAX], realbuf[PATH_MAX], *dir;
  138. struct rspamd_http_message *reply_msg;
  139. rspamd_snprintf(filebuf, sizeof(filebuf), "%s%c%T",
  140. entry->rt->default_fs_path, G_DIR_SEPARATOR, lookup);
  141. if (realpath(filebuf, realbuf) == NULL ||
  142. lstat(realbuf, &st) == -1) {
  143. return FALSE;
  144. }
  145. if (S_ISDIR(st.st_mode) && expand_path) {
  146. /* Try to append 'index.html' to the url */
  147. rspamd_fstring_t *nlookup;
  148. rspamd_ftok_t tok;
  149. gboolean ret;
  150. nlookup = rspamd_fstring_sized_new(lookup->len + sizeof("index.html"));
  151. rspamd_printf_fstring(&nlookup, "%T%c%s", lookup, G_DIR_SEPARATOR,
  152. "index.html");
  153. tok.begin = nlookup->str;
  154. tok.len = nlookup->len;
  155. ret = rspamd_http_router_try_file(entry, &tok, FALSE);
  156. rspamd_fstring_free(nlookup);
  157. return ret;
  158. }
  159. else if (!S_ISREG(st.st_mode)) {
  160. return FALSE;
  161. }
  162. /* We also need to ensure that file is inside the defined dir */
  163. rspamd_strlcpy(filebuf, realbuf, sizeof(filebuf));
  164. dir = dirname(filebuf);
  165. if (dir == NULL ||
  166. !rspamd_http_router_is_subdir(entry->rt->default_fs_path,
  167. dir)) {
  168. return FALSE;
  169. }
  170. fd = open(realbuf, O_RDONLY);
  171. if (fd == -1) {
  172. return FALSE;
  173. }
  174. reply_msg = rspamd_http_new_message(HTTP_RESPONSE);
  175. reply_msg->date = time(NULL);
  176. reply_msg->code = 200;
  177. rspamd_http_router_insert_headers(entry->rt, reply_msg);
  178. if (!rspamd_http_message_set_body_from_fd(reply_msg, fd)) {
  179. rspamd_http_message_free(reply_msg);
  180. close(fd);
  181. return FALSE;
  182. }
  183. close(fd);
  184. rspamd_http_connection_reset(entry->conn);
  185. msg_debug("requested file %s", realbuf);
  186. rspamd_http_connection_write_message(entry->conn, reply_msg, NULL,
  187. rspamd_http_router_detect_ct(realbuf), entry,
  188. entry->rt->timeout);
  189. return TRUE;
  190. }
  191. static void
  192. rspamd_http_router_send_error(GError *err,
  193. struct rspamd_http_connection_entry *entry)
  194. {
  195. struct rspamd_http_message *err_msg;
  196. err_msg = rspamd_http_new_message(HTTP_RESPONSE);
  197. err_msg->date = time(NULL);
  198. err_msg->code = err->code;
  199. rspamd_http_message_set_body(err_msg, err->message,
  200. strlen(err->message));
  201. entry->is_reply = TRUE;
  202. err_msg->status = rspamd_fstring_new_init(err->message, strlen(err->message));
  203. rspamd_http_router_insert_headers(entry->rt, err_msg);
  204. rspamd_http_connection_reset(entry->conn);
  205. rspamd_http_connection_write_message(entry->conn,
  206. err_msg,
  207. NULL,
  208. "text/plain",
  209. entry,
  210. entry->rt->timeout);
  211. }
  212. static int
  213. rspamd_http_router_finish_handler(struct rspamd_http_connection *conn,
  214. struct rspamd_http_message *msg)
  215. {
  216. struct rspamd_http_connection_entry *entry = conn->ud;
  217. rspamd_http_router_handler_t handler = NULL;
  218. gpointer found;
  219. GError *err;
  220. rspamd_ftok_t lookup;
  221. const rspamd_ftok_t *encoding;
  222. struct http_parser_url u;
  223. unsigned int i;
  224. rspamd_regexp_t *re;
  225. struct rspamd_http_connection_router *router;
  226. char *pathbuf = NULL;
  227. G_STATIC_ASSERT(sizeof(rspamd_http_router_handler_t) ==
  228. sizeof(gpointer));
  229. memset(&lookup, 0, sizeof(lookup));
  230. router = entry->rt;
  231. if (entry->is_reply) {
  232. /* Request is finished, it is safe to free a connection */
  233. rspamd_http_entry_free(entry);
  234. }
  235. else {
  236. if (G_UNLIKELY(msg->method != HTTP_GET && msg->method != HTTP_POST)) {
  237. if (router->unknown_method_handler) {
  238. return router->unknown_method_handler(entry, msg);
  239. }
  240. else {
  241. err = g_error_new(HTTP_ERROR, 500,
  242. "Invalid method");
  243. if (entry->rt->error_handler != NULL) {
  244. entry->rt->error_handler(entry, err);
  245. }
  246. rspamd_http_router_send_error(err, entry);
  247. g_error_free(err);
  248. return 0;
  249. }
  250. }
  251. /* Search for path */
  252. if (msg->url != NULL && msg->url->len != 0) {
  253. http_parser_parse_url(msg->url->str, msg->url->len, TRUE, &u);
  254. if (u.field_set & (1 << UF_PATH)) {
  255. gsize unnorm_len;
  256. pathbuf = g_malloc(u.field_data[UF_PATH].len);
  257. memcpy(pathbuf, msg->url->str + u.field_data[UF_PATH].off,
  258. u.field_data[UF_PATH].len);
  259. lookup.begin = pathbuf;
  260. lookup.len = u.field_data[UF_PATH].len;
  261. rspamd_normalize_path_inplace(pathbuf,
  262. lookup.len,
  263. &unnorm_len);
  264. lookup.len = unnorm_len;
  265. }
  266. else {
  267. lookup.begin = msg->url->str;
  268. lookup.len = msg->url->len;
  269. }
  270. found = g_hash_table_lookup(entry->rt->paths, &lookup);
  271. memcpy(&handler, &found, sizeof(found));
  272. msg_debug("requested known path: %T", &lookup);
  273. }
  274. else {
  275. err = g_error_new(HTTP_ERROR, 404,
  276. "Empty path requested");
  277. if (entry->rt->error_handler != NULL) {
  278. entry->rt->error_handler(entry, err);
  279. }
  280. rspamd_http_router_send_error(err, entry);
  281. g_error_free(err);
  282. return 0;
  283. }
  284. entry->is_reply = TRUE;
  285. encoding = rspamd_http_message_find_header(msg, "Accept-Encoding");
  286. if (encoding && rspamd_substring_search(encoding->begin, encoding->len,
  287. "gzip", 4) != -1) {
  288. entry->support_gzip = TRUE;
  289. }
  290. if (handler != NULL) {
  291. if (pathbuf) {
  292. g_free(pathbuf);
  293. }
  294. return handler(entry, msg);
  295. }
  296. else {
  297. /* Try regexps */
  298. for (i = 0; i < router->regexps->len; i++) {
  299. re = g_ptr_array_index(router->regexps, i);
  300. if (rspamd_regexp_match(re, lookup.begin, lookup.len,
  301. TRUE)) {
  302. found = rspamd_regexp_get_ud(re);
  303. memcpy(&handler, &found, sizeof(found));
  304. if (pathbuf) {
  305. g_free(pathbuf);
  306. }
  307. return handler(entry, msg);
  308. }
  309. }
  310. /* Now try plain file */
  311. if (entry->rt->default_fs_path == NULL || lookup.len == 0 ||
  312. !rspamd_http_router_try_file(entry, &lookup, TRUE)) {
  313. err = g_error_new(HTTP_ERROR, 404,
  314. "Not found");
  315. if (entry->rt->error_handler != NULL) {
  316. entry->rt->error_handler(entry, err);
  317. }
  318. msg_info("path: %T not found", &lookup);
  319. rspamd_http_router_send_error(err, entry);
  320. g_error_free(err);
  321. }
  322. }
  323. }
  324. if (pathbuf) {
  325. g_free(pathbuf);
  326. }
  327. return 0;
  328. }
  329. struct rspamd_http_connection_router *
  330. rspamd_http_router_new(rspamd_http_router_error_handler_t eh,
  331. rspamd_http_router_finish_handler_t fh,
  332. ev_tstamp timeout,
  333. const char *default_fs_path,
  334. struct rspamd_http_context *ctx)
  335. {
  336. struct rspamd_http_connection_router *nrouter;
  337. struct stat st;
  338. nrouter = g_malloc0(sizeof(struct rspamd_http_connection_router));
  339. nrouter->paths = g_hash_table_new_full(rspamd_ftok_icase_hash,
  340. rspamd_ftok_icase_equal, rspamd_fstring_mapped_ftok_free, NULL);
  341. nrouter->regexps = g_ptr_array_new();
  342. nrouter->conns = NULL;
  343. nrouter->error_handler = eh;
  344. nrouter->finish_handler = fh;
  345. nrouter->response_headers = g_hash_table_new_full(rspamd_strcase_hash,
  346. rspamd_strcase_equal, g_free, g_free);
  347. nrouter->event_loop = ctx->event_loop;
  348. nrouter->timeout = timeout;
  349. nrouter->default_fs_path = NULL;
  350. if (default_fs_path != NULL) {
  351. if (stat(default_fs_path, &st) == -1) {
  352. msg_err("cannot stat %s", default_fs_path);
  353. }
  354. else {
  355. if (!S_ISDIR(st.st_mode)) {
  356. msg_err("path %s is not a directory", default_fs_path);
  357. }
  358. else {
  359. nrouter->default_fs_path = realpath(default_fs_path, NULL);
  360. }
  361. }
  362. }
  363. nrouter->ctx = ctx;
  364. return nrouter;
  365. }
  366. void rspamd_http_router_set_key(struct rspamd_http_connection_router *router,
  367. struct rspamd_cryptobox_keypair *key)
  368. {
  369. g_assert(key != NULL);
  370. router->key = rspamd_keypair_ref(key);
  371. }
  372. void rspamd_http_router_add_path(struct rspamd_http_connection_router *router,
  373. const char *path, rspamd_http_router_handler_t handler)
  374. {
  375. gpointer ptr;
  376. rspamd_ftok_t *key;
  377. rspamd_fstring_t *storage;
  378. G_STATIC_ASSERT(sizeof(rspamd_http_router_handler_t) ==
  379. sizeof(gpointer));
  380. if (path != NULL && handler != NULL && router != NULL) {
  381. memcpy(&ptr, &handler, sizeof(ptr));
  382. storage = rspamd_fstring_new_init(path, strlen(path));
  383. key = g_malloc0(sizeof(*key));
  384. key->begin = storage->str;
  385. key->len = storage->len;
  386. g_hash_table_insert(router->paths, key, ptr);
  387. }
  388. }
  389. void rspamd_http_router_set_unknown_handler(struct rspamd_http_connection_router *router,
  390. rspamd_http_router_handler_t handler)
  391. {
  392. if (router != NULL) {
  393. router->unknown_method_handler = handler;
  394. }
  395. }
  396. void rspamd_http_router_add_header(struct rspamd_http_connection_router *router,
  397. const char *name, const char *value)
  398. {
  399. if (name != NULL && value != NULL && router != NULL) {
  400. g_hash_table_replace(router->response_headers, g_strdup(name),
  401. g_strdup(value));
  402. }
  403. }
  404. void rspamd_http_router_insert_headers(struct rspamd_http_connection_router *router,
  405. struct rspamd_http_message *msg)
  406. {
  407. GHashTableIter it;
  408. gpointer k, v;
  409. if (router && msg) {
  410. g_hash_table_iter_init(&it, router->response_headers);
  411. while (g_hash_table_iter_next(&it, &k, &v)) {
  412. rspamd_http_message_add_header(msg, k, v);
  413. }
  414. }
  415. }
  416. void rspamd_http_router_add_regexp(struct rspamd_http_connection_router *router,
  417. struct rspamd_regexp_s *re, rspamd_http_router_handler_t handler)
  418. {
  419. gpointer ptr;
  420. G_STATIC_ASSERT(sizeof(rspamd_http_router_handler_t) ==
  421. sizeof(gpointer));
  422. if (re != NULL && handler != NULL && router != NULL) {
  423. memcpy(&ptr, &handler, sizeof(ptr));
  424. rspamd_regexp_set_ud(re, ptr);
  425. g_ptr_array_add(router->regexps, rspamd_regexp_ref(re));
  426. }
  427. }
  428. void rspamd_http_router_handle_socket(struct rspamd_http_connection_router *router,
  429. int fd, gpointer ud)
  430. {
  431. struct rspamd_http_connection_entry *conn;
  432. conn = g_malloc0(sizeof(struct rspamd_http_connection_entry));
  433. conn->rt = router;
  434. conn->ud = ud;
  435. conn->is_reply = FALSE;
  436. conn->conn = rspamd_http_connection_new_server(router->ctx,
  437. fd,
  438. NULL,
  439. rspamd_http_router_error_handler,
  440. rspamd_http_router_finish_handler,
  441. 0);
  442. if (router->key) {
  443. rspamd_http_connection_set_key(conn->conn, router->key);
  444. }
  445. rspamd_http_connection_read_message(conn->conn, conn, router->timeout);
  446. DL_PREPEND(router->conns, conn);
  447. }
  448. void rspamd_http_router_free(struct rspamd_http_connection_router *router)
  449. {
  450. struct rspamd_http_connection_entry *conn, *tmp;
  451. rspamd_regexp_t *re;
  452. unsigned int i;
  453. if (router) {
  454. DL_FOREACH_SAFE(router->conns, conn, tmp)
  455. {
  456. rspamd_http_entry_free(conn);
  457. }
  458. if (router->key) {
  459. rspamd_keypair_unref(router->key);
  460. }
  461. if (router->default_fs_path != NULL) {
  462. g_free(router->default_fs_path);
  463. }
  464. for (i = 0; i < router->regexps->len; i++) {
  465. re = g_ptr_array_index(router->regexps, i);
  466. rspamd_regexp_unref(re);
  467. }
  468. g_ptr_array_free(router->regexps, TRUE);
  469. g_hash_table_unref(router->paths);
  470. g_hash_table_unref(router->response_headers);
  471. g_free(router);
  472. }
  473. }