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

Contents of /trunk/jscoverage-server.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 179 - (show annotations)
Sun Sep 21 18:35:21 2008 UTC (11 years, 2 months ago) by siliconforks
File MIME type: text/plain
File size: 36762 byte(s)
Do source code highlighting during instrumentation.

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 const char * get_entity(char c) {
241 switch(c) {
242 case '<':
243 return "&lt;";
244 case '>':
245 return "&gt;";
246 case '&':
247 return "&amp;";
248 case '\'':
249 return "&apos;";
250 case '"':
251 return "&quot;";
252 default:
253 return NULL;
254 }
255 }
256
257 static char * encode_html(const char * s) {
258 size_t length = 0;
259 for (const char * p = s; *p != '\0'; p++) {
260 const char * entity = get_entity(*p);
261 if (entity == NULL) {
262 length = addst(length, 1);
263 }
264 else {
265 length = addst(length, strlen(entity));
266 }
267 }
268
269 length = addst(length, 1);
270 char * result = xmalloc(length);
271 size_t i = 0;
272 for (const char * p = s; *p != '\0'; p++) {
273 const char * entity = get_entity(*p);
274 if (entity == NULL) {
275 result[i] = *p;
276 i++;
277 }
278 else {
279 strcpy(result + i, entity);
280 i += strlen(entity);
281 }
282 }
283 result[i] = '\0';
284
285 return result;
286 }
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 /**
302 Checks whether a URI is on the no-instrument list.
303 @param uri the HTTP "Request-URI"; must not be NULL, and must not be a zero-length string
304 @return true if the URI is on the no-instrument list, false otherwise
305 */
306 static bool is_no_instrument(const char * uri) {
307 assert(*uri != '\0');
308
309 for (size_t i = 0; i < num_no_instrument; i++) {
310 if (str_starts_with(uri, no_instrument[i])) {
311 return true;
312 }
313
314 /*
315 For a local URL, accept "/foo/bar" and "foo/bar" on the no-instrument list.
316 */
317 if (! proxy && str_starts_with(uri + 1, no_instrument[i])) {
318 return true;
319 }
320 }
321
322 return false;
323 }
324
325 static bool is_javascript(HTTPExchange * exchange) {
326 const char * header = HTTPExchange_find_response_header(exchange, HTTP_CONTENT_TYPE);
327 if (header == NULL) {
328 /* guess based on extension */
329 return str_ends_with(HTTPExchange_get_request_uri(exchange), ".js");
330 }
331 else {
332 char * semicolon = strchr(header, ';');
333 char * content_type;
334 if (semicolon == NULL) {
335 content_type = xstrdup(header);
336 }
337 else {
338 content_type = xstrndup(header, semicolon - header);
339 }
340 /* RFC 4329 */
341 bool result = strcmp(content_type, "text/javascript") == 0 ||
342 strcmp(content_type, "text/ecmascript") == 0 ||
343 strcmp(content_type, "text/javascript1.0") == 0 ||
344 strcmp(content_type, "text/javascript1.1") == 0 ||
345 strcmp(content_type, "text/javascript1.2") == 0 ||
346 strcmp(content_type, "text/javascript1.3") == 0 ||
347 strcmp(content_type, "text/javascript1.4") == 0 ||
348 strcmp(content_type, "text/javascript1.5") == 0 ||
349 strcmp(content_type, "text/jscript") == 0 ||
350 strcmp(content_type, "text/livescript") == 0 ||
351 strcmp(content_type, "text/x-javascript") == 0 ||
352 strcmp(content_type, "text/x-ecmascript") == 0 ||
353 strcmp(content_type, "application/x-javascript") == 0 ||
354 strcmp(content_type, "application/x-ecmascript") == 0 ||
355 strcmp(content_type, "application/javascript") == 0 ||
356 strcmp(content_type, "application/ecmascript") == 0;
357 free(content_type);
358 return result;
359 }
360 }
361
362 static bool should_instrument_request(HTTPExchange * exchange) {
363 if (! is_javascript(exchange)) {
364 return false;
365 }
366
367 if (is_no_instrument(HTTPExchange_get_request_uri(exchange))) {
368 return false;
369 }
370
371 return true;
372 }
373
374 static int merge(Coverage * coverage, FILE * f) __attribute__((warn_unused_result));
375
376 static int merge(Coverage * coverage, FILE * f) {
377 Stream * stream = Stream_new(0);
378 Stream_write_file_contents(stream, f);
379
380 LOCK(&javascript_mutex);
381 int result = jscoverage_parse_json(coverage, stream->data, stream->length);
382 UNLOCK(&javascript_mutex);
383
384 Stream_delete(stream);
385 return result;
386 }
387
388 static void write_js_quoted_string(FILE * f, char * data, size_t length) {
389 putc('"', f);
390 for (size_t i = 0; i < length; i++) {
391 char c = data[i];
392 switch (c) {
393 case '\b':
394 fputs("\\b", f);
395 break;
396 case '\f':
397 fputs("\\f", f);
398 break;
399 case '\n':
400 fputs("\\n", f);
401 break;
402 case '\r':
403 fputs("\\r", f);
404 break;
405 case '\t':
406 fputs("\\t", f);
407 break;
408 case '\v':
409 fputs("\\v", f);
410 break;
411 case '"':
412 fputs("\\\"", f);
413 break;
414 case '\\':
415 fputs("\\\\", f);
416 break;
417 default:
418 putc(c, f);
419 break;
420 }
421 }
422 putc('"', f);
423 }
424
425 static void write_source(const char * id, const uint16_t * characters, size_t num_characters, FILE * f) {
426 Stream * output = Stream_new(num_characters);
427 jscoverage_write_source(id, characters, num_characters, output);
428 fwrite(output->data, 1, output->length, f);
429 Stream_delete(output);
430 }
431
432 static void write_json_for_file(const FileCoverage * file_coverage, int i, void * p) {
433 FILE * f = p;
434
435 if (i > 0) {
436 putc(',', f);
437 }
438
439 write_js_quoted_string(f, file_coverage->id, strlen(file_coverage->id));
440
441 fputs(":{\"coverage\":[", f);
442 for (uint32_t i = 0; i < file_coverage->num_coverage_lines; i++) {
443 if (i > 0) {
444 putc(',', f);
445 }
446 int timesExecuted = file_coverage->coverage_lines[i];
447 if (timesExecuted < 0) {
448 fputs("null", f);
449 }
450 else {
451 fprintf(f, "%d", timesExecuted);
452 }
453 }
454 fputs("],\"source\":", f);
455 if (file_coverage->source_lines == NULL) {
456 if (proxy) {
457 const SourceCache * cached = find_cached_source(file_coverage->id);
458 if (cached == NULL) {
459 uint16_t * characters;
460 size_t num_characters;
461 if (get(file_coverage->id, &characters, &num_characters) == 0) {
462 write_source(file_coverage->id, characters, num_characters, f);
463 add_cached_source(file_coverage->id, characters, num_characters);
464 }
465 else {
466 fputs("[]", f);
467 HTTPServer_log_err("Warning: cannot retrieve URL: %s\n", file_coverage->id);
468 }
469 }
470 else {
471 write_source(file_coverage->id, cached->characters, cached->num_characters, f);
472 }
473 }
474 else {
475 /* check that the path begins with / */
476 if (file_coverage->id[0] == '/') {
477 char * source_path = make_path(document_root, file_coverage->id + 1);
478 FILE * source_file = fopen(source_path, "rb");
479 free(source_path);
480 if (source_file == NULL) {
481 fputs("[]", f);
482 HTTPServer_log_err("Warning: cannot open file: %s\n", file_coverage->id);
483 }
484 else {
485 Stream * stream = Stream_new(0);
486 Stream_write_file_contents(stream, source_file);
487 fclose(source_file);
488 uint16_t * characters;
489 size_t num_characters;
490 int result = jscoverage_bytes_to_characters(jscoverage_encoding, stream->data, stream->length, &characters, &num_characters);
491 Stream_delete(stream);
492 if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) {
493 fputs("[]", f);
494 HTTPServer_log_err("Warning: encoding %s not supported\n", jscoverage_encoding);
495 }
496 else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) {
497 fputs("[]", f);
498 HTTPServer_log_err("Warning: error decoding %s in file %s\n", jscoverage_encoding, file_coverage->id);
499 }
500 else {
501 write_source(file_coverage->id, characters, num_characters, f);
502 free(characters);
503 }
504 }
505 }
506 else {
507 /* path does not begin with / */
508 fputs("[]", f);
509 HTTPServer_log_err("Warning: invalid source path: %s\n", file_coverage->id);
510 }
511 }
512 }
513 else {
514 fputc('[', f);
515 for (uint32_t i = 0; i < file_coverage->num_source_lines; i++) {
516 if (i > 0) {
517 fputc(',', f);
518 }
519 char * source_line = file_coverage->source_lines[i];
520 write_js_quoted_string(f, source_line, strlen(source_line));
521 }
522 fputc(']', f);
523 }
524 fputc('}', f);
525 }
526
527 static int write_json(Coverage * coverage, const char * path) __attribute__((warn_unused_result));
528
529 static int write_json(Coverage * coverage, const char * path) {
530 /* write the JSON */
531 FILE * f = fopen(path, "w");
532 if (f == NULL) {
533 return -1;
534 }
535 putc('{', f);
536 Coverage_foreach_file(coverage, write_json_for_file, f);
537 putc('}', f);
538 if (fclose(f) == EOF) {
539 return -1;
540 }
541 return 0;
542 }
543
544 static void handle_jscoverage_request(HTTPExchange * exchange) {
545 /* set the `Server' response-header (RFC 2616 14.38, 3.8) */
546 HTTPExchange_set_response_header(exchange, HTTP_SERVER, "jscoverage-server/" VERSION);
547
548 const char * abs_path = HTTPExchange_get_abs_path(exchange);
549 assert(*abs_path != '\0');
550 if (str_starts_with(abs_path, "/jscoverage-store")) {
551 if (strcmp(HTTPExchange_get_method(exchange), "POST") != 0) {
552 HTTPExchange_set_response_header(exchange, HTTP_ALLOW, "POST");
553 send_response(exchange, 405, "Method not allowed\n");
554 return;
555 }
556
557 Stream * json = Stream_new(0);
558
559 /* read the POST body */
560 if (HTTPExchange_read_entire_request_entity_body(exchange, json) != 0) {
561 Stream_delete(json);
562 send_response(exchange, 400, "Could not read request body\n");
563 return;
564 }
565
566 Coverage * coverage = Coverage_new();
567 LOCK(&javascript_mutex);
568 int result = jscoverage_parse_json(coverage, json->data, json->length);
569 UNLOCK(&javascript_mutex);
570 Stream_delete(json);
571
572 if (result != 0) {
573 Coverage_delete(coverage);
574 send_response(exchange, 400, "Could not parse coverage data\n");
575 return;
576 }
577
578 mkdir_if_necessary(report_directory);
579 char * path = make_path(report_directory, "jscoverage.json");
580
581 /* check if the JSON file exists */
582 struct stat buf;
583 if (stat(path, &buf) == 0) {
584 /* it exists: merge */
585 FILE * f = fopen(path, "r");
586 if (f == NULL) {
587 result = 1;
588 }
589 else {
590 result = merge(coverage, f);
591 if (fclose(f) == EOF) {
592 result = 1;
593 }
594 }
595 if (result != 0) {
596 free(path);
597 Coverage_delete(coverage);
598 send_response(exchange, 500, "Could not merge with existing coverage data\n");
599 return;
600 }
601 }
602
603 result = write_json(coverage, path);
604 free(path);
605 Coverage_delete(coverage);
606 if (result != 0) {
607 send_response(exchange, 500, "Could not write coverage data\n");
608 return;
609 }
610
611 /* copy other files */
612 jscoverage_copy_resources(report_directory);
613 path = make_path(report_directory, "jscoverage.js");
614 FILE * f = fopen(path, "ab");
615 free(path);
616 if (f == NULL) {
617 send_response(exchange, 500, "Could not write to file: jscoverage.js\n");
618 return;
619 }
620 fputs("jscoverage_isReport = true;\r\n", f);
621 if (fclose(f) == EOF) {
622 send_response(exchange, 500, "Could not write to file: jscoverage.js\n");
623 return;
624 }
625
626 send_response(exchange, 200, "Coverage data stored\n");
627 }
628 else if (str_starts_with(abs_path, "/jscoverage-shutdown")) {
629 if (strcmp(HTTPExchange_get_method(exchange), "POST") != 0) {
630 HTTPExchange_set_response_header(exchange, HTTP_ALLOW, "POST");
631 send_response(exchange, 405, "Method not allowed\n");
632 return;
633 }
634
635 /* allow only from localhost */
636 struct sockaddr_in client;
637 if (HTTPExchange_get_peer(exchange, &client) != 0) {
638 send_response(exchange, 500, "Cannot get client address\n");
639 return;
640 }
641 if (client.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) {
642 send_response(exchange, 403, "This operation can be performed only by localhost\n");
643 return;
644 }
645
646 send_response(exchange, 200, "The server will now shut down\n");
647 HTTPServer_shutdown();
648 }
649 else {
650 const char * path = abs_path + 1;
651 const struct Resource * resource = get_resource(path);
652 if (resource == NULL) {
653 send_response(exchange, 404, "Not found\n");
654 return;
655 }
656 HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, get_content_type(path));
657 if (HTTPExchange_write_response(exchange, resource->data, resource->length) != 0) {
658 HTTPServer_log_err("Warning: error writing to client\n");
659 return;
660 }
661 if (strcmp(abs_path, "/jscoverage.js") == 0) {
662 const char * s = "jscoverage_isServer = true;\r\n";
663 if (HTTPExchange_write_response(exchange, s, strlen(s)) != 0) {
664 HTTPServer_log_err("Warning: error writing to client\n");
665 }
666 }
667 }
668 }
669
670 static void instrument_js(const char * id, const uint16_t * characters, size_t num_characters, Stream * output_stream) {
671 LOCK(&javascript_mutex);
672 jscoverage_instrument_js(id, characters, num_characters, output_stream);
673 UNLOCK(&javascript_mutex);
674
675 const struct Resource * resource = get_resource("report.js");
676 Stream_write(output_stream, resource->data, resource->length);
677 }
678
679 static bool is_hop_by_hop_header(const char * h) {
680 /* hop-by-hop headers (RFC 2616 13.5.1) */
681 return strcasecmp(h, HTTP_CONNECTION) == 0 ||
682 strcasecmp(h, "Keep-Alive") == 0 ||
683 strcasecmp(h, HTTP_PROXY_AUTHENTICATE) == 0 ||
684 strcasecmp(h, HTTP_PROXY_AUTHORIZATION) == 0 ||
685 strcasecmp(h, HTTP_TE) == 0 ||
686 strcasecmp(h, HTTP_TRAILER) == 0 ||
687 strcasecmp(h, HTTP_TRANSFER_ENCODING) == 0 ||
688 strcasecmp(h, HTTP_UPGRADE) == 0;
689 }
690
691 static void add_via_header(HTTPMessage * message, const char * version) {
692 char * value;
693 xasprintf(&value, "%s jscoverage-server", version);
694 HTTPMessage_add_header(message, HTTP_VIA, value);
695 free(value);
696 }
697
698 static int copy_http_message_body(HTTPMessage * from, HTTPMessage * to) __attribute__((warn_unused_result));
699
700 static int copy_http_message_body(HTTPMessage * from, HTTPMessage * to) {
701 uint8_t * buffer[8192];
702 for (;;) {
703 size_t bytes_read;
704 int result = HTTPMessage_read_message_body(from, buffer, 8192, &bytes_read);
705 if (result != 0) {
706 return result;
707 }
708 if (bytes_read == 0) {
709 return 0;
710 }
711 result = HTTPMessage_write(to, buffer, bytes_read);
712 if (result != 0) {
713 return result;
714 }
715 }
716 }
717
718 static void handle_proxy_request(HTTPExchange * client_exchange) {
719 HTTPConnection * server_connection = NULL;
720 HTTPExchange * server_exchange = NULL;
721
722 const char * abs_path = HTTPExchange_get_abs_path(client_exchange);
723 if (str_starts_with(abs_path, "/jscoverage")) {
724 handle_jscoverage_request(client_exchange);
725 return;
726 }
727
728 const char * host = HTTPExchange_get_host(client_exchange);
729 uint16_t port = HTTPExchange_get_port(client_exchange);
730
731 /* create a new connection */
732 server_connection = HTTPConnection_new_client(host, port);
733 if (server_connection == NULL) {
734 send_response(client_exchange, 504, "Could not connect to server\n");
735 goto done;
736 }
737
738 /* create a new exchange */
739 server_exchange = HTTPExchange_new(server_connection);
740 HTTPExchange_set_method(server_exchange, HTTPExchange_get_method(client_exchange));
741 HTTPExchange_set_request_uri(server_exchange, HTTPExchange_get_request_uri(client_exchange));
742 for (const HTTPHeader * h = HTTPExchange_get_request_headers(client_exchange); h != NULL; h = h->next) {
743 if (strcasecmp(h->name, HTTP_TRAILER) == 0 || strcasecmp(h->name, HTTP_TRANSFER_ENCODING) == 0) {
744 /* do nothing: we want to keep this header */
745 }
746 else if (is_hop_by_hop_header(h->name) ||
747 strcasecmp(h->name, HTTP_ACCEPT_ENCODING) == 0 ||
748 strcasecmp(h->name, HTTP_RANGE) == 0) {
749 continue;
750 }
751 HTTPExchange_add_request_header(server_exchange, h->name, h->value);
752 }
753 add_via_header(HTTPExchange_get_request_message(server_exchange), HTTPExchange_get_request_http_version(client_exchange));
754
755 /* send the request */
756 if (HTTPExchange_write_request_headers(server_exchange) != 0) {
757 send_response(client_exchange, 502, "Could not write to server\n");
758 goto done;
759 }
760
761 /* handle POST or PUT */
762 if (HTTPExchange_request_has_body(client_exchange)) {
763 HTTPMessage * client_request = HTTPExchange_get_request_message(client_exchange);
764 HTTPMessage * server_request = HTTPExchange_get_request_message(server_exchange);
765 if (copy_http_message_body(client_request, server_request) != 0) {
766 send_response(client_exchange, 400, "Error copying request body from client to server\n");
767 goto done;
768 }
769 }
770
771 if (HTTPExchange_flush_request(server_exchange) != 0) {
772 send_response(client_exchange, 502, "Could not write to server\n");
773 goto done;
774 }
775
776 /* receive the response */
777 if (HTTPExchange_read_response_headers(server_exchange) != 0) {
778 send_response(client_exchange, 502, "Could not read headers from server\n");
779 goto done;
780 }
781
782 HTTPExchange_set_status_code(client_exchange, HTTPExchange_get_status_code(server_exchange));
783
784 if (HTTPExchange_response_has_body(server_exchange) && should_instrument_request(server_exchange)) {
785 /* needs instrumentation */
786 Stream * input_stream = Stream_new(0);
787 if (HTTPExchange_read_entire_response_entity_body(server_exchange, input_stream) != 0) {
788 Stream_delete(input_stream);
789 send_response(client_exchange, 502, "Could not read body from server\n");
790 goto done;
791 }
792
793 const char * request_uri = HTTPExchange_get_request_uri(client_exchange);
794 char * encoding = HTTPMessage_get_charset(HTTPExchange_get_response_message(server_exchange));
795 if (encoding == NULL) {
796 encoding = xstrdup(jscoverage_encoding);
797 }
798 uint16_t * characters;
799 size_t num_characters;
800 int result = jscoverage_bytes_to_characters(encoding, input_stream->data, input_stream->length, &characters, &num_characters);
801 free(encoding);
802 Stream_delete(input_stream);
803 if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) {
804 free(characters);
805 send_response(client_exchange, 502, "Encoding not supported\n");
806 goto done;
807 }
808 else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) {
809 free(characters);
810 send_response(client_exchange, 502, "Error decoding response\n");
811 goto done;
812 }
813
814 Stream * output_stream = Stream_new(0);
815 instrument_js(request_uri, characters, num_characters, output_stream);
816
817 /* send the headers to the client */
818 for (const HTTPHeader * h = HTTPExchange_get_response_headers(server_exchange); h != NULL; h = h->next) {
819 if (is_hop_by_hop_header(h->name) || strcasecmp(h->name, HTTP_CONTENT_LENGTH) == 0) {
820 continue;
821 }
822 HTTPExchange_add_response_header(client_exchange, h->name, h->value);
823 }
824 add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange));
825 HTTPExchange_set_response_content_length(client_exchange, output_stream->length);
826
827 /* send the instrumented code to the client */
828 if (HTTPExchange_write_response(client_exchange, output_stream->data, output_stream->length) != 0) {
829 HTTPServer_log_err("Warning: error writing to client\n");
830 }
831
832 /* characters go on the cache */
833 /*
834 free(characters);
835 */
836 Stream_delete(output_stream);
837 add_cached_source(request_uri, characters, num_characters);
838 }
839 else {
840 /* does not need instrumentation */
841
842 /* send the headers to the client */
843 for (const HTTPHeader * h = HTTPExchange_get_response_headers(server_exchange); h != NULL; h = h->next) {
844 if (strcasecmp(h->name, HTTP_TRAILER) == 0 || strcasecmp(h->name, HTTP_TRANSFER_ENCODING) == 0) {
845 /* do nothing: we want to keep this header */
846 }
847 else if (is_hop_by_hop_header(h->name)) {
848 continue;
849 }
850 HTTPExchange_add_response_header(client_exchange, h->name, h->value);
851 }
852 add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange));
853
854 if (HTTPExchange_write_response_headers(client_exchange) != 0) {
855 HTTPServer_log_err("Warning: error writing to client\n");
856 goto done;
857 }
858
859 if (HTTPExchange_response_has_body(server_exchange)) {
860 /* read the body from the server and send it to the client */
861 HTTPMessage * client_response = HTTPExchange_get_response_message(client_exchange);
862 HTTPMessage * server_response = HTTPExchange_get_response_message(server_exchange);
863 if (copy_http_message_body(server_response, client_response) != 0) {
864 HTTPServer_log_err("Warning: error copying response body from server to client\n");
865 goto done;
866 }
867 }
868 }
869
870 done:
871 if (server_exchange != NULL) {
872 HTTPExchange_delete(server_exchange);
873 }
874 if (server_connection != NULL) {
875 if (HTTPConnection_delete(server_connection) != 0) {
876 HTTPServer_log_err("Warning: error closing connection to server\n");
877 }
878 }
879 }
880
881 static void handle_local_request(HTTPExchange * exchange) {
882 /* add the `Server' response-header (RFC 2616 14.38, 3.8) */
883 HTTPExchange_add_response_header(exchange, HTTP_SERVER, "jscoverage-server/" VERSION);
884
885 char * filesystem_path = NULL;
886
887 const char * abs_path = HTTPExchange_get_abs_path(exchange);
888 assert(*abs_path != '\0');
889
890 if (str_starts_with(abs_path, "/jscoverage")) {
891 handle_jscoverage_request(exchange);
892 goto done;
893 }
894
895 if (strstr(abs_path, "..") != NULL) {
896 send_response(exchange, 403, "Forbidden\n");
897 goto done;
898 }
899
900 filesystem_path = make_path(document_root, abs_path + 1);
901 size_t filesystem_path_length = strlen(filesystem_path);
902 if (filesystem_path_length > 0 && filesystem_path[filesystem_path_length - 1] == '/') {
903 /* stat on Windows doesn't work with trailing slash */
904 filesystem_path[filesystem_path_length - 1] = '\0';
905 }
906
907 struct stat buf;
908 if (stat(filesystem_path, &buf) == -1) {
909 send_response(exchange, 404, "Not found\n");
910 goto done;
911 }
912
913 if (S_ISDIR(buf.st_mode)) {
914 if (abs_path[strlen(abs_path) - 1] != '/') {
915 const char * request_uri = HTTPExchange_get_request_uri(exchange);
916 char * uri = xmalloc(strlen(request_uri) + 2);
917 strcpy(uri, request_uri);
918 strcat(uri, "/");
919 HTTPExchange_add_response_header(exchange, "Location", uri);
920 free(uri);
921 send_response(exchange, 301, "Moved permanently\n");
922 goto done;
923 }
924
925 DIR * d = opendir(filesystem_path);
926 if (d == NULL) {
927 send_response(exchange, 404, "Not found\n");
928 goto done;
929 }
930
931 struct dirent * entry;
932 while ((entry = readdir(d)) != NULL) {
933 char * href = encode_uri_component(entry->d_name);
934 char * html_href = encode_html(href);
935 char * link = encode_html(entry->d_name);
936 char * directory_entry;
937 xasprintf(&directory_entry, "<a href=\"%s\">%s</a><br>\n", html_href, link);
938 if (HTTPExchange_write_response(exchange, directory_entry, strlen(directory_entry)) != 0) {
939 HTTPServer_log_err("Warning: error writing to client\n");
940 }
941 free(directory_entry);
942 free(href);
943 free(html_href);
944 free(link);
945 }
946 closedir(d);
947 }
948 else if (S_ISREG(buf.st_mode)) {
949 FILE * f = fopen(filesystem_path, "rb");
950 if (f == NULL) {
951 send_response(exchange, 404, "Not found\n");
952 goto done;
953 }
954
955 const char * content_type = get_content_type(filesystem_path);
956 HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type);
957 if (strcmp(content_type, "text/javascript") == 0 && ! is_no_instrument(abs_path)) {
958 Stream * input_stream = Stream_new(0);
959 Stream_write_file_contents(input_stream, f);
960
961 uint16_t * characters;
962 size_t num_characters;
963 int result = jscoverage_bytes_to_characters(jscoverage_encoding, input_stream->data, input_stream->length, &characters, &num_characters);
964 Stream_delete(input_stream);
965
966 if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) {
967 free(characters);
968 send_response(exchange, 500, "Encoding not supported\n");
969 goto done;
970 }
971 else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) {
972 free(characters);
973 send_response(exchange, 500, "Error decoding JavaScript file\n");
974 goto done;
975 }
976
977 Stream * output_stream = Stream_new(0);
978 instrument_js(abs_path, characters, num_characters, output_stream);
979 free(characters);
980
981 if (HTTPExchange_write_response(exchange, output_stream->data, output_stream->length) != 0) {
982 HTTPServer_log_err("Warning: error writing to client\n");
983 }
984 Stream_delete(output_stream);
985 }
986 else {
987 char buffer[8192];
988 size_t bytes_read;
989 while ((bytes_read = fread(buffer, 1, 8192, f)) > 0) {
990 if (HTTPExchange_write_response(exchange, buffer, bytes_read) != 0) {
991 HTTPServer_log_err("Warning: error writing to client\n");
992 }
993 }
994 }
995 fclose(f);
996 }
997 else {
998 send_response(exchange, 404, "Not found\n");
999 goto done;
1000 }
1001
1002 done:
1003 free(filesystem_path);
1004 }
1005
1006 static void handler(HTTPExchange * exchange) {
1007 if (verbose) {
1008 HTTPServer_log_out("%s", HTTPExchange_get_request_line(exchange));
1009 }
1010
1011 if (proxy) {
1012 handle_proxy_request(exchange);
1013 }
1014 else {
1015 handle_local_request(exchange);
1016 }
1017 }
1018
1019 int main(int argc, char ** argv) {
1020 program = "jscoverage-server";
1021
1022 const char * ip_address = "127.0.0.1";
1023 const char * port = "8080";
1024 int shutdown = 0;
1025
1026 no_instrument = xnew(const char *, argc - 1);
1027
1028 for (int i = 1; i < argc; i++) {
1029 if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
1030 copy_resource_to_stream("jscoverage-server-help.txt", stdout);
1031 exit(EXIT_SUCCESS);
1032 }
1033 else if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) {
1034 printf("jscoverage-server %s\n", VERSION);
1035 exit(EXIT_SUCCESS);
1036 }
1037 else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
1038 verbose = 1;
1039 }
1040
1041 else if (strcmp(argv[i], "--report-dir") == 0) {
1042 i++;
1043 if (i == argc) {
1044 fatal("--report-dir: option requires an argument");
1045 }
1046 report_directory = argv[i];
1047 }
1048 else if (strncmp(argv[i], "--report-dir=", 13) == 0) {
1049 report_directory = argv[i] + 13;
1050 }
1051
1052 else if (strcmp(argv[i], "--document-root") == 0) {
1053 i++;
1054 if (i == argc) {
1055 fatal("--document-root: option requires an argument");
1056 }
1057 document_root = argv[i];
1058 }
1059 else if (strncmp(argv[i], "--document-root=", 16) == 0) {
1060 document_root = argv[i] + 16;
1061 }
1062
1063 else if (strcmp(argv[i], "--encoding") == 0) {
1064 i++;
1065 if (i == argc) {
1066 fatal("--encoding: option requires an argument");
1067 }
1068 jscoverage_encoding = argv[i];
1069 }
1070 else if (strncmp(argv[i], "--encoding=", 11) == 0) {
1071 jscoverage_encoding = argv[i] + 11;
1072 }
1073
1074 else if (strcmp(argv[i], "--ip-address") == 0) {
1075 i++;
1076 if (i == argc) {
1077 fatal("--ip-address: option requires an argument");
1078 }
1079 ip_address = argv[i];
1080 }
1081 else if (strncmp(argv[i], "--ip-address=", 13) == 0) {
1082 ip_address = argv[i] + 13;
1083 }
1084
1085 else if (strcmp(argv[i], "--no-highlight") == 0) {
1086 jscoverage_highlight = false;
1087 }
1088
1089 else if (strcmp(argv[i], "--no-instrument") == 0) {
1090 i++;
1091 if (i == argc) {
1092 fatal("--no-instrument: option requires an argument");
1093 }
1094 no_instrument[num_no_instrument] = argv[i];
1095 num_no_instrument++;
1096 }
1097 else if (strncmp(argv[i], "--no-instrument=", 16) == 0) {
1098 no_instrument[num_no_instrument] = argv[i] + 16;
1099 num_no_instrument++;
1100 }
1101
1102 else if (strcmp(argv[i], "--port") == 0) {
1103 i++;
1104 if (i == argc) {
1105 fatal("--port: option requires an argument");
1106 }
1107 port = argv[i];
1108 }
1109 else if (strncmp(argv[i], "--port=", 7) == 0) {
1110 port = argv[i] + 7;
1111 }
1112
1113 else if (strcmp(argv[i], "--proxy") == 0) {
1114 proxy = 1;
1115 }
1116
1117 else if (strcmp(argv[i], "--shutdown") == 0) {
1118 shutdown = 1;
1119 }
1120
1121 else if (strncmp(argv[i], "-", 1) == 0) {
1122 fatal("unrecognized option `%s'", argv[i]);
1123 }
1124 else {
1125 fatal("too many arguments");
1126 }
1127 }
1128
1129 /* check the port */
1130 char * end;
1131 unsigned long numeric_port = strtoul(port, &end, 10);
1132 if (*end != '\0') {
1133 fatal("--port: option must be an integer");
1134 }
1135 if (numeric_port > UINT16_MAX) {
1136 fatal("--port: option must be 16 bits");
1137 }
1138
1139 /* is this a shutdown? */
1140 if (shutdown) {
1141 #ifdef __MINGW32__
1142 WSADATA data;
1143 if (WSAStartup(MAKEWORD(1, 1), &data) != 0) {
1144 fatal("could not start Winsock");
1145 }
1146 #endif
1147
1148 /* INADDR_LOOPBACK */
1149 HTTPConnection * connection = HTTPConnection_new_client("127.0.0.1", numeric_port);
1150 if (connection == NULL) {
1151 fatal("could not connect to server");
1152 }
1153 HTTPExchange * exchange = HTTPExchange_new(connection);
1154 HTTPExchange_set_method(exchange, "POST");
1155 HTTPExchange_set_request_uri(exchange, "/jscoverage-shutdown");
1156 if (HTTPExchange_write_request_headers(exchange) != 0) {
1157 fatal("could not write request headers to server");
1158 }
1159 if (HTTPExchange_read_response_headers(exchange) != 0) {
1160 fatal("could not read response headers from server");
1161 }
1162 Stream * stream = Stream_new(0);
1163 if (HTTPExchange_read_entire_response_entity_body(exchange, stream) != 0) {
1164 fatal("could not read response body from server");
1165 }
1166 fwrite(stream->data, 1, stream->length, stdout);
1167 Stream_delete(stream);
1168 HTTPExchange_delete(exchange);
1169 if (HTTPConnection_delete(connection) != 0) {
1170 fatal("could not close connection with server");
1171 }
1172 exit(EXIT_SUCCESS);
1173 }
1174
1175 jscoverage_init();
1176
1177 #ifndef __MINGW32__
1178 /* handle broken pipe */
1179 signal(SIGPIPE, SIG_IGN);
1180 #endif
1181
1182 #ifdef __MINGW32__
1183 InitializeCriticalSection(&javascript_mutex);
1184 InitializeCriticalSection(&source_cache_mutex);
1185 #endif
1186
1187 if (verbose) {
1188 printf("Starting HTTP server on %s:%lu\n", ip_address, numeric_port);
1189 fflush(stdout);
1190 }
1191 HTTPServer_run(ip_address, (uint16_t) numeric_port, handler);
1192 if (verbose) {
1193 printf("Stopping HTTP server\n");
1194 fflush(stdout);
1195 }
1196
1197 jscoverage_cleanup();
1198
1199 free(no_instrument);
1200
1201 LOCK(&source_cache_mutex);
1202 while (source_cache != NULL) {
1203 SourceCache * p = source_cache;
1204 source_cache = source_cache->next;
1205 free(p->url);
1206 free(p->characters);
1207 free(p);
1208 }
1209 UNLOCK(&source_cache_mutex);
1210
1211 return 0;
1212 }

  ViewVC Help
Powered by ViewVC 1.1.24