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

Contents of /trunk/jscoverage-server.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 125 - (show annotations)
Mon Jun 2 17:52:38 2008 UTC (11 years, 1 month ago) by siliconforks
File MIME type: text/plain
File size: 32757 byte(s)
Fixes for compiling with MinGW.

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 FILE * f = fopen(path, "r");
530 if (f != NULL) {
531 /* it exists: merge */
532 result = merge(coverage, f);
533 if (fclose(f) == EOF) {
534 result = 1;
535 }
536 if (result != 0) {
537 free(path);
538 Coverage_delete(coverage);
539 send_response(exchange, 500, "Could not merge with existing coverage data\n");
540 return;
541 }
542 }
543
544 result = write_json(coverage, path);
545 free(path);
546 Coverage_delete(coverage);
547 if (result != 0) {
548 send_response(exchange, 500, "Could not write coverage data\n");
549 return;
550 }
551
552 /* copy other files */
553 jscoverage_copy_resources(report_directory);
554 path = make_path(report_directory, "jscoverage.js");
555 f = fopen(path, "ab");
556 free(path);
557 if (f == NULL) {
558 send_response(exchange, 500, "Could not write to file: jscoverage.js\n");
559 return;
560 }
561 fputs("jscoverage_isReport = true;\r\n", f);
562 if (fclose(f) == EOF) {
563 send_response(exchange, 500, "Could not write to file: jscoverage.js\n");
564 return;
565 }
566
567 send_response(exchange, 200, "Coverage data stored\n");
568 }
569 else if (str_starts_with(abs_path, "/jscoverage-shutdown")) {
570 if (strcmp(HTTPExchange_get_method(exchange), "POST") != 0) {
571 HTTPExchange_set_response_header(exchange, HTTP_ALLOW, "POST");
572 send_response(exchange, 405, "Method not allowed\n");
573 return;
574 }
575
576 /* allow only from localhost */
577 struct sockaddr_in client;
578 if (HTTPExchange_get_peer(exchange, &client) != 0) {
579 send_response(exchange, 500, "Cannot get client address\n");
580 return;
581 }
582 if (client.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) {
583 send_response(exchange, 403, "This operation can be performed only by localhost\n");
584 return;
585 }
586
587 send_response(exchange, 200, "The server will now shut down\n");
588 HTTPServer_shutdown();
589 }
590 else {
591 const char * path = abs_path + 1;
592 const struct Resource * resource = get_resource(path);
593 if (resource == NULL) {
594 send_response(exchange, 404, "Not found\n");
595 return;
596 }
597 HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, get_content_type(path));
598 if (HTTPExchange_write_response(exchange, resource->data, resource->length) != 0) {
599 HTTPServer_log_err("Warning: error writing to client\n");
600 return;
601 }
602 if (strcmp(abs_path, "/jscoverage.js") == 0) {
603 const char * s = "jscoverage_isServer = true;\r\n";
604 if (HTTPExchange_write_response(exchange, s, strlen(s)) != 0) {
605 HTTPServer_log_err("Warning: error writing to client\n");
606 }
607 }
608 }
609 }
610
611 static void instrument_js(const char * id, Stream * input_stream, Stream * output_stream) {
612 LOCK(&javascript_mutex);
613 jscoverage_instrument_js(id, input_stream, output_stream);
614 UNLOCK(&javascript_mutex);
615
616 const struct Resource * resource = get_resource("report.js");
617 Stream_write(output_stream, resource->data, resource->length);
618 }
619
620 static bool is_hop_by_hop_header(const char * h) {
621 /* hop-by-hop headers (RFC 2616 13.5.1) */
622 return strcasecmp(h, HTTP_CONNECTION) == 0 ||
623 strcasecmp(h, "Keep-Alive") == 0 ||
624 strcasecmp(h, HTTP_PROXY_AUTHENTICATE) == 0 ||
625 strcasecmp(h, HTTP_PROXY_AUTHORIZATION) == 0 ||
626 strcasecmp(h, HTTP_TE) == 0 ||
627 strcasecmp(h, HTTP_TRAILER) == 0 ||
628 strcasecmp(h, HTTP_TRANSFER_ENCODING) == 0 ||
629 strcasecmp(h, HTTP_UPGRADE) == 0;
630 }
631
632 static void add_via_header(HTTPMessage * message, const char * version) {
633 char * value;
634 xasprintf(&value, "%s jscoverage-server", version);
635 HTTPMessage_add_header(message, HTTP_VIA, value);
636 free(value);
637 }
638
639 static int copy_http_message_body(HTTPMessage * from, HTTPMessage * to) __attribute__((warn_unused_result));
640
641 static int copy_http_message_body(HTTPMessage * from, HTTPMessage * to) {
642 uint8_t * buffer[8192];
643 for (;;) {
644 size_t bytes_read;
645 int result = HTTPMessage_read_message_body(from, buffer, 8192, &bytes_read);
646 if (result != 0) {
647 return result;
648 }
649 if (bytes_read == 0) {
650 return 0;
651 }
652 result = HTTPMessage_write(to, buffer, bytes_read);
653 if (result != 0) {
654 return result;
655 }
656 }
657 }
658
659 static void handle_proxy_request(HTTPExchange * client_exchange) {
660 HTTPConnection * server_connection = NULL;
661 HTTPExchange * server_exchange = NULL;
662
663 const char * abs_path = HTTPExchange_get_abs_path(client_exchange);
664 if (str_starts_with(abs_path, "/jscoverage")) {
665 handle_jscoverage_request(client_exchange);
666 return;
667 }
668
669 const char * host = HTTPExchange_get_host(client_exchange);
670 uint16_t port = HTTPExchange_get_port(client_exchange);
671
672 /* create a new connection */
673 server_connection = HTTPConnection_new_client(host, port);
674 if (server_connection == NULL) {
675 send_response(client_exchange, 504, "Could not connect to server\n");
676 goto done;
677 }
678
679 /* create a new exchange */
680 server_exchange = HTTPExchange_new(server_connection);
681 HTTPExchange_set_method(server_exchange, HTTPExchange_get_method(client_exchange));
682 HTTPExchange_set_request_uri(server_exchange, HTTPExchange_get_request_uri(client_exchange));
683 for (const HTTPHeader * h = HTTPExchange_get_request_headers(client_exchange); h != NULL; h = h->next) {
684 if (strcasecmp(h->name, HTTP_TRAILER) == 0 || strcasecmp(h->name, HTTP_TRANSFER_ENCODING) == 0) {
685 /* do nothing: we want to keep this header */
686 }
687 else if (is_hop_by_hop_header(h->name) ||
688 strcasecmp(h->name, HTTP_ACCEPT_ENCODING) == 0 ||
689 strcasecmp(h->name, HTTP_RANGE) == 0) {
690 continue;
691 }
692 HTTPExchange_add_request_header(server_exchange, h->name, h->value);
693 }
694 add_via_header(HTTPExchange_get_request_message(server_exchange), HTTPExchange_get_request_http_version(client_exchange));
695
696 /* send the request */
697 if (HTTPExchange_write_request_headers(server_exchange) != 0) {
698 send_response(client_exchange, 502, "Could not write to server\n");
699 goto done;
700 }
701
702 /* handle POST or PUT */
703 if (HTTPExchange_request_has_body(client_exchange)) {
704 HTTPMessage * client_request = HTTPExchange_get_request_message(client_exchange);
705 HTTPMessage * server_request = HTTPExchange_get_request_message(server_exchange);
706 if (copy_http_message_body(client_request, server_request) != 0) {
707 send_response(client_exchange, 400, "Error copying request body from client to server\n");
708 goto done;
709 }
710 }
711
712 if (HTTPExchange_flush_request(server_exchange) != 0) {
713 send_response(client_exchange, 502, "Could not write to server\n");
714 goto done;
715 }
716
717 /* receive the response */
718 if (HTTPExchange_read_response_headers(server_exchange) != 0) {
719 send_response(client_exchange, 502, "Could not read headers from server\n");
720 goto done;
721 }
722
723 HTTPExchange_set_status_code(client_exchange, HTTPExchange_get_status_code(server_exchange));
724
725 if (HTTPExchange_response_has_body(server_exchange) && should_instrument_request(server_exchange)) {
726 /* needs instrumentation */
727 Stream * input_stream = Stream_new(0);
728 if (HTTPExchange_read_entire_response_entity_body(server_exchange, input_stream) != 0) {
729 Stream_delete(input_stream);
730 send_response(client_exchange, 502, "Could not read body from server\n");
731 goto done;
732 }
733
734 const char * request_uri = HTTPExchange_get_request_uri(client_exchange);
735 Stream * output_stream = Stream_new(0);
736 instrument_js(request_uri, input_stream, output_stream);
737
738 /* send the headers to the client */
739 for (const HTTPHeader * h = HTTPExchange_get_response_headers(server_exchange); h != NULL; h = h->next) {
740 if (is_hop_by_hop_header(h->name) || strcasecmp(h->name, HTTP_CONTENT_LENGTH) == 0) {
741 continue;
742 }
743 HTTPExchange_add_response_header(client_exchange, h->name, h->value);
744 }
745 add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange));
746 HTTPExchange_set_response_content_length(client_exchange, output_stream->length);
747
748 /* send the instrumented code to the client */
749 if (HTTPExchange_write_response(client_exchange, output_stream->data, output_stream->length) != 0) {
750 HTTPServer_log_err("Warning: error writing to client\n");
751 }
752
753 /* input_stream goes on the cache */
754 /*
755 Stream_delete(input_stream);
756 */
757 Stream_delete(output_stream);
758 add_cached_source(request_uri, input_stream);
759 }
760 else {
761 /* does not need instrumentation */
762
763 /* send the headers to the client */
764 for (const HTTPHeader * h = HTTPExchange_get_response_headers(server_exchange); h != NULL; h = h->next) {
765 if (strcasecmp(h->name, HTTP_TRAILER) == 0 || strcasecmp(h->name, HTTP_TRANSFER_ENCODING) == 0) {
766 /* do nothing: we want to keep this header */
767 }
768 else if (is_hop_by_hop_header(h->name)) {
769 continue;
770 }
771 HTTPExchange_add_response_header(client_exchange, h->name, h->value);
772 }
773 add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange));
774
775 if (HTTPExchange_write_response_headers(client_exchange) != 0) {
776 HTTPServer_log_err("Warning: error writing to client\n");
777 goto done;
778 }
779
780 if (HTTPExchange_response_has_body(server_exchange)) {
781 /* read the body from the server and send it to the client */
782 HTTPMessage * client_response = HTTPExchange_get_response_message(client_exchange);
783 HTTPMessage * server_response = HTTPExchange_get_response_message(server_exchange);
784 if (copy_http_message_body(server_response, client_response) != 0) {
785 HTTPServer_log_err("Warning: error copying response body from server to client\n");
786 goto done;
787 }
788 }
789 }
790
791 done:
792 if (server_exchange != NULL) {
793 HTTPExchange_delete(server_exchange);
794 }
795 if (server_connection != NULL) {
796 if (HTTPConnection_delete(server_connection) != 0) {
797 HTTPServer_log_err("Warning: error closing connection to server\n");
798 }
799 }
800 }
801
802 static void handle_local_request(HTTPExchange * exchange) {
803 /* add the `Server' response-header (RFC 2616 14.38, 3.8) */
804 HTTPExchange_add_response_header(exchange, HTTP_SERVER, "jscoverage-server/" VERSION);
805
806 char * filesystem_path = NULL;
807
808 const char * abs_path = HTTPExchange_get_abs_path(exchange);
809 assert(*abs_path != '\0');
810
811 if (str_starts_with(abs_path, "/jscoverage")) {
812 handle_jscoverage_request(exchange);
813 goto done;
814 }
815
816 if (strstr(abs_path, "..") != NULL) {
817 send_response(exchange, 403, "Forbidden\n");
818 goto done;
819 }
820
821 filesystem_path = make_path(document_root, abs_path + 1);
822 size_t filesystem_path_length = strlen(filesystem_path);
823 if (filesystem_path_length > 0 && filesystem_path[filesystem_path_length - 1] == '/') {
824 /* stat on Windows doesn't work with trailing slash */
825 filesystem_path[filesystem_path_length - 1] = '\0';
826 }
827
828 struct stat buf;
829 if (stat(filesystem_path, &buf) == -1) {
830 send_response(exchange, 404, "Not found\n");
831 goto done;
832 }
833
834 if (S_ISDIR(buf.st_mode)) {
835 if (abs_path[strlen(abs_path) - 1] != '/') {
836 const char * request_uri = HTTPExchange_get_request_uri(exchange);
837 char * uri = xmalloc(strlen(request_uri) + 2);
838 strcpy(uri, request_uri);
839 strcat(uri, "/");
840 HTTPExchange_add_response_header(exchange, "Location", uri);
841 free(uri);
842 send_response(exchange, 301, "Moved permanently\n");
843 goto done;
844 }
845
846 DIR * d = opendir(filesystem_path);
847 if (d == NULL) {
848 send_response(exchange, 404, "Not found\n");
849 goto done;
850 }
851
852 struct dirent * entry;
853 while ((entry = readdir(d)) != NULL) {
854 char * href = encode_uri_component(entry->d_name);
855 char * html_href = encode_html(href);
856 char * link = encode_html(entry->d_name);
857 char * directory_entry;
858 xasprintf(&directory_entry, "<a href=\"%s\">%s</a><br>\n", html_href, link);
859 if (HTTPExchange_write_response(exchange, directory_entry, strlen(directory_entry)) != 0) {
860 HTTPServer_log_err("Warning: error writing to client\n");
861 }
862 free(directory_entry);
863 free(href);
864 free(html_href);
865 free(link);
866 }
867 closedir(d);
868 }
869 else if (S_ISREG(buf.st_mode)) {
870 FILE * f = fopen(filesystem_path, "rb");
871 if (f == NULL) {
872 send_response(exchange, 404, "Not found\n");
873 goto done;
874 }
875
876 const char * content_type = get_content_type(filesystem_path);
877 HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type);
878 const char * request_uri = HTTPExchange_get_request_uri(exchange);
879 if (strcmp(content_type, "text/javascript") == 0 && ! is_no_instrument(request_uri)) {
880 Stream * input_stream = Stream_new(0);
881 Stream * output_stream = Stream_new(0);
882
883 Stream_write_file_contents(input_stream, f);
884
885 instrument_js(request_uri, input_stream, output_stream);
886
887 if (HTTPExchange_write_response(exchange, output_stream->data, output_stream->length) != 0) {
888 HTTPServer_log_err("Warning: error writing to client\n");
889 }
890
891 Stream_delete(input_stream);
892 Stream_delete(output_stream);
893 }
894 else {
895 char buffer[8192];
896 size_t bytes_read;
897 while ((bytes_read = fread(buffer, 1, 8192, f)) > 0) {
898 if (HTTPExchange_write_response(exchange, buffer, bytes_read) != 0) {
899 HTTPServer_log_err("Warning: error writing to client\n");
900 }
901 }
902 }
903 fclose(f);
904 }
905 else {
906 send_response(exchange, 404, "Not found\n");
907 goto done;
908 }
909
910 done:
911 free(filesystem_path);
912 }
913
914 static void handler(HTTPExchange * exchange) {
915 if (verbose) {
916 HTTPServer_log_out("%s", HTTPExchange_get_request_line(exchange));
917 }
918
919 if (proxy) {
920 handle_proxy_request(exchange);
921 }
922 else {
923 handle_local_request(exchange);
924 }
925 }
926
927 int main(int argc, char ** argv) {
928 program = "jscoverage-server";
929
930 const char * ip_address = "127.0.0.1";
931 const char * port = "8080";
932 int shutdown = 0;
933
934 no_instrument = xnew(const char *, argc - 1);
935
936 for (int i = 1; i < argc; i++) {
937 if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
938 copy_resource_to_stream("jscoverage-server-help.txt", stdout);
939 exit(EXIT_SUCCESS);
940 }
941 else if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) {
942 printf("jscoverage-server %s\n", VERSION);
943 exit(EXIT_SUCCESS);
944 }
945 else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
946 verbose = 1;
947 }
948
949 else if (strcmp(argv[i], "--report-dir") == 0) {
950 i++;
951 if (i == argc) {
952 fatal("--report-dir: option requires an argument");
953 }
954 report_directory = argv[i];
955 }
956 else if (strncmp(argv[i], "--report-dir=", 13) == 0) {
957 report_directory = argv[i] + 13;
958 }
959
960 else if (strcmp(argv[i], "--document-root") == 0) {
961 i++;
962 if (i == argc) {
963 fatal("--document-root: option requires an argument");
964 }
965 document_root = argv[i];
966 }
967 else if (strncmp(argv[i], "--document-root=", 16) == 0) {
968 document_root = argv[i] + 16;
969 }
970
971 else if (strcmp(argv[i], "--ip-address") == 0) {
972 i++;
973 if (i == argc) {
974 fatal("--ip-address: option requires an argument");
975 }
976 ip_address = argv[i];
977 }
978 else if (strncmp(argv[i], "--ip-address=", 13) == 0) {
979 ip_address = argv[i] + 13;
980 }
981
982 else if (strcmp(argv[i], "--no-instrument") == 0) {
983 i++;
984 if (i == argc) {
985 fatal("--no-instrument: option requires an argument");
986 }
987 no_instrument[num_no_instrument] = argv[i];
988 num_no_instrument++;
989 }
990 else if (strncmp(argv[i], "--no-instrument=", 16) == 0) {
991 no_instrument[num_no_instrument] = argv[i] + 16;
992 num_no_instrument++;
993 }
994
995 else if (strcmp(argv[i], "--port") == 0) {
996 i++;
997 if (i == argc) {
998 fatal("--port: option requires an argument");
999 }
1000 port = argv[i];
1001 }
1002 else if (strncmp(argv[i], "--port=", 7) == 0) {
1003 port = argv[i] + 7;
1004 }
1005
1006 else if (strcmp(argv[i], "--proxy") == 0) {
1007 proxy = 1;
1008 }
1009
1010 else if (strcmp(argv[i], "--shutdown") == 0) {
1011 shutdown = 1;
1012 }
1013
1014 else if (strncmp(argv[i], "-", 1) == 0) {
1015 fatal("unrecognized option `%s'", argv[i]);
1016 }
1017 else {
1018 fatal("too many arguments");
1019 }
1020 }
1021
1022 /* check the port */
1023 char * end;
1024 unsigned long numeric_port = strtoul(port, &end, 10);
1025 if (*end != '\0') {
1026 fatal("--port: option must be an integer");
1027 }
1028 if (numeric_port > UINT16_MAX) {
1029 fatal("--port: option must be 16 bits");
1030 }
1031
1032 /* is this a shutdown? */
1033 if (shutdown) {
1034 #ifdef __MINGW32__
1035 WSADATA data;
1036 if (WSAStartup(MAKEWORD(1, 1), &data) != 0) {
1037 fatal("Could not start Winsock");
1038 }
1039 #endif
1040
1041 /* INADDR_LOOPBACK */
1042 HTTPConnection * connection = HTTPConnection_new_client("127.0.0.1", numeric_port);
1043 if (connection == NULL) {
1044 fatal("could not connect to server");
1045 }
1046 HTTPExchange * exchange = HTTPExchange_new(connection);
1047 HTTPExchange_set_method(exchange, "POST");
1048 HTTPExchange_set_request_uri(exchange, "/jscoverage-shutdown");
1049 if (HTTPExchange_write_request_headers(exchange) != 0) {
1050 fatal("could not write request headers to server");
1051 }
1052 if (HTTPExchange_read_response_headers(exchange) != 0) {
1053 fatal("could not read response headers from server");
1054 }
1055 Stream * stream = Stream_new(0);
1056 if (HTTPExchange_read_entire_response_entity_body(exchange, stream) != 0) {
1057 fatal("could not read response body from server");
1058 }
1059 fwrite(stream->data, 1, stream->length, stdout);
1060 Stream_delete(stream);
1061 HTTPExchange_delete(exchange);
1062 if (HTTPConnection_delete(connection) != 0) {
1063 fatal("could not close connection with server");
1064 }
1065 exit(EXIT_SUCCESS);
1066 }
1067
1068 jscoverage_init();
1069
1070 #ifndef __MINGW32__
1071 /* handle broken pipe */
1072 signal(SIGPIPE, SIG_IGN);
1073 #endif
1074
1075 #ifdef __MINGW32__
1076 InitializeCriticalSection(&javascript_mutex);
1077 InitializeCriticalSection(&source_cache_mutex);
1078 #endif
1079
1080 if (verbose) {
1081 printf("Starting HTTP server on %s:%lu\n", ip_address, numeric_port);
1082 fflush(stdout);
1083 }
1084 HTTPServer_run(ip_address, (uint16_t) numeric_port, handler);
1085 if (verbose) {
1086 printf("Stopping HTTP server\n");
1087 fflush(stdout);
1088 }
1089
1090 jscoverage_cleanup();
1091
1092 free(no_instrument);
1093
1094 LOCK(&source_cache_mutex);
1095 while (source_cache != NULL) {
1096 SourceCache * p = source_cache;
1097 source_cache = source_cache->next;
1098 free(p->url);
1099 Stream_delete(p->source);
1100 free(p);
1101 }
1102 UNLOCK(&source_cache_mutex);
1103
1104 return 0;
1105 }

  ViewVC Help
Powered by ViewVC 1.1.24