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

  ViewVC Help
Powered by ViewVC 1.1.24