2 Copyright (c) 2003-2006 by Juliusz Chroboczek
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 int disableLocalInterface
= 0;
26 int disableConfiguration
= 0;
27 int disableIndexing
= 1;
28 int disableServersList
= 1;
30 AtomPtr atomInitForbidden
;
31 AtomPtr atomReopenLog
;
32 AtomPtr atomDiscardObjects
;
33 AtomPtr atomWriteoutObjects
;
34 AtomPtr atomFreeChunkArenas
;
39 atomInitForbidden
= internAtom("init-forbidden");
40 atomReopenLog
= internAtom("reopen-log");
41 atomDiscardObjects
= internAtom("discard-objects");
42 atomWriteoutObjects
= internAtom("writeout-objects");
43 atomFreeChunkArenas
= internAtom("free-chunk-arenas");
45 /* These should not be settable for obvious reasons */
46 CONFIG_VARIABLE(disableLocalInterface
, CONFIG_BOOLEAN
,
47 "Disable the local configuration pages.");
48 CONFIG_VARIABLE(disableConfiguration
, CONFIG_BOOLEAN
,
49 "Disable reconfiguring Polipo at runtime.");
50 CONFIG_VARIABLE(disableIndexing
, CONFIG_BOOLEAN
,
51 "Disable indexing of the local cache.");
52 CONFIG_VARIABLE(disableServersList
, CONFIG_BOOLEAN
,
53 "Disable the list of known servers.");
56 static void fillSpecialObject(ObjectPtr
, void (*)(FILE*, char*), void*);
59 httpLocalRequest(ObjectPtr object
, int method
, int from
, int to
,
60 HTTPRequestPtr requestor
, void *closure
)
62 if(object
->requestor
== NULL
)
63 object
->requestor
= requestor
;
65 if(!disableLocalInterface
&& urlIsSpecial(object
->key
, object
->key_size
))
66 return httpSpecialRequest(object
, method
, from
, to
,
69 if(method
>= METHOD_POST
) {
70 httpClientDiscardBody(requestor
->connection
);
71 httpClientError(requestor
, 405, internAtom("Method not allowed"));
75 /* objectFillFromDisk already did the real work but we have to
76 make sure we don't get into an infinite loop. */
77 if(object
->flags
& OBJECT_INITIAL
) {
78 abortObject(object
, 404, internAtom("Not found"));
80 object
->age
= current_time
.tv_sec
;
81 object
->date
= current_time
.tv_sec
;
83 object
->flags
&= ~OBJECT_VALIDATING
;
89 alternatingHttpStyle(FILE *out
, char *id
)
92 "<style type=\"text/css\">\n"
93 "#%s tbody tr.even td { background-color: #eee; }\n"
94 "#%s tbody tr.odd td { background-color: #fff; }\n"
95 "</style>\n", id
, id
);
99 printConfig(FILE *out
, char *dummy
)
102 "<!DOCTYPE HTML PUBLIC "
103 "\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
104 "\"http://www.w3.org/TR/html4/loose.dtd\">\n"
106 "<title>Polipo configuration</title>\n"
108 "<h1>Polipo configuration</h1>\n");
109 printConfigVariables(out
, 1);
110 fprintf(out
, "<p><a href=\"/polipo/\">back</a></p>");
111 fprintf(out
, "</body></html>\n");
114 #ifndef NO_DISK_CACHE
117 recursiveIndexDiskObjects(FILE *out
, char *root
)
119 indexDiskObjects(out
, root
, 1);
123 plainIndexDiskObjects(FILE *out
, char *root
)
125 indexDiskObjects(out
, root
, 0);
130 serversList(FILE *out
, char *dummy
)
136 matchUrl(char *base
, ObjectPtr object
)
138 int n
= strlen(base
);
139 if(object
->key_size
< n
)
141 if(memcmp(base
, object
->key
, n
) != 0)
143 return (object
->key_size
== n
) || (((char*)object
->key
)[n
] == '?');
147 httpSpecialRequest(ObjectPtr object
, int method
, int from
, int to
,
148 HTTPRequestPtr requestor
, void *closure
)
153 if(method
>= METHOD_POST
) {
154 return httpSpecialSideRequest(object
, method
, from
, to
,
158 if(!(object
->flags
& OBJECT_INITIAL
)) {
159 privatiseObject(object
, 0);
160 supersedeObject(object
);
161 object
->flags
&= ~(OBJECT_VALIDATING
| OBJECT_INPROGRESS
);
162 notifyObject(object
);
166 hlen
= snnprintf(buffer
, 0, 1024,
168 "\r\nContent-Type: text/html");
169 object
->date
= current_time
.tv_sec
;
170 object
->age
= current_time
.tv_sec
;
171 object
->headers
= internAtomN(buffer
, hlen
);
173 object
->message
= internAtom("Okay");
174 object
->flags
&= ~OBJECT_INITIAL
;
175 object
->flags
|= OBJECT_DYNAMIC
;
177 if(object
->key_size
== 8 && memcmp(object
->key
, "/polipo/", 8) == 0) {
178 objectPrintf(object
, 0,
179 "<!DOCTYPE HTML PUBLIC "
180 "\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
181 "\"http://www.w3.org/TR/html4/loose.dtd\">\n"
183 "<title>Polipo</title>\n"
186 "<p><a href=\"status?\">Status report</a>.</p>\n"
187 "<p><a href=\"config?\">Current configuration</a>.</p>\n"
188 "<p><a href=\"servers?\">Known servers</a>.</p>\n"
189 #ifndef NO_DISK_CACHE
190 "<p><a href=\"index?\">Disk cache index</a>.</p>\n"
193 object
->length
= object
->size
;
194 } else if(matchUrl("/polipo/status", object
)) {
195 objectPrintf(object
, 0,
196 "<!DOCTYPE HTML PUBLIC "
197 "\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
198 "\"http://www.w3.org/TR/html4/loose.dtd\">\n"
200 "<title>Polipo status report</title>\n"
202 "<h1>Polipo proxy on %s:%d: status report</h1>\n"
203 "<p>The %s proxy on %s:%d is %s.</p>\n"
204 "<p>There are %d public and %d private objects "
205 "currently in memory using %d KB in %d chunks "
206 "(%d KB allocated).</p>\n"
207 "<p>There are %d atoms.</p>"
208 "<p><form method=POST action=\"/polipo/status?\">"
209 "<input type=submit name=\"init-forbidden\" "
210 "value=\"Read forbidden file\"></form>\n"
211 "<form method=POST action=\"/polipo/status?\">"
212 "<input type=submit name=\"writeout-objects\" "
213 "value=\"Write out in-memory cache\"></form>\n"
214 "<form method=POST action=\"/polipo/status?\">"
215 "<input type=submit name=\"discard-objects\" "
216 "value=\"Discard in-memory cache\"></form>\n"
217 "<form method=POST action=\"/polipo/status?\">"
218 "<input type=submit name=\"reopen-log\" "
219 "value=\"Reopen log file\"></form>\n"
220 "<form method=POST action=\"/polipo/status?\">"
221 "<input type=submit name=\"free-chunk-arenas\" "
222 "value=\"Free chunk arenas\"></form></p>\n"
223 "<p><a href=\"/polipo/\">back</a></p>"
225 proxyName
->string
, proxyPort
,
226 cacheIsShared
? "shared" : "private",
227 proxyName
->string
, proxyPort
,
228 proxyOffline
? "off line" :
230 "on line (transparency relaxed)" :
232 publicObjectCount
, privateObjectCount
,
233 used_chunks
* CHUNK_SIZE
/ 1024, used_chunks
,
234 totalChunkArenaSize() / 1024,
236 object
->expires
= current_time
.tv_sec
;
237 object
->length
= object
->size
;
238 } else if(matchUrl("/polipo/config", object
)) {
239 fillSpecialObject(object
, printConfig
, NULL
);
240 object
->expires
= current_time
.tv_sec
+ 5;
241 #ifndef NO_DISK_CACHE
242 } else if(matchUrl("/polipo/index", object
)) {
245 if(disableIndexing
) {
246 abortObject(object
, 403, internAtom("Action not allowed"));
247 notifyObject(object
);
250 len
= MAX(0, object
->key_size
- 14);
251 root
= strdup_n((char*)object
->key
+ 14, len
);
253 abortObject(object
, 503, internAtom("Couldn't allocate root"));
254 notifyObject(object
);
258 fillSpecialObject(object
, plainIndexDiskObjects
, root
);
260 object
->expires
= current_time
.tv_sec
+ 5;
261 } else if(matchUrl("/polipo/recursive-index", object
)) {
264 if(disableIndexing
) {
265 abortObject(object
, 403, internAtom("Action not allowed"));
266 notifyObject(object
);
269 len
= MAX(0, object
->key_size
- 24);
270 root
= strdup_n((char*)object
->key
+ 24, len
);
272 abortObject(object
, 503, internAtom("Couldn't allocate root"));
273 notifyObject(object
);
277 fillSpecialObject(object
, recursiveIndexDiskObjects
, root
);
279 object
->expires
= current_time
.tv_sec
+ 20;
281 } else if(matchUrl("/polipo/servers", object
)) {
282 if(disableServersList
) {
283 abortObject(object
, 403, internAtom("Action not allowed"));
284 notifyObject(object
);
287 fillSpecialObject(object
, serversList
, NULL
);
288 object
->expires
= current_time
.tv_sec
+ 2;
290 abortObject(object
, 404, internAtom("Not found"));
293 object
->flags
&= ~OBJECT_VALIDATING
;
294 notifyObject(object
);
298 #define MAXBODY(c) (((c)->flags & CONN_BIGREQBUF)? bigBufferSize : CHUNK_SIZE)
301 writeContinue(HTTPConnectionPtr client
)
303 static char httpContinue
[] = "HTTP/1.1 100 Continue\r\n\r\n";
305 /* don't bother writing continue if the post is already completed */
306 if (client
->reqlen
- client
->reqbegin
< client
->bodylen
) {
307 do_stream(IO_WRITE
, client
->fd
, 0, httpContinue
, 25,
308 httpErrorNofinishStreamHandler
, client
);
313 validateRequest(HTTPConnectionPtr client
)
315 ObjectPtr object
= client
->request
->object
;
317 if (disableConfiguration
) {
318 abortObject(object
, 403, internAtom("Action not allowed"));
319 } else if (client
->bodylen
> MAXBODY(client
)) {
320 abortObject(object
, 411, internAtom("POST too large"));
321 } else if (!matchUrl("/polipo/status", object
) &&
322 !matchUrl("/polipo/config", object
)) {
323 abortObject(object
, 404, internAtom("Not found"));
327 httpClientDiscardBody(client
);
328 notifyObject(object
);
334 httpSpecialSideRequest(ObjectPtr object
, int method
, int from
, int to
,
335 HTTPRequestPtr requestor
, void *closure
)
337 HTTPConnectionPtr client
= requestor
->connection
;
340 assert(client
->request
== requestor
);
342 if(method
!= METHOD_POST
) {
343 httpClientError(requestor
, 405, internAtom("Method not allowed"));
344 requestor
->connection
->flags
&= ~CONN_READER
;
348 if (requestor
->flags
& REQUEST_WAIT_CONTINUE
) {
349 requestor
->flags
&= ~REQUEST_WAIT_CONTINUE
;
353 if (validateRequest(client
) < 0)
357 writeContinue(client
);
359 return httpSpecialDoSide(requestor
);
363 readFinished(HTTPConnectionPtr client
)
365 HTTPRequestPtr request
= client
->request
;
367 if(client
->reqlen
- client
->reqbegin
>= client
->bodylen
) {
369 data
= internAtomN(client
->reqbuf
+ client
->reqbegin
,
370 client
->reqlen
- client
->reqbegin
);
371 client
->reqbegin
= 0;
374 do_log(L_ERROR
, "Couldn't allocate data.\n");
375 httpClientError(request
, 500,
376 internAtom("Couldn't allocate data"));
379 httpSpecialDoSideFinish(data
, request
);
387 httpSpecialDoSide(HTTPRequestPtr requestor
)
389 HTTPConnectionPtr client
= requestor
->connection
;
391 if (readFinished(client
))
394 if(client
->reqbegin
> 0 && client
->reqlen
> client
->reqbegin
) {
395 memmove(client
->reqbuf
, client
->reqbuf
+ client
->reqbegin
,
396 client
->reqlen
- client
->reqbegin
);
398 client
->reqlen
-= client
->reqbegin
;
399 client
->reqbegin
= 0;
401 do_stream(IO_READ
| IO_NOTNOW
, client
->fd
,
402 client
->reqlen
, client
->reqbuf
, MAXBODY(client
),
403 httpSpecialClientSideHandler
, client
);
408 httpSpecialClientSideHandler(int status
,
409 FdEventHandlerPtr event
,
410 StreamRequestPtr srequest
)
412 HTTPConnectionPtr connection
= srequest
->data
;
413 HTTPRequestPtr request
= connection
->request
;
416 connection
->flags
&= ~CONN_READER
;
417 if (request
->chandler
) {
418 unregisterConditionHandler(request
->chandler
);
419 request
->chandler
= NULL
;
421 do_log(L_ERROR
, "Incomplete client request.\n");
422 httpClientRawError(connection
, 502,
423 internAtom("Incomplete client request"), 1);
427 connection
->reqlen
= srequest
->offset
;
429 return readFinished(connection
);
433 httpSpecialDoSideFinish(AtomPtr data
, HTTPRequestPtr requestor
)
435 ObjectPtr object
= requestor
->object
;
437 if(matchUrl("/polipo/config", object
)) {
438 AtomListPtr list
= NULL
;
441 if(disableConfiguration
) {
442 abortObject(object
, 403, internAtom("Action not allowed"));
446 list
= urlDecode(data
->string
, data
->length
);
448 abortObject(object
, 400,
449 internAtom("Couldn't parse variable to set"));
452 for(i
= 0; i
< list
->length
; i
++) {
453 rc
= parseConfigLine(list
->list
[i
]->string
, NULL
, 0, 1);
455 abortObject(object
, 400,
457 internAtom("Couldn't parse variable to set") :
458 internAtom("Variable is not settable"));
459 destroyAtomList(list
);
463 destroyAtomList(list
);
464 object
->date
= current_time
.tv_sec
;
465 object
->age
= current_time
.tv_sec
;
466 object
->headers
= internAtom("\r\nLocation: /polipo/config?");
468 object
->message
= internAtom("Done");
469 object
->flags
&= ~OBJECT_INITIAL
;
471 } else if(matchUrl("/polipo/status", object
)) {
472 AtomListPtr list
= NULL
;
475 if(disableConfiguration
) {
476 abortObject(object
, 403, internAtom("Action not allowed"));
480 list
= urlDecode(data
->string
, data
->length
);
482 abortObject(object
, 400,
483 internAtom("Couldn't parse action"));
486 for(i
= 0; i
< list
->length
; i
++) {
488 memchr(list
->list
[i
]->string
, '=', list
->list
[i
]->length
);
491 internAtomN(list
->list
[i
]->string
,
492 equals
- list
->list
[i
]->string
) :
493 retainAtom(list
->list
[i
]);
494 if(name
== atomInitForbidden
)
496 else if(name
== atomReopenLog
)
498 else if(name
== atomDiscardObjects
)
499 discardObjects(1, 0);
500 else if(name
== atomWriteoutObjects
)
502 else if(name
== atomFreeChunkArenas
)
505 abortObject(object
, 400, internAtomF("Unknown action %s",
508 destroyAtomList(list
);
513 destroyAtomList(list
);
514 object
->date
= current_time
.tv_sec
;
515 object
->age
= current_time
.tv_sec
;
516 object
->headers
= internAtom("\r\nLocation: /polipo/status?");
518 object
->message
= internAtom("Done");
519 object
->flags
&= ~OBJECT_INITIAL
;
522 abortObject(object
, 404, internAtom("Not found"));
527 notifyObject(object
);
528 requestor
->connection
->flags
&= ~CONN_READER
;
534 fillSpecialObject(ObjectPtr object
, void (*fn
)(FILE*, char*), void* closure
)
539 sigset_t ss
, old_mask
;
541 if(object
->flags
& OBJECT_INPROGRESS
)
546 do_log_error(L_ERROR
, errno
, "Couldn't create pipe");
547 abortObject(object
, 503,
548 internAtomError(errno
, "Couldn't create pipe"));
556 /* Block signals that we handle specially until the child can
557 disable the handlers. */
558 interestingSignals(&ss
);
559 /* I'm a little confused. POSIX doesn't allow EINTR here, but I
560 think that both Linux and SVR4 do. */
562 rc
= sigprocmask(SIG_BLOCK
, &ss
, &old_mask
);
563 } while (rc
< 0 && errno
== EINTR
);
565 do_log_error(L_ERROR
, errno
, "Sigprocmask failed");
566 abortObject(object
, 503, internAtomError(errno
, "Sigprocmask failed"));
574 do_log_error(L_ERROR
, errno
, "Couldn't fork");
575 abortObject(object
, 503, internAtomError(errno
, "Couldn't fork"));
579 rc
= sigprocmask(SIG_SETMASK
, &old_mask
, NULL
);
580 } while (rc
< 0 && errno
== EINTR
);
582 do_log_error(L_ERROR
, errno
, "Couldn't restore signal mask");
589 SpecialRequestPtr request
;
592 rc
= sigprocmask(SIG_SETMASK
, &old_mask
, NULL
);
593 } while (rc
< 0 && errno
== EINTR
);
595 do_log_error(L_ERROR
, errno
, "Couldn't restore signal mask");
600 request
= malloc(sizeof(SpecialRequestRec
));
601 if(request
== NULL
) {
604 abortObject(object
, 503,
605 internAtom("Couldn't allocate request\n"));
606 notifyObject(object
);
607 /* specialRequestHandler will take care of the rest. */
609 request
->buf
= get_chunk();
610 if(request
->buf
== NULL
) {
614 abortObject(object
, 503,
615 internAtom("Couldn't allocate request\n"));
616 notifyObject(object
);
619 object
->flags
|= OBJECT_INPROGRESS
;
620 retainObject(object
);
621 request
->object
= object
;
622 request
->fd
= filedes
[0];
625 /* Under any sensible scheduler, the child will run before the
626 parent. So no need for IO_NOTNOW. */
627 do_stream(IO_READ
, filedes
[0], 0, request
->buf
, CHUNK_SIZE
,
628 specialRequestHandler
, request
);
634 rc
= sigprocmask(SIG_SETMASK
, &old_mask
, NULL
);
635 } while (rc
< 0 && errno
== EINTR
);
642 (*fn
)(stdout
, closure
);
648 specialRequestHandler(int status
,
649 FdEventHandlerPtr event
, StreamRequestPtr srequest
)
651 SpecialRequestPtr request
= srequest
->data
;
656 kill(request
->pid
, SIGTERM
);
658 request
->object
->flags
&= ~OBJECT_INPROGRESS
;
659 abortObject(request
->object
, 502,
660 internAtomError(-status
, "Couldn't read from client"));
664 if(srequest
->offset
> 0) {
665 rc
= objectAddData(request
->object
, request
->buf
,
666 request
->offset
, srequest
->offset
);
668 kill(request
->pid
, SIGTERM
);
670 request
->object
->flags
&= ~OBJECT_INPROGRESS
;
671 abortObject(request
->object
, 503,
672 internAtom("Couldn't add data to connection"));
675 request
->offset
+= srequest
->offset
;
678 request
->object
->flags
&= ~OBJECT_INPROGRESS
;
679 request
->object
->length
= request
->object
->size
;
683 /* If we're the only person interested in this object, let's abort
685 if(request
->object
->refcount
<= 1) {
686 kill(request
->pid
, SIGTERM
);
688 request
->object
->flags
&= ~OBJECT_INPROGRESS
;
689 abortObject(request
->object
, 500, internAtom("Aborted"));
692 notifyObject(request
->object
);
693 do_stream(IO_READ
| IO_NOTNOW
, request
->fd
, 0, request
->buf
, CHUNK_SIZE
,
694 specialRequestHandler
, request
);
699 dispose_chunk(request
->buf
);
700 releaseNotifyObject(request
->object
);
701 /* That's a blocking wait. It shouldn't block for long, as we've
702 either already killed the child, or else we got EOF from it. */
704 rc
= waitpid(request
->pid
, &status
, 0);
705 } while(rc
< 0 && errno
== EINTR
);
707 do_log(L_ERROR
, "Wait for %d: %d\n", (int)request
->pid
, errno
);
710 (WIFEXITED(status
) && WEXITSTATUS(status
) == 0) ||
711 (killed
&& WIFSIGNALED(status
) && WTERMSIG(status
) == SIGTERM
);
713 WIFEXITED(status
) ? "with status" :
714 WIFSIGNALED(status
) ? "on signal" :
715 "with unknown status";
717 WIFEXITED(status
) ? WEXITSTATUS(status
) :
718 WIFSIGNALED(status
) ? WTERMSIG(status
) :
720 do_log(normal
? D_CHILD
: L_ERROR
,
721 "Child %d exited %s %d.\n",
722 (int)request
->pid
, reason
, value
);
729 fillSpecialObject(ObjectPtr object
, void (*fn
)(FILE*, char*), void* closure
)
735 if(object
->flags
& OBJECT_INPROGRESS
)
740 abortObject(object
, 503, internAtom("Couldn't allocate chunk"));
746 abortObject(object
, 503, internAtom(pstrerror(errno
)));
756 len
= fread(buf
, 1, CHUNK_SIZE
, tmp
);
757 if(len
<= 0 && ferror(tmp
)) {
758 abortObject(object
, 503, internAtom(pstrerror(errno
)));
764 rc
= objectAddData(object
, buf
, offset
, len
);
766 abortObject(object
, 503, internAtom("Couldn't add data to object"));
773 object
->length
= offset
;
780 notifyObject(object
);