/[jscoverage]/trunk/jscoverage-server.c
ViewVC logotype

Contents of /trunk/jscoverage-server.c

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC 1.1.24