/[jscoverage]/tags/jscoverage-0.4/jscoverage-server.c
ViewVC logotype

Contents of /tags/jscoverage-0.4/jscoverage-server.c

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC 1.1.24