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;
29 AtomPtr atomInitForbidden
;
30 AtomPtr atomReopenLog
;
31 AtomPtr atomDiscardObjects
;
32 AtomPtr atomWriteoutObjects
;
33 AtomPtr atomFreeChunkArenas
;
38 atomInitForbidden
= internAtom("init-forbidden");
39 atomReopenLog
= internAtom("reopen-log");
40 atomDiscardObjects
= internAtom("discard-objects");
41 atomWriteoutObjects
= internAtom("writeout-objects");
42 atomFreeChunkArenas
= internAtom("free-chunk-arenas");
44 /* These should not be settable for obvious reasons */
45 CONFIG_VARIABLE(disableLocalInterface
, CONFIG_BOOLEAN
,
46 "Disable the local configuration pages.");
47 CONFIG_VARIABLE(disableConfiguration
, CONFIG_BOOLEAN
,
48 "Disable reconfiguring Polipo at runtime.");
49 CONFIG_VARIABLE(disableIndexing
, CONFIG_BOOLEAN
,
50 "Disable indexing of the local cache.");
53 static void fillSpecialObject(ObjectPtr
, void (*)(FILE*, char*), void*);
56 httpLocalRequest(ObjectPtr object
, int method
, int from
, int to
,
57 HTTPRequestPtr requestor
, void *closure
)
59 if(object
->requestor
== NULL
)
60 object
->requestor
= requestor
;
62 if(!disableLocalInterface
&& urlIsSpecial(object
->key
, object
->key_size
))
63 return httpSpecialRequest(object
, method
, from
, to
,
66 if(method
>= METHOD_POST
) {
67 httpClientError(requestor
, 405, internAtom("Method not allowed"));
68 requestor
->connection
->flags
&= ~CONN_READER
;
72 /* objectFillFromDisk already did the real work but we have to
73 make sure we don't get into an infinite loop. */
74 if(object
->flags
& OBJECT_INITIAL
) {
75 abortObject(object
, 404, internAtom("Not found"));
77 object
->age
= current_time
.tv_sec
;
78 object
->date
= current_time
.tv_sec
;
80 object
->flags
&= ~OBJECT_VALIDATING
;
86 alternatingHttpStyle(FILE *out
, char *id
)
89 "<style type=\"text/css\">\n"
90 "#%s tbody tr.even td { background-color: #eee; }\n"
91 "#%s tbody tr.odd td { background-color: #fff; }\n"
92 "</style>\n", id
, id
);
96 printConfig(FILE *out
, char *dummy
)
99 "<!DOCTYPE HTML PUBLIC "
100 "\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
101 "\"http://www.w3.org/TR/html4/loose.dtd\">\n"
103 "<title>Polipo configuration</title>\n"
105 "<h1>Polipo configuration</h1>\n");
106 printConfigVariables(out
, 1);
107 fprintf(out
, "<p><a href=\"/polipo/\">back</a></p>");
108 fprintf(out
, "</body></html>\n");
111 #ifndef NO_DISK_CACHE
114 recursiveIndexDiskObjects(FILE *out
, char *root
)
116 indexDiskObjects(out
, root
, 1);
120 plainIndexDiskObjects(FILE *out
, char *root
)
122 indexDiskObjects(out
, root
, 0);
127 serversList(FILE *out
, char *dummy
)
133 matchUrl(char *base
, ObjectPtr object
)
135 int n
= strlen(base
);
136 if(object
->key_size
< n
)
138 if(memcmp(base
, object
->key
, n
) != 0)
140 return (object
->key_size
== n
) || (((char*)object
->key
)[n
] == '?');
144 httpSpecialRequest(ObjectPtr object
, int method
, int from
, int to
,
145 HTTPRequestPtr requestor
, void *closure
)
150 if(method
>= METHOD_POST
) {
151 return httpSpecialSideRequest(object
, method
, from
, to
,
155 if(!(object
->flags
& OBJECT_INITIAL
)) {
156 privatiseObject(object
, 0);
157 supersedeObject(object
);
158 object
->flags
&= ~(OBJECT_VALIDATING
| OBJECT_INPROGRESS
);
159 notifyObject(object
);
163 hlen
= snnprintf(buffer
, 0, 1024,
165 "\r\nContent-Type: text/html");
166 object
->date
= current_time
.tv_sec
;
167 object
->age
= current_time
.tv_sec
;
168 object
->headers
= internAtomN(buffer
, hlen
);
170 object
->message
= internAtom("Okay");
171 object
->flags
&= ~OBJECT_INITIAL
;
172 object
->flags
|= OBJECT_DYNAMIC
;
174 if(object
->key_size
== 8 && memcmp(object
->key
, "/polipo/", 8) == 0) {
175 objectPrintf(object
, 0,
176 "<!DOCTYPE HTML PUBLIC "
177 "\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
178 "\"http://www.w3.org/TR/html4/loose.dtd\">\n"
180 "<title>Polipo</title>\n"
183 "<p><a href=\"status?\">Status report</a>.</p>\n"
184 "<p><a href=\"config?\">Current configuration</a>.</p>\n"
185 "<p><a href=\"servers?\">Known servers</a>.</p>\n"
186 #ifndef NO_DISK_CACHE
187 "<p><a href=\"index?\">Disk cache index</a>.</p>\n"
190 object
->length
= object
->size
;
191 } else if(matchUrl("/polipo/status", object
)) {
192 objectPrintf(object
, 0,
193 "<!DOCTYPE HTML PUBLIC "
194 "\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
195 "\"http://www.w3.org/TR/html4/loose.dtd\">\n"
197 "<title>Polipo status report</title>\n"
199 "<h1>Polipo proxy on %s:%d: status report</h1>\n"
200 "<p>The %s proxy on %s:%d is %s.</p>\n"
201 "<p>There are %d public and %d private objects "
202 "currently in memory using %d KB in %d chunks "
203 "(%d KB allocated).</p>\n"
204 "<p>There are %d atoms.</p>"
205 "<p><form method=POST action=\"/polipo/status?\">"
206 "<input type=submit name=\"init-forbidden\" "
207 "value=\"Read forbidden file\"></form>\n"
208 "<form method=POST action=\"/polipo/status?\">"
209 "<input type=submit name=\"writeout-objects\" "
210 "value=\"Write out in-memory cache\"></form>\n"
211 "<form method=POST action=\"/polipo/status?\">"
212 "<input type=submit name=\"discard-objects\" "
213 "value=\"Discard in-memory cache\"></form>\n"
214 "<form method=POST action=\"/polipo/status?\">"
215 "<input type=submit name=\"reopen-log\" "
216 "value=\"Reopen log file\"></form>\n"
217 "<form method=POST action=\"/polipo/status?\">"
218 "<input type=submit name=\"free-chunk-arenas\" "
219 "value=\"Free chunk arenas\"></form></p>\n"
220 "<p><a href=\"/polipo/\">back</a></p>"
222 proxyName
->string
, proxyPort
,
223 cacheIsShared
? "shared" : "private",
224 proxyName
->string
, proxyPort
,
225 proxyOffline
? "off line" :
227 "on line (transparency relaxed)" :
229 publicObjectCount
, privateObjectCount
,
230 used_chunks
* CHUNK_SIZE
/ 1024, used_chunks
,
231 totalChunkArenaSize() / 1024,
233 object
->expires
= current_time
.tv_sec
;
234 object
->length
= object
->size
;
235 } else if(matchUrl("/polipo/config", object
)) {
236 fillSpecialObject(object
, printConfig
, NULL
);
237 object
->expires
= current_time
.tv_sec
+ 5;
238 #ifndef NO_DISK_CACHE
239 } else if(matchUrl("/polipo/index", object
)) {
242 if(disableIndexing
) {
243 abortObject(object
, 403, internAtom("Action not allowed"));
244 notifyObject(object
);
247 len
= MAX(0, object
->key_size
- 14);
248 root
= strdup_n((char*)object
->key
+ 14, len
);
250 abortObject(object
, 503, internAtom("Couldn't allocate root"));
251 notifyObject(object
);
255 fillSpecialObject(object
, plainIndexDiskObjects
, root
);
257 object
->expires
= current_time
.tv_sec
+ 5;
258 } else if(matchUrl("/polipo/recursive-index", object
)) {
261 if(disableIndexing
) {
262 abortObject(object
, 403, internAtom("Action not allowed"));
263 notifyObject(object
);
266 len
= MAX(0, object
->key_size
- 24);
267 root
= strdup_n((char*)object
->key
+ 24, len
);
269 abortObject(object
, 503, internAtom("Couldn't allocate root"));
270 notifyObject(object
);
274 fillSpecialObject(object
, recursiveIndexDiskObjects
, root
);
276 object
->expires
= current_time
.tv_sec
+ 20;
278 } else if(matchUrl("/polipo/servers", object
)) {
279 fillSpecialObject(object
, serversList
, NULL
);
280 object
->expires
= current_time
.tv_sec
+ 2;
282 abortObject(object
, 404, internAtom("Not found"));
285 object
->flags
&= ~OBJECT_VALIDATING
;
286 notifyObject(object
);
291 httpSpecialSideRequest(ObjectPtr object
, int method
, int from
, int to
,
292 HTTPRequestPtr requestor
, void *closure
)
294 HTTPConnectionPtr client
= requestor
->connection
;
296 assert(client
->request
== requestor
);
298 if(method
!= METHOD_POST
) {
299 httpClientError(requestor
, 405, internAtom("Method not allowed"));
300 requestor
->connection
->flags
&= ~CONN_READER
;
304 return httpSpecialDoSide(requestor
);
308 httpSpecialDoSide(HTTPRequestPtr requestor
)
310 HTTPConnectionPtr client
= requestor
->connection
;
312 if(client
->reqlen
- client
->reqbegin
>= client
->bodylen
) {
314 data
= internAtomN(client
->reqbuf
+ client
->reqbegin
,
315 client
->reqlen
- client
->reqbegin
);
316 client
->reqbegin
= 0;
319 do_log(L_ERROR
, "Couldn't allocate data.\n");
320 httpClientError(requestor
, 500,
321 internAtom("Couldn't allocate data"));
324 httpSpecialDoSideFinish(data
, requestor
);
328 if(client
->reqlen
- client
->reqbegin
>= CHUNK_SIZE
) {
329 httpClientError(requestor
, 500, internAtom("POST too large"));
333 if(client
->reqbegin
> 0 && client
->reqlen
> client
->reqbegin
) {
334 memmove(client
->reqbuf
, client
->reqbuf
+ client
->reqbegin
,
335 client
->reqlen
- client
->reqbegin
);
337 client
->reqlen
-= client
->reqbegin
;
338 client
->reqbegin
= 0;
340 do_stream(IO_READ
| IO_NOTNOW
, client
->fd
,
341 client
->reqlen
, client
->reqbuf
, CHUNK_SIZE
,
342 httpSpecialClientSideHandler
, client
);
347 httpSpecialClientSideHandler(int status
,
348 FdEventHandlerPtr event
,
349 StreamRequestPtr srequest
)
351 HTTPConnectionPtr connection
= srequest
->data
;
352 HTTPRequestPtr request
= connection
->request
;
355 if((request
->object
->flags
& OBJECT_ABORTED
) ||
356 !(request
->object
->flags
& OBJECT_INPROGRESS
)) {
357 httpClientDiscardBody(connection
);
358 httpClientError(request
, 503, internAtom("Post aborted"));
363 do_log_error(L_ERROR
, -status
, "Reading from client");
364 if(status
== -EDOGRACEFUL
)
365 httpClientFinish(connection
, 1);
367 httpClientFinish(connection
, 2);
371 push
= MIN(srequest
->offset
- connection
->reqlen
,
372 connection
->bodylen
- connection
->reqoffset
);
374 connection
->reqlen
+= push
;
375 httpSpecialDoSide(request
);
378 do_log(L_ERROR
, "Incomplete client request.\n");
379 connection
->flags
&= ~CONN_READER
;
380 httpClientRawError(connection
, 502,
381 internAtom("Incomplete client request"), 1);
386 httpSpecialDoSideFinish(AtomPtr data
, HTTPRequestPtr requestor
)
388 ObjectPtr object
= requestor
->object
;
390 if(matchUrl("/polipo/config", object
)) {
391 AtomListPtr list
= NULL
;
394 if(disableConfiguration
) {
395 abortObject(object
, 403, internAtom("Action not allowed"));
399 list
= urlDecode(data
->string
, data
->length
);
401 abortObject(object
, 400,
402 internAtom("Couldn't parse variable to set"));
405 for(i
= 0; i
< list
->length
; i
++) {
406 rc
= parseConfigLine(list
->list
[i
]->string
, NULL
, 0, 1);
408 abortObject(object
, 400,
410 internAtom("Couldn't parse variable to set") :
411 internAtom("Variable is not settable"));
412 destroyAtomList(list
);
416 destroyAtomList(list
);
417 object
->date
= current_time
.tv_sec
;
418 object
->age
= current_time
.tv_sec
;
419 object
->headers
= internAtom("\r\nLocation: /polipo/config?");
421 object
->message
= internAtom("Done");
422 object
->flags
&= ~OBJECT_INITIAL
;
424 } else if(matchUrl("/polipo/status", object
)) {
425 AtomListPtr list
= NULL
;
428 if(disableConfiguration
) {
429 abortObject(object
, 403, internAtom("Action not allowed"));
433 list
= urlDecode(data
->string
, data
->length
);
435 abortObject(object
, 400,
436 internAtom("Couldn't parse action"));
439 for(i
= 0; i
< list
->length
; i
++) {
441 memchr(list
->list
[i
]->string
, '=', list
->list
[i
]->length
);
444 internAtomN(list
->list
[i
]->string
,
445 equals
- list
->list
[i
]->string
) :
446 retainAtom(list
->list
[i
]);
447 if(name
== atomInitForbidden
)
449 else if(name
== atomReopenLog
)
451 else if(name
== atomDiscardObjects
)
452 discardObjects(1, 0);
453 else if(name
== atomWriteoutObjects
)
455 else if(name
== atomFreeChunkArenas
)
458 abortObject(object
, 400, internAtomF("Unknown action %s",
461 destroyAtomList(list
);
466 destroyAtomList(list
);
467 object
->date
= current_time
.tv_sec
;
468 object
->age
= current_time
.tv_sec
;
469 object
->headers
= internAtom("\r\nLocation: /polipo/status?");
471 object
->message
= internAtom("Done");
472 object
->flags
&= ~OBJECT_INITIAL
;
475 abortObject(object
, 405, internAtom("Method not allowed"));
480 notifyObject(object
);
481 requestor
->connection
->flags
&= ~CONN_READER
;
487 fillSpecialObject(ObjectPtr object
, void (*fn
)(FILE*, char*), void* closure
)
492 sigset_t ss
, old_mask
;
494 if(object
->flags
& OBJECT_INPROGRESS
)
499 do_log_error(L_ERROR
, errno
, "Couldn't create pipe");
500 abortObject(object
, 503,
501 internAtomError(errno
, "Couldn't create pipe"));
509 /* Block signals that we handle specially until the child can
510 disable the handlers. */
511 interestingSignals(&ss
);
512 /* I'm a little confused. POSIX doesn't allow EINTR here, but I
513 think that both Linux and SVR4 do. */
515 rc
= sigprocmask(SIG_BLOCK
, &ss
, &old_mask
);
516 } while (rc
< 0 && errno
== EINTR
);
518 do_log_error(L_ERROR
, errno
, "Sigprocmask failed");
519 abortObject(object
, 503, internAtomError(errno
, "Sigprocmask failed"));
527 do_log_error(L_ERROR
, errno
, "Couldn't fork");
528 abortObject(object
, 503, internAtomError(errno
, "Couldn't fork"));
532 rc
= sigprocmask(SIG_SETMASK
, &old_mask
, NULL
);
533 } while (rc
< 0 && errno
== EINTR
);
535 do_log_error(L_ERROR
, errno
, "Couldn't restore signal mask");
542 SpecialRequestPtr request
;
545 rc
= sigprocmask(SIG_SETMASK
, &old_mask
, NULL
);
546 } while (rc
< 0 && errno
== EINTR
);
548 do_log_error(L_ERROR
, errno
, "Couldn't restore signal mask");
553 request
= malloc(sizeof(SpecialRequestRec
));
554 if(request
== NULL
) {
557 abortObject(object
, 503,
558 internAtom("Couldn't allocate request\n"));
559 notifyObject(object
);
560 /* specialRequestHandler will take care of the rest. */
562 request
->buf
= get_chunk();
563 if(request
->buf
== NULL
) {
567 abortObject(object
, 503,
568 internAtom("Couldn't allocate request\n"));
569 notifyObject(object
);
572 object
->flags
|= OBJECT_INPROGRESS
;
573 retainObject(object
);
574 request
->object
= object
;
575 request
->fd
= filedes
[0];
578 /* Under any sensible scheduler, the child will run before the
579 parent. So no need for IO_NOTNOW. */
580 do_stream(IO_READ
, filedes
[0], 0, request
->buf
, CHUNK_SIZE
,
581 specialRequestHandler
, request
);
587 rc
= sigprocmask(SIG_SETMASK
, &old_mask
, NULL
);
588 } while (rc
< 0 && errno
== EINTR
);
595 (*fn
)(stdout
, closure
);
601 specialRequestHandler(int status
,
602 FdEventHandlerPtr event
, StreamRequestPtr srequest
)
604 SpecialRequestPtr request
= srequest
->data
;
609 kill(request
->pid
, SIGTERM
);
611 request
->object
->flags
&= ~OBJECT_INPROGRESS
;
612 abortObject(request
->object
, 502,
613 internAtomError(-status
, "Couldn't read from client"));
617 if(srequest
->offset
> 0) {
618 rc
= objectAddData(request
->object
, request
->buf
,
619 request
->offset
, srequest
->offset
);
621 kill(request
->pid
, SIGTERM
);
623 request
->object
->flags
&= ~OBJECT_INPROGRESS
;
624 abortObject(request
->object
, 503,
625 internAtom("Couldn't add data to connection"));
628 request
->offset
+= srequest
->offset
;
631 request
->object
->flags
&= ~OBJECT_INPROGRESS
;
632 request
->object
->length
= request
->object
->size
;
636 /* If we're the only person interested in this object, let's abort
638 if(request
->object
->refcount
<= 1) {
639 kill(request
->pid
, SIGTERM
);
641 request
->object
->flags
&= ~OBJECT_INPROGRESS
;
642 abortObject(request
->object
, 500, internAtom("Aborted"));
645 notifyObject(request
->object
);
646 do_stream(IO_READ
| IO_NOTNOW
, request
->fd
, 0, request
->buf
, CHUNK_SIZE
,
647 specialRequestHandler
, request
);
652 dispose_chunk(request
->buf
);
653 releaseNotifyObject(request
->object
);
654 /* That's a blocking wait. It shouldn't block for long, as we've
655 either already killed the child, or else we got EOF from it. */
657 rc
= waitpid(request
->pid
, &status
, 0);
658 } while(rc
< 0 && errno
== EINTR
);
660 do_log(L_ERROR
, "Wait for %d: %d\n", (int)request
->pid
, errno
);
663 (WIFEXITED(status
) && WEXITSTATUS(status
) == 0) ||
664 (killed
&& WIFSIGNALED(status
) && WTERMSIG(status
) == SIGTERM
);
666 WIFEXITED(status
) ? "with status" :
667 WIFSIGNALED(status
) ? "on signal" :
668 "with unknown status";
670 WIFEXITED(status
) ? WEXITSTATUS(status
) :
671 WIFSIGNALED(status
) ? WTERMSIG(status
) :
673 do_log(normal
? D_CHILD
: L_ERROR
,
674 "Child %d exited %s %d.\n",
675 (int)request
->pid
, reason
, value
);
682 fillSpecialObject(ObjectPtr object
, void (*fn
)(FILE*, char*), void* closure
)
688 if(object
->flags
& OBJECT_INPROGRESS
)
693 abortObject(object
, 503, internAtom("Couldn't allocate chunk"));
699 abortObject(object
, 503, internAtom(pstrerror(errno
)));
709 len
= fread(buf
, 1, CHUNK_SIZE
, tmp
);
710 if(len
<= 0 && ferror(tmp
)) {
711 abortObject(object
, 503, internAtom(pstrerror(errno
)));
717 rc
= objectAddData(object
, buf
, offset
, len
);
719 abortObject(object
, 503, internAtom("Couldn't add data to object"));
726 object
->length
= offset
;
733 notifyObject(object
);