PageRenderTime 46ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/js/lib/Socket.IO-node/support/expresso/deps/jscoverage/jscoverage-server.c

http://github.com/onedayitwillmake/RealtimeMultiplayerNodeJs
C | 1307 lines | 1246 code | 32 blank | 29 comment | 61 complexity | a82a7f18446fdbb10937257b2ccf7c0e MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, BSD-3-Clause
  1. /*
  2. jscoverage-server.c - JSCoverage server main routine
  3. Copyright (C) 2008 siliconforks.com
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License along
  13. with this program; if not, write to the Free Software Foundation, Inc.,
  14. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  15. */
  16. #include <config.h>
  17. #include <assert.h>
  18. #include <ctype.h>
  19. #include <signal.h>
  20. #include <stdint.h>
  21. #include <string.h>
  22. #include <dirent.h>
  23. #ifdef HAVE_PTHREAD_H
  24. #include <pthread.h>
  25. #endif
  26. #include "encoding.h"
  27. #include "global.h"
  28. #include "http-server.h"
  29. #include "instrument-js.h"
  30. #include "resource-manager.h"
  31. #include "stream.h"
  32. #include "util.h"
  33. static const char * specified_encoding = NULL;
  34. const char * jscoverage_encoding = "ISO-8859-1";
  35. bool jscoverage_highlight = true;
  36. typedef struct SourceCache {
  37. char * url;
  38. uint16_t * characters;
  39. size_t num_characters;
  40. struct SourceCache * next;
  41. } SourceCache;
  42. static SourceCache * source_cache = NULL;
  43. static const struct {
  44. const char * extension;
  45. const char * mime_type;
  46. } mime_types[] = {
  47. {".gif", "image/gif"},
  48. {".jpg", "image/jpeg"},
  49. {".jpeg", "image/jpeg"},
  50. {".png", "image/png"},
  51. {".css", "text/css"},
  52. {".html", "text/html"},
  53. {".htm", "text/html"},
  54. {".js", "text/javascript"},
  55. {".txt", "text/plain"},
  56. {".xml", "application/xml"},
  57. };
  58. static bool verbose = false;
  59. static const char * report_directory = "jscoverage-report";
  60. static const char * document_root = ".";
  61. static bool proxy = false;
  62. static const char ** no_instrument;
  63. static size_t num_no_instrument = 0;
  64. #ifdef __MINGW32__
  65. CRITICAL_SECTION javascript_mutex;
  66. CRITICAL_SECTION source_cache_mutex;
  67. #define LOCK EnterCriticalSection
  68. #define UNLOCK LeaveCriticalSection
  69. #else
  70. pthread_mutex_t javascript_mutex = PTHREAD_MUTEX_INITIALIZER;
  71. pthread_mutex_t source_cache_mutex = PTHREAD_MUTEX_INITIALIZER;
  72. #define LOCK pthread_mutex_lock
  73. #define UNLOCK pthread_mutex_unlock
  74. #endif
  75. static const SourceCache * find_cached_source(const char * url) {
  76. SourceCache * result = NULL;
  77. LOCK(&source_cache_mutex);
  78. for (SourceCache * p = source_cache; p != NULL; p = p->next) {
  79. if (strcmp(url, p->url) == 0) {
  80. result = p;
  81. break;
  82. }
  83. }
  84. UNLOCK(&source_cache_mutex);
  85. return result;
  86. }
  87. static void add_cached_source(const char * url, uint16_t * characters, size_t num_characters) {
  88. SourceCache * new_source_cache = xmalloc(sizeof(SourceCache));
  89. new_source_cache->url = xstrdup(url);
  90. new_source_cache->characters = characters;
  91. new_source_cache->num_characters = num_characters;
  92. LOCK(&source_cache_mutex);
  93. new_source_cache->next = source_cache;
  94. source_cache = new_source_cache;
  95. UNLOCK(&source_cache_mutex);
  96. }
  97. static int get(const char * url, uint16_t ** characters, size_t * num_characters) __attribute__((warn_unused_result));
  98. static int get(const char * url, uint16_t ** characters, size_t * num_characters) {
  99. char * host = NULL;
  100. uint16_t port;
  101. char * abs_path = NULL;
  102. char * query = NULL;
  103. HTTPConnection * connection = NULL;
  104. HTTPExchange * exchange = NULL;
  105. Stream * stream = NULL;
  106. int result = URL_parse(url, &host, &port, &abs_path, &query);
  107. if (result != 0) {
  108. goto done;
  109. }
  110. connection = HTTPConnection_new_client(host, port);
  111. if (connection == NULL) {
  112. result = -1;
  113. goto done;
  114. }
  115. exchange = HTTPExchange_new(connection);
  116. HTTPExchange_set_request_uri(exchange, url);
  117. result = HTTPExchange_write_request_headers(exchange);
  118. if (result != 0) {
  119. goto done;
  120. }
  121. result = HTTPExchange_read_response_headers(exchange);
  122. if (result != 0) {
  123. goto done;
  124. }
  125. stream = Stream_new(0);
  126. result = HTTPExchange_read_entire_response_entity_body(exchange, stream);
  127. if (result != 0) {
  128. goto done;
  129. }
  130. char * encoding = HTTPMessage_get_charset(HTTPExchange_get_response_message(exchange));
  131. if (encoding == NULL) {
  132. encoding = xstrdup(jscoverage_encoding);
  133. }
  134. result = jscoverage_bytes_to_characters(encoding, stream->data, stream->length, characters, num_characters);
  135. free(encoding);
  136. if (result != 0) {
  137. goto done;
  138. }
  139. result = 0;
  140. done:
  141. if (stream != NULL) {
  142. Stream_delete(stream);
  143. }
  144. if (exchange != NULL) {
  145. HTTPExchange_delete(exchange);
  146. }
  147. if (connection != NULL) {
  148. if (HTTPConnection_delete(connection) != 0) {
  149. HTTPServer_log_err("Warning: error closing connection after retrieving URL: %s\n", url);
  150. }
  151. }
  152. free(host);
  153. free(abs_path);
  154. free(query);
  155. return result;
  156. }
  157. static void send_response(HTTPExchange * exchange, uint16_t status_code, const char * html) {
  158. HTTPExchange_set_status_code(exchange, status_code);
  159. if (HTTPExchange_write_response(exchange, html, strlen(html)) != 0) {
  160. HTTPServer_log_err("Warning: error writing to client\n");
  161. }
  162. }
  163. /*
  164. RFC 2396, Appendix A: we are checking for `pchar'
  165. */
  166. static bool is_escaped(char c) {
  167. /* `pchar' */
  168. if (strchr(":@&=+$,", c) != NULL) {
  169. return false;
  170. }
  171. if (isalnum((unsigned char) c)) {
  172. return false;
  173. }
  174. /* `mark' */
  175. if (strchr("-_.!~*'()", c) != NULL) {
  176. return false;
  177. }
  178. return true;
  179. }
  180. static char * encode_uri_component(const char * s) {
  181. size_t length = 0;
  182. for (const char * p = s; *p != '\0'; p++) {
  183. if (is_escaped(*p)) {
  184. length = addst(length, 3);
  185. }
  186. else {
  187. length = addst(length, 1);
  188. }
  189. }
  190. length = addst(length, 1);
  191. char * result = xmalloc(length);
  192. size_t i = 0;
  193. for (const char * p = s; *p != '\0'; p++) {
  194. if (is_escaped(*p)) {
  195. result[i] = '%';
  196. i++;
  197. snprintf(result + i, 3, "%02X", *p);
  198. i += 2;
  199. }
  200. else {
  201. result[i] = *p;
  202. i++;
  203. }
  204. }
  205. result[i] = '\0';
  206. return result;
  207. }
  208. static unsigned int hex_value(char c) {
  209. if ('0' <= c && c <= '9') {
  210. return c - '0';
  211. }
  212. else if ('A' <= c && c <= 'F') {
  213. return c - 'A' + 10;
  214. }
  215. else if ('a' <= c && c <= 'f') {
  216. return c - 'a' + 10;
  217. }
  218. else {
  219. return 0;
  220. }
  221. }
  222. static char * decode_uri_component(const char * s) {
  223. size_t length = strlen(s);
  224. char * result = xmalloc(length + 1);
  225. char * p = result;
  226. while (*s != '\0') {
  227. if (*s == '%') {
  228. if (s[1] == '\0' || s[2] == '\0') {
  229. *p = '\0';
  230. return result;
  231. }
  232. *p = hex_value(s[1]) * 16 + hex_value(s[2]);
  233. s += 2;
  234. }
  235. else {
  236. *p = *s;
  237. }
  238. p++;
  239. s++;
  240. }
  241. *p = '\0';
  242. return result;
  243. }
  244. static const char * get_entity(char c) {
  245. switch(c) {
  246. case '<':
  247. return "&lt;";
  248. case '>':
  249. return "&gt;";
  250. case '&':
  251. return "&amp;";
  252. case '\'':
  253. return "&apos;";
  254. case '"':
  255. return "&quot;";
  256. default:
  257. return NULL;
  258. }
  259. }
  260. static char * encode_html(const char * s) {
  261. size_t length = 0;
  262. for (const char * p = s; *p != '\0'; p++) {
  263. const char * entity = get_entity(*p);
  264. if (entity == NULL) {
  265. length = addst(length, 1);
  266. }
  267. else {
  268. length = addst(length, strlen(entity));
  269. }
  270. }
  271. length = addst(length, 1);
  272. char * result = xmalloc(length);
  273. size_t i = 0;
  274. for (const char * p = s; *p != '\0'; p++) {
  275. const char * entity = get_entity(*p);
  276. if (entity == NULL) {
  277. result[i] = *p;
  278. i++;
  279. }
  280. else {
  281. strcpy(result + i, entity);
  282. i += strlen(entity);
  283. }
  284. }
  285. result[i] = '\0';
  286. return result;
  287. }
  288. static const char * get_content_type(const char * path) {
  289. char * last_dot = strrchr(path, '.');
  290. if (last_dot == NULL) {
  291. return "application/octet-stream";
  292. }
  293. for (size_t i = 0; i < sizeof(mime_types) / sizeof(mime_types[0]); i++) {
  294. if (strcmp(last_dot, mime_types[i].extension) == 0) {
  295. return mime_types[i].mime_type;
  296. }
  297. }
  298. return "application/octet-stream";
  299. }
  300. /**
  301. Checks whether a URI is on the no-instrument list.
  302. @param uri the HTTP "Request-URI"; must not be NULL, and must not be a zero-length string
  303. @return true if the URI is on the no-instrument list, false otherwise
  304. */
  305. static bool is_no_instrument(const char * uri) {
  306. assert(*uri != '\0');
  307. for (size_t i = 0; i < num_no_instrument; i++) {
  308. if (str_starts_with(uri, no_instrument[i])) {
  309. return true;
  310. }
  311. /*
  312. For a local URL, accept "/foo/bar" and "foo/bar" on the no-instrument list.
  313. */
  314. if (! proxy && str_starts_with(uri + 1, no_instrument[i])) {
  315. return true;
  316. }
  317. }
  318. return false;
  319. }
  320. static bool is_javascript(HTTPExchange * exchange) {
  321. const char * header = HTTPExchange_find_response_header(exchange, HTTP_CONTENT_TYPE);
  322. if (header == NULL) {
  323. /* guess based on extension */
  324. return str_ends_with(HTTPExchange_get_request_uri(exchange), ".js");
  325. }
  326. else {
  327. char * semicolon = strchr(header, ';');
  328. char * content_type;
  329. if (semicolon == NULL) {
  330. content_type = xstrdup(header);
  331. }
  332. else {
  333. content_type = xstrndup(header, semicolon - header);
  334. }
  335. /* RFC 4329 */
  336. bool result = strcmp(content_type, "text/javascript") == 0 ||
  337. strcmp(content_type, "text/ecmascript") == 0 ||
  338. strcmp(content_type, "text/javascript1.0") == 0 ||
  339. strcmp(content_type, "text/javascript1.1") == 0 ||
  340. strcmp(content_type, "text/javascript1.2") == 0 ||
  341. strcmp(content_type, "text/javascript1.3") == 0 ||
  342. strcmp(content_type, "text/javascript1.4") == 0 ||
  343. strcmp(content_type, "text/javascript1.5") == 0 ||
  344. strcmp(content_type, "text/jscript") == 0 ||
  345. strcmp(content_type, "text/livescript") == 0 ||
  346. strcmp(content_type, "text/x-javascript") == 0 ||
  347. strcmp(content_type, "text/x-ecmascript") == 0 ||
  348. strcmp(content_type, "application/x-javascript") == 0 ||
  349. strcmp(content_type, "application/x-ecmascript") == 0 ||
  350. strcmp(content_type, "application/javascript") == 0 ||
  351. strcmp(content_type, "application/ecmascript") == 0;
  352. free(content_type);
  353. return result;
  354. }
  355. }
  356. static bool should_instrument_request(HTTPExchange * exchange) {
  357. if (! is_javascript(exchange)) {
  358. return false;
  359. }
  360. if (is_no_instrument(HTTPExchange_get_request_uri(exchange))) {
  361. return false;
  362. }
  363. return true;
  364. }
  365. static int merge(Coverage * coverage, FILE * f) __attribute__((warn_unused_result));
  366. static int merge(Coverage * coverage, FILE * f) {
  367. Stream * stream = Stream_new(0);
  368. Stream_write_file_contents(stream, f);
  369. LOCK(&javascript_mutex);
  370. int result = jscoverage_parse_json(coverage, stream->data, stream->length);
  371. UNLOCK(&javascript_mutex);
  372. Stream_delete(stream);
  373. return result;
  374. }
  375. static void write_js_quoted_string(FILE * f, char * data, size_t length) {
  376. putc('"', f);
  377. for (size_t i = 0; i < length; i++) {
  378. char c = data[i];
  379. switch (c) {
  380. case '\b':
  381. fputs("\\b", f);
  382. break;
  383. case '\f':
  384. fputs("\\f", f);
  385. break;
  386. case '\n':
  387. fputs("\\n", f);
  388. break;
  389. case '\r':
  390. fputs("\\r", f);
  391. break;
  392. case '\t':
  393. fputs("\\t", f);
  394. break;
  395. /* IE doesn't support this */
  396. /*
  397. case '\v':
  398. fputs("\\v", f);
  399. break;
  400. */
  401. case '"':
  402. fputs("\\\"", f);
  403. break;
  404. case '\\':
  405. fputs("\\\\", f);
  406. break;
  407. default:
  408. putc(c, f);
  409. break;
  410. }
  411. }
  412. putc('"', f);
  413. }
  414. static void write_source(const char * id, const uint16_t * characters, size_t num_characters, FILE * f) {
  415. Stream * output = Stream_new(num_characters);
  416. jscoverage_write_source(id, characters, num_characters, output);
  417. fwrite(output->data, 1, output->length, f);
  418. Stream_delete(output);
  419. }
  420. static void write_json_for_file(const FileCoverage * file_coverage, int i, void * p) {
  421. FILE * f = p;
  422. if (i > 0) {
  423. putc(',', f);
  424. }
  425. write_js_quoted_string(f, file_coverage->id, strlen(file_coverage->id));
  426. fputs(":{\"coverage\":[", f);
  427. for (uint32_t i = 0; i < file_coverage->num_coverage_lines; i++) {
  428. if (i > 0) {
  429. putc(',', f);
  430. }
  431. int timesExecuted = file_coverage->coverage_lines[i];
  432. if (timesExecuted < 0) {
  433. fputs("null", f);
  434. }
  435. else {
  436. fprintf(f, "%d", timesExecuted);
  437. }
  438. }
  439. fputs("],\"source\":", f);
  440. if (file_coverage->source_lines == NULL) {
  441. if (proxy) {
  442. const SourceCache * cached = find_cached_source(file_coverage->id);
  443. if (cached == NULL) {
  444. uint16_t * characters;
  445. size_t num_characters;
  446. if (get(file_coverage->id, &characters, &num_characters) == 0) {
  447. write_source(file_coverage->id, characters, num_characters, f);
  448. add_cached_source(file_coverage->id, characters, num_characters);
  449. }
  450. else {
  451. fputs("[]", f);
  452. HTTPServer_log_err("Warning: cannot retrieve URL: %s\n", file_coverage->id);
  453. }
  454. }
  455. else {
  456. write_source(file_coverage->id, cached->characters, cached->num_characters, f);
  457. }
  458. }
  459. else {
  460. /* check that the path begins with / */
  461. if (file_coverage->id[0] == '/') {
  462. char * source_path = make_path(document_root, file_coverage->id + 1);
  463. FILE * source_file = fopen(source_path, "rb");
  464. free(source_path);
  465. if (source_file == NULL) {
  466. fputs("[]", f);
  467. HTTPServer_log_err("Warning: cannot open file: %s\n", file_coverage->id);
  468. }
  469. else {
  470. Stream * stream = Stream_new(0);
  471. Stream_write_file_contents(stream, source_file);
  472. fclose(source_file);
  473. uint16_t * characters;
  474. size_t num_characters;
  475. int result = jscoverage_bytes_to_characters(jscoverage_encoding, stream->data, stream->length, &characters, &num_characters);
  476. Stream_delete(stream);
  477. if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) {
  478. fputs("[]", f);
  479. HTTPServer_log_err("Warning: encoding %s not supported\n", jscoverage_encoding);
  480. }
  481. else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) {
  482. fputs("[]", f);
  483. HTTPServer_log_err("Warning: error decoding %s in file %s\n", jscoverage_encoding, file_coverage->id);
  484. }
  485. else {
  486. write_source(file_coverage->id, characters, num_characters, f);
  487. free(characters);
  488. }
  489. }
  490. }
  491. else {
  492. /* path does not begin with / */
  493. fputs("[]", f);
  494. HTTPServer_log_err("Warning: invalid source path: %s\n", file_coverage->id);
  495. }
  496. }
  497. }
  498. else {
  499. fputc('[', f);
  500. for (uint32_t i = 0; i < file_coverage->num_source_lines; i++) {
  501. if (i > 0) {
  502. fputc(',', f);
  503. }
  504. char * source_line = file_coverage->source_lines[i];
  505. write_js_quoted_string(f, source_line, strlen(source_line));
  506. }
  507. fputc(']', f);
  508. }
  509. fputc('}', f);
  510. }
  511. static int write_json(Coverage * coverage, const char * path) __attribute__((warn_unused_result));
  512. static int write_json(Coverage * coverage, const char * path) {
  513. /* write the JSON */
  514. FILE * f = fopen(path, "wb");
  515. if (f == NULL) {
  516. return -1;
  517. }
  518. putc('{', f);
  519. Coverage_foreach_file(coverage, write_json_for_file, f);
  520. putc('}', f);
  521. if (fclose(f) == EOF) {
  522. return -1;
  523. }
  524. return 0;
  525. }
  526. static void handle_jscoverage_request(HTTPExchange * exchange) {
  527. /* set the `Server' response-header (RFC 2616 14.38, 3.8) */
  528. HTTPExchange_set_response_header(exchange, HTTP_SERVER, "jscoverage-server/" VERSION);
  529. const char * abs_path = HTTPExchange_get_abs_path(exchange);
  530. assert(*abs_path != '\0');
  531. if (str_starts_with(abs_path, "/jscoverage-store")) {
  532. if (strcmp(HTTPExchange_get_method(exchange), "POST") != 0) {
  533. HTTPExchange_set_response_header(exchange, HTTP_ALLOW, "POST");
  534. send_response(exchange, 405, "Method not allowed\n");
  535. return;
  536. }
  537. Stream * json = Stream_new(0);
  538. /* read the POST body */
  539. if (HTTPExchange_read_entire_request_entity_body(exchange, json) != 0) {
  540. Stream_delete(json);
  541. send_response(exchange, 400, "Could not read request body\n");
  542. return;
  543. }
  544. Coverage * coverage = Coverage_new();
  545. LOCK(&javascript_mutex);
  546. int result = jscoverage_parse_json(coverage, json->data, json->length);
  547. UNLOCK(&javascript_mutex);
  548. Stream_delete(json);
  549. if (result != 0) {
  550. Coverage_delete(coverage);
  551. send_response(exchange, 400, "Could not parse coverage data\n");
  552. return;
  553. }
  554. mkdir_if_necessary(report_directory);
  555. char * current_report_directory;
  556. if (str_starts_with(abs_path, "/jscoverage-store/") && abs_path[18] != '\0') {
  557. char * dir = decode_uri_component(abs_path + 18);
  558. current_report_directory = make_path(report_directory, dir);
  559. free(dir);
  560. }
  561. else {
  562. current_report_directory = xstrdup(report_directory);
  563. }
  564. mkdir_if_necessary(current_report_directory);
  565. char * path = make_path(current_report_directory, "jscoverage.json");
  566. /* check if the JSON file exists */
  567. struct stat buf;
  568. if (stat(path, &buf) == 0) {
  569. /* it exists: merge */
  570. FILE * f = fopen(path, "rb");
  571. if (f == NULL) {
  572. result = 1;
  573. }
  574. else {
  575. result = merge(coverage, f);
  576. if (fclose(f) == EOF) {
  577. result = 1;
  578. }
  579. }
  580. if (result != 0) {
  581. free(current_report_directory);
  582. free(path);
  583. Coverage_delete(coverage);
  584. send_response(exchange, 500, "Could not merge with existing coverage data\n");
  585. return;
  586. }
  587. }
  588. result = write_json(coverage, path);
  589. free(path);
  590. Coverage_delete(coverage);
  591. if (result != 0) {
  592. free(current_report_directory);
  593. send_response(exchange, 500, "Could not write coverage data\n");
  594. return;
  595. }
  596. /* copy other files */
  597. jscoverage_copy_resources(current_report_directory);
  598. path = make_path(current_report_directory, "jscoverage.js");
  599. free(current_report_directory);
  600. FILE * f = fopen(path, "ab");
  601. free(path);
  602. if (f == NULL) {
  603. send_response(exchange, 500, "Could not write to file: jscoverage.js\n");
  604. return;
  605. }
  606. fputs("jscoverage_isReport = true;\r\n", f);
  607. if (fclose(f) == EOF) {
  608. send_response(exchange, 500, "Could not write to file: jscoverage.js\n");
  609. return;
  610. }
  611. send_response(exchange, 200, "Coverage data stored\n");
  612. }
  613. else if (str_starts_with(abs_path, "/jscoverage-shutdown")) {
  614. if (strcmp(HTTPExchange_get_method(exchange), "POST") != 0) {
  615. HTTPExchange_set_response_header(exchange, HTTP_ALLOW, "POST");
  616. send_response(exchange, 405, "Method not allowed\n");
  617. return;
  618. }
  619. /* allow only from localhost */
  620. struct sockaddr_in client;
  621. if (HTTPExchange_get_peer(exchange, &client) != 0) {
  622. send_response(exchange, 500, "Cannot get client address\n");
  623. return;
  624. }
  625. if (client.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) {
  626. send_response(exchange, 403, "This operation can be performed only by localhost\n");
  627. return;
  628. }
  629. send_response(exchange, 200, "The server will now shut down\n");
  630. HTTPServer_shutdown();
  631. }
  632. else {
  633. const char * path = abs_path + 1;
  634. const struct Resource * resource = get_resource(path);
  635. if (resource == NULL) {
  636. send_response(exchange, 404, "Not found\n");
  637. return;
  638. }
  639. HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, get_content_type(path));
  640. if (HTTPExchange_write_response(exchange, resource->data, resource->length) != 0) {
  641. HTTPServer_log_err("Warning: error writing to client\n");
  642. return;
  643. }
  644. if (strcmp(abs_path, "/jscoverage.js") == 0) {
  645. const char * s = "jscoverage_isServer = true;\r\n";
  646. if (HTTPExchange_write_response(exchange, s, strlen(s)) != 0) {
  647. HTTPServer_log_err("Warning: error writing to client\n");
  648. }
  649. }
  650. }
  651. }
  652. static void instrument_js(const char * id, const uint16_t * characters, size_t num_characters, Stream * output_stream) {
  653. const struct Resource * resource = get_resource("report.js");
  654. Stream_write(output_stream, resource->data, resource->length);
  655. LOCK(&javascript_mutex);
  656. jscoverage_instrument_js(id, characters, num_characters, output_stream);
  657. UNLOCK(&javascript_mutex);
  658. }
  659. static bool is_hop_by_hop_header(const char * h) {
  660. /* hop-by-hop headers (RFC 2616 13.5.1) */
  661. return strcasecmp(h, HTTP_CONNECTION) == 0 ||
  662. strcasecmp(h, "Keep-Alive") == 0 ||
  663. strcasecmp(h, HTTP_PROXY_AUTHENTICATE) == 0 ||
  664. strcasecmp(h, HTTP_PROXY_AUTHORIZATION) == 0 ||
  665. strcasecmp(h, HTTP_TE) == 0 ||
  666. strcasecmp(h, HTTP_TRAILER) == 0 ||
  667. strcasecmp(h, HTTP_TRANSFER_ENCODING) == 0 ||
  668. strcasecmp(h, HTTP_UPGRADE) == 0;
  669. }
  670. static void add_via_header(HTTPMessage * message, const char * version) {
  671. char * value;
  672. xasprintf(&value, "%s jscoverage-server", version);
  673. HTTPMessage_add_header(message, HTTP_VIA, value);
  674. free(value);
  675. }
  676. static int copy_http_message_body(HTTPMessage * from, HTTPMessage * to) __attribute__((warn_unused_result));
  677. static int copy_http_message_body(HTTPMessage * from, HTTPMessage * to) {
  678. uint8_t * buffer[8192];
  679. for (;;) {
  680. size_t bytes_read;
  681. int result = HTTPMessage_read_message_body(from, buffer, 8192, &bytes_read);
  682. if (result != 0) {
  683. return result;
  684. }
  685. if (bytes_read == 0) {
  686. return 0;
  687. }
  688. result = HTTPMessage_write(to, buffer, bytes_read);
  689. if (result != 0) {
  690. return result;
  691. }
  692. }
  693. }
  694. static void handle_proxy_request(HTTPExchange * client_exchange) {
  695. HTTPConnection * server_connection = NULL;
  696. HTTPExchange * server_exchange = NULL;
  697. const char * abs_path = HTTPExchange_get_abs_path(client_exchange);
  698. if (str_starts_with(abs_path, "/jscoverage")) {
  699. handle_jscoverage_request(client_exchange);
  700. return;
  701. }
  702. const char * host = HTTPExchange_get_host(client_exchange);
  703. uint16_t port = HTTPExchange_get_port(client_exchange);
  704. /* create a new connection */
  705. server_connection = HTTPConnection_new_client(host, port);
  706. if (server_connection == NULL) {
  707. send_response(client_exchange, 504, "Could not connect to server\n");
  708. goto done;
  709. }
  710. /* create a new exchange */
  711. server_exchange = HTTPExchange_new(server_connection);
  712. HTTPExchange_set_method(server_exchange, HTTPExchange_get_method(client_exchange));
  713. HTTPExchange_set_request_uri(server_exchange, HTTPExchange_get_request_uri(client_exchange));
  714. for (const HTTPHeader * h = HTTPExchange_get_request_headers(client_exchange); h != NULL; h = h->next) {
  715. if (strcasecmp(h->name, HTTP_TRAILER) == 0 || strcasecmp(h->name, HTTP_TRANSFER_ENCODING) == 0) {
  716. /* do nothing: we want to keep this header */
  717. }
  718. else if (is_hop_by_hop_header(h->name) ||
  719. strcasecmp(h->name, HTTP_ACCEPT_ENCODING) == 0 ||
  720. strcasecmp(h->name, HTTP_RANGE) == 0) {
  721. continue;
  722. }
  723. HTTPExchange_add_request_header(server_exchange, h->name, h->value);
  724. }
  725. add_via_header(HTTPExchange_get_request_message(server_exchange), HTTPExchange_get_request_http_version(client_exchange));
  726. /* send the request */
  727. if (HTTPExchange_write_request_headers(server_exchange) != 0) {
  728. send_response(client_exchange, 502, "Could not write to server\n");
  729. goto done;
  730. }
  731. /* handle POST or PUT */
  732. if (HTTPExchange_request_has_body(client_exchange)) {
  733. HTTPMessage * client_request = HTTPExchange_get_request_message(client_exchange);
  734. HTTPMessage * server_request = HTTPExchange_get_request_message(server_exchange);
  735. if (copy_http_message_body(client_request, server_request) != 0) {
  736. send_response(client_exchange, 400, "Error copying request body from client to server\n");
  737. goto done;
  738. }
  739. }
  740. if (HTTPExchange_flush_request(server_exchange) != 0) {
  741. send_response(client_exchange, 502, "Could not write to server\n");
  742. goto done;
  743. }
  744. /* receive the response */
  745. if (HTTPExchange_read_response_headers(server_exchange) != 0) {
  746. send_response(client_exchange, 502, "Could not read headers from server\n");
  747. goto done;
  748. }
  749. HTTPExchange_set_status_code(client_exchange, HTTPExchange_get_status_code(server_exchange));
  750. if (HTTPExchange_response_has_body(server_exchange) && should_instrument_request(server_exchange)) {
  751. /* needs instrumentation */
  752. Stream * input_stream = Stream_new(0);
  753. if (HTTPExchange_read_entire_response_entity_body(server_exchange, input_stream) != 0) {
  754. Stream_delete(input_stream);
  755. send_response(client_exchange, 502, "Could not read body from server\n");
  756. goto done;
  757. }
  758. const char * request_uri = HTTPExchange_get_request_uri(client_exchange);
  759. char * encoding = HTTPMessage_get_charset(HTTPExchange_get_response_message(server_exchange));
  760. if (encoding == NULL) {
  761. encoding = xstrdup(jscoverage_encoding);
  762. }
  763. uint16_t * characters;
  764. size_t num_characters;
  765. int result = jscoverage_bytes_to_characters(encoding, input_stream->data, input_stream->length, &characters, &num_characters);
  766. free(encoding);
  767. Stream_delete(input_stream);
  768. if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) {
  769. send_response(client_exchange, 500, "Encoding not supported\n");
  770. goto done;
  771. }
  772. else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) {
  773. send_response(client_exchange, 502, "Error decoding response\n");
  774. goto done;
  775. }
  776. Stream * output_stream = Stream_new(0);
  777. instrument_js(request_uri, characters, num_characters, output_stream);
  778. /* send the headers to the client */
  779. for (const HTTPHeader * h = HTTPExchange_get_response_headers(server_exchange); h != NULL; h = h->next) {
  780. if (is_hop_by_hop_header(h->name) || strcasecmp(h->name, HTTP_CONTENT_LENGTH) == 0) {
  781. continue;
  782. }
  783. else if (strcasecmp(h->name, HTTP_CONTENT_TYPE) == 0) {
  784. HTTPExchange_add_response_header(client_exchange, HTTP_CONTENT_TYPE, "text/javascript; charset=ISO-8859-1");
  785. continue;
  786. }
  787. HTTPExchange_add_response_header(client_exchange, h->name, h->value);
  788. }
  789. add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange));
  790. HTTPExchange_set_response_content_length(client_exchange, output_stream->length);
  791. /* send the instrumented code to the client */
  792. if (HTTPExchange_write_response(client_exchange, output_stream->data, output_stream->length) != 0) {
  793. HTTPServer_log_err("Warning: error writing to client\n");
  794. }
  795. /* characters go on the cache */
  796. /*
  797. free(characters);
  798. */
  799. Stream_delete(output_stream);
  800. add_cached_source(request_uri, characters, num_characters);
  801. }
  802. else {
  803. /* does not need instrumentation */
  804. /* send the headers to the client */
  805. for (const HTTPHeader * h = HTTPExchange_get_response_headers(server_exchange); h != NULL; h = h->next) {
  806. if (strcasecmp(h->name, HTTP_TRAILER) == 0 || strcasecmp(h->name, HTTP_TRANSFER_ENCODING) == 0) {
  807. /* do nothing: we want to keep this header */
  808. }
  809. else if (is_hop_by_hop_header(h->name)) {
  810. continue;
  811. }
  812. HTTPExchange_add_response_header(client_exchange, h->name, h->value);
  813. }
  814. add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange));
  815. if (HTTPExchange_write_response_headers(client_exchange) != 0) {
  816. HTTPServer_log_err("Warning: error writing to client\n");
  817. goto done;
  818. }
  819. if (HTTPExchange_response_has_body(server_exchange)) {
  820. /* read the body from the server and send it to the client */
  821. HTTPMessage * client_response = HTTPExchange_get_response_message(client_exchange);
  822. HTTPMessage * server_response = HTTPExchange_get_response_message(server_exchange);
  823. if (copy_http_message_body(server_response, client_response) != 0) {
  824. HTTPServer_log_err("Warning: error copying response body from server to client\n");
  825. goto done;
  826. }
  827. }
  828. }
  829. done:
  830. if (server_exchange != NULL) {
  831. HTTPExchange_delete(server_exchange);
  832. }
  833. if (server_connection != NULL) {
  834. if (HTTPConnection_delete(server_connection) != 0) {
  835. HTTPServer_log_err("Warning: error closing connection to server\n");
  836. }
  837. }
  838. }
  839. static void handle_local_request(HTTPExchange * exchange) {
  840. /* add the `Server' response-header (RFC 2616 14.38, 3.8) */
  841. HTTPExchange_add_response_header(exchange, HTTP_SERVER, "jscoverage-server/" VERSION);
  842. char * decoded_path = NULL;
  843. char * filesystem_path = NULL;
  844. const char * abs_path = HTTPExchange_get_abs_path(exchange);
  845. assert(*abs_path != '\0');
  846. decoded_path = decode_uri_component(abs_path);
  847. if (str_starts_with(decoded_path, "/jscoverage")) {
  848. handle_jscoverage_request(exchange);
  849. goto done;
  850. }
  851. if (strstr(decoded_path, "..") != NULL) {
  852. send_response(exchange, 403, "Forbidden\n");
  853. goto done;
  854. }
  855. filesystem_path = make_path(document_root, decoded_path + 1);
  856. size_t filesystem_path_length = strlen(filesystem_path);
  857. if (filesystem_path_length > 0 && filesystem_path[filesystem_path_length - 1] == '/') {
  858. /* stat on Windows doesn't work with trailing slash */
  859. filesystem_path[filesystem_path_length - 1] = '\0';
  860. }
  861. struct stat buf;
  862. if (stat(filesystem_path, &buf) == -1) {
  863. send_response(exchange, 404, "Not found\n");
  864. goto done;
  865. }
  866. if (S_ISDIR(buf.st_mode)) {
  867. if (abs_path[strlen(abs_path) - 1] != '/') {
  868. const char * request_uri = HTTPExchange_get_request_uri(exchange);
  869. char * uri = xmalloc(strlen(request_uri) + 2);
  870. strcpy(uri, request_uri);
  871. strcat(uri, "/");
  872. HTTPExchange_add_response_header(exchange, "Location", uri);
  873. free(uri);
  874. send_response(exchange, 301, "Moved permanently\n");
  875. goto done;
  876. }
  877. DIR * d = opendir(filesystem_path);
  878. if (d == NULL) {
  879. send_response(exchange, 404, "Not found\n");
  880. goto done;
  881. }
  882. struct dirent * entry;
  883. while ((entry = readdir(d)) != NULL) {
  884. char * href = encode_uri_component(entry->d_name);
  885. char * html_href = encode_html(href);
  886. char * link = encode_html(entry->d_name);
  887. char * directory_entry;
  888. xasprintf(&directory_entry, "<a href=\"%s\">%s</a><br>\n", html_href, link);
  889. if (HTTPExchange_write_response(exchange, directory_entry, strlen(directory_entry)) != 0) {
  890. HTTPServer_log_err("Warning: error writing to client\n");
  891. }
  892. free(directory_entry);
  893. free(href);
  894. free(html_href);
  895. free(link);
  896. }
  897. closedir(d);
  898. }
  899. else if (S_ISREG(buf.st_mode)) {
  900. FILE * f = fopen(filesystem_path, "rb");
  901. if (f == NULL) {
  902. send_response(exchange, 404, "Not found\n");
  903. goto done;
  904. }
  905. /*
  906. When do we send a charset with Content-Type?
  907. if Content-Type is "text" or "application"
  908. if instrumented JavaScript
  909. use Content-Type: application/javascript; charset=ISO-8859-1
  910. else if --encoding is given
  911. use that encoding
  912. else
  913. send no charset
  914. else
  915. send no charset
  916. */
  917. const char * content_type = get_content_type(filesystem_path);
  918. if (strcmp(content_type, "text/javascript") == 0 && ! is_no_instrument(abs_path)) {
  919. HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, "text/javascript; charset=ISO-8859-1");
  920. Stream * input_stream = Stream_new(0);
  921. Stream_write_file_contents(input_stream, f);
  922. uint16_t * characters;
  923. size_t num_characters;
  924. int result = jscoverage_bytes_to_characters(jscoverage_encoding, input_stream->data, input_stream->length, &characters, &num_characters);
  925. Stream_delete(input_stream);
  926. if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) {
  927. send_response(exchange, 500, "Encoding not supported\n");
  928. goto done;
  929. }
  930. else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) {
  931. send_response(exchange, 500, "Error decoding JavaScript file\n");
  932. goto done;
  933. }
  934. Stream * output_stream = Stream_new(0);
  935. instrument_js(abs_path, characters, num_characters, output_stream);
  936. free(characters);
  937. if (HTTPExchange_write_response(exchange, output_stream->data, output_stream->length) != 0) {
  938. HTTPServer_log_err("Warning: error writing to client\n");
  939. }
  940. Stream_delete(output_stream);
  941. }
  942. else {
  943. /* send the Content-Type with charset if necessary */
  944. if (specified_encoding != NULL && (str_starts_with(content_type, "text/") || str_starts_with(content_type, "application/"))) {
  945. char * content_type_with_charset = NULL;
  946. xasprintf(&content_type_with_charset, "%s; charset=%s", content_type, specified_encoding);
  947. HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type_with_charset);
  948. free(content_type_with_charset);
  949. }
  950. else {
  951. HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type);
  952. }
  953. char buffer[8192];
  954. size_t bytes_read;
  955. while ((bytes_read = fread(buffer, 1, 8192, f)) > 0) {
  956. if (HTTPExchange_write_response(exchange, buffer, bytes_read) != 0) {
  957. HTTPServer_log_err("Warning: error writing to client\n");
  958. }
  959. }
  960. }
  961. fclose(f);
  962. }
  963. else {
  964. send_response(exchange, 404, "Not found\n");
  965. goto done;
  966. }
  967. done:
  968. free(filesystem_path);
  969. free(decoded_path);
  970. }
  971. static void handler(HTTPExchange * exchange) {
  972. if (verbose) {
  973. HTTPServer_log_out("%s", HTTPExchange_get_request_line(exchange));
  974. }
  975. if (proxy) {
  976. handle_proxy_request(exchange);
  977. }
  978. else {
  979. handle_local_request(exchange);
  980. }
  981. }
  982. int main(int argc, char ** argv) {
  983. program = "jscoverage-server";
  984. const char * ip_address = "127.0.0.1";
  985. const char * port = "8080";
  986. int shutdown = 0;
  987. no_instrument = xnew(const char *, argc - 1);
  988. for (int i = 1; i < argc; i++) {
  989. if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
  990. copy_resource_to_stream("jscoverage-server-help.txt", stdout);
  991. exit(EXIT_SUCCESS);
  992. }
  993. else if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) {
  994. version();
  995. }
  996. else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
  997. verbose = 1;
  998. }
  999. else if (strcmp(argv[i], "--report-dir") == 0) {
  1000. i++;
  1001. if (i == argc) {
  1002. fatal_command_line("--report-dir: option requires an argument");
  1003. }
  1004. report_directory = argv[i];
  1005. }
  1006. else if (strncmp(argv[i], "--report-dir=", 13) == 0) {
  1007. report_directory = argv[i] + 13;
  1008. }
  1009. else if (strcmp(argv[i], "--document-root") == 0) {
  1010. i++;
  1011. if (i == argc) {
  1012. fatal_command_line("--document-root: option requires an argument");
  1013. }
  1014. document_root = argv[i];
  1015. }
  1016. else if (strncmp(argv[i], "--document-root=", 16) == 0) {
  1017. document_root = argv[i] + 16;
  1018. }
  1019. else if (strcmp(argv[i], "--encoding") == 0) {
  1020. i++;
  1021. if (i == argc) {
  1022. fatal_command_line("--encoding: option requires an argument");
  1023. }
  1024. jscoverage_encoding = argv[i];
  1025. specified_encoding = jscoverage_encoding;
  1026. }
  1027. else if (strncmp(argv[i], "--encoding=", 11) == 0) {
  1028. jscoverage_encoding = argv[i] + 11;
  1029. specified_encoding = jscoverage_encoding;
  1030. }
  1031. else if (strcmp(argv[i], "--ip-address") == 0) {
  1032. i++;
  1033. if (i == argc) {
  1034. fatal_command_line("--ip-address: option requires an argument");
  1035. }
  1036. ip_address = argv[i];
  1037. }
  1038. else if (strncmp(argv[i], "--ip-address=", 13) == 0) {
  1039. ip_address = argv[i] + 13;
  1040. }
  1041. else if (strcmp(argv[i], "--js-version") == 0) {
  1042. i++;
  1043. if (i == argc) {
  1044. fatal_command_line("--js-version: option requires an argument");
  1045. }
  1046. jscoverage_set_js_version(argv[i]);
  1047. }
  1048. else if (strncmp(argv[i], "--js-version=", 13) == 0) {
  1049. jscoverage_set_js_version(argv[i] + 13);
  1050. }
  1051. else if (strcmp(argv[i], "--no-highlight") == 0) {
  1052. jscoverage_highlight = false;
  1053. }
  1054. else if (strcmp(argv[i], "--no-instrument") == 0) {
  1055. i++;
  1056. if (i == argc) {
  1057. fatal_command_line("--no-instrument: option requires an argument");
  1058. }
  1059. no_instrument[num_no_instrument] = argv[i];
  1060. num_no_instrument++;
  1061. }
  1062. else if (strncmp(argv[i], "--no-instrument=", 16) == 0) {
  1063. no_instrument[num_no_instrument] = argv[i] + 16;
  1064. num_no_instrument++;
  1065. }
  1066. else if (strcmp(argv[i], "--port") == 0) {
  1067. i++;
  1068. if (i == argc) {
  1069. fatal_command_line("--port: option requires an argument");
  1070. }
  1071. port = argv[i];
  1072. }
  1073. else if (strncmp(argv[i], "--port=", 7) == 0) {
  1074. port = argv[i] + 7;
  1075. }
  1076. else if (strcmp(argv[i], "--proxy") == 0) {
  1077. proxy = 1;
  1078. }
  1079. else if (strcmp(argv[i], "--shutdown") == 0) {
  1080. shutdown = 1;
  1081. }
  1082. else if (strncmp(argv[i], "-", 1) == 0) {
  1083. fatal_command_line("unrecognized option `%s'", argv[i]);
  1084. }
  1085. else {
  1086. fatal_command_line("too many arguments");
  1087. }
  1088. }
  1089. /* check the port */
  1090. char * end;
  1091. unsigned long numeric_port = strtoul(port, &end, 10);
  1092. if (*end != '\0') {
  1093. fatal_command_line("--port: option must be an integer");
  1094. }
  1095. if (numeric_port > UINT16_MAX) {
  1096. fatal_command_line("--port: option must be 16 bits");
  1097. }
  1098. /* is this a shutdown? */
  1099. if (shutdown) {
  1100. #ifdef __MINGW32__
  1101. WSADATA data;
  1102. if (WSAStartup(MAKEWORD(1, 1), &data) != 0) {
  1103. fatal("could not start Winsock");
  1104. }
  1105. #endif
  1106. /* INADDR_LOOPBACK */
  1107. HTTPConnection * connection = HTTPConnection_new_client("127.0.0.1", numeric_port);
  1108. if (connection == NULL) {
  1109. fatal("could not connect to server");
  1110. }
  1111. HTTPExchange * exchange = HTTPExchange_new(connection);
  1112. HTTPExchange_set_method(exchange, "POST");
  1113. HTTPExchange_set_request_uri(exchange, "/jscoverage-shutdown");
  1114. if (HTTPExchange_write_request_headers(exchange) != 0) {
  1115. fatal("could not write request headers to server");
  1116. }
  1117. if (HTTPExchange_read_response_headers(exchange) != 0) {
  1118. fatal("could not read response headers from server");
  1119. }
  1120. Stream * stream = Stream_new(0);
  1121. if (HTTPExchange_read_entire_response_entity_body(exchange, stream) != 0) {
  1122. fatal("could not read response body from server");
  1123. }
  1124. fwrite(stream->data, 1, stream->length, stdout);
  1125. Stream_delete(stream);
  1126. HTTPExchange_delete(exchange);
  1127. if (HTTPConnection_delete(connection) != 0) {
  1128. fatal("could not close connection with server");
  1129. }
  1130. exit(EXIT_SUCCESS);
  1131. }
  1132. jscoverage_init();
  1133. #ifndef __MINGW32__
  1134. /* handle broken pipe */
  1135. signal(SIGPIPE, SIG_IGN);
  1136. #endif
  1137. #ifdef __MINGW32__
  1138. InitializeCriticalSection(&javascript_mutex);
  1139. InitializeCriticalSection(&source_cache_mutex);
  1140. #endif
  1141. if (verbose) {
  1142. printf("Starting HTTP server on %s:%lu\n", ip_address, numeric_port);
  1143. fflush(stdout);
  1144. }
  1145. HTTPServer_run(ip_address, (uint16_t) numeric_port, handler);
  1146. if (verbose) {
  1147. printf("Stopping HTTP server\n");
  1148. fflush(stdout);
  1149. }
  1150. jscoverage_cleanup();
  1151. free(no_instrument);
  1152. LOCK(&source_cache_mutex);
  1153. while (source_cache != NULL) {
  1154. SourceCache * p = source_cache;
  1155. source_cache = source_cache->next;
  1156. free(p->url);
  1157. free(p->characters);
  1158. free(p);
  1159. }
  1160. UNLOCK(&source_cache_mutex);
  1161. return 0;
  1162. }