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

Annotation of /trunk/jscoverage-server.c

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC 1.1.24