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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 116 - (hide annotations)
Sat May 31 21:42:36 2008 UTC (11 years, 4 months ago) by siliconforks
Original Path: trunk/jscoverage-server.c
File MIME type: text/plain
File size: 32190 byte(s)
Add jscoverage-server.

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

  ViewVC Help
Powered by ViewVC 1.1.24