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

Contents of /trunk/jscoverage-server.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 133 - (show annotations)
Thu Jun 19 05:53:29 2008 UTC (11 years, 1 month ago) by siliconforks
File MIME type: text/plain
File size: 32920 byte(s)
When storing coverage report, check for existing JSON file, and fail if it is not readable.

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

  ViewVC Help
Powered by ViewVC 1.1.24