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 "<"; |
211 |
|
|
case '>': |
212 |
|
|
return ">"; |
213 |
|
|
case '&': |
214 |
|
|
return "&"; |
215 |
|
|
case '\'': |
216 |
|
|
return "'"; |
217 |
|
|
case '"': |
218 |
|
|
return """; |
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 |
siliconforks |
119 |
static int merge(Coverage * coverage, FILE * f) __attribute__((warn_unused_result)); |
342 |
siliconforks |
116 |
|
343 |
siliconforks |
119 |
static int merge(Coverage * coverage, FILE * f) { |
344 |
|
|
Stream * stream = Stream_new(0); |
345 |
|
|
Stream_write_file_contents(stream, f); |
346 |
siliconforks |
116 |
|
347 |
|
|
pthread_mutex_lock(&javascript_mutex); |
348 |
siliconforks |
119 |
int result = jscoverage_parse_json(coverage, stream->data, stream->length); |
349 |
siliconforks |
116 |
pthread_mutex_unlock(&javascript_mutex); |
350 |
|
|
|
351 |
siliconforks |
119 |
Stream_delete(stream); |
352 |
siliconforks |
116 |
return result; |
353 |
|
|
} |
354 |
|
|
|
355 |
|
|
static void write_js_quoted_string(FILE * f, char * data, size_t length) { |
356 |
|
|
putc('"', f); |
357 |
|
|
for (size_t i = 0; i < length; i++) { |
358 |
|
|
char c = data[i]; |
359 |
|
|
switch (c) { |
360 |
|
|
case '\b': |
361 |
|
|
fputs("\\b", f); |
362 |
|
|
break; |
363 |
|
|
case '\f': |
364 |
|
|
fputs("\\f", f); |
365 |
|
|
break; |
366 |
|
|
case '\n': |
367 |
|
|
fputs("\\n", f); |
368 |
|
|
break; |
369 |
|
|
case '\r': |
370 |
|
|
fputs("\\r", f); |
371 |
|
|
break; |
372 |
|
|
case '\t': |
373 |
|
|
fputs("\\t", f); |
374 |
|
|
break; |
375 |
|
|
case '\v': |
376 |
|
|
fputs("\\v", f); |
377 |
|
|
break; |
378 |
|
|
case '"': |
379 |
|
|
fputs("\\\"", f); |
380 |
|
|
break; |
381 |
|
|
case '\\': |
382 |
|
|
fputs("\\\\", f); |
383 |
|
|
break; |
384 |
|
|
default: |
385 |
|
|
putc(c, f); |
386 |
|
|
break; |
387 |
|
|
} |
388 |
|
|
} |
389 |
|
|
putc('"', f); |
390 |
|
|
} |
391 |
|
|
|
392 |
|
|
static void write_json_for_file(const FileCoverage * file_coverage, int i, void * p) { |
393 |
|
|
FILE * f = p; |
394 |
|
|
|
395 |
|
|
if (i > 0) { |
396 |
|
|
putc(',', f); |
397 |
|
|
} |
398 |
|
|
|
399 |
|
|
write_js_quoted_string(f, file_coverage->id, strlen(file_coverage->id)); |
400 |
|
|
|
401 |
|
|
fputs(":{\"coverage\":[", f); |
402 |
|
|
for (uint32_t i = 0; i <= file_coverage->num_lines; i++) { |
403 |
|
|
if (i > 0) { |
404 |
|
|
putc(',', f); |
405 |
|
|
} |
406 |
|
|
int timesExecuted = file_coverage->lines[i]; |
407 |
|
|
if (timesExecuted < 0) { |
408 |
|
|
fputs("null", f); |
409 |
|
|
} |
410 |
|
|
else { |
411 |
|
|
fprintf(f, "%d", timesExecuted); |
412 |
|
|
} |
413 |
|
|
} |
414 |
|
|
fputs("],\"source\":", f); |
415 |
|
|
if (file_coverage->source == NULL) { |
416 |
|
|
if (proxy) { |
417 |
|
|
Stream * stream = find_cached_source(file_coverage->id); |
418 |
|
|
if (stream == NULL) { |
419 |
|
|
stream = Stream_new(0); |
420 |
|
|
if (get(file_coverage->id, stream) == 0) { |
421 |
|
|
write_js_quoted_string(f, stream->data, stream->length); |
422 |
|
|
add_cached_source(file_coverage->id, stream); |
423 |
|
|
} |
424 |
|
|
else { |
425 |
|
|
fputs("\"\"", f); |
426 |
|
|
HTTPServer_log_err("Warning: cannot retrieve URL: %s\n", file_coverage->id); |
427 |
|
|
Stream_delete(stream); |
428 |
|
|
} |
429 |
|
|
} |
430 |
|
|
else { |
431 |
|
|
write_js_quoted_string(f, stream->data, stream->length); |
432 |
|
|
} |
433 |
|
|
} |
434 |
|
|
else { |
435 |
|
|
/* check that the path begins with / */ |
436 |
|
|
if (file_coverage->id[0] == '/') { |
437 |
|
|
char * source_path = make_path(document_root, file_coverage->id + 1); |
438 |
|
|
FILE * source_file = fopen(source_path, "r"); |
439 |
|
|
free(source_path); |
440 |
|
|
if (source_file == NULL) { |
441 |
|
|
fputs("\"\"", f); |
442 |
|
|
HTTPServer_log_err("Warning: cannot open file: %s\n", file_coverage->id); |
443 |
|
|
} |
444 |
|
|
else { |
445 |
|
|
Stream * stream = Stream_new(0); |
446 |
|
|
Stream_write_file_contents(stream, source_file); |
447 |
|
|
fclose(source_file); |
448 |
|
|
write_js_quoted_string(f, stream->data, stream->length); |
449 |
|
|
Stream_delete(stream); |
450 |
|
|
} |
451 |
|
|
} |
452 |
|
|
else { |
453 |
|
|
/* path does not begin with / */ |
454 |
|
|
fputs("\"\"", f); |
455 |
|
|
HTTPServer_log_err("Warning: invalid source path: %s\n", file_coverage->id); |
456 |
|
|
} |
457 |
|
|
} |
458 |
|
|
} |
459 |
|
|
else { |
460 |
|
|
write_js_quoted_string(f, file_coverage->source, strlen(file_coverage->source)); |
461 |
|
|
} |
462 |
|
|
fputc('}', f); |
463 |
|
|
} |
464 |
|
|
|
465 |
|
|
static int write_json(Coverage * coverage, const char * path) __attribute__((warn_unused_result)); |
466 |
|
|
|
467 |
|
|
static int write_json(Coverage * coverage, const char * path) { |
468 |
|
|
/* write the JSON */ |
469 |
|
|
FILE * f = fopen(path, "w"); |
470 |
|
|
if (f == NULL) { |
471 |
|
|
return -1; |
472 |
|
|
} |
473 |
|
|
putc('{', f); |
474 |
|
|
Coverage_foreach_file(coverage, write_json_for_file, f); |
475 |
|
|
putc('}', f); |
476 |
|
|
if (fclose(f) == EOF) { |
477 |
|
|
return -1; |
478 |
|
|
} |
479 |
|
|
return 0; |
480 |
|
|
} |
481 |
|
|
|
482 |
|
|
static void handle_jscoverage_request(HTTPExchange * exchange) { |
483 |
|
|
/* set the `Server' response-header (RFC 2616 14.38, 3.8) */ |
484 |
|
|
HTTPExchange_set_response_header(exchange, HTTP_SERVER, "jscoverage-server/" VERSION); |
485 |
|
|
|
486 |
|
|
const char * abs_path = HTTPExchange_get_abs_path(exchange); |
487 |
|
|
assert(*abs_path != '\0'); |
488 |
|
|
if (str_starts_with(abs_path, "/jscoverage-store")) { |
489 |
|
|
if (strcmp(HTTPExchange_get_method(exchange), "POST") != 0) { |
490 |
|
|
HTTPExchange_set_response_header(exchange, HTTP_ALLOW, "POST"); |
491 |
|
|
send_response(exchange, 405, "Method not allowed\n"); |
492 |
|
|
return; |
493 |
|
|
} |
494 |
|
|
|
495 |
|
|
Stream * json = Stream_new(0); |
496 |
|
|
|
497 |
|
|
/* read the POST body */ |
498 |
|
|
if (HTTPExchange_read_entire_request_entity_body(exchange, json) != 0) { |
499 |
|
|
Stream_delete(json); |
500 |
|
|
send_response(exchange, 400, "Could not read request body\n"); |
501 |
|
|
return; |
502 |
|
|
} |
503 |
|
|
|
504 |
|
|
Coverage * coverage = Coverage_new(); |
505 |
|
|
pthread_mutex_lock(&javascript_mutex); |
506 |
|
|
int result = jscoverage_parse_json(coverage, json->data, json->length); |
507 |
|
|
pthread_mutex_unlock(&javascript_mutex); |
508 |
|
|
Stream_delete(json); |
509 |
|
|
|
510 |
|
|
if (result != 0) { |
511 |
|
|
Coverage_delete(coverage); |
512 |
|
|
send_response(exchange, 400, "Could not parse coverage data\n"); |
513 |
|
|
return; |
514 |
|
|
} |
515 |
|
|
|
516 |
|
|
mkdir_if_necessary(report_directory); |
517 |
|
|
char * path = make_path(report_directory, "jscoverage.json"); |
518 |
siliconforks |
119 |
FILE * f = fopen(path, "r"); |
519 |
|
|
if (f != NULL) { |
520 |
siliconforks |
116 |
/* it exists: merge */ |
521 |
siliconforks |
119 |
result = merge(coverage, f); |
522 |
|
|
if (fclose(f) == EOF) { |
523 |
|
|
result = 1; |
524 |
|
|
} |
525 |
siliconforks |
116 |
if (result != 0) { |
526 |
|
|
free(path); |
527 |
|
|
Coverage_delete(coverage); |
528 |
|
|
send_response(exchange, 500, "Could not merge with existing coverage data\n"); |
529 |
|
|
return; |
530 |
|
|
} |
531 |
|
|
} |
532 |
|
|
|
533 |
|
|
result = write_json(coverage, path); |
534 |
|
|
free(path); |
535 |
|
|
Coverage_delete(coverage); |
536 |
|
|
if (result != 0) { |
537 |
|
|
send_response(exchange, 500, "Could not write coverage data\n"); |
538 |
|
|
return; |
539 |
|
|
} |
540 |
|
|
|
541 |
|
|
/* copy other files */ |
542 |
|
|
jscoverage_copy_resources(report_directory); |
543 |
|
|
path = make_path(report_directory, "jscoverage.js"); |
544 |
siliconforks |
119 |
f = fopen(path, "ab"); |
545 |
siliconforks |
116 |
free(path); |
546 |
|
|
if (f == NULL) { |
547 |
|
|
send_response(exchange, 500, "Could not write to file: jscoverage.js\n"); |
548 |
|
|
return; |
549 |
|
|
} |
550 |
|
|
fputs("jscoverage_isReport = true;\r\n", f); |
551 |
|
|
if (fclose(f) == EOF) { |
552 |
|
|
send_response(exchange, 500, "Could not write to file: jscoverage.js\n"); |
553 |
|
|
return; |
554 |
|
|
} |
555 |
|
|
|
556 |
|
|
send_response(exchange, 200, "Coverage data stored\n"); |
557 |
|
|
} |
558 |
|
|
else if (str_starts_with(abs_path, "/jscoverage-shutdown")) { |
559 |
|
|
if (strcmp(HTTPExchange_get_method(exchange), "POST") != 0) { |
560 |
|
|
HTTPExchange_set_response_header(exchange, HTTP_ALLOW, "POST"); |
561 |
|
|
send_response(exchange, 405, "Method not allowed\n"); |
562 |
|
|
return; |
563 |
|
|
} |
564 |
|
|
|
565 |
|
|
/* allow only from localhost */ |
566 |
|
|
struct sockaddr_in client; |
567 |
|
|
if (HTTPExchange_get_peer(exchange, &client) != 0) { |
568 |
|
|
send_response(exchange, 500, "Cannot get client address\n"); |
569 |
|
|
return; |
570 |
|
|
} |
571 |
|
|
if (client.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) { |
572 |
|
|
send_response(exchange, 403, "This operation can be performed only by localhost\n"); |
573 |
|
|
return; |
574 |
|
|
} |
575 |
|
|
|
576 |
|
|
send_response(exchange, 200, "The server will now shut down\n"); |
577 |
|
|
HTTPServer_shutdown(); |
578 |
|
|
} |
579 |
|
|
else { |
580 |
|
|
const char * path = abs_path + 1; |
581 |
|
|
const struct Resource * resource = get_resource(path); |
582 |
|
|
if (resource == NULL) { |
583 |
|
|
send_response(exchange, 404, "Not found\n"); |
584 |
|
|
return; |
585 |
|
|
} |
586 |
|
|
HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, get_content_type(path)); |
587 |
|
|
if (HTTPExchange_write_response(exchange, resource->data, resource->length) != 0) { |
588 |
|
|
HTTPServer_log_err("Warning: error writing to client\n"); |
589 |
|
|
return; |
590 |
|
|
} |
591 |
|
|
if (strcmp(abs_path, "/jscoverage.js") == 0) { |
592 |
|
|
const char * s = "jscoverage_isServer = true;\r\n"; |
593 |
|
|
if (HTTPExchange_write_response(exchange, s, strlen(s)) != 0) { |
594 |
|
|
HTTPServer_log_err("Warning: error writing to client\n"); |
595 |
|
|
} |
596 |
|
|
} |
597 |
|
|
} |
598 |
|
|
} |
599 |
|
|
|
600 |
|
|
static void instrument_js(const char * id, Stream * input_stream, Stream * output_stream) { |
601 |
|
|
pthread_mutex_lock(&javascript_mutex); |
602 |
|
|
jscoverage_instrument_js(id, input_stream, output_stream); |
603 |
|
|
pthread_mutex_unlock(&javascript_mutex); |
604 |
|
|
|
605 |
|
|
const struct Resource * resource = get_resource("report.js"); |
606 |
|
|
Stream_write(output_stream, resource->data, resource->length); |
607 |
|
|
} |
608 |
|
|
|
609 |
|
|
static bool is_hop_by_hop_header(const char * h) { |
610 |
|
|
/* hop-by-hop headers (RFC 2616 13.5.1) */ |
611 |
|
|
return strcasecmp(h, HTTP_CONNECTION) == 0 || |
612 |
|
|
strcasecmp(h, "Keep-Alive") == 0 || |
613 |
|
|
strcasecmp(h, HTTP_PROXY_AUTHENTICATE) == 0 || |
614 |
|
|
strcasecmp(h, HTTP_PROXY_AUTHORIZATION) == 0 || |
615 |
|
|
strcasecmp(h, HTTP_TE) == 0 || |
616 |
|
|
strcasecmp(h, HTTP_TRAILER) == 0 || |
617 |
|
|
strcasecmp(h, HTTP_TRANSFER_ENCODING) == 0 || |
618 |
|
|
strcasecmp(h, HTTP_UPGRADE) == 0; |
619 |
|
|
} |
620 |
|
|
|
621 |
|
|
static void add_via_header(HTTPMessage * message, const char * version) { |
622 |
|
|
char * value; |
623 |
|
|
xasprintf(&value, "%s jscoverage-server", version); |
624 |
|
|
HTTPMessage_add_header(message, HTTP_VIA, value); |
625 |
|
|
free(value); |
626 |
|
|
} |
627 |
|
|
|
628 |
|
|
static int copy_http_message_body(HTTPMessage * from, HTTPMessage * to) __attribute__((warn_unused_result)); |
629 |
|
|
|
630 |
|
|
static int copy_http_message_body(HTTPMessage * from, HTTPMessage * to) { |
631 |
|
|
uint8_t * buffer[8192]; |
632 |
|
|
for (;;) { |
633 |
|
|
size_t bytes_read; |
634 |
|
|
int result = HTTPMessage_read_message_body(from, buffer, 8192, &bytes_read); |
635 |
|
|
if (result != 0) { |
636 |
|
|
return result; |
637 |
|
|
} |
638 |
|
|
if (bytes_read == 0) { |
639 |
|
|
return 0; |
640 |
|
|
} |
641 |
|
|
result = HTTPMessage_write(to, buffer, bytes_read); |
642 |
|
|
if (result != 0) { |
643 |
|
|
return result; |
644 |
|
|
} |
645 |
|
|
} |
646 |
|
|
} |
647 |
|
|
|
648 |
|
|
static void handle_proxy_request(HTTPExchange * client_exchange) { |
649 |
|
|
HTTPConnection * server_connection = NULL; |
650 |
|
|
HTTPExchange * server_exchange = NULL; |
651 |
|
|
|
652 |
|
|
const char * abs_path = HTTPExchange_get_abs_path(client_exchange); |
653 |
|
|
if (str_starts_with(abs_path, "/jscoverage")) { |
654 |
|
|
handle_jscoverage_request(client_exchange); |
655 |
|
|
return; |
656 |
|
|
} |
657 |
|
|
|
658 |
|
|
const char * host = HTTPExchange_get_host(client_exchange); |
659 |
|
|
uint16_t port = HTTPExchange_get_port(client_exchange); |
660 |
|
|
|
661 |
|
|
/* create a new connection */ |
662 |
|
|
server_connection = HTTPConnection_new_client(host, port); |
663 |
|
|
if (server_connection == NULL) { |
664 |
|
|
send_response(client_exchange, 504, "Could not connect to server\n"); |
665 |
|
|
goto done; |
666 |
|
|
} |
667 |
|
|
|
668 |
|
|
/* create a new exchange */ |
669 |
|
|
server_exchange = HTTPExchange_new(server_connection); |
670 |
|
|
HTTPExchange_set_method(server_exchange, HTTPExchange_get_method(client_exchange)); |
671 |
|
|
HTTPExchange_set_request_uri(server_exchange, HTTPExchange_get_request_uri(client_exchange)); |
672 |
|
|
for (const HTTPHeader * h = HTTPExchange_get_request_headers(client_exchange); h != NULL; h = h->next) { |
673 |
|
|
if (strcasecmp(h->name, HTTP_TRAILER) == 0 || strcasecmp(h->name, HTTP_TRANSFER_ENCODING) == 0) { |
674 |
|
|
/* do nothing: we want to keep this header */ |
675 |
|
|
} |
676 |
|
|
else if (is_hop_by_hop_header(h->name) || |
677 |
|
|
strcasecmp(h->name, HTTP_ACCEPT_ENCODING) == 0 || |
678 |
|
|
strcasecmp(h->name, HTTP_RANGE) == 0) { |
679 |
|
|
continue; |
680 |
|
|
} |
681 |
|
|
HTTPExchange_add_request_header(server_exchange, h->name, h->value); |
682 |
|
|
} |
683 |
|
|
add_via_header(HTTPExchange_get_request_message(server_exchange), HTTPExchange_get_request_http_version(client_exchange)); |
684 |
|
|
|
685 |
|
|
/* send the request */ |
686 |
|
|
if (HTTPExchange_write_request_headers(server_exchange) != 0) { |
687 |
|
|
send_response(client_exchange, 502, "Could not write to server\n"); |
688 |
|
|
goto done; |
689 |
|
|
} |
690 |
|
|
|
691 |
|
|
/* handle POST or PUT */ |
692 |
|
|
if (HTTPExchange_request_has_body(client_exchange)) { |
693 |
|
|
HTTPMessage * client_request = HTTPExchange_get_request_message(client_exchange); |
694 |
|
|
HTTPMessage * server_request = HTTPExchange_get_request_message(server_exchange); |
695 |
|
|
if (copy_http_message_body(client_request, server_request) != 0) { |
696 |
|
|
send_response(client_exchange, 400, "Error copying request body from client to server\n"); |
697 |
|
|
goto done; |
698 |
|
|
} |
699 |
|
|
} |
700 |
|
|
|
701 |
|
|
if (HTTPExchange_flush_request(server_exchange) != 0) { |
702 |
|
|
send_response(client_exchange, 502, "Could not write to server\n"); |
703 |
|
|
goto done; |
704 |
|
|
} |
705 |
|
|
|
706 |
|
|
/* receive the response */ |
707 |
|
|
if (HTTPExchange_read_response_headers(server_exchange) != 0) { |
708 |
|
|
send_response(client_exchange, 502, "Could not read headers from server\n"); |
709 |
|
|
goto done; |
710 |
|
|
} |
711 |
|
|
|
712 |
|
|
HTTPExchange_set_status_code(client_exchange, HTTPExchange_get_status_code(server_exchange)); |
713 |
|
|
|
714 |
|
|
if (HTTPExchange_response_has_body(server_exchange) && should_instrument_request(server_exchange)) { |
715 |
|
|
/* needs instrumentation */ |
716 |
|
|
Stream * input_stream = Stream_new(0); |
717 |
|
|
if (HTTPExchange_read_entire_response_entity_body(server_exchange, input_stream) != 0) { |
718 |
|
|
Stream_delete(input_stream); |
719 |
|
|
send_response(client_exchange, 502, "Could not read body from server\n"); |
720 |
|
|
goto done; |
721 |
|
|
} |
722 |
|
|
|
723 |
|
|
const char * request_uri = HTTPExchange_get_request_uri(client_exchange); |
724 |
|
|
Stream * output_stream = Stream_new(0); |
725 |
|
|
instrument_js(request_uri, input_stream, output_stream); |
726 |
|
|
|
727 |
|
|
/* send the headers to the client */ |
728 |
|
|
for (const HTTPHeader * h = HTTPExchange_get_response_headers(server_exchange); h != NULL; h = h->next) { |
729 |
|
|
if (is_hop_by_hop_header(h->name) || strcasecmp(h->name, HTTP_CONTENT_LENGTH) == 0) { |
730 |
|
|
continue; |
731 |
|
|
} |
732 |
|
|
HTTPExchange_add_response_header(client_exchange, h->name, h->value); |
733 |
|
|
} |
734 |
|
|
add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange)); |
735 |
|
|
HTTPExchange_set_response_content_length(client_exchange, output_stream->length); |
736 |
|
|
|
737 |
|
|
/* send the instrumented code to the client */ |
738 |
|
|
if (HTTPExchange_write_response(client_exchange, output_stream->data, output_stream->length) != 0) { |
739 |
|
|
HTTPServer_log_err("Warning: error writing to client\n"); |
740 |
|
|
} |
741 |
|
|
|
742 |
|
|
/* input_stream goes on the cache */ |
743 |
|
|
/* |
744 |
|
|
Stream_delete(input_stream); |
745 |
|
|
*/ |
746 |
|
|
Stream_delete(output_stream); |
747 |
|
|
add_cached_source(request_uri, input_stream); |
748 |
|
|
} |
749 |
|
|
else { |
750 |
|
|
/* does not need instrumentation */ |
751 |
|
|
|
752 |
|
|
/* send the headers to the client */ |
753 |
|
|
for (const HTTPHeader * h = HTTPExchange_get_response_headers(server_exchange); h != NULL; h = h->next) { |
754 |
|
|
if (strcasecmp(h->name, HTTP_TRAILER) == 0 || strcasecmp(h->name, HTTP_TRANSFER_ENCODING) == 0) { |
755 |
|
|
/* do nothing: we want to keep this header */ |
756 |
|
|
} |
757 |
|
|
else if (is_hop_by_hop_header(h->name)) { |
758 |
|
|
continue; |
759 |
|
|
} |
760 |
|
|
HTTPExchange_add_response_header(client_exchange, h->name, h->value); |
761 |
|
|
} |
762 |
|
|
add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange)); |
763 |
|
|
|
764 |
|
|
if (HTTPExchange_write_response_headers(client_exchange) != 0) { |
765 |
|
|
HTTPServer_log_err("Warning: error writing to client\n"); |
766 |
|
|
goto done; |
767 |
|
|
} |
768 |
|
|
|
769 |
|
|
if (HTTPExchange_response_has_body(server_exchange)) { |
770 |
|
|
/* read the body from the server and send it to the client */ |
771 |
|
|
HTTPMessage * client_response = HTTPExchange_get_response_message(client_exchange); |
772 |
|
|
HTTPMessage * server_response = HTTPExchange_get_response_message(server_exchange); |
773 |
|
|
if (copy_http_message_body(server_response, client_response) != 0) { |
774 |
|
|
HTTPServer_log_err("Warning: error copying response body from server to client\n"); |
775 |
|
|
goto done; |
776 |
|
|
} |
777 |
|
|
} |
778 |
|
|
} |
779 |
|
|
|
780 |
|
|
done: |
781 |
|
|
if (server_exchange != NULL) { |
782 |
|
|
HTTPExchange_delete(server_exchange); |
783 |
|
|
} |
784 |
|
|
if (server_connection != NULL) { |
785 |
|
|
if (HTTPConnection_delete(server_connection) != 0) { |
786 |
|
|
HTTPServer_log_err("Warning: error closing connection to server\n"); |
787 |
|
|
} |
788 |
|
|
} |
789 |
|
|
} |
790 |
|
|
|
791 |
|
|
static void handle_local_request(HTTPExchange * exchange) { |
792 |
|
|
/* add the `Server' response-header (RFC 2616 14.38, 3.8) */ |
793 |
|
|
HTTPExchange_add_response_header(exchange, HTTP_SERVER, "jscoverage-server/" VERSION); |
794 |
|
|
|
795 |
|
|
char * filesystem_path = NULL; |
796 |
|
|
|
797 |
|
|
const char * abs_path = HTTPExchange_get_abs_path(exchange); |
798 |
|
|
assert(*abs_path != '\0'); |
799 |
|
|
|
800 |
|
|
if (str_starts_with(abs_path, "/jscoverage")) { |
801 |
|
|
handle_jscoverage_request(exchange); |
802 |
|
|
goto done; |
803 |
|
|
} |
804 |
|
|
|
805 |
|
|
if (strstr(abs_path, "..") != NULL) { |
806 |
|
|
send_response(exchange, 403, "Forbidden\n"); |
807 |
|
|
goto done; |
808 |
|
|
} |
809 |
|
|
|
810 |
|
|
filesystem_path = make_path(document_root, abs_path + 1); |
811 |
|
|
|
812 |
|
|
struct stat buf; |
813 |
|
|
if (stat(filesystem_path, &buf) == -1) { |
814 |
|
|
send_response(exchange, 404, "Not found\n"); |
815 |
|
|
goto done; |
816 |
|
|
} |
817 |
|
|
|
818 |
|
|
if (S_ISDIR(buf.st_mode)) { |
819 |
|
|
if (filesystem_path[strlen(filesystem_path) - 1] != '/') { |
820 |
|
|
const char * request_uri = HTTPExchange_get_request_uri(exchange); |
821 |
|
|
char * uri = xmalloc(strlen(request_uri) + 2); |
822 |
|
|
strcpy(uri, request_uri); |
823 |
|
|
strcat(uri, "/"); |
824 |
|
|
HTTPExchange_add_response_header(exchange, "Location", uri); |
825 |
|
|
free(uri); |
826 |
|
|
send_response(exchange, 301, "Moved permanently\n"); |
827 |
|
|
goto done; |
828 |
|
|
} |
829 |
|
|
|
830 |
|
|
DIR * d = opendir(filesystem_path); |
831 |
|
|
if (d == NULL) { |
832 |
|
|
send_response(exchange, 404, "Not found\n"); |
833 |
|
|
goto done; |
834 |
|
|
} |
835 |
|
|
|
836 |
|
|
struct dirent * entry; |
837 |
|
|
while ((entry = readdir(d)) != NULL) { |
838 |
|
|
char * href = encode_uri_component(entry->d_name); |
839 |
|
|
char * html_href = encode_html(href); |
840 |
|
|
char * link = encode_html(entry->d_name); |
841 |
|
|
char * directory_entry; |
842 |
|
|
xasprintf(&directory_entry, "<a href=\"%s\">%s</a><br>\n", html_href, link); |
843 |
|
|
if (HTTPExchange_write_response(exchange, directory_entry, strlen(directory_entry)) != 0) { |
844 |
|
|
HTTPServer_log_err("Warning: error writing to client\n"); |
845 |
|
|
} |
846 |
|
|
free(directory_entry); |
847 |
|
|
free(href); |
848 |
|
|
free(html_href); |
849 |
|
|
free(link); |
850 |
|
|
} |
851 |
|
|
closedir(d); |
852 |
|
|
} |
853 |
|
|
else if (S_ISREG(buf.st_mode)) { |
854 |
|
|
FILE * f = fopen(filesystem_path, "r"); |
855 |
|
|
if (f == NULL) { |
856 |
|
|
send_response(exchange, 404, "Not found\n"); |
857 |
|
|
goto done; |
858 |
|
|
} |
859 |
|
|
|
860 |
|
|
const char * content_type = get_content_type(filesystem_path); |
861 |
|
|
HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type); |
862 |
|
|
const char * request_uri = HTTPExchange_get_request_uri(exchange); |
863 |
|
|
if (strcmp(content_type, "text/javascript") == 0 && ! is_no_instrument(request_uri)) { |
864 |
|
|
Stream * input_stream = Stream_new(0); |
865 |
|
|
Stream * output_stream = Stream_new(0); |
866 |
|
|
|
867 |
|
|
Stream_write_file_contents(input_stream, f); |
868 |
|
|
|
869 |
|
|
instrument_js(request_uri, input_stream, output_stream); |
870 |
|
|
|
871 |
|
|
if (HTTPExchange_write_response(exchange, output_stream->data, output_stream->length) != 0) { |
872 |
|
|
HTTPServer_log_err("Warning: error writing to client\n"); |
873 |
|
|
} |
874 |
|
|
|
875 |
|
|
Stream_delete(input_stream); |
876 |
|
|
Stream_delete(output_stream); |
877 |
|
|
} |
878 |
|
|
else { |
879 |
|
|
char buffer[8192]; |
880 |
|
|
size_t bytes_read; |
881 |
|
|
while ((bytes_read = fread(buffer, 1, 8192, f)) > 0) { |
882 |
|
|
if (HTTPExchange_write_response(exchange, buffer, bytes_read) != 0) { |
883 |
|
|
HTTPServer_log_err("Warning: error writing to client\n"); |
884 |
|
|
} |
885 |
|
|
} |
886 |
|
|
} |
887 |
|
|
fclose(f); |
888 |
|
|
} |
889 |
|
|
else { |
890 |
|
|
send_response(exchange, 404, "Not found\n"); |
891 |
|
|
goto done; |
892 |
|
|
} |
893 |
|
|
|
894 |
|
|
done: |
895 |
|
|
free(filesystem_path); |
896 |
|
|
} |
897 |
|
|
|
898 |
|
|
static void handler(HTTPExchange * exchange) { |
899 |
|
|
if (verbose) { |
900 |
|
|
HTTPServer_log_out("%s", HTTPExchange_get_request_line(exchange)); |
901 |
|
|
} |
902 |
|
|
|
903 |
|
|
if (proxy) { |
904 |
|
|
handle_proxy_request(exchange); |
905 |
|
|
} |
906 |
|
|
else { |
907 |
|
|
handle_local_request(exchange); |
908 |
|
|
} |
909 |
|
|
} |
910 |
|
|
|
911 |
|
|
int main(int argc, char ** argv) { |
912 |
|
|
program = "jscoverage-server"; |
913 |
|
|
|
914 |
|
|
const char * ip_address = "127.0.0.1"; |
915 |
|
|
const char * port = "8080"; |
916 |
|
|
int shutdown = 0; |
917 |
|
|
|
918 |
|
|
no_instrument = xnew(const char *, argc - 1); |
919 |
|
|
|
920 |
|
|
for (int i = 1; i < argc; i++) { |
921 |
|
|
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { |
922 |
|
|
copy_resource_to_stream("jscoverage-server-help.txt", stdout); |
923 |
|
|
exit(EXIT_SUCCESS); |
924 |
|
|
} |
925 |
|
|
else if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) { |
926 |
|
|
printf("jscoverage-server %s\n", VERSION); |
927 |
|
|
exit(EXIT_SUCCESS); |
928 |
|
|
} |
929 |
|
|
else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) { |
930 |
|
|
verbose = 1; |
931 |
|
|
} |
932 |
|
|
|
933 |
|
|
else if (strcmp(argv[i], "--report-dir") == 0) { |
934 |
|
|
i++; |
935 |
|
|
if (i == argc) { |
936 |
|
|
fatal("--report-dir: option requires an argument"); |
937 |
|
|
} |
938 |
|
|
report_directory = argv[i]; |
939 |
|
|
} |
940 |
|
|
else if (strncmp(argv[i], "--report-dir=", 13) == 0) { |
941 |
|
|
report_directory = argv[i] + 13; |
942 |
|
|
} |
943 |
|
|
|
944 |
|
|
else if (strcmp(argv[i], "--document-root") == 0) { |
945 |
|
|
i++; |
946 |
|
|
if (i == argc) { |
947 |
|
|
fatal("--document-root: option requires an argument"); |
948 |
|
|
} |
949 |
|
|
document_root = argv[i]; |
950 |
|
|
} |
951 |
|
|
else if (strncmp(argv[i], "--document-root=", 16) == 0) { |
952 |
|
|
document_root = argv[i] + 16; |
953 |
|
|
} |
954 |
|
|
|
955 |
|
|
else if (strcmp(argv[i], "--ip-address") == 0) { |
956 |
|
|
i++; |
957 |
|
|
if (i == argc) { |
958 |
|
|
fatal("--ip-address: option requires an argument"); |
959 |
|
|
} |
960 |
|
|
ip_address = argv[i]; |
961 |
|
|
} |
962 |
|
|
else if (strncmp(argv[i], "--ip-address=", 13) == 0) { |
963 |
|
|
ip_address = argv[i] + 13; |
964 |
|
|
} |
965 |
|
|
|
966 |
|
|
else if (strcmp(argv[i], "--no-instrument") == 0) { |
967 |
|
|
i++; |
968 |
|
|
if (i == argc) { |
969 |
|
|
fatal("--no-instrument: option requires an argument"); |
970 |
|
|
} |
971 |
|
|
no_instrument[num_no_instrument] = argv[i]; |
972 |
|
|
num_no_instrument++; |
973 |
|
|
} |
974 |
|
|
else if (strncmp(argv[i], "--no-instrument=", 16) == 0) { |
975 |
|
|
no_instrument[num_no_instrument] = argv[i] + 16; |
976 |
|
|
num_no_instrument++; |
977 |
|
|
} |
978 |
|
|
|
979 |
|
|
else if (strcmp(argv[i], "--port") == 0) { |
980 |
|
|
i++; |
981 |
|
|
if (i == argc) { |
982 |
|
|
fatal("--port: option requires an argument"); |
983 |
|
|
} |
984 |
|
|
port = argv[i]; |
985 |
|
|
} |
986 |
|
|
else if (strncmp(argv[i], "--port=", 7) == 0) { |
987 |
|
|
port = argv[i] + 7; |
988 |
|
|
} |
989 |
|
|
|
990 |
|
|
else if (strcmp(argv[i], "--proxy") == 0) { |
991 |
|
|
proxy = 1; |
992 |
|
|
} |
993 |
|
|
|
994 |
|
|
else if (strcmp(argv[i], "--shutdown") == 0) { |
995 |
|
|
shutdown = 1; |
996 |
|
|
} |
997 |
|
|
|
998 |
|
|
else if (strncmp(argv[i], "-", 1) == 0) { |
999 |
|
|
fatal("unrecognized option `%s'", argv[i]); |
1000 |
|
|
} |
1001 |
|
|
else { |
1002 |
|
|
fatal("too many arguments"); |
1003 |
|
|
} |
1004 |
|
|
} |
1005 |
|
|
|
1006 |
|
|
/* check the port */ |
1007 |
|
|
char * end; |
1008 |
|
|
unsigned long numeric_port = strtoul(port, &end, 10); |
1009 |
|
|
if (*end != '\0') { |
1010 |
|
|
fatal("--port: option must be an integer"); |
1011 |
|
|
} |
1012 |
|
|
if (numeric_port > UINT16_MAX) { |
1013 |
|
|
fatal("--port: option must be 16 bits"); |
1014 |
|
|
} |
1015 |
|
|
|
1016 |
|
|
/* is this a shutdown? */ |
1017 |
|
|
if (shutdown) { |
1018 |
|
|
/* INADDR_LOOPBACK */ |
1019 |
|
|
HTTPConnection * connection = HTTPConnection_new_client("127.0.0.1", numeric_port); |
1020 |
|
|
if (connection == NULL) { |
1021 |
|
|
fatal("could not connect to server"); |
1022 |
|
|
} |
1023 |
|
|
HTTPExchange * exchange = HTTPExchange_new(connection); |
1024 |
|
|
HTTPExchange_set_method(exchange, "POST"); |
1025 |
|
|
HTTPExchange_set_request_uri(exchange, "/jscoverage-shutdown"); |
1026 |
|
|
if (HTTPExchange_write_request_headers(exchange) != 0) { |
1027 |
|
|
fatal("could not write request headers to server"); |
1028 |
|
|
} |
1029 |
|
|
if (HTTPExchange_read_response_headers(exchange) != 0) { |
1030 |
|
|
fatal("could not read response headers from server"); |
1031 |
|
|
} |
1032 |
|
|
Stream * stream = Stream_new(0); |
1033 |
|
|
if (HTTPExchange_read_entire_response_entity_body(exchange, stream) != 0) { |
1034 |
|
|
fatal("could not read response body from server"); |
1035 |
|
|
} |
1036 |
|
|
fwrite(stream->data, 1, stream->length, stdout); |
1037 |
|
|
Stream_delete(stream); |
1038 |
|
|
HTTPExchange_delete(exchange); |
1039 |
|
|
if (HTTPConnection_delete(connection) != 0) { |
1040 |
|
|
fatal("could not close connection with server"); |
1041 |
|
|
} |
1042 |
|
|
exit(EXIT_SUCCESS); |
1043 |
|
|
} |
1044 |
|
|
|
1045 |
|
|
jscoverage_init(); |
1046 |
|
|
|
1047 |
|
|
/* handle broken pipe */ |
1048 |
|
|
signal(SIGPIPE, SIG_IGN); |
1049 |
|
|
|
1050 |
|
|
if (verbose) { |
1051 |
|
|
printf("Starting HTTP server on %s:%lu\n", ip_address, numeric_port); |
1052 |
|
|
} |
1053 |
|
|
HTTPServer_run(ip_address, (uint16_t) numeric_port, handler); |
1054 |
|
|
if (verbose) { |
1055 |
|
|
printf("Stopping HTTP server\n"); |
1056 |
|
|
} |
1057 |
|
|
|
1058 |
|
|
jscoverage_cleanup(); |
1059 |
|
|
|
1060 |
|
|
free(no_instrument); |
1061 |
|
|
|
1062 |
|
|
pthread_mutex_lock(&source_cache_mutex); |
1063 |
|
|
while (source_cache != NULL) { |
1064 |
|
|
SourceCache * p = source_cache; |
1065 |
|
|
source_cache = source_cache->next; |
1066 |
|
|
free(p->url); |
1067 |
|
|
Stream_delete(p->source); |
1068 |
|
|
free(p); |
1069 |
|
|
} |
1070 |
|
|
pthread_mutex_unlock(&source_cache_mutex); |
1071 |
|
|
|
1072 |
|
|
return 0; |
1073 |
|
|
} |