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
26 AtomPtr proxyName
= NULL
;
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;
46 AtomPtr atom100Continue
;
50 /* 0 means that all failures lead to errors. 1 means that failures to
51 connect are reported in a Warning header when stale objects are
52 served. 2 means that only missing data is fetched from the net,
53 stale data is served without revalidation (browser-side
54 Cache-Control directives are still honoured). 3 means that no
55 connections are ever attempted. */
58 int relaxTransparency
= 0;
59 AtomPtr proxyAddress
= NULL
;
61 static int timeoutSetter(ConfigVariablePtr var
, void *value
);
66 proxyAddress
= internAtom("127.0.0.1");
67 CONFIG_VARIABLE_SETTABLE(disableProxy
, CONFIG_BOOLEAN
, configIntSetter
,
68 "Whether to be a web server only.");
69 CONFIG_VARIABLE_SETTABLE(proxyOffline
, CONFIG_BOOLEAN
, configIntSetter
,
70 "Avoid contacting remote servers.");
71 CONFIG_VARIABLE_SETTABLE(relaxTransparency
, CONFIG_TRISTATE
,
73 "Avoid contacting remote servers.");
74 CONFIG_VARIABLE(proxyPort
, CONFIG_INT
,
75 "The TCP port on which the proxy listens.");
76 CONFIG_VARIABLE(proxyAddress
, CONFIG_ATOM_LOWER
,
77 "The IP address on which the proxy listens.");
78 CONFIG_VARIABLE_SETTABLE(proxyName
, CONFIG_ATOM_LOWER
, configAtomSetter
,
79 "The name by which the proxy is known.");
80 CONFIG_VARIABLE_SETTABLE(clientTimeout
, CONFIG_TIME
,
81 timeoutSetter
, "Client-side timeout.");
82 CONFIG_VARIABLE_SETTABLE(serverTimeout
, CONFIG_TIME
,
83 timeoutSetter
, "Server-side timeout.");
84 CONFIG_VARIABLE(authRealm
, CONFIG_ATOM
,
85 "Authentication realm.");
86 CONFIG_VARIABLE(authCredentials
, CONFIG_PASSWORD
,
87 "username:password.");
88 CONFIG_VARIABLE(parentAuthCredentials
, CONFIG_PASSWORD
,
89 "username:password.");
90 CONFIG_VARIABLE(allowedClients
, CONFIG_ATOM_LIST_LOWER
,
91 "Networks from which clients are allowed to connect.");
92 CONFIG_VARIABLE(tunnelAllowedPorts
, CONFIG_INT_LIST
,
93 "Ports to which tunnelled connections are allowed.");
94 CONFIG_VARIABLE(allowedPorts
, CONFIG_INT_LIST
,
95 "Ports to which connections are allowed.");
96 CONFIG_VARIABLE(expectContinue
, CONFIG_TRISTATE
,
97 "Send Expect-Continue to servers.");
98 CONFIG_VARIABLE(bigBufferSize
, CONFIG_INT
,
99 "Size of big buffers (max size of headers).");
100 CONFIG_VARIABLE_SETTABLE(disableVia
, CONFIG_BOOLEAN
, configIntSetter
,
101 "Don't use Via headers.");
106 timeoutSetter(ConfigVariablePtr var
, void *value
)
108 configIntSetter(var
, value
);
109 if(clientTimeout
<= serverTimeout
)
110 clientTimeout
= serverTimeout
+ 1;
117 char *buf
= get_chunk();
120 struct hostent
*host
;
124 atom100Continue
= internAtom("100-continue");
126 if(clientTimeout
<= serverTimeout
) {
127 clientTimeout
= serverTimeout
+ 1;
128 do_log(L_WARN
, "Value of clientTimeout too small -- setting to %d.\n",
132 if(authCredentials
!= NULL
&& authRealm
== NULL
)
133 authRealm
= internAtom("Polipo");
136 allowedNets
= parseNetAddress(allowedClients
);
137 if(allowedNets
== NULL
)
141 if(allowedPorts
== NULL
) {
142 allowedPorts
= makeIntList(0);
143 if(allowedPorts
== NULL
) {
144 do_log(L_ERROR
, "Couldn't allocate allowedPorts.\n");
147 intListCons(80, 100, allowedPorts
);
148 intListCons(1024, 0xFFFF, allowedPorts
);
151 if(tunnelAllowedPorts
== NULL
) {
152 tunnelAllowedPorts
= makeIntList(0);
153 if(tunnelAllowedPorts
== NULL
) {
154 do_log(L_ERROR
, "Couldn't allocate tunnelAllowedPorts.\n");
157 intListCons(22, 22, tunnelAllowedPorts
);
158 intListCons(80, 80, tunnelAllowedPorts
);
159 intListCons(443, 443, tunnelAllowedPorts
);
160 intListCons(873, 873, tunnelAllowedPorts
);
167 do_log(L_ERROR
, "Couldn't allocate chunk for host name.\n");
171 n
= gethostname(buf
, CHUNK_SIZE
);
173 do_log_error(L_WARN
, errno
, "Gethostname");
174 strcpy(buf
, "polipo");
177 /* gethostname doesn't necessarily NUL-terminate on overflow */
178 buf
[CHUNK_SIZE
- 1] = '\0';
180 if(strcmp(buf
, "(none)") == 0 ||
181 strcmp(buf
, "localhost") == 0 ||
182 strcmp(buf
, "localhost.localdomain") == 0) {
183 do_log(L_WARN
, "Couldn't determine host name -- using ``polipo''.\n");
184 strcpy(buf
, "polipo");
188 if(strchr(buf
, '.') != NULL
)
191 host
= gethostbyname(buf
);
196 if(host
->h_addrtype
!= AF_INET
)
199 host
= gethostbyaddr(host
->h_addr_list
[0], host
->h_length
, AF_INET
);
201 if(!host
|| !host
->h_name
|| strcmp(host
->h_name
, "localhost") == 0 ||
202 strcmp(host
->h_name
, "localhost.localdomain") == 0)
205 namelen
= strlen(host
->h_name
);
206 if(namelen
>= CHUNK_SIZE
) {
207 do_log(L_ERROR
, "Host name too long.\n");
211 memcpy(buf
, host
->h_name
, namelen
+ 1);
214 proxyName
= internAtom(buf
);
215 if(proxyName
== NULL
) {
216 do_log(L_ERROR
, "Couldn't allocate proxy name.\n");
230 httpSetTimeout(HTTPConnectionPtr connection
, int secs
)
232 TimeEventHandlerPtr
new;
234 if(connection
->timeout
)
235 cancelTimeEvent(connection
->timeout
);
236 connection
->timeout
= NULL
;
239 new = scheduleTimeEvent(secs
, httpTimeoutHandler
,
240 sizeof(connection
), &connection
);
242 do_log(L_ERROR
, "Couldn't schedule timeout for connection 0x%x\n",
243 (unsigned)connection
);
250 connection
->timeout
= new;
255 httpTimeoutHandler(TimeEventHandlerPtr event
)
257 HTTPConnectionPtr connection
= *(HTTPConnectionPtr
*)event
->data
;
259 if(connection
->fd
>= 0) {
261 rc
= shutdown(connection
->fd
, 2);
262 if(rc
< 0 && errno
!= ENOTCONN
)
263 do_log_error(L_ERROR
, errno
, "Timeout: shutdown failed");
264 pokeFdEvent(connection
->fd
, -EDOTIMEOUT
, POLLIN
| POLLOUT
);
266 connection
->timeout
= NULL
;
271 httpWriteObjectHeaders(char *buf
, int offset
, int len
,
272 ObjectPtr object
, int from
, int to
)
276 if(from
<= 0 && to
< 0) {
277 if(object
->length
>= 0) {
278 n
= snnprintf(buf
, n
, len
,
279 "\r\nContent-Length: %d", object
->length
);
283 n
= snnprintf(buf
, n
, len
,
284 "\r\nContent-Length: %d", to
- from
);
288 if(from
> 0 || to
> 0) {
289 if(object
->length
>= 0) {
291 n
= snnprintf(buf
, n
, len
,
292 "\r\nContent-Range: bytes */%d",
295 n
= snnprintf(buf
, n
, len
,
296 "\r\nContent-Range: bytes %d-%d/%d",
302 n
= snnprintf(buf
, n
, len
,
303 "\r\nContent-Range: bytes %d-/*",
306 n
= snnprintf(buf
, n
, len
,
307 "\r\nContent-Range: bytes %d-%d/*",
314 n
= snnprintf(buf
, n
, len
, "\r\nETag: \"%s\"", object
->etag
);
316 if((object
->flags
& OBJECT_LOCAL
) || object
->date
>= 0) {
317 n
= snnprintf(buf
, n
, len
, "\r\nDate: ");
318 n
= format_time(buf
, n
, len
,
319 (object
->flags
& OBJECT_LOCAL
) ?
320 current_time
.tv_sec
: object
->date
);
325 if(object
->last_modified
>= 0) {
326 n
= snnprintf(buf
, n
, len
, "\r\nLast-Modified: ");
327 n
= format_time(buf
, n
, len
, object
->last_modified
);
332 if(object
->expires
>= 0) {
333 n
= snnprintf(buf
, n
, len
, "\r\nExpires: ");
334 n
= format_time(buf
, n
, len
, object
->expires
);
339 n
= httpPrintCacheControl(buf
, n
, len
,
340 object
->cache_control
, NULL
);
344 if(!disableVia
&& object
->via
)
345 n
= snnprintf(buf
, n
, len
, "\r\nVia: %s", object
->via
->string
);
348 n
= snnprint_n(buf
, n
, len
, object
->headers
->string
,
349 object
->headers
->length
);
361 cachePrintSeparator(char *buf
, int offset
, int len
,
366 n
= snnprintf(buf
, offset
, len
, ", ");
368 n
= snnprintf(buf
, offset
, len
, "\r\nCache-Control: ");
373 httpPrintCacheControl(char *buf
, int offset
, int len
,
374 int flags
, CacheControlPtr cache_control
)
379 #define PRINT_SEP() \
381 n = cachePrintSeparator(buf, n, len, sub); \
386 flags
|= cache_control
->flags
;
388 if(flags
& CACHE_NO
) {
390 n
= snnprintf(buf
, n
, len
, "no-cache");
392 if(flags
& CACHE_PUBLIC
) {
394 n
= snnprintf(buf
, n
, len
, "public");
396 if(flags
& CACHE_PRIVATE
) {
398 n
= snnprintf(buf
, n
, len
, "private");
400 if(flags
& CACHE_NO_STORE
) {
402 n
= snnprintf(buf
, n
, len
, "no-store");
404 if(flags
& CACHE_NO_TRANSFORM
) {
406 n
= snnprintf(buf
, n
, len
, "no-transform");
408 if(flags
& CACHE_MUST_REVALIDATE
) {
410 n
= snnprintf(buf
, n
, len
, "must-revalidate");
412 if(flags
& CACHE_PROXY_REVALIDATE
) {
414 n
= snnprintf(buf
, n
, len
, "proxy-revalidate");
416 if(flags
& CACHE_ONLY_IF_CACHED
) {
418 n
= snnprintf(buf
, n
, len
, "only-if-cached");
421 if(cache_control
->max_age
>= 0) {
423 n
= snnprintf(buf
, n
, len
, "max-age=%d",
424 cache_control
->max_age
);
426 if(cache_control
->s_maxage
>= 0) {
428 n
= snnprintf(buf
, n
, len
, "s-maxage=%d",
429 cache_control
->s_maxage
);
431 if(cache_control
->min_fresh
> 0) {
433 n
= snnprintf(buf
, n
, len
, "min-fresh=%d",
434 cache_control
->min_fresh
);
436 if(cache_control
->max_stale
> 0) {
438 n
= snnprintf(buf
, n
, len
, "max-stale=%d",
439 cache_control
->min_fresh
);
447 httpMessage(int code
)
453 return "Partial content";
455 return "Multiple choices";
457 return "Moved permanently";
463 return "Not changed";
465 return "Temporary redirect";
467 return "Authentication Required";
473 return "Method not allowed";
475 return "Proxy authentication required";
477 return "Unknown error code";
482 htmlString(char *buf
, int n
, int len
, char *s
, int slen
)
485 while(i
< slen
&& n
+ 5 < len
) {
488 buf
[n
++] = '&'; buf
[n
++] = 'a'; buf
[n
++] = 'm'; buf
[n
++] = 'p';
492 buf
[n
++] = '&'; buf
[n
++] = 'l'; buf
[n
++] = 't'; buf
[n
++] = ';';
495 buf
[n
++] = '&'; buf
[n
++] = 'g'; buf
[n
++] = 't'; buf
[n
++] = ';';
498 buf
[n
++] = '&'; buf
[n
++] = 'q'; buf
[n
++] = 'u'; buf
[n
++] = 'o';
499 buf
[n
++] = 't'; buf
[n
++] = ';';
512 htmlPrint(FILE *out
, char *s
, int slen
)
515 for(i
= 0; i
< slen
; i
++) {
535 HTTPConnectionPtr connection
;
536 connection
= malloc(sizeof(HTTPConnectionRec
));
537 if(connection
== NULL
)
539 connection
->flags
= 0;
541 connection
->buf
= NULL
;
543 connection
->offset
= 0;
544 connection
->request
= NULL
;
545 connection
->request_last
= NULL
;
546 connection
->serviced
= 0;
547 connection
->version
= HTTP_UNKNOWN
;
548 connection
->timeout
= NULL
;
549 connection
->te
= TE_IDENTITY
;
550 connection
->reqbuf
= NULL
;
551 connection
->reqlen
= 0;
552 connection
->reqbegin
= 0;
553 connection
->reqoffset
= 0;
554 connection
->bodylen
= -1;
555 connection
->reqte
= TE_IDENTITY
;
556 connection
->chunk_remaining
= 0;
557 connection
->server
= NULL
;
558 connection
->pipelined
= 0;
559 connection
->connecting
= 0;
560 connection
->server
= NULL
;
565 httpDestroyConnection(HTTPConnectionPtr connection
)
567 assert(connection
->flags
== 0);
568 httpConnectionDestroyBuf(connection
);
569 assert(!connection
->request
);
570 assert(!connection
->request_last
);
571 httpConnectionDestroyReqbuf(connection
);
572 assert(!connection
->timeout
);
573 assert(!connection
->server
);
578 httpConnectionDestroyBuf(HTTPConnectionPtr connection
)
580 if(connection
->buf
) {
581 if(connection
->flags
& CONN_BIGBUF
)
582 free(connection
->buf
);
584 dispose_chunk(connection
->buf
);
586 connection
->flags
&= ~CONN_BIGBUF
;
587 connection
->buf
= NULL
;
591 httpConnectionDestroyReqbuf(HTTPConnectionPtr connection
)
593 if(connection
->reqbuf
) {
594 if(connection
->flags
& CONN_BIGREQBUF
)
595 free(connection
->reqbuf
);
597 dispose_chunk(connection
->reqbuf
);
599 connection
->flags
&= ~CONN_BIGREQBUF
;
600 connection
->reqbuf
= NULL
;
606 HTTPRequestPtr request
;
607 request
= malloc(sizeof(HTTPRequestRec
));
611 request
->connection
= NULL
;
612 request
->object
= NULL
;
613 request
->method
= METHOD_UNKNOWN
;
616 request
->cache_control
= no_cache_control
;
617 request
->condition
= NULL
;
619 request
->chandler
= NULL
;
620 request
->can_mutate
= NULL
;
621 request
->error_code
= 0;
622 request
->error_message
= NULL
;
623 request
->error_headers
= NULL
;
624 request
->headers
= NULL
;
625 request
->time0
= null_time
;
626 request
->time1
= null_time
;
627 request
->request
= NULL
;
628 request
->next
= NULL
;
633 httpDestroyRequest(HTTPRequestPtr request
)
636 releaseObject(request
->object
);
637 if(request
->condition
)
638 httpDestroyCondition(request
->condition
);
639 releaseAtom(request
->via
);
640 assert(request
->chandler
== NULL
);
641 releaseAtom(request
->error_message
);
642 releaseAtom(request
->headers
);
643 releaseAtom(request
->error_headers
);
644 assert(request
->request
== NULL
);
645 assert(request
->next
== NULL
);
650 httpQueueRequest(HTTPConnectionPtr connection
, HTTPRequestPtr request
)
652 assert(request
->next
== NULL
&& request
->connection
== NULL
);
653 request
->connection
= connection
;
654 if(connection
->request_last
) {
655 assert(connection
->request
);
656 connection
->request_last
->next
= request
;
657 connection
->request_last
= request
;
659 assert(!connection
->request_last
);
660 connection
->request
= request
;
661 connection
->request_last
= request
;
666 httpDequeueRequest(HTTPConnectionPtr connection
)
668 HTTPRequestPtr request
= connection
->request
;
670 assert(connection
->request_last
);
671 connection
->request
= request
->next
;
672 if(!connection
->request
) connection
->request_last
= NULL
;
673 request
->next
= NULL
;
679 httpConnectionBigify(HTTPConnectionPtr connection
)
682 assert(!(connection
->flags
& CONN_BIGBUF
));
684 if(bigBufferSize
<= CHUNK_SIZE
)
687 bigbuf
= malloc(bigBufferSize
);
690 if(connection
->len
> 0)
691 memcpy(bigbuf
, connection
->buf
, connection
->len
);
693 dispose_chunk(connection
->buf
);
694 connection
->buf
= bigbuf
;
695 connection
->flags
|= CONN_BIGBUF
;
700 httpConnectionBigifyReqbuf(HTTPConnectionPtr connection
)
703 assert(!(connection
->flags
& CONN_BIGREQBUF
));
705 if(bigBufferSize
<= CHUNK_SIZE
)
708 bigbuf
= malloc(bigBufferSize
);
711 if(connection
->reqlen
> 0)
712 memcpy(bigbuf
, connection
->reqbuf
, connection
->reqlen
);
713 if(connection
->reqbuf
)
714 dispose_chunk(connection
->reqbuf
);
715 connection
->reqbuf
= bigbuf
;
716 connection
->flags
|= CONN_BIGREQBUF
;
721 httpConnectionUnbigify(HTTPConnectionPtr connection
)
724 assert(connection
->flags
& CONN_BIGBUF
);
725 assert(connection
->len
< CHUNK_SIZE
);
730 if(connection
->len
> 0)
731 memcpy(buf
, connection
->buf
, connection
->len
);
732 free(connection
->buf
);
733 connection
->buf
= buf
;
734 connection
->flags
&= ~CONN_BIGBUF
;
739 httpConnectionUnbigifyReqbuf(HTTPConnectionPtr connection
)
742 assert(connection
->flags
& CONN_BIGREQBUF
);
743 assert(connection
->reqlen
< CHUNK_SIZE
);
748 if(connection
->reqlen
> 0)
749 memcpy(buf
, connection
->reqbuf
, connection
->reqlen
);
750 free(connection
->reqbuf
);
751 connection
->reqbuf
= buf
;
752 connection
->flags
&= ~CONN_BIGREQBUF
;
759 HTTPConditionPtr condition
;
760 condition
= malloc(sizeof(HTTPConditionRec
));
761 if(condition
== NULL
)
764 condition
->inms
= -1;
765 condition
->im
= NULL
;
766 condition
->inm
= NULL
;
767 condition
->ifrange
= NULL
;
772 httpDestroyCondition(HTTPConditionPtr condition
)
775 free(condition
->inm
);
778 if(condition
->ifrange
)
779 free(condition
->ifrange
);
784 httpCondition(ObjectPtr object
, HTTPConditionPtr condition
)
786 int rc
= CONDITION_MATCH
;
788 assert(!(object
->flags
& OBJECT_INITIAL
));
790 if(!condition
) return CONDITION_MATCH
;
792 if(condition
->ims
>= 0) {
793 if(object
->last_modified
< 0 ||
794 condition
->ims
< object
->last_modified
)
797 rc
= CONDITION_NOT_MODIFIED
;
800 if(condition
->inms
>= 0) {
801 if(object
->last_modified
< 0 ||
802 condition
->inms
>= object
->last_modified
)
805 rc
= CONDITION_FAILED
;
809 if(!object
->etag
|| strcmp(object
->etag
, condition
->inm
) != 0)
812 rc
= CONDITION_NOT_MODIFIED
;
816 if(!object
->etag
|| strcmp(object
->etag
, condition
->im
) != 0)
817 rc
= CONDITION_FAILED
;
826 httpWriteErrorHeaders(char *buf
, int size
, int offset
, int do_body
,
827 int code
, AtomPtr message
, int close
, AtomPtr headers
,
828 char *url
, int url_len
, char *etag
)
832 char htmlMessage
[100];
837 i
= htmlString(htmlMessage
, 0, 100, message
->string
, message
->length
);
839 strcpy(htmlMessage
, "(Couldn't format message)");
841 htmlMessage
[MIN(i
, 99)] = '\0';
846 do_log(L_ERROR
, "Couldn't allocate body buffer.\n");
849 m
= snnprintf(body
, 0, CHUNK_SIZE
,
850 "<!DOCTYPE HTML PUBLIC "
851 "\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
852 "\"http://www.w3.org/TR/html4/loose.dtd\">"
854 "\n<title>Proxy %s: %3d %s.</title>"
857 "\n<p>The following %s",
858 code
>= 400 ? "error" : "result",
863 "status was returned");
865 m
= snnprintf(body
, m
, CHUNK_SIZE
,
866 " while trying to access <strong>");
867 m
= htmlString(body
, m
, CHUNK_SIZE
, url
, url_len
);
868 m
= snnprintf(body
, m
, CHUNK_SIZE
, "</strong>");
871 /*Mon, 24 Sep 2004 17:46:35 GMT*/
872 strftime(timeStr
, sizeof(timeStr
), "%a, %d %b %Y %H:%M:%S %Z",
873 localtime(&(current_time
.tv_sec
)));
875 m
= snnprintf(body
, m
, CHUNK_SIZE
,
877 "\n<strong>%3d %s</strong></p>"
878 "\n<hr>Generated %s by Polipo on <em>%s:%d</em>."
879 "\n</body></html>\r\n",
881 timeStr
, proxyName
->string
, proxyPort
);
882 if(m
<= 0 || m
>= CHUNK_SIZE
) {
883 do_log(L_ERROR
, "Couldn't write error body.\n");
892 n
= snnprintf(buf
, 0, size
,
896 code
, atomString(message
),
897 close
? "close" : "keep-alive");
898 n
= format_time(buf
, n
, size
, current_time
.tv_sec
);
900 n
= snnprintf(buf
, n
, size
,
901 "\r\nContent-Type: text/html"
902 "\r\nContent-Length: %d", m
);
905 n
= snnprintf(buf
, n
, size
, "\r\nETag: \"%s\"", etag
);
908 if(code
!= 304 && code
!= 412) {
909 n
= snnprintf(buf
, n
, size
,
911 "\r\nCache-Control: no-cache"
912 "\r\nPragma: no-cache");
916 n
= snnprint_n(buf
, n
, size
,
917 headers
->string
, headers
->length
);
919 n
= snnprintf(buf
, n
, size
, "\r\n\r\n");
921 if(n
< 0 || n
>= size
) {
922 do_log(L_ERROR
, "Couldn't write error.\n");
927 if(code
!= 304 && do_body
) {
928 if(m
> 0) memcpy(buf
+ n
, body
, m
);
939 urlDecode(char *buf
, int n
)
946 list
= makeAtomList(NULL
, 0);
960 mybuf
[j
++] = (char)((a
<< 4) | b
);
962 if(j
> 500) goto fail
;
963 } else if(buf
[i
] == '&') {
964 atom
= internAtomN(mybuf
, j
);
967 atomListCons(atom
, list
);
971 mybuf
[j
++] = buf
[i
++];
972 if(j
> 500) goto fail
;
976 atom
= internAtomN(mybuf
, j
);
979 atomListCons(atom
, list
);
983 destroyAtomList(list
);