Fix slightly incorrect parsing of requests.
[polipo.git] / http.c
blobcbcf5d444e32f176bf3628fd9ffc8255df82e8ca
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;
32 int bigBufferSize = (32 * 1024);
34 AtomPtr authRealm = NULL;
35 AtomPtr authCredentials = NULL;
37 AtomPtr parentAuthCredentials = NULL;
39 AtomListPtr allowedClients = NULL;
40 NetAddressPtr allowedNets = NULL;
42 IntListPtr allowedPorts = NULL;
43 IntListPtr tunnelAllowedPorts = NULL;
44 int expectContinue = 1;
45 int dontTrustVaryETag = 1;
47 AtomPtr atom100Continue;
49 int disableVia = 1;
51 /* 0 means that all failures lead to errors. 1 means that failures to
52 connect are reported in a Warning header when stale objects are
53 served. 2 means that only missing data is fetched from the net,
54 stale data is served without revalidation (browser-side
55 Cache-Control directives are still honoured). 3 means that no
56 connections are ever attempted. */
58 int proxyOffline = 0;
59 int relaxTransparency = 0;
60 AtomPtr proxyAddress = NULL;
62 static int timeoutSetter(ConfigVariablePtr var, void *value);
64 void
65 preinitHttp()
67 proxyAddress = internAtom("127.0.0.1");
68 CONFIG_VARIABLE_SETTABLE(disableProxy, CONFIG_BOOLEAN, configIntSetter,
69 "Whether to be a web server only.");
70 CONFIG_VARIABLE_SETTABLE(proxyOffline, CONFIG_BOOLEAN, configIntSetter,
71 "Avoid contacting remote servers.");
72 CONFIG_VARIABLE_SETTABLE(relaxTransparency, CONFIG_TRISTATE,
73 configIntSetter,
74 "Avoid contacting remote servers.");
75 CONFIG_VARIABLE(proxyPort, CONFIG_INT,
76 "The TCP port on which the proxy listens.");
77 CONFIG_VARIABLE(proxyAddress, CONFIG_ATOM_LOWER,
78 "The IP address on which the proxy listens.");
79 CONFIG_VARIABLE_SETTABLE(proxyName, CONFIG_ATOM_LOWER, configAtomSetter,
80 "The name by which the proxy is known.");
81 CONFIG_VARIABLE_SETTABLE(clientTimeout, CONFIG_TIME,
82 timeoutSetter, "Client-side timeout.");
83 CONFIG_VARIABLE_SETTABLE(serverTimeout, CONFIG_TIME,
84 timeoutSetter, "Server-side timeout.");
85 CONFIG_VARIABLE(authRealm, CONFIG_ATOM,
86 "Authentication realm.");
87 CONFIG_VARIABLE(authCredentials, CONFIG_PASSWORD,
88 "username:password.");
89 CONFIG_VARIABLE(parentAuthCredentials, CONFIG_PASSWORD,
90 "username:password.");
91 CONFIG_VARIABLE(allowedClients, CONFIG_ATOM_LIST_LOWER,
92 "Networks from which clients are allowed to connect.");
93 CONFIG_VARIABLE(tunnelAllowedPorts, CONFIG_INT_LIST,
94 "Ports to which tunnelled connections are allowed.");
95 CONFIG_VARIABLE(allowedPorts, CONFIG_INT_LIST,
96 "Ports to which connections are allowed.");
97 CONFIG_VARIABLE(expectContinue, CONFIG_TRISTATE,
98 "Send Expect-Continue to servers.");
99 CONFIG_VARIABLE(bigBufferSize, CONFIG_INT,
100 "Size of big buffers (max size of headers).");
101 CONFIG_VARIABLE_SETTABLE(disableVia, CONFIG_BOOLEAN, configIntSetter,
102 "Don't use Via headers.");
103 CONFIG_VARIABLE(dontTrustVaryETag, CONFIG_TRISTATE,
104 "Whether to trust the ETag when there's Vary.");
105 preinitHttpParser();
108 static int
109 timeoutSetter(ConfigVariablePtr var, void *value)
111 configIntSetter(var, value);
112 if(clientTimeout <= serverTimeout)
113 clientTimeout = serverTimeout + 1;
114 return 1;
117 void
118 initHttp()
120 char *buf = NULL;
121 int namelen;
122 int n;
123 struct hostent *host;
125 initHttpParser();
127 atom100Continue = internAtom("100-continue");
129 if(clientTimeout <= serverTimeout) {
130 clientTimeout = serverTimeout + 1;
131 do_log(L_WARN, "Value of clientTimeout too small -- setting to %d.\n",
132 clientTimeout);
135 if(authCredentials != NULL && authRealm == NULL)
136 authRealm = internAtom("Polipo");
138 if(allowedClients) {
139 allowedNets = parseNetAddress(allowedClients);
140 if(allowedNets == NULL)
141 exit(1);
144 if(allowedPorts == NULL) {
145 allowedPorts = makeIntList(0);
146 if(allowedPorts == NULL) {
147 do_log(L_ERROR, "Couldn't allocate allowedPorts.\n");
148 exit(1);
150 intListCons(80, 100, allowedPorts);
151 intListCons(1024, 0xFFFF, allowedPorts);
154 if(tunnelAllowedPorts == NULL) {
155 tunnelAllowedPorts = makeIntList(0);
156 if(tunnelAllowedPorts == NULL) {
157 do_log(L_ERROR, "Couldn't allocate tunnelAllowedPorts.\n");
158 exit(1);
160 intListCons(22, 22, tunnelAllowedPorts); /* ssh */
161 intListCons(80, 80, tunnelAllowedPorts); /* HTTP */
162 intListCons(109, 110, tunnelAllowedPorts); /* POP 2 and 3*/
163 intListCons(143, 143, tunnelAllowedPorts); /* IMAP 2/4 */
164 intListCons(443, 443, tunnelAllowedPorts); /* HTTP/SSL */
165 intListCons(873, 873, tunnelAllowedPorts); /* rsync */
166 intListCons(993, 993, tunnelAllowedPorts); /* IMAP/SSL */
167 intListCons(995, 995, tunnelAllowedPorts); /* POP/SSL */
170 if(proxyName)
171 return;
173 buf = get_chunk();
174 if(buf == NULL) {
175 do_log(L_ERROR, "Couldn't allocate chunk for host name.\n");
176 goto fail;
179 n = gethostname(buf, CHUNK_SIZE);
180 if(n != 0) {
181 do_log_error(L_WARN, errno, "Gethostname");
182 strcpy(buf, "polipo");
183 goto success;
185 /* gethostname doesn't necessarily NUL-terminate on overflow */
186 buf[CHUNK_SIZE - 1] = '\0';
188 if(strcmp(buf, "(none)") == 0 ||
189 strcmp(buf, "localhost") == 0 ||
190 strcmp(buf, "localhost.localdomain") == 0) {
191 do_log(L_WARN, "Couldn't determine host name -- using ``polipo''.\n");
192 strcpy(buf, "polipo");
193 goto success;
196 if(strchr(buf, '.') != NULL)
197 goto success;
199 host = gethostbyname(buf);
200 if(host == NULL) {
201 goto success;
204 if(host->h_addrtype != AF_INET)
205 goto success;
207 host = gethostbyaddr(host->h_addr_list[0], host->h_length, AF_INET);
209 if(!host || !host->h_name || strcmp(host->h_name, "localhost") == 0 ||
210 strcmp(host->h_name, "localhost.localdomain") == 0)
211 goto success;
213 namelen = strlen(host->h_name);
214 if(namelen >= CHUNK_SIZE) {
215 do_log(L_ERROR, "Host name too long.\n");
216 goto success;
219 memcpy(buf, host->h_name, namelen + 1);
221 success:
222 proxyName = internAtom(buf);
223 if(proxyName == NULL) {
224 do_log(L_ERROR, "Couldn't allocate proxy name.\n");
225 goto fail;
227 dispose_chunk(buf);
228 return;
230 fail:
231 if(buf)
232 dispose_chunk(buf);
233 exit(1);
234 return;
238 httpSetTimeout(HTTPConnectionPtr connection, int secs)
240 TimeEventHandlerPtr new;
242 if(connection->timeout)
243 cancelTimeEvent(connection->timeout);
244 connection->timeout = NULL;
246 if(secs > 0) {
247 new = scheduleTimeEvent(secs, httpTimeoutHandler,
248 sizeof(connection), &connection);
249 if(!new) {
250 do_log(L_ERROR, "Couldn't schedule timeout for connection 0x%lx\n",
251 (unsigned long)connection);
252 return -1;
254 } else {
255 new = NULL;
258 connection->timeout = new;
259 return 1;
262 int
263 httpTimeoutHandler(TimeEventHandlerPtr event)
265 HTTPConnectionPtr connection = *(HTTPConnectionPtr*)event->data;
267 if(connection->fd >= 0) {
268 int rc;
269 rc = shutdown(connection->fd, 2);
270 if(rc < 0 && errno != ENOTCONN)
271 do_log_error(L_ERROR, errno, "Timeout: shutdown failed");
272 pokeFdEvent(connection->fd, -EDOTIMEOUT, POLLIN | POLLOUT);
274 connection->timeout = NULL;
275 return 1;
279 httpWriteObjectHeaders(char *buf, int offset, int len,
280 ObjectPtr object, int from, int to)
282 int n = offset;
284 if(from <= 0 && to < 0) {
285 if(object->length >= 0) {
286 n = snnprintf(buf, n, len,
287 "\r\nContent-Length: %d", object->length);
289 } else {
290 if(to >= 0) {
291 n = snnprintf(buf, n, len,
292 "\r\nContent-Length: %d", to - from);
296 if(from > 0 || to > 0) {
297 if(object->length >= 0) {
298 if(from >= to) {
299 n = snnprintf(buf, n, len,
300 "\r\nContent-Range: bytes */%d",
301 object->length);
302 } else {
303 n = snnprintf(buf, n, len,
304 "\r\nContent-Range: bytes %d-%d/%d",
305 from, to - 1,
306 object->length);
308 } else {
309 if(to >= 0) {
310 n = snnprintf(buf, n, len,
311 "\r\nContent-Range: bytes %d-/*",
312 from);
313 } else {
314 n = snnprintf(buf, n, len,
315 "\r\nContent-Range: bytes %d-%d/*",
316 from, to);
321 if(object->etag) {
322 n = snnprintf(buf, n, len, "\r\nETag: \"%s\"", object->etag);
324 if((object->flags & OBJECT_LOCAL) || object->date >= 0) {
325 n = snnprintf(buf, n, len, "\r\nDate: ");
326 n = format_time(buf, n, len,
327 (object->flags & OBJECT_LOCAL) ?
328 current_time.tv_sec : object->date);
329 if(n < 0)
330 goto fail;
333 if(object->last_modified >= 0) {
334 n = snnprintf(buf, n, len, "\r\nLast-Modified: ");
335 n = format_time(buf, n, len, object->last_modified);
336 if(n < 0)
337 goto fail;
340 if(object->expires >= 0) {
341 n = snnprintf(buf, n, len, "\r\nExpires: ");
342 n = format_time(buf, n, len, object->expires);
343 if(n < 0)
344 goto fail;
347 n = httpPrintCacheControl(buf, n, len,
348 object->cache_control, NULL);
349 if(n < 0)
350 goto fail;
352 if(!disableVia && object->via)
353 n = snnprintf(buf, n, len, "\r\nVia: %s", object->via->string);
355 if(object->headers)
356 n = snnprint_n(buf, n, len, object->headers->string,
357 object->headers->length);
359 if(n < len)
360 return n;
361 else
362 return -1;
364 fail:
365 return -1;
368 static int
369 cachePrintSeparator(char *buf, int offset, int len,
370 int subsequent)
372 int n = offset;
373 if(subsequent)
374 n = snnprintf(buf, offset, len, ", ");
375 else
376 n = snnprintf(buf, offset, len, "\r\nCache-Control: ");
377 return n;
381 httpPrintCacheControl(char *buf, int offset, int len,
382 int flags, CacheControlPtr cache_control)
384 int n = offset;
385 int sub = 0;
387 #define PRINT_SEP() \
388 do {\
389 n = cachePrintSeparator(buf, n, len, sub); \
390 sub = 1; \
391 } while(0)
393 if(cache_control)
394 flags |= cache_control->flags;
396 if(flags & CACHE_NO) {
397 PRINT_SEP();
398 n = snnprintf(buf, n, len, "no-cache");
400 if(flags & CACHE_PUBLIC) {
401 PRINT_SEP();
402 n = snnprintf(buf, n, len, "public");
404 if(flags & CACHE_PRIVATE) {
405 PRINT_SEP();
406 n = snnprintf(buf, n, len, "private");
408 if(flags & CACHE_NO_STORE) {
409 PRINT_SEP();
410 n = snnprintf(buf, n, len, "no-store");
412 if(flags & CACHE_NO_TRANSFORM) {
413 PRINT_SEP();
414 n = snnprintf(buf, n, len, "no-transform");
416 if(flags & CACHE_MUST_REVALIDATE) {
417 PRINT_SEP();
418 n = snnprintf(buf, n, len, "must-revalidate");
420 if(flags & CACHE_PROXY_REVALIDATE) {
421 PRINT_SEP();
422 n = snnprintf(buf, n, len, "proxy-revalidate");
424 if(flags & CACHE_ONLY_IF_CACHED) {
425 PRINT_SEP();
426 n = snnprintf(buf, n, len, "only-if-cached");
428 if(cache_control) {
429 if(cache_control->max_age >= 0) {
430 PRINT_SEP();
431 n = snnprintf(buf, n, len, "max-age=%d",
432 cache_control->max_age);
434 if(cache_control->s_maxage >= 0) {
435 PRINT_SEP();
436 n = snnprintf(buf, n, len, "s-maxage=%d",
437 cache_control->s_maxage);
439 if(cache_control->min_fresh > 0) {
440 PRINT_SEP();
441 n = snnprintf(buf, n, len, "min-fresh=%d",
442 cache_control->min_fresh);
444 if(cache_control->max_stale > 0) {
445 PRINT_SEP();
446 n = snnprintf(buf, n, len, "max-stale=%d",
447 cache_control->min_fresh);
450 return n;
451 #undef PRINT_SEP
454 char *
455 httpMessage(int code)
457 switch(code) {
458 case 200:
459 return "Okay";
460 case 206:
461 return "Partial content";
462 case 300:
463 return "Multiple choices";
464 case 301:
465 return "Moved permanently";
466 case 302:
467 return "Found";
468 case 303:
469 return "See other";
470 case 304:
471 return "Not changed";
472 case 307:
473 return "Temporary redirect";
474 case 401:
475 return "Authentication Required";
476 case 403:
477 return "Forbidden";
478 case 404:
479 return "Not found";
480 case 405:
481 return "Method not allowed";
482 case 407:
483 return "Proxy authentication required";
484 default:
485 return "Unknown error code";
490 htmlString(char *buf, int n, int len, char *s, int slen)
492 int i = 0;
493 while(i < slen && n + 5 < len) {
494 switch(s[i]) {
495 case '&':
496 buf[n++] = '&'; buf[n++] = 'a'; buf[n++] = 'm'; buf[n++] = 'p';
497 buf[n++] = ';';
498 break;
499 case '<':
500 buf[n++] = '&'; buf[n++] = 'l'; buf[n++] = 't'; buf[n++] = ';';
501 break;
502 case '>':
503 buf[n++] = '&'; buf[n++] = 'g'; buf[n++] = 't'; buf[n++] = ';';
504 break;
505 case '"':
506 buf[n++] = '&'; buf[n++] = 'q'; buf[n++] = 'u'; buf[n++] = 'o';
507 buf[n++] = 't'; buf[n++] = ';';
508 break;
509 case '\0':
510 break;
511 default:
512 buf[n++] = s[i];
514 i++;
516 return n;
519 void
520 htmlPrint(FILE *out, char *s, int slen)
522 int i;
523 for(i = 0; i < slen; i++) {
524 switch(s[i]) {
525 case '&':
526 fputs("&amp;", out);
527 break;
528 case '<':
529 fputs("&lt;", out);
530 break;
531 case '>':
532 fputs("&gt;", out);
533 break;
534 default:
535 fputc(s[i], out);
540 HTTPConnectionPtr
541 httpMakeConnection()
543 HTTPConnectionPtr connection;
544 connection = malloc(sizeof(HTTPConnectionRec));
545 if(connection == NULL)
546 return NULL;
547 connection->flags = 0;
548 connection->fd = -1;
549 connection->buf = NULL;
550 connection->len = 0;
551 connection->offset = 0;
552 connection->request = NULL;
553 connection->request_last = NULL;
554 connection->serviced = 0;
555 connection->version = HTTP_UNKNOWN;
556 connection->timeout = NULL;
557 connection->te = TE_IDENTITY;
558 connection->reqbuf = NULL;
559 connection->reqlen = 0;
560 connection->reqbegin = 0;
561 connection->reqoffset = 0;
562 connection->bodylen = -1;
563 connection->reqte = TE_IDENTITY;
564 connection->chunk_remaining = 0;
565 connection->server = NULL;
566 connection->pipelined = 0;
567 connection->connecting = 0;
568 connection->server = NULL;
569 return connection;
572 void
573 httpDestroyConnection(HTTPConnectionPtr connection)
575 assert(connection->flags == 0);
576 httpConnectionDestroyBuf(connection);
577 assert(!connection->request);
578 assert(!connection->request_last);
579 httpConnectionDestroyReqbuf(connection);
580 assert(!connection->timeout);
581 assert(!connection->server);
582 free(connection);
585 void
586 httpConnectionDestroyBuf(HTTPConnectionPtr connection)
588 if(connection->buf) {
589 if(connection->flags & CONN_BIGBUF)
590 free(connection->buf);
591 else
592 dispose_chunk(connection->buf);
594 connection->flags &= ~CONN_BIGBUF;
595 connection->buf = NULL;
598 void
599 httpConnectionDestroyReqbuf(HTTPConnectionPtr connection)
601 if(connection->reqbuf) {
602 if(connection->flags & CONN_BIGREQBUF)
603 free(connection->reqbuf);
604 else
605 dispose_chunk(connection->reqbuf);
607 connection->flags &= ~CONN_BIGREQBUF;
608 connection->reqbuf = NULL;
611 HTTPRequestPtr
612 httpMakeRequest()
614 HTTPRequestPtr request;
615 request = malloc(sizeof(HTTPRequestRec));
616 if(request == NULL)
617 return NULL;
618 request->flags = 0;
619 request->connection = NULL;
620 request->object = NULL;
621 request->method = METHOD_UNKNOWN;
622 request->from = 0;
623 request->to = -1;
624 request->cache_control = no_cache_control;
625 request->condition = NULL;
626 request->via = NULL;
627 request->chandler = NULL;
628 request->can_mutate = NULL;
629 request->error_code = 0;
630 request->error_message = NULL;
631 request->error_headers = NULL;
632 request->headers = NULL;
633 request->time0 = null_time;
634 request->time1 = null_time;
635 request->request = NULL;
636 request->next = NULL;
637 return request;
640 void
641 httpDestroyRequest(HTTPRequestPtr request)
643 if(request->object)
644 releaseObject(request->object);
645 if(request->condition)
646 httpDestroyCondition(request->condition);
647 releaseAtom(request->via);
648 assert(request->chandler == NULL);
649 releaseAtom(request->error_message);
650 releaseAtom(request->headers);
651 releaseAtom(request->error_headers);
652 assert(request->request == NULL);
653 assert(request->next == NULL);
654 free(request);
657 void
658 httpQueueRequest(HTTPConnectionPtr connection, HTTPRequestPtr request)
660 assert(request->next == NULL && request->connection == NULL);
661 request->connection = connection;
662 if(connection->request_last) {
663 assert(connection->request);
664 connection->request_last->next = request;
665 connection->request_last = request;
666 } else {
667 assert(!connection->request_last);
668 connection->request = request;
669 connection->request_last = request;
673 HTTPRequestPtr
674 httpDequeueRequest(HTTPConnectionPtr connection)
676 HTTPRequestPtr request = connection->request;
677 if(request) {
678 assert(connection->request_last);
679 connection->request = request->next;
680 if(!connection->request) connection->request_last = NULL;
681 request->next = NULL;
683 return request;
687 httpConnectionBigify(HTTPConnectionPtr connection)
689 char *bigbuf;
690 assert(!(connection->flags & CONN_BIGBUF));
692 if(bigBufferSize <= CHUNK_SIZE)
693 return 0;
695 bigbuf = malloc(bigBufferSize);
696 if(bigbuf == NULL)
697 return -1;
698 if(connection->len > 0)
699 memcpy(bigbuf, connection->buf, connection->len);
700 if(connection->buf)
701 dispose_chunk(connection->buf);
702 connection->buf = bigbuf;
703 connection->flags |= CONN_BIGBUF;
704 return 1;
708 httpConnectionBigifyReqbuf(HTTPConnectionPtr connection)
710 char *bigbuf;
711 assert(!(connection->flags & CONN_BIGREQBUF));
713 if(bigBufferSize <= CHUNK_SIZE)
714 return 0;
716 bigbuf = malloc(bigBufferSize);
717 if(bigbuf == NULL)
718 return -1;
719 if(connection->reqlen > 0)
720 memcpy(bigbuf, connection->reqbuf, connection->reqlen);
721 if(connection->reqbuf)
722 dispose_chunk(connection->reqbuf);
723 connection->reqbuf = bigbuf;
724 connection->flags |= CONN_BIGREQBUF;
725 return 1;
729 httpConnectionUnbigify(HTTPConnectionPtr connection)
731 char *buf;
732 assert(connection->flags & CONN_BIGBUF);
733 assert(connection->len < CHUNK_SIZE);
735 buf = get_chunk();
736 if(buf == NULL)
737 return -1;
738 if(connection->len > 0)
739 memcpy(buf, connection->buf, connection->len);
740 free(connection->buf);
741 connection->buf = buf;
742 connection->flags &= ~CONN_BIGBUF;
743 return 1;
747 httpConnectionUnbigifyReqbuf(HTTPConnectionPtr connection)
749 char *buf;
750 assert(connection->flags & CONN_BIGREQBUF);
751 assert(connection->reqlen < CHUNK_SIZE);
753 buf = get_chunk();
754 if(buf == NULL)
755 return -1;
756 if(connection->reqlen > 0)
757 memcpy(buf, connection->reqbuf, connection->reqlen);
758 free(connection->reqbuf);
759 connection->reqbuf = buf;
760 connection->flags &= ~CONN_BIGREQBUF;
761 return 1;
764 HTTPConditionPtr
765 httpMakeCondition()
767 HTTPConditionPtr condition;
768 condition = malloc(sizeof(HTTPConditionRec));
769 if(condition == NULL)
770 return NULL;
771 condition->ims = -1;
772 condition->inms = -1;
773 condition->im = NULL;
774 condition->inm = NULL;
775 condition->ifrange = NULL;
776 return condition;
779 void
780 httpDestroyCondition(HTTPConditionPtr condition)
782 if(condition->inm)
783 free(condition->inm);
784 if(condition->im)
785 free(condition->im);
786 if(condition->ifrange)
787 free(condition->ifrange);
788 free(condition);
792 httpCondition(ObjectPtr object, HTTPConditionPtr condition)
794 int rc = CONDITION_MATCH;
796 assert(!(object->flags & OBJECT_INITIAL));
798 if(!condition) return CONDITION_MATCH;
800 if(condition->ims >= 0) {
801 if(object->last_modified < 0 ||
802 condition->ims < object->last_modified)
803 return rc;
804 else
805 rc = CONDITION_NOT_MODIFIED;
808 if(condition->inms >= 0) {
809 if(object->last_modified < 0 ||
810 condition->inms >= object->last_modified)
811 return rc;
812 else
813 rc = CONDITION_FAILED;
816 if(condition->inm) {
817 if(!object->etag || strcmp(object->etag, condition->inm) != 0)
818 return rc;
819 else
820 rc = CONDITION_NOT_MODIFIED;
823 if(condition->im) {
824 if(!object->etag || strcmp(object->etag, condition->im) != 0)
825 rc = CONDITION_FAILED;
826 else
827 return rc;
830 return rc;
834 httpWriteErrorHeaders(char *buf, int size, int offset, int do_body,
835 int code, AtomPtr message, int close, AtomPtr headers,
836 char *url, int url_len, char *etag)
838 int n, m, i;
839 char *body;
840 char htmlMessage[100];
841 char timeStr[100];
843 assert(code != 0);
845 i = htmlString(htmlMessage, 0, 100, message->string, message->length);
846 if(i < 0)
847 strcpy(htmlMessage, "(Couldn't format message)");
848 else
849 htmlMessage[MIN(i, 99)] = '\0';
851 if(code != 304) {
852 body = get_chunk();
853 if(!body) {
854 do_log(L_ERROR, "Couldn't allocate body buffer.\n");
855 return -1;
857 m = snnprintf(body, 0, CHUNK_SIZE,
858 "<!DOCTYPE HTML PUBLIC "
859 "\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
860 "\"http://www.w3.org/TR/html4/loose.dtd\">"
861 "\n<html><head>"
862 "\n<title>Proxy %s: %3d %s.</title>"
863 "\n</head><body>"
864 "\n<h1>%3d %s</h1>"
865 "\n<p>The following %s",
866 code >= 400 ? "error" : "result",
867 code, htmlMessage,
868 code, htmlMessage,
869 code >= 400 ?
870 "error occurred" :
871 "status was returned");
872 if(url_len > 0) {
873 m = snnprintf(body, m, CHUNK_SIZE,
874 " while trying to access <strong>");
875 m = htmlString(body, m, CHUNK_SIZE, url, url_len);
876 m = snnprintf(body, m, CHUNK_SIZE, "</strong>");
880 /* On BSD systems, tv_sec is a long. */
881 const time_t ct = current_time.tv_sec;
882 /*Mon, 24 Sep 2004 17:46:35 GMT*/
883 strftime(timeStr, sizeof(timeStr), "%a, %d %b %Y %H:%M:%S %Z",
884 localtime(&ct));
887 m = snnprintf(body, m, CHUNK_SIZE,
888 ":<br><br>"
889 "\n<strong>%3d %s</strong></p>"
890 "\n<hr>Generated %s by Polipo on <em>%s:%d</em>."
891 "\n</body></html>\r\n",
892 code, htmlMessage,
893 timeStr, proxyName->string, proxyPort);
894 if(m <= 0 || m >= CHUNK_SIZE) {
895 do_log(L_ERROR, "Couldn't write error body.\n");
896 dispose_chunk(body);
897 return -1;
899 } else {
900 body = NULL;
901 m = 0;
904 n = snnprintf(buf, 0, size,
905 "HTTP/1.1 %3d %s"
906 "\r\nConnection: %s"
907 "\r\nDate: ",
908 code, atomString(message),
909 close ? "close" : "keep-alive");
910 n = format_time(buf, n, size, current_time.tv_sec);
911 if(code != 304) {
912 n = snnprintf(buf, n, size,
913 "\r\nContent-Type: text/html"
914 "\r\nContent-Length: %d", m);
915 } else {
916 if(etag)
917 n = snnprintf(buf, n, size, "\r\nETag: \"%s\"", etag);
920 if(code != 304 && code != 412) {
921 n = snnprintf(buf, n, size,
922 "\r\nExpires: 0"
923 "\r\nCache-Control: no-cache"
924 "\r\nPragma: no-cache");
927 if(headers)
928 n = snnprint_n(buf, n, size,
929 headers->string, headers->length);
931 n = snnprintf(buf, n, size, "\r\n\r\n");
933 if(n < 0 || n >= size) {
934 do_log(L_ERROR, "Couldn't write error.\n");
935 dispose_chunk(body);
936 return -1;
939 if(code != 304 && do_body) {
940 if(m > 0) memcpy(buf + n, body, m);
941 n += m;
944 if(body)
945 dispose_chunk(body);
947 return n;
950 AtomListPtr
951 urlDecode(char *buf, int n)
953 char mybuf[500];
954 int i, j = 0;
955 AtomListPtr list;
956 AtomPtr atom;
958 list = makeAtomList(NULL, 0);
959 if(list == NULL)
960 return NULL;
962 i = 0;
963 while(i < n) {
964 if(buf[i] == '%') {
965 int a, b;
966 if(i + 3 > n)
967 goto fail;
968 a = h2i(buf[i + 1]);
969 b = h2i(buf[i + 2]);
970 if(a < 0 || b < 0)
971 goto fail;
972 mybuf[j++] = (char)((a << 4) | b);
973 i += 3;
974 if(j > 500) goto fail;
975 } else if(buf[i] == '&') {
976 atom = internAtomN(mybuf, j);
977 if(atom == NULL)
978 goto fail;
979 atomListCons(atom, list);
980 j = 0;
981 i++;
982 } else {
983 mybuf[j++] = buf[i++];
984 if(j > 500) goto fail;
988 atom = internAtomN(mybuf, j);
989 if(atom == NULL)
990 goto fail;
991 atomListCons(atom, list);
992 return list;
994 fail:
995 destroyAtomList(list);
996 return NULL;
999 void
1000 httpTweakCachability(ObjectPtr object)
1002 int code = object->code;
1004 if((object->cache_control & CACHE_AUTHORIZATION) &&
1005 !(object->cache_control & CACHE_PUBLIC))
1006 object->cache_control |= (CACHE_NO_HIDDEN | OBJECT_LINEAR);
1008 /* This is not required by RFC 2616 -- but see RFC 3143 2.1.1. We
1009 manically avoid caching replies that we don't know how to
1010 handle, even if Expires or Cache-Control says otherwise. As to
1011 known uncacheable replies, we obey Cache-Control and default to
1012 allowing sharing but not caching. */
1013 if(code != 200 && code != 206 &&
1014 code != 300 && code != 301 && code != 302 && code != 303 &&
1015 code != 304 && code != 307 &&
1016 code != 403 && code != 404 && code != 405 && code != 416) {
1017 object->cache_control |=
1018 (CACHE_NO_HIDDEN | CACHE_MISMATCH | OBJECT_LINEAR);
1019 } else if(code != 200 && code != 206 &&
1020 code != 300 && code != 301 && code != 304 &&
1021 code != 410) {
1022 if(object->expires < 0 && !(object->cache_control & CACHE_PUBLIC)) {
1023 object->cache_control |= CACHE_NO_HIDDEN;
1025 } else if(dontCacheRedirects && (code == 301 || code == 302)) {
1026 object->cache_control |= CACHE_NO_HIDDEN;
1029 if(urlIsUncachable(object->key, object->key_size)) {
1030 object->cache_control |= CACHE_NO_HIDDEN;
1033 if((object->cache_control & CACHE_NO_STORE) != 0) {
1034 object->cache_control |= CACHE_NO_HIDDEN;
1037 if(object->cache_control & CACHE_VARY) {
1038 if(!object->etag || dontTrustVaryETag >= 2) {
1039 object->cache_control |= CACHE_MISMATCH;
1045 httpHeaderMatch(AtomPtr header, AtomPtr headers1, AtomPtr headers2)
1047 int rc1, b1, e1, rc2, b2, e2;
1049 /* Short cut if both sets of headers are identical */
1050 if(headers1 == headers2)
1051 return 1;
1053 rc1 = httpFindHeader(header, headers1->string, headers1->length,
1054 &b1, &e1);
1055 rc2 = httpFindHeader(header, headers2->string, headers2->length,
1056 &b2, &e2);
1058 if(rc1 == 0 && rc2 == 0)
1059 return 1;
1061 if(rc1 == 0 || rc2 == 0)
1062 return 0;
1064 if(e1 - b1 != e2 - b2)
1065 return 0;
1067 if(memcmp(headers1->string + b1, headers2->string + b2, e1 - b1) != 0)
1068 return 0;
1070 return 1;