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

Contents of /trunk/jscoverage-server.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 174 - (show annotations)
Sat Sep 20 23:27:14 2008 UTC (11 years, 1 month ago) by siliconforks
File MIME type: text/plain
File size: 33288 byte(s)
Provide better character encoding support.
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 instrument_js(request_uri, jscoverage_encoding, input_stream, output_stream);
748
749 /* send the headers to the client */
750 for (const HTTPHeader * h = HTTPExchange_get_response_headers(server_exchange); h != NULL; h = h->next) {
751 if (is_hop_by_hop_header(h->name) || strcasecmp(h->name, HTTP_CONTENT_LENGTH) == 0) {
752 continue;
753 }
754 HTTPExchange_add_response_header(client_exchange, h->name, h->value);
755 }
756 add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange));
757 HTTPExchange_set_response_content_length(client_exchange, output_stream->length);
758
759 /* send the instrumented code to the client */
760 if (HTTPExchange_write_response(client_exchange, output_stream->data, output_stream->length) != 0) {
761 HTTPServer_log_err("Warning: error writing to client\n");
762 }
763
764 /* input_stream goes on the cache */
765 /*
766 Stream_delete(input_stream);
767 */
768 Stream_delete(output_stream);
769 add_cached_source(request_uri, input_stream);
770 }
771 else {
772 /* does not need instrumentation */
773
774 /* send the headers to the client */
775 for (const HTTPHeader * h = HTTPExchange_get_response_headers(server_exchange); h != NULL; h = h->next) {
776 if (strcasecmp(h->name, HTTP_TRAILER) == 0 || strcasecmp(h->name, HTTP_TRANSFER_ENCODING) == 0) {
777 /* do nothing: we want to keep this header */
778 }
779 else if (is_hop_by_hop_header(h->name)) {
780 continue;
781 }
782 HTTPExchange_add_response_header(client_exchange, h->name, h->value);
783 }
784 add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange));
785
786 if (HTTPExchange_write_response_headers(client_exchange) != 0) {
787 HTTPServer_log_err("Warning: error writing to client\n");
788 goto done;
789 }
790
791 if (HTTPExchange_response_has_body(server_exchange)) {
792 /* read the body from the server and send it to the client */
793 HTTPMessage * client_response = HTTPExchange_get_response_message(client_exchange);
794 HTTPMessage * server_response = HTTPExchange_get_response_message(server_exchange);
795 if (copy_http_message_body(server_response, client_response) != 0) {
796 HTTPServer_log_err("Warning: error copying response body from server to client\n");
797 goto done;
798 }
799 }
800 }
801
802 done:
803 if (server_exchange != NULL) {
804 HTTPExchange_delete(server_exchange);
805 }
806 if (server_connection != NULL) {
807 if (HTTPConnection_delete(server_connection) != 0) {
808 HTTPServer_log_err("Warning: error closing connection to server\n");
809 }
810 }
811 }
812
813 static void handle_local_request(HTTPExchange * exchange) {
814 /* add the `Server' response-header (RFC 2616 14.38, 3.8) */
815 HTTPExchange_add_response_header(exchange, HTTP_SERVER, "jscoverage-server/" VERSION);
816
817 char * filesystem_path = NULL;
818
819 const char * abs_path = HTTPExchange_get_abs_path(exchange);
820 assert(*abs_path != '\0');
821
822 if (str_starts_with(abs_path, "/jscoverage")) {
823 handle_jscoverage_request(exchange);
824 goto done;
825 }
826
827 if (strstr(abs_path, "..") != NULL) {
828 send_response(exchange, 403, "Forbidden\n");
829 goto done;
830 }
831
832 filesystem_path = make_path(document_root, abs_path + 1);
833 size_t filesystem_path_length = strlen(filesystem_path);
834 if (filesystem_path_length > 0 && filesystem_path[filesystem_path_length - 1] == '/') {
835 /* stat on Windows doesn't work with trailing slash */
836 filesystem_path[filesystem_path_length - 1] = '\0';
837 }
838
839 struct stat buf;
840 if (stat(filesystem_path, &buf) == -1) {
841 send_response(exchange, 404, "Not found\n");
842 goto done;
843 }
844
845 if (S_ISDIR(buf.st_mode)) {
846 if (abs_path[strlen(abs_path) - 1] != '/') {
847 const char * request_uri = HTTPExchange_get_request_uri(exchange);
848 char * uri = xmalloc(strlen(request_uri) + 2);
849 strcpy(uri, request_uri);
850 strcat(uri, "/");
851 HTTPExchange_add_response_header(exchange, "Location", uri);
852 free(uri);
853 send_response(exchange, 301, "Moved permanently\n");
854 goto done;
855 }
856
857 DIR * d = opendir(filesystem_path);
858 if (d == NULL) {
859 send_response(exchange, 404, "Not found\n");
860 goto done;
861 }
862
863 struct dirent * entry;
864 while ((entry = readdir(d)) != NULL) {
865 char * href = encode_uri_component(entry->d_name);
866 char * html_href = encode_html(href);
867 char * link = encode_html(entry->d_name);
868 char * directory_entry;
869 xasprintf(&directory_entry, "<a href=\"%s\">%s</a><br>\n", html_href, link);
870 if (HTTPExchange_write_response(exchange, directory_entry, strlen(directory_entry)) != 0) {
871 HTTPServer_log_err("Warning: error writing to client\n");
872 }
873 free(directory_entry);
874 free(href);
875 free(html_href);
876 free(link);
877 }
878 closedir(d);
879 }
880 else if (S_ISREG(buf.st_mode)) {
881 FILE * f = fopen(filesystem_path, "rb");
882 if (f == NULL) {
883 send_response(exchange, 404, "Not found\n");
884 goto done;
885 }
886
887 const char * content_type = get_content_type(filesystem_path);
888 HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type);
889 if (strcmp(content_type, "text/javascript") == 0 && ! is_no_instrument(abs_path)) {
890 Stream * input_stream = Stream_new(0);
891 Stream * output_stream = Stream_new(0);
892
893 Stream_write_file_contents(input_stream, f);
894
895 instrument_js(abs_path, jscoverage_encoding, input_stream, output_stream);
896
897 if (HTTPExchange_write_response(exchange, output_stream->data, output_stream->length) != 0) {
898 HTTPServer_log_err("Warning: error writing to client\n");
899 }
900
901 Stream_delete(input_stream);
902 Stream_delete(output_stream);
903 }
904 else {
905 char buffer[8192];
906 size_t bytes_read;
907 while ((bytes_read = fread(buffer, 1, 8192, f)) > 0) {
908 if (HTTPExchange_write_response(exchange, buffer, bytes_read) != 0) {
909 HTTPServer_log_err("Warning: error writing to client\n");
910 }
911 }
912 }
913 fclose(f);
914 }
915 else {
916 send_response(exchange, 404, "Not found\n");
917 goto done;
918 }
919
920 done:
921 free(filesystem_path);
922 }
923
924 static void handler(HTTPExchange * exchange) {
925 if (verbose) {
926 HTTPServer_log_out("%s", HTTPExchange_get_request_line(exchange));
927 }
928
929 if (proxy) {
930 handle_proxy_request(exchange);
931 }
932 else {
933 handle_local_request(exchange);
934 }
935 }
936
937 int main(int argc, char ** argv) {
938 program = "jscoverage-server";
939
940 const char * ip_address = "127.0.0.1";
941 const char * port = "8080";
942 int shutdown = 0;
943
944 no_instrument = xnew(const char *, argc - 1);
945
946 for (int i = 1; i < argc; i++) {
947 if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
948 copy_resource_to_stream("jscoverage-server-help.txt", stdout);
949 exit(EXIT_SUCCESS);
950 }
951 else if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) {
952 printf("jscoverage-server %s\n", VERSION);
953 exit(EXIT_SUCCESS);
954 }
955 else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
956 verbose = 1;
957 }
958
959 else if (strcmp(argv[i], "--report-dir") == 0) {
960 i++;
961 if (i == argc) {
962 fatal("--report-dir: option requires an argument");
963 }
964 report_directory = argv[i];
965 }
966 else if (strncmp(argv[i], "--report-dir=", 13) == 0) {
967 report_directory = argv[i] + 13;
968 }
969
970 else if (strcmp(argv[i], "--document-root") == 0) {
971 i++;
972 if (i == argc) {
973 fatal("--document-root: option requires an argument");
974 }
975 document_root = argv[i];
976 }
977 else if (strncmp(argv[i], "--document-root=", 16) == 0) {
978 document_root = argv[i] + 16;
979 }
980
981 else if (strcmp(argv[i], "--encoding") == 0) {
982 i++;
983 if (i == argc) {
984 fatal("--encoding: option requires an argument");
985 }
986 jscoverage_encoding = argv[i];
987 }
988 else if (strncmp(argv[i], "--encoding=", 11) == 0) {
989 jscoverage_encoding = argv[i] + 11;
990 }
991
992 else if (strcmp(argv[i], "--ip-address") == 0) {
993 i++;
994 if (i == argc) {
995 fatal("--ip-address: option requires an argument");
996 }
997 ip_address = argv[i];
998 }
999 else if (strncmp(argv[i], "--ip-address=", 13) == 0) {
1000 ip_address = argv[i] + 13;
1001 }
1002
1003 else if (strcmp(argv[i], "--no-instrument") == 0) {
1004 i++;
1005 if (i == argc) {
1006 fatal("--no-instrument: option requires an argument");
1007 }
1008 no_instrument[num_no_instrument] = argv[i];
1009 num_no_instrument++;
1010 }
1011 else if (strncmp(argv[i], "--no-instrument=", 16) == 0) {
1012 no_instrument[num_no_instrument] = argv[i] + 16;
1013 num_no_instrument++;
1014 }
1015
1016 else if (strcmp(argv[i], "--port") == 0) {
1017 i++;
1018 if (i == argc) {
1019 fatal("--port: option requires an argument");
1020 }
1021 port = argv[i];
1022 }
1023 else if (strncmp(argv[i], "--port=", 7) == 0) {
1024 port = argv[i] + 7;
1025 }
1026
1027 else if (strcmp(argv[i], "--proxy") == 0) {
1028 proxy = 1;
1029 }
1030
1031 else if (strcmp(argv[i], "--shutdown") == 0) {
1032 shutdown = 1;
1033 }
1034
1035 else if (strncmp(argv[i], "-", 1) == 0) {
1036 fatal("unrecognized option `%s'", argv[i]);
1037 }
1038 else {
1039 fatal("too many arguments");
1040 }
1041 }
1042
1043 /* check the port */
1044 char * end;
1045 unsigned long numeric_port = strtoul(port, &end, 10);
1046 if (*end != '\0') {
1047 fatal("--port: option must be an integer");
1048 }
1049 if (numeric_port > UINT16_MAX) {
1050 fatal("--port: option must be 16 bits");
1051 }
1052
1053 /* is this a shutdown? */
1054 if (shutdown) {
1055 #ifdef __MINGW32__
1056 WSADATA data;
1057 if (WSAStartup(MAKEWORD(1, 1), &data) != 0) {
1058 fatal("could not start Winsock");
1059 }
1060 #endif
1061
1062 /* INADDR_LOOPBACK */
1063 HTTPConnection * connection = HTTPConnection_new_client("127.0.0.1", numeric_port);
1064 if (connection == NULL) {
1065 fatal("could not connect to server");
1066 }
1067 HTTPExchange * exchange = HTTPExchange_new(connection);
1068 HTTPExchange_set_method(exchange, "POST");
1069 HTTPExchange_set_request_uri(exchange, "/jscoverage-shutdown");
1070 if (HTTPExchange_write_request_headers(exchange) != 0) {
1071 fatal("could not write request headers to server");
1072 }
1073 if (HTTPExchange_read_response_headers(exchange) != 0) {
1074 fatal("could not read response headers from server");
1075 }
1076 Stream * stream = Stream_new(0);
1077 if (HTTPExchange_read_entire_response_entity_body(exchange, stream) != 0) {
1078 fatal("could not read response body from server");
1079 }
1080 fwrite(stream->data, 1, stream->length, stdout);
1081 Stream_delete(stream);
1082 HTTPExchange_delete(exchange);
1083 if (HTTPConnection_delete(connection) != 0) {
1084 fatal("could not close connection with server");
1085 }
1086 exit(EXIT_SUCCESS);
1087 }
1088
1089 jscoverage_init();
1090
1091 #ifndef __MINGW32__
1092 /* handle broken pipe */
1093 signal(SIGPIPE, SIG_IGN);
1094 #endif
1095
1096 #ifdef __MINGW32__
1097 InitializeCriticalSection(&javascript_mutex);
1098 InitializeCriticalSection(&source_cache_mutex);
1099 #endif
1100
1101 if (verbose) {
1102 printf("Starting HTTP server on %s:%lu\n", ip_address, numeric_port);
1103 fflush(stdout);
1104 }
1105 HTTPServer_run(ip_address, (uint16_t) numeric_port, handler);
1106 if (verbose) {
1107 printf("Stopping HTTP server\n");
1108 fflush(stdout);
1109 }
1110
1111 jscoverage_cleanup();
1112
1113 free(no_instrument);
1114
1115 LOCK(&source_cache_mutex);
1116 while (source_cache != NULL) {
1117 SourceCache * p = source_cache;
1118 source_cache = source_cache->next;
1119 free(p->url);
1120 Stream_delete(p->source);
1121 free(p);
1122 }
1123 UNLOCK(&source_cache_mutex);
1124
1125 return 0;
1126 }

  ViewVC Help
Powered by ViewVC 1.1.24