Call ServerReply rather than ServerFinish(-1) upon 100 Continue.
[polipo.git] / http.c
blobdb35bbfb7109bc3cfb422ed1d6d4ef1e04b79b1a
1 /*
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
20 THE SOFTWARE.
23 #include "polipo.h"
25 int disableProxy = 0;
26 AtomPtr proxyName = NULL;
27 int proxyPort = 8123;
29 int clientTimeout = 120;
30 int serverTimeout = 90;
31 int serverIdleTimeout = 45;
33 int bigBufferSize = (32 * 1024);
35 AtomPtr authRealm = NULL;
36 AtomPtr authCredentials = NULL;
38 AtomPtr parentAuthCredentials = NULL;
40 AtomListPtr allowedClients = NULL;
41 NetAddressPtr allowedNets = NULL;
43 IntListPtr allowedPorts = NULL;
44 IntListPtr tunnelAllowedPorts = NULL;
45 int expectContinue = 1;
46 int dontTrustVaryETag = 1;
48 AtomPtr atom100Continue;
50 int disableVia = 1;
52 /* 0 means that all failures lead to errors. 1 means that failures to
53 connect are reported in a Warning header when stale objects are
54 served. 2 means that only missing data is fetched from the net,
55 stale data is served without revalidation (browser-side
56 Cache-Control directives are still honoured). 3 means that no
57 connections are ever attempted. */
59 int proxyOffline = 0;
60 int relaxTransparency = 0;
61 AtomPtr proxyAddress = NULL;
63 static int timeoutSetter(ConfigVariablePtr var, void *value);
65 void
66 preinitHttp()
68 proxyAddress = internAtom("127.0.0.1");
69 CONFIG_VARIABLE_SETTABLE(disableProxy, CONFIG_BOOLEAN, configIntSetter,
70 "Whether to be a web server only.");
71 CONFIG_VARIABLE_SETTABLE(proxyOffline, CONFIG_BOOLEAN, configIntSetter,
72 "Avoid contacting remote servers.");
73 CONFIG_VARIABLE_SETTABLE(relaxTransparency, CONFIG_TRISTATE,
74 configIntSetter,
75 "Avoid contacting remote servers.");
76 CONFIG_VARIABLE(proxyPort, CONFIG_INT,
77 "The TCP port on which the proxy listens.");
78 CONFIG_VARIABLE(proxyAddress, CONFIG_ATOM_LOWER,
79 "The IP address on which the proxy listens.");
80 CONFIG_VARIABLE_SETTABLE(proxyName, CONFIG_ATOM_LOWER, configAtomSetter,
81 "The name by which the proxy is known.");
82 CONFIG_VARIABLE_SETTABLE(clientTimeout, CONFIG_TIME,
83 timeoutSetter, "Client-side timeout.");
84 CONFIG_VARIABLE_SETTABLE(serverTimeout, CONFIG_TIME,
85 timeoutSetter, "Server-side timeout.");
86 CONFIG_VARIABLE_SETTABLE(serverIdleTimeout, CONFIG_TIME,
87 timeoutSetter, "Server-side idle timeout.");
88 CONFIG_VARIABLE(authRealm, CONFIG_ATOM,
89 "Authentication realm.");
90 CONFIG_VARIABLE(authCredentials, CONFIG_PASSWORD,
91 "username:password.");
92 CONFIG_VARIABLE(parentAuthCredentials, CONFIG_PASSWORD,
93 "username:password.");
94 CONFIG_VARIABLE(allowedClients, CONFIG_ATOM_LIST_LOWER,
95 "Networks from which clients are allowed to connect.");
96 CONFIG_VARIABLE(tunnelAllowedPorts, CONFIG_INT_LIST,
97 "Ports to which tunnelled connections are allowed.");
98 CONFIG_VARIABLE(allowedPorts, CONFIG_INT_LIST,
99 "Ports to which connections are allowed.");
100 CONFIG_VARIABLE(expectContinue, CONFIG_TRISTATE,
101 "Send Expect-Continue to servers.");
102 CONFIG_VARIABLE(bigBufferSize, CONFIG_INT,
103 "Size of big buffers (max size of headers).");
104 CONFIG_VARIABLE_SETTABLE(disableVia, CONFIG_BOOLEAN, configIntSetter,
105 "Don't use Via headers.");
106 CONFIG_VARIABLE(dontTrustVaryETag, CONFIG_TRISTATE,
107 "Whether to trust the ETag when there's Vary.");
108 preinitHttpParser();
111 static int
112 timeoutSetter(ConfigVariablePtr var, void *value)
114 configIntSetter(var, value);
115 if(clientTimeout <= serverTimeout)
116 clientTimeout = serverTimeout + 1;
117 return 1;
120 void
121 initHttp()
123 char *buf = NULL;
124 int namelen;
125 int n;
126 struct hostent *host;
128 initHttpParser();
130 atom100Continue = internAtom("100-continue");
132 if(clientTimeout <= serverTimeout) {
133 clientTimeout = serverTimeout + 1;
134 do_log(L_WARN, "Value of clientTimeout too small -- setting to %d.\n",
135 clientTimeout);
138 if(authCredentials != NULL && authRealm == NULL)
139 authRealm = internAtom("Polipo");
141 if(allowedClients) {
142 allowedNets = parseNetAddress(allowedClients);
143 if(allowedNets == NULL)
144 exit(1);
147 if(allowedPorts == NULL) {
148 allowedPorts = makeIntList(0);
149 if(allowedPorts == NULL) {
150 do_log(L_ERROR, "Couldn't allocate allowedPorts.\n");
151 exit(1);
153 intListCons(80, 100, allowedPorts);
154 intListCons(1024, 0xFFFF, allowedPorts);
157 if(tunnelAllowedPorts == NULL) {
158 tunnelAllowedPorts = makeIntList(0);
159 if(tunnelAllowedPorts == NULL) {
160 do_log(L_ERROR, "Couldn't allocate tunnelAllowedPorts.\n");
161 exit(1);
163 intListCons(22, 22, tunnelAllowedPorts); /* ssh */
164 intListCons(80, 80, tunnelAllowedPorts); /* HTTP */
165 intListCons(109, 110, tunnelAllowedPorts); /* POP 2 and 3*/
166 intListCons(143, 143, tunnelAllowedPorts); /* IMAP 2/4 */
167 intListCons(443, 443, tunnelAllowedPorts); /* HTTP/SSL */
168 intListCons(873, 873, tunnelAllowedPorts); /* rsync */
169 intListCons(993, 993, tunnelAllowedPorts); /* IMAP/SSL */
170 intListCons(995, 995, tunnelAllowedPorts); /* POP/SSL */
171 intListCons(2401, 2401, tunnelAllowedPorts); /* CVS */
172 intListCons(5222, 5223, tunnelAllowedPorts); /* Jabber */
173 intListCons(9418, 9418, tunnelAllowedPorts); /* Git */
176 if(proxyName)
177 return;
179 buf = get_chunk();
180 if(buf == NULL) {
181 do_log(L_ERROR, "Couldn't allocate chunk for host name.\n");
182 goto fail;
185 n = gethostname(buf, CHUNK_SIZE);
186 if(n != 0) {
187 do_log_error(L_WARN, errno, "Gethostname");
188 strcpy(buf, "polipo");
189 goto success;
191 /* gethostname doesn't necessarily NUL-terminate on overflow */
192 buf[CHUNK_SIZE - 1] = '\0';
194 if(strcmp(buf, "(none)") == 0 ||
195 strcmp(buf, "localhost") == 0 ||
196 strcmp(buf, "localhost.localdomain") == 0) {
197 do_log(L_WARN, "Couldn't determine host name -- using ``polipo''.\n");
198 strcpy(buf, "polipo");
199 goto success;
202 if(strchr(buf, '.') != NULL)
203 goto success;
205 host = gethostbyname(buf);
206 if(host == NULL) {
207 goto success;
210 if(host->h_addrtype != AF_INET)
211 goto success;
213 host = gethostbyaddr(host->h_addr_list[0], host->h_length, AF_INET);
215 if(!host || !host->h_name || strcmp(host->h_name, "localhost") == 0 ||
216 strcmp(host->h_name, "localhost.localdomain") == 0)
217 goto success;
219 namelen = strlen(host->h_name);
220 if(namelen >= CHUNK_SIZE) {
221 do_log(L_ERROR, "Host name too long.\n");
222 goto success;
225 memcpy(buf, host->h_name, namelen + 1);
227 success:
228 proxyName = internAtom(buf);
229 if(proxyName == NULL) {
230 do_log(L_ERROR, "Couldn't allocate proxy name.\n");
231 goto fail;
233 dispose_chunk(buf);
234 return;
236 fail:
237 if(buf)
238 dispose_chunk(buf);
239 exit(1);
240 return;
244 httpSetTimeout(HTTPConnectionPtr connection, int secs)
246 TimeEventHandlerPtr new;
248 if(connection->timeout)
249 cancelTimeEvent(connection->timeout);
250 connection->timeout = NULL;
252 if(secs > 0) {
253 new = scheduleTimeEvent(secs, httpTimeoutHandler,
254 sizeof(connection), &connection);
255 if(!new) {
256 do_log(L_ERROR, "Couldn't schedule timeout for connection 0x%lx\n",
257 (unsigned long)connection);
258 return -1;
260 } else {
261 new = NULL;
264 connection->timeout = new;
265 return 1;
268 int
269 httpTimeoutHandler(TimeEventHandlerPtr event)
271 HTTPConnectionPtr connection = *(HTTPConnectionPtr*)event->data;
273 if(connection->fd >= 0) {
274 int rc;
275 rc = shutdown(connection->fd, 2);
276 if(rc < 0 && errno != ENOTCONN)
277 do_log_error(L_ERROR, errno, "Timeout: shutdown failed");
278 pokeFdEvent(connection->fd, -EDOTIMEOUT, POLLIN | POLLOUT);
280 connection->timeout = NULL;
281 return 1;
285 httpWriteObjectHeaders(char *buf, int offset, int len,
286 ObjectPtr object, int from, int to)
288 int n = offset;
290 if(from <= 0 && to < 0) {
291 if(object->length >= 0) {
292 n = snnprintf(buf, n, len,
293 "\r\nContent-Length: %d", object->length);
295 } else {
296 if(to >= 0) {
297 n = snnprintf(buf, n, len,
298 "\r\nContent-Length: %d", to - from);
302 if(from > 0 || to > 0) {
303 if(object->length >= 0) {
304 if(from >= to) {
305 n = snnprintf(buf, n, len,
306 "\r\nContent-Range: bytes */%d",
307 object->length);
308 } else {
309 n = snnprintf(buf, n, len,
310 "\r\nContent-Range: bytes %d-%d/%d",
311 from, to - 1,
312 object->length);
314 } else {
315 if(to >= 0) {
316 n = snnprintf(buf, n, len,
317 "\r\nContent-Range: bytes %d-/*",
318 from);
319 } else {
320 n = snnprintf(buf, n, len,
321 "\r\nContent-Range: bytes %d-%d/*",
322 from, to);
327 if(object->etag) {
328 n = snnprintf(buf, n, len, "\r\nETag: \"%s\"", object->etag);
330 if((object->flags & OBJECT_LOCAL) || object->date >= 0) {
331 n = snnprintf(buf, n, len, "\r\nDate: ");
332 n = format_time(buf, n, len,
333 (object->flags & OBJECT_LOCAL) ?
334 current_time.tv_sec : object->date);
335 if(n < 0)
336 goto fail;
339 if(object->last_modified >= 0) {
340 n = snnprintf(buf, n, len, "\r\nLast-Modified: ");
341 n = format_time(buf, n, len, object->last_modified);
342 if(n < 0)
343 goto fail;
346 if(object->expires >= 0) {
347 n = snnprintf(buf, n, len, "\r\nExpires: ");
348 n = format_time(buf, n, len, object->expires);
349 if(n < 0)
350 goto fail;
353 n = httpPrintCacheControl(buf, n, len,
354 object->cache_control, NULL);
355 if(n < 0)
356 goto fail;
358 if(!disableVia && object->via)
359 n = snnprintf(buf, n, len, "\r\nVia: %s", object->via->string);
361 if(object->headers)
362 n = snnprint_n(buf, n, len, object->headers->string,
363 object->headers->length);
365 if(n < len)
366 return n;
367 else
368 return -1;
370 fail:
371 return -1;
374 static int
375 cachePrintSeparator(char *buf, int offset, int len,
376 int subsequent)
378 int n = offset;
379 if(subsequent)
380 n = snnprintf(buf, offset, len, ", ");
381 else
382 n = snnprintf(buf, offset, len, "\r\nCache-Control: ");
383 return n;
387 httpPrintCacheControl(char *buf, int offset, int len,
388 int flags, CacheControlPtr cache_control)
390 int n = offset;
391 int sub = 0;
393 #define PRINT_SEP() \
394 do {\
395 n = cachePrintSeparator(buf, n, len, sub); \
396 sub = 1; \
397 } while(0)
399 if(cache_control)
400 flags |= cache_control->flags;
402 if(flags & CACHE_NO) {
403 PRINT_SEP();
404 n = snnprintf(buf, n, len, "no-cache");
406 if(flags & CACHE_PUBLIC) {
407 PRINT_SEP();
408 n = snnprintf(buf, n, len, "public");
410 if(flags & CACHE_PRIVATE) {
411 PRINT_SEP();
412 n = snnprintf(buf, n, len, "private");
414 if(flags & CACHE_NO_STORE) {
415 PRINT_SEP();
416 n = snnprintf(buf, n, len, "no-store");
418 if(flags & CACHE_NO_TRANSFORM) {
419 PRINT_SEP();
420 n = snnprintf(buf, n, len, "no-transform");
422 if(flags & CACHE_MUST_REVALIDATE) {
423 PRINT_SEP();
424 n = snnprintf(buf, n, len, "must-revalidate");
426 if(flags & CACHE_PROXY_REVALIDATE) {
427 PRINT_SEP();
428 n = snnprintf(buf, n, len, "proxy-revalidate");
430 if(flags & CACHE_ONLY_IF_CACHED) {
431 PRINT_SEP();
432 n = snnprintf(buf, n, len, "only-if-cached");
434 if(cache_control) {
435 if(cache_control->max_age >= 0) {
436 PRINT_SEP();
437 n = snnprintf(buf, n, len, "max-age=%d",
438 cache_control->max_age);
440 if(cache_control->s_maxage >= 0) {
441 PRINT_SEP();
442 n = snnprintf(buf, n, len, "s-maxage=%d",
443 cache_control->s_maxage);
445 if(cache_control->min_fresh > 0) {
446 PRINT_SEP();
447 n = snnprintf(buf, n, len, "min-fresh=%d",
448 cache_control->min_fresh);
450 if(cache_control->max_stale > 0) {
451 PRINT_SEP();
452 n = snnprintf(buf, n, len, "max-stale=%d",
453 cache_control->min_fresh);
456 return n;
457 #undef PRINT_SEP
460 char *
461 httpMessage(int code)
463 switch(code) {
464 case 200:
465 return "Okay";
466 case 206:
467 return "Partial content";
468 case 300:
469 return "Multiple choices";
470 case 301:
471 return "Moved permanently";
472 case 302:
473 return "Found";
474 case 303:
475 return "See other";
476 case 304:
477 return "Not changed";
478 case 307:
479 return "Temporary redirect";
480 case 401:
481 return "Authentication Required";
482 case 403:
483 return "Forbidden";
484 case 404:
485 return "Not found";
486 case 405:
487 return "Method not allowed";
488 case 407:
489 return "Proxy authentication required";
490 default:
491 return "Unknown error code";
496 htmlString(char *buf, int n, int len, char *s, int slen)
498 int i = 0;
499 while(i < slen && n + 5 < len) {
500 switch(s[i]) {
501 case '&':
502 buf[n++] = '&'; buf[n++] = 'a'; buf[n++] = 'm'; buf[n++] = 'p';
503 buf[n++] = ';';
504 break;
505 case '<':
506 buf[n++] = '&'; buf[n++] = 'l'; buf[n++] = 't'; buf[n++] = ';';
507 break;
508 case '>':
509 buf[n++] = '&'; buf[n++] = 'g'; buf[n++] = 't'; buf[n++] = ';';
510 break;
511 case '"':
512 buf[n++] = '&'; buf[n++] = 'q'; buf[n++] = 'u'; buf[n++] = 'o';
513 buf[n++] = 't'; buf[n++] = ';';
514 break;
515 case '\0':
516 break;
517 default:
518 buf[n++] = s[i];
520 i++;
522 return n;
525 void
526 htmlPrint(FILE *out, char *s, int slen)
528 int i;
529 for(i = 0; i < slen; i++) {
530 switch(s[i]) {
531 case '&':
532 fputs("&amp;", out);
533 break;
534 case '<':
535 fputs("&lt;", out);
536 break;
537 case '>':
538 fputs("&gt;", out);
539 break;
540 default:
541 fputc(s[i], out);
546 HTTPConnectionPtr
547 httpMakeConnection()
549 HTTPConnectionPtr connection;
550 connection = malloc(sizeof(HTTPConnectionRec));
551 if(connection == NULL)
552 return NULL;
553 connection->flags = 0;
554 connection->fd = -1;
555 connection->buf = NULL;
556 connection->len = 0;
557 connection->offset = 0;
558 connection->request = NULL;
559 connection->request_last = NULL;
560 connection->serviced = 0;
561 connection->version = HTTP_UNKNOWN;
562 connection->time = current_time.tv_sec;
563 connection->timeout = NULL;
564 connection->te = TE_IDENTITY;
565 connection->reqbuf = NULL;
566 connection->reqlen = 0;
567 connection->reqbegin = 0;
568 connection->reqoffset = 0;
569 connection->bodylen = -1;
570 connection->reqte = TE_IDENTITY;
571 connection->chunk_remaining = 0;
572 connection->server = NULL;
573 connection->pipelined = 0;
574 connection->connecting = 0;
575 connection->server = NULL;
576 return connection;
579 void
580 httpDestroyConnection(HTTPConnectionPtr connection)
582 assert(connection->flags == 0);
583 httpConnectionDestroyBuf(connection);
584 assert(!connection->request);
585 assert(!connection->request_last);
586 httpConnectionDestroyReqbuf(connection);
587 assert(!connection->timeout);
588 assert(!connection->server);
589 free(connection);
592 void
593 httpConnectionDestroyBuf(HTTPConnectionPtr connection)
595 if(connection->buf) {
596 if(connection->flags & CONN_BIGBUF)
597 free(connection->buf);
598 else
599 dispose_chunk(connection->buf);
601 connection->flags &= ~CONN_BIGBUF;
602 connection->buf = NULL;
605 void
606 httpConnectionDestroyReqbuf(HTTPConnectionPtr connection)
608 if(connection->reqbuf) {
609 if(connection->flags & CONN_BIGREQBUF)
610 free(connection->reqbuf);
611 else
612 dispose_chunk(connection->reqbuf);
614 connection->flags &= ~CONN_BIGREQBUF;
615 connection->reqbuf = NULL;
618 HTTPRequestPtr
619 httpMakeRequest()
621 HTTPRequestPtr request;
622 request = malloc(sizeof(HTTPRequestRec));
623 if(request == NULL)
624 return NULL;
625 request->flags = 0;
626 request->connection = NULL;
627 request->object = NULL;
628 request->method = METHOD_UNKNOWN;
629 request->from = 0;
630 request->to = -1;
631 request->cache_control = no_cache_control;
632 request->condition = NULL;
633 request->via = NULL;
634 request->chandler = NULL;
635 request->can_mutate = NULL;
636 request->error_code = 0;
637 request->error_message = NULL;
638 request->error_headers = NULL;
639 request->headers = NULL;
640 request->time0 = null_time;
641 request->time1 = null_time;
642 request->request = NULL;
643 request->next = NULL;
644 return request;
647 void
648 httpDestroyRequest(HTTPRequestPtr request)
650 if(request->object)
651 releaseObject(request->object);
652 if(request->condition)
653 httpDestroyCondition(request->condition);
654 releaseAtom(request->via);
655 assert(request->chandler == NULL);
656 releaseAtom(request->error_message);
657 releaseAtom(request->headers);
658 releaseAtom(request->error_headers);
659 assert(request->request == NULL);
660 assert(request->next == NULL);
661 free(request);
664 void
665 httpQueueRequest(HTTPConnectionPtr connection, HTTPRequestPtr request)
667 assert(request->next == NULL && request->connection == NULL);
668 request->connection = connection;
669 if(connection->request_last) {
670 assert(connection->request);
671 connection->request_last->next = request;
672 connection->request_last = request;
673 } else {
674 assert(!connection->request_last);
675 connection->request = request;
676 connection->request_last = request;
680 HTTPRequestPtr
681 httpDequeueRequest(HTTPConnectionPtr connection)
683 HTTPRequestPtr request = connection->request;
684 if(request) {
685 assert(connection->request_last);
686 connection->request = request->next;
687 if(!connection->request) connection->request_last = NULL;
688 request->next = NULL;
690 return request;
694 httpConnectionBigify(HTTPConnectionPtr connection)
696 char *bigbuf;
697 assert(!(connection->flags & CONN_BIGBUF));
699 if(bigBufferSize <= CHUNK_SIZE)
700 return 0;
702 bigbuf = malloc(bigBufferSize);
703 if(bigbuf == NULL)
704 return -1;
705 if(connection->len > 0)
706 memcpy(bigbuf, connection->buf, connection->len);
707 if(connection->buf)
708 dispose_chunk(connection->buf);
709 connection->buf = bigbuf;
710 connection->flags |= CONN_BIGBUF;
711 return 1;
715 httpConnectionBigifyReqbuf(HTTPConnectionPtr connection)
717 char *bigbuf;
718 assert(!(connection->flags & CONN_BIGREQBUF));
720 if(bigBufferSize <= CHUNK_SIZE)
721 return 0;
723 bigbuf = malloc(bigBufferSize);
724 if(bigbuf == NULL)
725 return -1;
726 if(connection->reqlen > 0)
727 memcpy(bigbuf, connection->reqbuf, connection->reqlen);
728 if(connection->reqbuf)
729 dispose_chunk(connection->reqbuf);
730 connection->reqbuf = bigbuf;
731 connection->flags |= CONN_BIGREQBUF;
732 return 1;
736 httpConnectionUnbigify(HTTPConnectionPtr connection)
738 char *buf;
739 assert(connection->flags & CONN_BIGBUF);
740 assert(connection->len < CHUNK_SIZE);
742 buf = get_chunk();
743 if(buf == NULL)
744 return -1;
745 if(connection->len > 0)
746 memcpy(buf, connection->buf, connection->len);
747 free(connection->buf);
748 connection->buf = buf;
749 connection->flags &= ~CONN_BIGBUF;
750 return 1;
754 httpConnectionUnbigifyReqbuf(HTTPConnectionPtr connection)
756 char *buf;
757 assert(connection->flags & CONN_BIGREQBUF);
758 assert(connection->reqlen < CHUNK_SIZE);
760 buf = get_chunk();
761 if(buf == NULL)
762 return -1;
763 if(connection->reqlen > 0)
764 memcpy(buf, connection->reqbuf, connection->reqlen);
765 free(connection->reqbuf);
766 connection->reqbuf = buf;
767 connection->flags &= ~CONN_BIGREQBUF;
768 return 1;
771 HTTPConditionPtr
772 httpMakeCondition()
774 HTTPConditionPtr condition;
775 condition = malloc(sizeof(HTTPConditionRec));
776 if(condition == NULL)
777 return NULL;
778 condition->ims = -1;
779 condition->inms = -1;
780 condition->im = NULL;
781 condition->inm = NULL;
782 condition->ifrange = NULL;
783 return condition;
786 void
787 httpDestroyCondition(HTTPConditionPtr condition)
789 if(condition->inm)
790 free(condition->inm);
791 if(condition->im)
792 free(condition->im);
793 if(condition->ifrange)
794 free(condition->ifrange);
795 free(condition);
799 httpCondition(ObjectPtr object, HTTPConditionPtr condition)
801 int rc = CONDITION_MATCH;
803 assert(!(object->flags & OBJECT_INITIAL));
805 if(!condition) return CONDITION_MATCH;
807 if(condition->ims >= 0) {
808 if(object->last_modified < 0 ||
809 condition->ims < object->last_modified)
810 return rc;
811 else
812 rc = CONDITION_NOT_MODIFIED;
815 if(condition->inms >= 0) {
816 if(object->last_modified < 0 ||
817 condition->inms >= object->last_modified)
818 return rc;
819 else
820 rc = CONDITION_FAILED;
823 if(condition->inm) {
824 if(!object->etag || strcmp(object->etag, condition->inm) != 0)
825 return rc;
826 else
827 rc = CONDITION_NOT_MODIFIED;
830 if(condition->im) {
831 if(!object->etag || strcmp(object->etag, condition->im) != 0)
832 rc = CONDITION_FAILED;
833 else
834 return rc;
837 return rc;
841 httpWriteErrorHeaders(char *buf, int size, int offset, int do_body,
842 int code, AtomPtr message, int close, AtomPtr headers,
843 char *url, int url_len, char *etag)
845 int n, m, i;
846 char *body;
847 char htmlMessage[100];
848 char timeStr[100];
850 assert(code != 0);
852 i = htmlString(htmlMessage, 0, 100, message->string, message->length);
853 if(i < 0)
854 strcpy(htmlMessage, "(Couldn't format message)");
855 else
856 htmlMessage[MIN(i, 99)] = '\0';
858 if(code != 304) {
859 body = get_chunk();
860 if(!body) {
861 do_log(L_ERROR, "Couldn't allocate body buffer.\n");
862 return -1;
864 m = snnprintf(body, 0, CHUNK_SIZE,
865 "<!DOCTYPE HTML PUBLIC "
866 "\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
867 "\"http://www.w3.org/TR/html4/loose.dtd\">"
868 "\n<html><head>"
869 "\n<title>Proxy %s: %3d %s.</title>"
870 "\n</head><body>"
871 "\n<h1>%3d %s</h1>"
872 "\n<p>The following %s",
873 code >= 400 ? "error" : "result",
874 code, htmlMessage,
875 code, htmlMessage,
876 code >= 400 ?
877 "error occurred" :
878 "status was returned");
879 if(url_len > 0) {
880 m = snnprintf(body, m, CHUNK_SIZE,
881 " while trying to access <strong>");
882 m = htmlString(body, m, CHUNK_SIZE, url, url_len);
883 m = snnprintf(body, m, CHUNK_SIZE, "</strong>");
887 /* On BSD systems, tv_sec is a long. */
888 const time_t ct = current_time.tv_sec;
889 /*Mon, 24 Sep 2004 17:46:35 GMT*/
890 strftime(timeStr, sizeof(timeStr), "%a, %d %b %Y %H:%M:%S %Z",
891 localtime(&ct));
894 m = snnprintf(body, m, CHUNK_SIZE,
895 ":<br><br>"
896 "\n<strong>%3d %s</strong></p>"
897 "\n<hr>Generated %s by Polipo on <em>%s:%d</em>."
898 "\n</body></html>\r\n",
899 code, htmlMessage,
900 timeStr, proxyName->string, proxyPort);
901 if(m <= 0 || m >= CHUNK_SIZE) {
902 do_log(L_ERROR, "Couldn't write error body.\n");
903 dispose_chunk(body);
904 return -1;
906 } else {
907 body = NULL;
908 m = 0;
911 n = snnprintf(buf, 0, size,
912 "HTTP/1.1 %3d %s"
913 "\r\nConnection: %s"
914 "\r\nDate: ",
915 code, atomString(message),
916 close ? "close" : "keep-alive");
917 n = format_time(buf, n, size, current_time.tv_sec);
918 if(code != 304) {
919 n = snnprintf(buf, n, size,
920 "\r\nContent-Type: text/html"
921 "\r\nContent-Length: %d", m);
922 } else {
923 if(etag)
924 n = snnprintf(buf, n, size, "\r\nETag: \"%s\"", etag);
927 if(code != 304 && code != 412) {
928 n = snnprintf(buf, n, size,
929 "\r\nExpires: 0"
930 "\r\nCache-Control: no-cache"
931 "\r\nPragma: no-cache");
934 if(headers)
935 n = snnprint_n(buf, n, size,
936 headers->string, headers->length);
938 n = snnprintf(buf, n, size, "\r\n\r\n");
940 if(n < 0 || n >= size) {
941 do_log(L_ERROR, "Couldn't write error.\n");
942 dispose_chunk(body);
943 return -1;
946 if(code != 304 && do_body) {
947 if(m > 0) memcpy(buf + n, body, m);
948 n += m;
951 if(body)
952 dispose_chunk(body);
954 return n;
957 AtomListPtr
958 urlDecode(char *buf, int n)
960 char mybuf[500];
961 int i, j = 0;
962 AtomListPtr list;
963 AtomPtr atom;
965 list = makeAtomList(NULL, 0);
966 if(list == NULL)
967 return NULL;
969 i = 0;
970 while(i < n) {
971 if(buf[i] == '%') {
972 int a, b;
973 if(i + 3 > n)
974 goto fail;
975 a = h2i(buf[i + 1]);
976 b = h2i(buf[i + 2]);
977 if(a < 0 || b < 0)
978 goto fail;
979 mybuf[j++] = (char)((a << 4) | b);
980 i += 3;
981 if(j > 500) goto fail;
982 } else if(buf[i] == '&') {
983 atom = internAtomN(mybuf, j);
984 if(atom == NULL)
985 goto fail;
986 atomListCons(atom, list);
987 j = 0;
988 i++;
989 } else {
990 mybuf[j++] = buf[i++];
991 if(j > 500) goto fail;
995 atom = internAtomN(mybuf, j);
996 if(atom == NULL)
997 goto fail;
998 atomListCons(atom, list);
999 return list;
1001 fail:
1002 destroyAtomList(list);
1003 return NULL;
1006 void
1007 httpTweakCachability(ObjectPtr object)
1009 int code = object->code;
1011 if((object->cache_control & CACHE_AUTHORIZATION) &&
1012 !(object->cache_control & CACHE_PUBLIC))
1013 object->cache_control |= (CACHE_NO_HIDDEN | OBJECT_LINEAR);
1015 /* This is not required by RFC 2616 -- but see RFC 3143 2.1.1. We
1016 manically avoid caching replies that we don't know how to
1017 handle, even if Expires or Cache-Control says otherwise. As to
1018 known uncacheable replies, we obey Cache-Control and default to
1019 allowing sharing but not caching. */
1020 if(code != 200 && code != 206 &&
1021 code != 300 && code != 301 && code != 302 && code != 303 &&
1022 code != 304 && code != 307 &&
1023 code != 403 && code != 404 && code != 405 && code != 416) {
1024 object->cache_control |=
1025 (CACHE_NO_HIDDEN | CACHE_MISMATCH | OBJECT_LINEAR);
1026 } else if(code != 200 && code != 206 &&
1027 code != 300 && code != 301 && code != 304 &&
1028 code != 410) {
1029 if(object->expires < 0 && !(object->cache_control & CACHE_PUBLIC)) {
1030 object->cache_control |= CACHE_NO_HIDDEN;
1032 } else if(dontCacheRedirects && (code == 301 || code == 302)) {
1033 object->cache_control |= CACHE_NO_HIDDEN;
1036 if(urlIsUncachable(object->key, object->key_size)) {
1037 object->cache_control |= CACHE_NO_HIDDEN;
1040 if((object->cache_control & CACHE_NO_STORE) != 0) {
1041 object->cache_control |= CACHE_NO_HIDDEN;
1044 if(object->cache_control & CACHE_VARY) {
1045 if(!object->etag || dontTrustVaryETag >= 2) {
1046 object->cache_control |= CACHE_MISMATCH;
1052 httpHeaderMatch(AtomPtr header, AtomPtr headers1, AtomPtr headers2)
1054 int rc1, b1, e1, rc2, b2, e2;
1056 /* Short cut if both sets of headers are identical */
1057 if(headers1 == headers2)
1058 return 1;
1060 rc1 = httpFindHeader(header, headers1->string, headers1->length,
1061 &b1, &e1);
1062 rc2 = httpFindHeader(header, headers2->string, headers2->length,
1063 &b2, &e2);
1065 if(rc1 == 0 && rc2 == 0)
1066 return 1;
1068 if(rc1 == 0 || rc2 == 0)
1069 return 0;
1071 if(e1 - b1 != e2 - b2)
1072 return 0;
1074 if(memcmp(headers1->string + b1, headers2->string + b2, e1 - b1) != 0)
1075 return 0;
1077 return 1;