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 httpClientError(requestor
, 405, internAtom("Method not allowed"));
71 requestor
->connection
->flags
&= ~CONN_READER
;
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
);
299 httpSpecialSideRequest(ObjectPtr object
, int method
, int from
, int to
,
300 HTTPRequestPtr requestor
, void *closure
)
302 HTTPConnectionPtr client
= requestor
->connection
;
304 assert(client
->request
== requestor
);
306 if(method
!= METHOD_POST
) {
307 httpClientError(requestor
, 405, internAtom("Method not allowed"));
308 requestor
->connection
->flags
&= ~CONN_READER
;
312 return httpSpecialDoSide(requestor
);
316 httpSpecialDoSide(HTTPRequestPtr requestor
)
318 HTTPConnectionPtr client
= requestor
->connection
;
320 if(client
->reqlen
- client
->reqbegin
>= client
->bodylen
) {
322 data
= internAtomN(client
->reqbuf
+ client
->reqbegin
,
323 client
->reqlen
- client
->reqbegin
);
324 client
->reqbegin
= 0;
327 do_log(L_ERROR
, "Couldn't allocate data.\n");
328 httpClientError(requestor
, 500,
329 internAtom("Couldn't allocate data"));
332 httpSpecialDoSideFinish(data
, requestor
);
336 if(client
->reqlen
- client
->reqbegin
>= CHUNK_SIZE
) {
337 httpClientError(requestor
, 500, internAtom("POST too large"));
341 if(client
->reqbegin
> 0 && client
->reqlen
> client
->reqbegin
) {
342 memmove(client
->reqbuf
, client
->reqbuf
+ client
->reqbegin
,
343 client
->reqlen
- client
->reqbegin
);
345 client
->reqlen
-= client
->reqbegin
;
346 client
->reqbegin
= 0;
348 do_stream(IO_READ
| IO_NOTNOW
, client
->fd
,
349 client
->reqlen
, client
->reqbuf
, CHUNK_SIZE
,
350 httpSpecialClientSideHandler
, client
);
355 httpSpecialClientSideHandler(int status
,
356 FdEventHandlerPtr event
,
357 StreamRequestPtr srequest
)
359 HTTPConnectionPtr connection
= srequest
->data
;
360 HTTPRequestPtr request
= connection
->request
;
363 if((request
->object
->flags
& OBJECT_ABORTED
) ||
364 !(request
->object
->flags
& OBJECT_INPROGRESS
)) {
365 httpClientDiscardBody(connection
);
366 httpClientError(request
, 503, internAtom("Post aborted"));
371 do_log_error(L_ERROR
, -status
, "Reading from client");
372 if(status
== -EDOGRACEFUL
)
373 httpClientFinish(connection
, 1);
375 httpClientFinish(connection
, 2);
379 push
= MIN(srequest
->offset
- connection
->reqlen
,
380 connection
->bodylen
- connection
->reqoffset
);
382 connection
->reqlen
+= push
;
383 httpSpecialDoSide(request
);
386 do_log(L_ERROR
, "Incomplete client request.\n");
387 connection
->flags
&= ~CONN_READER
;
388 httpClientRawError(connection
, 502,
389 internAtom("Incomplete client request"), 1);
394 httpSpecialDoSideFinish(AtomPtr data
, HTTPRequestPtr requestor
)
396 ObjectPtr object
= requestor
->object
;
398 if(matchUrl("/polipo/config", object
)) {
399 AtomListPtr list
= NULL
;
402 if(disableConfiguration
) {
403 abortObject(object
, 403, internAtom("Action not allowed"));
407 list
= urlDecode(data
->string
, data
->length
);
409 abortObject(object
, 400,
410 internAtom("Couldn't parse variable to set"));
413 for(i
= 0; i
< list
->length
; i
++) {
414 rc
= parseConfigLine(list
->list
[i
]->string
, NULL
, 0, 1);
416 abortObject(object
, 400,
418 internAtom("Couldn't parse variable to set") :
419 internAtom("Variable is not settable"));
420 destroyAtomList(list
);
424 destroyAtomList(list
);
425 object
->date
= current_time
.tv_sec
;
426 object
->age
= current_time
.tv_sec
;
427 object
->headers
= internAtom("\r\nLocation: /polipo/config?");
429 object
->message
= internAtom("Done");
430 object
->flags
&= ~OBJECT_INITIAL
;
432 } else if(matchUrl("/polipo/status", object
)) {
433 AtomListPtr list
= NULL
;
436 if(disableConfiguration
) {
437 abortObject(object
, 403, internAtom("Action not allowed"));
441 list
= urlDecode(data
->string
, data
->length
);
443 abortObject(object
, 400,
444 internAtom("Couldn't parse action"));
447 for(i
= 0; i
< list
->length
; i
++) {
449 memchr(list
->list
[i
]->string
, '=', list
->list
[i
]->length
);
452 internAtomN(list
->list
[i
]->string
,
453 equals
- list
->list
[i
]->string
) :
454 retainAtom(list
->list
[i
]);
455 if(name
== atomInitForbidden
)
457 else if(name
== atomReopenLog
)
459 else if(name
== atomDiscardObjects
)
460 discardObjects(1, 0);
461 else if(name
== atomWriteoutObjects
)
463 else if(name
== atomFreeChunkArenas
)
466 abortObject(object
, 400, internAtomF("Unknown action %s",
469 destroyAtomList(list
);
474 destroyAtomList(list
);
475 object
->date
= current_time
.tv_sec
;
476 object
->age
= current_time
.tv_sec
;
477 object
->headers
= internAtom("\r\nLocation: /polipo/status?");
479 object
->message
= internAtom("Done");
480 object
->flags
&= ~OBJECT_INITIAL
;
483 abortObject(object
, 405, internAtom("Method not allowed"));
488 notifyObject(object
);
489 requestor
->connection
->flags
&= ~CONN_READER
;
495 fillSpecialObject(ObjectPtr object
, void (*fn
)(FILE*, char*), void* closure
)
500 sigset_t ss
, old_mask
;
502 if(object
->flags
& OBJECT_INPROGRESS
)
507 do_log_error(L_ERROR
, errno
, "Couldn't create pipe");
508 abortObject(object
, 503,
509 internAtomError(errno
, "Couldn't create pipe"));
517 /* Block signals that we handle specially until the child can
518 disable the handlers. */
519 interestingSignals(&ss
);
520 /* I'm a little confused. POSIX doesn't allow EINTR here, but I
521 think that both Linux and SVR4 do. */
523 rc
= sigprocmask(SIG_BLOCK
, &ss
, &old_mask
);
524 } while (rc
< 0 && errno
== EINTR
);
526 do_log_error(L_ERROR
, errno
, "Sigprocmask failed");
527 abortObject(object
, 503, internAtomError(errno
, "Sigprocmask failed"));
535 do_log_error(L_ERROR
, errno
, "Couldn't fork");
536 abortObject(object
, 503, internAtomError(errno
, "Couldn't fork"));
540 rc
= sigprocmask(SIG_SETMASK
, &old_mask
, NULL
);
541 } while (rc
< 0 && errno
== EINTR
);
543 do_log_error(L_ERROR
, errno
, "Couldn't restore signal mask");
550 SpecialRequestPtr request
;
553 rc
= sigprocmask(SIG_SETMASK
, &old_mask
, NULL
);
554 } while (rc
< 0 && errno
== EINTR
);
556 do_log_error(L_ERROR
, errno
, "Couldn't restore signal mask");
561 request
= malloc(sizeof(SpecialRequestRec
));
562 if(request
== NULL
) {
565 abortObject(object
, 503,
566 internAtom("Couldn't allocate request\n"));
567 notifyObject(object
);
568 /* specialRequestHandler will take care of the rest. */
570 request
->buf
= get_chunk();
571 if(request
->buf
== NULL
) {
575 abortObject(object
, 503,
576 internAtom("Couldn't allocate request\n"));
577 notifyObject(object
);
580 object
->flags
|= OBJECT_INPROGRESS
;
581 retainObject(object
);
582 request
->object
= object
;
583 request
->fd
= filedes
[0];
586 /* Under any sensible scheduler, the child will run before the
587 parent. So no need for IO_NOTNOW. */
588 do_stream(IO_READ
, filedes
[0], 0, request
->buf
, CHUNK_SIZE
,
589 specialRequestHandler
, request
);
595 rc
= sigprocmask(SIG_SETMASK
, &old_mask
, NULL
);
596 } while (rc
< 0 && errno
== EINTR
);
603 (*fn
)(stdout
, closure
);
609 specialRequestHandler(int status
,
610 FdEventHandlerPtr event
, StreamRequestPtr srequest
)
612 SpecialRequestPtr request
= srequest
->data
;
617 kill(request
->pid
, SIGTERM
);
619 request
->object
->flags
&= ~OBJECT_INPROGRESS
;
620 abortObject(request
->object
, 502,
621 internAtomError(-status
, "Couldn't read from client"));
625 if(srequest
->offset
> 0) {
626 rc
= objectAddData(request
->object
, request
->buf
,
627 request
->offset
, srequest
->offset
);
629 kill(request
->pid
, SIGTERM
);
631 request
->object
->flags
&= ~OBJECT_INPROGRESS
;
632 abortObject(request
->object
, 503,
633 internAtom("Couldn't add data to connection"));
636 request
->offset
+= srequest
->offset
;
639 request
->object
->flags
&= ~OBJECT_INPROGRESS
;
640 request
->object
->length
= request
->object
->size
;
644 /* If we're the only person interested in this object, let's abort
646 if(request
->object
->refcount
<= 1) {
647 kill(request
->pid
, SIGTERM
);
649 request
->object
->flags
&= ~OBJECT_INPROGRESS
;
650 abortObject(request
->object
, 500, internAtom("Aborted"));
653 notifyObject(request
->object
);
654 do_stream(IO_READ
| IO_NOTNOW
, request
->fd
, 0, request
->buf
, CHUNK_SIZE
,
655 specialRequestHandler
, request
);
660 dispose_chunk(request
->buf
);
661 releaseNotifyObject(request
->object
);
662 /* That's a blocking wait. It shouldn't block for long, as we've
663 either already killed the child, or else we got EOF from it. */
665 rc
= waitpid(request
->pid
, &status
, 0);
666 } while(rc
< 0 && errno
== EINTR
);
668 do_log(L_ERROR
, "Wait for %d: %d\n", (int)request
->pid
, errno
);
671 (WIFEXITED(status
) && WEXITSTATUS(status
) == 0) ||
672 (killed
&& WIFSIGNALED(status
) && WTERMSIG(status
) == SIGTERM
);
674 WIFEXITED(status
) ? "with status" :
675 WIFSIGNALED(status
) ? "on signal" :
676 "with unknown status";
678 WIFEXITED(status
) ? WEXITSTATUS(status
) :
679 WIFSIGNALED(status
) ? WTERMSIG(status
) :
681 do_log(normal
? D_CHILD
: L_ERROR
,
682 "Child %d exited %s %d.\n",
683 (int)request
->pid
, reason
, value
);
690 fillSpecialObject(ObjectPtr object
, void (*fn
)(FILE*, char*), void* closure
)
696 if(object
->flags
& OBJECT_INPROGRESS
)
701 abortObject(object
, 503, internAtom("Couldn't allocate chunk"));
707 abortObject(object
, 503, internAtom(pstrerror(errno
)));
717 len
= fread(buf
, 1, CHUNK_SIZE
, tmp
);
718 if(len
<= 0 && ferror(tmp
)) {
719 abortObject(object
, 503, internAtom(pstrerror(errno
)));
725 rc
= objectAddData(object
, buf
, offset
, len
);
727 abortObject(object
, 503, internAtom("Couldn't add data to object"));
734 object
->length
= offset
;
741 notifyObject(object
);