Merge to Fedora kernel-2.6.18-1.2224_FC5 patched with stable patch-2.6.18.1-vs2.0...
[linux-2.6.git] / net / tux / proto_http.c
1 /*
2  * TUX - Integrated Application Protocols Layer and Object Cache
3  *
4  * Copyright (C) 2000, 2001, Ingo Molnar <mingo@redhat.com>
5  *
6  * proto_http.c: HTTP application protocol support
7  *
8  * Right now we detect simple GET headers, anything more
9  * subtle gets redirected to secondary server port.
10  */
11
12 #include <net/tux.h>
13 #include "parser.h"
14
15 /****************************************************************
16  *      This program is free software; you can redistribute it and/or modify
17  *      it under the terms of the GNU General Public License as published by
18  *      the Free Software Foundation; either version 2, or (at your option)
19  *      any later version.
20  *
21  *      This program is distributed in the hope that it will be useful,
22  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
23  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24  *      GNU General Public License for more details.
25  *
26  *      You should have received a copy of the GNU General Public License
27  *      along with this program; if not, write to the Free Software
28  *      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29  *
30  ****************************************************************/
31
32 /*
33  * Parse the HTTP message and put results into the request structure.
34  * CISAPI extensions do not see the actual message buffer.
35  *
36  * Any perceived irregularity is honored with a redirect to the
37  * secondary server - which in most cases should be Apache. So
38  * if TUX gets confused by some strange request we fall back
39  * to Apache to be RFC-correct.
40  *
41  * The parser is 'optimistic', ie. it's optimized for the case where
42  * the whole message is available and correct. The parser is also
43  * supposed to be 'robust', ie. it can be called multiple times with
44  * an incomplete message, as new packets arrive.
45  */
46
47 static inline int TOHEX (char c)
48 {
49         switch (c) {
50                 case '0' ... '9': c -= '0'; break;
51                 case 'a' ... 'f': c -= 'a'-10; break;
52                 case 'A' ... 'F': c -= 'A'-10; break;
53         default:
54                 c = -1;
55         }
56         return c;
57 }
58
59 /*
60  * This function determines whether the client supports
61  * gzip-type content-encoding.
62  */
63 static int may_gzip (const char *str, int len)
64 {
65         const char *tmp, *curr;
66         int i;
67
68         if (len <= 4)
69                 return 0;
70         tmp = str;
71         for (i = 0; i <= len-6; i++) {
72                 Dprintk("gzip-checking: {%s}\n", tmp);
73                 if (memcmp(tmp, " gzip", 5)) {
74                         tmp++;
75                         continue;
76                 }
77                 curr = tmp + 5;
78
79                 if (*curr == ',' || *curr == '\r')
80                         return 1;
81                 if (memcmp(curr, ";q=", 3))
82                         return 0;
83                 curr += 3;
84                 /*
85                  * Every qvalue except explicitly zero is accepted.
86                  * Zero values are "q=0.0", "q=0.00", "q=0.000".
87                  * Parsing is optimized.
88                  */
89                 if (*curr == '0') {
90                         curr += 2;
91                         if (*curr == '0') {
92                                 curr++;
93                                 if (*curr == ' ' || *curr == '\r')
94                                         return 0;
95                                 if (*curr == '0') {
96                                         curr++;
97                                         if (*curr == ' ' || *curr == '\r')
98                                                 return 0;
99                                         if (*curr == '0') {
100                                                 curr++;
101                                                 if (*curr == ' ' ||
102                                                                 *curr == '\r')
103                                                         return 0;
104                                         }
105                                 }
106                         }
107                 }
108                 return 1;
109         }
110         return 0;
111 }
112
113 /*
114  * This function strips off 'strip_host_tail' number of hostname
115  * components from the tail of the hostname.
116  *
117  * Eg. with a value of '1', the "somesite.hosting.com" hostname gets
118  * transformed into the "somesite" string.
119  */
120 static void strip_hostname(tux_req_t *req)
121 {
122         int strip = strip_host_tail;
123         int left = req->host_len;
124         int component = 0;
125
126         if (!strip || !left)
127                 return;
128
129         while (--left) {
130                 if (req->host[left] != '.')
131                         continue;
132                 if (++component == strip)
133                         break;
134         }
135         if (!left)
136                 return;
137         req->host[left] = 0;
138         req->host_len = left;
139 }
140
141 static void http_lookup_vhost (tux_req_t *req, int cachemiss);
142 static void http_process_message (tux_req_t *req, int cachemiss);
143
144 int parse_http_message (tux_req_t *req, const int total_len)
145 {
146         int hexhex = 0, hex_val_0 = 0, hex_val_1 = 0;
147         const char *curr, *uri, *message;
148         unsigned int objectname_len, left;
149         unsigned int have_r = 0;
150         char c;
151
152         left = total_len;
153         message = req->headers;
154         Dprintk("parsing request:\n---\n%s\n---\n", message);
155 /*
156  * RFC 2616, 5.1:
157  *
158  *       Request-Line   = Method SP Request-URI SP HTTP-Version CRLF
159  */
160
161         if (!total_len)
162                 TUX_BUG();
163
164         curr = message;
165
166 #define GOTO_INCOMPLETE do { Dprintk("incomplete at %s:%d.\n", __FILE__, __LINE__); goto incomplete_message; } while (0)
167 #define GOTO_REDIR do { TDprintk("redirect secondary at %s:%d.\n", __FILE__, __LINE__); goto error; } while (0)
168
169 #define PRINT_MESSAGE_LEFT \
170     Dprintk("message left (%d) at %s:%d:\n--->{%s}<---\n", left, __FILE__, __LINE__, curr)
171
172         switch (*curr) {
173                 case 'G':
174                         if (PARSE_METHOD(req,curr,GET,left))
175                                 break;
176                         GOTO_REDIR;
177
178                 case 'H':
179                         if (PARSE_METHOD(req,curr,HEAD,left))
180                                 break;
181                         GOTO_REDIR;
182
183                 case 'P':
184                         if (PARSE_METHOD(req,curr,POST,left))
185                                 break;
186                         if (PARSE_METHOD(req,curr,PUT,left))
187                                 break;
188                         GOTO_REDIR;
189
190                 default:
191                         GOTO_REDIR;
192         }
193
194         req->method_str = message;
195         req->method_len = curr-message-1;
196
197         Dprintk("got method %d\n", req->method);
198
199         PRINT_MESSAGE_LEFT;
200
201         /*
202          * Ok, we got one of the methods we can handle, parse
203          * the URI:
204          */
205
206         {
207                 // Do not allow leading "../" and intermediate "/../"
208                 int dotdot = 1;
209                 char *tmp = req->objectname;
210                 int slashcheck = 1;
211
212                 req->uri_str = uri = curr;
213
214                 for (;;) {
215                         c = get_c(curr,left);
216                         if (slashcheck) {
217                                 if (c == '/')
218                                         continue;
219                                 slashcheck = 0;
220                         }
221
222                         PRINT_MESSAGE_LEFT;
223                         if (c == ' ' || ((c == '?') && (tux_ignore_query != 1)) || c == '\r' || c == '\n')
224                                 break;
225                         if (c == '#')
226                                 GOTO_REDIR;
227
228                         Dprintk("hexhex: %d.\n", hexhex);
229                         /*
230                          * First handle HEX HEX encoding
231                          */
232                         switch (hexhex) {
233                                 case 0:
234                                         if (c == '%') {
235                                                 hexhex = 1;
236                                                 goto continue_parsing;
237                                         }
238                                         break;
239                                 case 1:
240                                         hex_val_0 = TOHEX(c);
241                                         if (hex_val_0 < 0)
242                                                 GOTO_REDIR;
243                                         hexhex = 2;
244                                         goto continue_parsing;
245                                 case 2:
246                                         hex_val_1 = TOHEX(c);
247                                         if (hex_val_1 < 0)
248                                                 GOTO_REDIR;
249                                         c = (hex_val_0 << 4) | hex_val_1;
250                                         if (!c)
251                                                 GOTO_REDIR;
252                                         hexhex = 0;
253                                         break;
254                                 default:
255                                         TUX_BUG();
256                         }
257                         if (hexhex)
258                                 TUX_BUG();
259
260                         switch (dotdot) {
261                                 case 0:
262                                         break;
263                                 case 1:
264                                         if (c == '.')
265                                                 dotdot = 2;
266                                         else
267                                                 dotdot = 0;
268                                         break;
269                                 case 2:
270                                         if (c == '.')
271                                                 dotdot = 3;
272                                         else
273                                                 dotdot = 0;
274                                         break;
275                                 case 3:
276                                         if (c == '/')
277                                                 GOTO_REDIR;
278                                         else
279                                                 dotdot = 0;
280                                         break;
281                                 default:
282                                         TUX_BUG();
283                         }
284                         if (!dotdot && (c == '/'))
285                                 dotdot = 1;
286
287                         *(tmp++) = c;
288 continue_parsing:
289                         if (curr - uri >= MAX_OBJECTNAME_LEN)
290                                 GOTO_REDIR;
291                 }
292                 PRINT_MESSAGE_LEFT;
293                 *tmp = 0;
294
295                 // handle trailing "/.."
296                 if (dotdot == 3)
297                         GOTO_REDIR;
298
299                 objectname_len = tmp - req->objectname;
300                 req->objectname_len = objectname_len;
301         }
302         Dprintk("got filename %s (%d)\n", req->objectname, req->objectname_len);
303
304         PRINT_MESSAGE_LEFT;
305
306         /*
307          * Parse optional query string. Copy until end-of-string or space.
308          */
309         if (c == '?') {
310                 int query_len;
311                 const char *query;
312
313                 req->query_str = query = curr;
314
315                 for (;;) {
316                         c = get_c(curr,left);
317                         if (c == ' ')
318                                 break;
319                         if (c == '#')
320                                 GOTO_REDIR;
321                 }
322                 if (unlikely(tux_ignore_query == 2))
323                         req->query_str = NULL;
324                 else {
325                         query_len = curr-query-1;
326                         req->query_len = query_len;
327                 }
328         }
329         if (req->query_len)
330                 Dprintk("got query string %s (%d)\n", req->query_str, req->query_len);
331         req->uri_len = curr-uri-1;
332         if (!req->uri_len)
333                 GOTO_REDIR;
334         Dprintk("got URI %s (%d)\n", req->uri_str, req->uri_len);
335
336         PRINT_MESSAGE_LEFT;
337         /*
338          * Parse the HTTP version field:
339          */
340         req->version_str = curr;
341         if (!PARSE_TOKEN(curr,"HTTP/1.",left))
342                 GOTO_REDIR;
343
344         switch (get_c(curr,left)) {
345                 case '0':
346                         req->version = HTTP_1_0;
347                         break;
348                 case '1':
349                         req->version = HTTP_1_1;
350                         break;
351                 default:
352                         GOTO_REDIR;
353         }
354         /*
355          * We default to keepalive in the HTTP/1.1 case and default
356          * to non-keepalive in the HTTP/1.0 case. If max_keepalives
357          * is 0 then we do no keepalives.
358          */
359         clear_keepalive(req);
360         if (tux_max_keepalives && (req->version == HTTP_1_1))
361                 req->keep_alive = 1;
362         req->version_len = curr - req->version_str;
363
364         if (get_c(curr,left) != '\r')
365                 GOTO_REDIR;
366         if (get_c(curr,left) != '\n')
367                 GOTO_REDIR;
368
369         Dprintk("got version %d [%d]\n", req->version, req->version_len);
370         PRINT_MESSAGE_LEFT;
371
372         /*
373          * Now parse (optional) request header fields:
374          */
375         for (;;) {
376                 char c;
377
378                 c = get_c(curr,left);
379                 switch (c) {
380                 case '\r':
381                         if (have_r)
382                                 GOTO_REDIR;
383                         have_r = 1;
384                         continue;
385                 case '\n':
386                         if (!have_r)
387                                 GOTO_REDIR;
388                         goto out;
389                 default:
390                         if (have_r)
391                                 GOTO_REDIR;
392                 }
393
394 #define PARSE_STR_FIELD(char,field,str,len)                             \
395         if (PARSE_TOKEN(curr,field,left)) {                             \
396                 req->str = curr;                                        \
397                 SKIP_LINE(curr,left);                                   \
398                 req->len = curr - req->str - 2;                         \
399                 Dprintk(char field "field: %s.\n", req->str);           \
400                 break;                                                  \
401         }
402
403 #define ALLOW_UNKNOWN_FIELDS 1
404 #ifdef ALLOW_UNKNOWN_FIELDS
405 # define UNKNOWN_FIELD { SKIP_LINE(curr,left); break; }
406 #else
407 # define UNKNOWN_FIELD GOTO_REDIR
408 #endif
409
410                 switch (c) {
411                 case 'A':
412                         PARSE_STR_FIELD("A","ccept: ",
413                                 accept_str,accept_len);
414                         if (PARSE_TOKEN(curr,"ccept-Encoding: ",left)) {
415                                 const char *str = curr-1;
416
417                                 req->accept_encoding_str = curr;
418                                 SKIP_LINE(curr,left);
419                                 req->accept_encoding_len = curr - req->accept_encoding_str - 2;
420                                 Dprintk("Accept-Encoding field: {%s}.\n", str);
421
422                                 if (tux_compression && may_gzip(str,curr-str)) {
423                                         Dprintk("client accepts gzip!.\n");
424                                         req->may_send_gzip = 1;
425                                 }
426                                 break;
427                         }
428                         PARSE_STR_FIELD("A","ccept-Charset: ",
429                                 accept_charset_str,accept_charset_len);
430                         PARSE_STR_FIELD("A","ccept-Language: ",
431                                 accept_language_str,accept_language_len);
432                         UNKNOWN_FIELD;
433
434                 case 'C':
435                         if (PARSE_TOKEN(curr,"onnection: ",left)) {
436 next_token:
437                         switch (get_c(curr,left)) {
438                         case 'K':
439                                 if (!PARSE_TOKEN(curr,"eep-Alive",left))
440                                         GOTO_REDIR;
441                                 if (tux_max_keepalives)
442                                         req->keep_alive = 1;
443                                 break;
444
445                         case 'C':
446                         case 'c':
447                                 if (!PARSE_TOKEN(curr,"lose",left))
448                                         GOTO_REDIR;
449                                 clear_keepalive(req);
450                                 break;
451
452                         case 'k':
453                                 if (!PARSE_TOKEN(curr,"eep-alive",left))
454                                         GOTO_REDIR;
455                                 if (tux_max_keepalives)
456                                         req->keep_alive = 1;
457                                 break;
458                         case 'T':
459                                 if (PARSE_TOKEN(curr,"E",left))
460                                         break;
461                                 if (PARSE_TOKEN(curr,"railers",left))
462                                         break;
463                                 if (PARSE_TOKEN(curr,"ransfer-Encoding",left))
464                                         break;
465                                 GOTO_REDIR;
466                         case 'P':
467                                 if (PARSE_TOKEN(curr,"roxy-Authenticate",left))
468                                         break;
469                                 if (PARSE_TOKEN(curr,"roxy-Authorization",left))
470                                         break;
471                                 GOTO_REDIR;
472                         case 'U':
473                                 if (!PARSE_TOKEN(curr,"pgrade",left))
474                                         GOTO_REDIR;
475                                 break;
476                         case ' ':
477                                 PRINT_MESSAGE_LEFT;
478                                 goto next_token;
479                         case ',':
480                                 PRINT_MESSAGE_LEFT;
481                                 goto next_token;
482                         default:
483                                 GOTO_REDIR;
484                         }
485                         PRINT_MESSAGE_LEFT;
486                         if (*curr != '\r')
487                                 goto next_token;
488                         // allow other tokens.
489                         SKIP_LINE(curr,left);
490                         break;
491                         }
492
493                         PARSE_STR_FIELD("C","ookie: ",
494                                 cookies_str,cookies_len);
495                         PARSE_STR_FIELD("C","ontent-Type: ",
496                                 content_type_str,content_type_len);
497
498                         if (PARSE_TOKEN(curr,"ontent-Length: ",left) ||
499                             PARSE_TOKEN(curr,"ontent-length: ",left)) {
500                                 const char *tmp;
501                                 req->contentlen_str = curr;
502                                 SKIP_LINE(curr,left);
503                                 req->contentlen_len = curr - req->contentlen_str - 2;
504                                 if (req->contentlen_len) {
505                                         tmp = req->contentlen_str;
506                                         req->content_len = simple_strtoul(tmp, NULL, 10);
507                                 }
508                                 Dprintk("Content-Length field: %s [%d].\n", req->contentlen_str, req->contentlen_len);
509                                 Dprintk("Content-Length value: %d.\n", req->content_len);
510                                 break;
511                         }
512                         PARSE_STR_FIELD("C","ache-Control: ",
513                                 cache_control_str,cache_control_len);
514                         UNKNOWN_FIELD;
515
516                 case 'H':
517                         if (PARSE_TOKEN(curr,"ost: ",left)) {
518                                 const char *tmp = curr;
519                                 char *tmp2 = req->host;
520
521                                 /*
522                                  * canonize the hostname:
523                                  *
524                                  * 1) strip off preceding 'www.' variants,
525                                  * 2) transform it to lowercase.
526                                  * 3) strip trailing dots
527                                  * 4) potentially strip off tail
528                                  */
529
530 #define is_w(n) ((curr[n] == 'w') || (curr[n] == 'W'))
531
532                                 if ((left > 4) && is_w(0) && is_w(1) &&
533                                                 is_w(2) && curr[3] == '.') {
534                                         curr += 4;
535                                         left -= 4;
536                                         tmp = curr;
537                                 }
538
539                                 COPY_LINE_TOLOWER(curr, tmp2, left, req->host+MAX_HOST_LEN-2);
540                                 req->host_len = curr - tmp - 2;
541                                 while (req->host[req->host_len] == '.') {
542                                         if (!req->host_len)
543                                                 break;
544                                         req->host_len--;
545                                 }
546                                 req->host[req->host_len] = 0;
547                                 if (strip_host_tail)
548                                         strip_hostname(req);
549                                 Dprintk("Host field: %s [%d].\n", req->host, req->host_len);
550                                 break;
551                         }
552                         UNKNOWN_FIELD;
553
554                 case 'I':
555                         PARSE_STR_FIELD("I","f-None-Match: ",
556                                 if_none_match_str,if_none_match_len);
557                         PARSE_STR_FIELD("I","f-Modified-Since: ",
558                                 if_modified_since_str,if_modified_since_len);
559                         PARSE_STR_FIELD("I","f-Range: ",
560                                 if_range_str,if_range_len);
561                         UNKNOWN_FIELD;
562
563                 case 'N':
564                         PARSE_STR_FIELD("N","egotiate: ",
565                                 negotiate_str,negotiate_len);
566                         UNKNOWN_FIELD;
567
568                 case 'P':
569                         PARSE_STR_FIELD("P","ragma: ",
570                                 pragma_str,pragma_len);
571                         UNKNOWN_FIELD;
572
573                 case 'R':
574
575                         PARSE_STR_FIELD("R","eferer: ",
576                                 referer_str,referer_len);
577                         if (!PARSE_TOKEN(curr,"ange: bytes=",left))
578                                 UNKNOWN_FIELD;
579                 {
580                         const char *tmp = curr;
581                         char *tmp2 = (char *)curr;
582                         unsigned int offset_start = 0, offset_end = 0;
583
584                         if (*tmp2 != '-')
585                                 offset_start = simple_strtoul(tmp2, &tmp2, 10);
586                         if (*tmp2 == '-') {
587                                 tmp2++;
588                                 if (*tmp2 != '\r')
589                                         offset_end = simple_strtoul(tmp2, &tmp2, 10) +1;
590                         }
591                         curr = tmp2;
592                         left -= tmp2-tmp;
593
594                         req->offset_start = offset_start;
595                         req->offset_end = offset_end;
596
597                         SKIP_LINE(curr,left);
598                         Dprintk("Range field: %s [%d] (%d-%d).\n", tmp, curr-tmp, offset_start, offset_end);
599                         break;
600                 }
601
602                 case 'U':
603                         PARSE_STR_FIELD("U","ser-Agent: ",
604                                 user_agent_str,user_agent_len);
605                         UNKNOWN_FIELD;
606
607                 default:
608                         UNKNOWN_FIELD;
609                 }
610                 PRINT_MESSAGE_LEFT;
611         }
612 out:
613         /*
614          * POST data.
615          */
616         if ((req->method == METHOD_POST) && req->content_len) {
617                 PRINT_MESSAGE_LEFT;
618                 if (curr + req->content_len > message + total_len)
619                         GOTO_INCOMPLETE;
620                 req->post_data_str = curr;
621                 req->post_data_len = req->content_len;
622                 curr += req->content_len;
623                 left -= req->content_len;
624                 Dprintk("POST-ed data: {%s}\n", req->post_data_str);
625         }
626
627         switch (req->method) {
628                 default:
629                         GOTO_REDIR;
630                 case METHOD_GET:
631                 case METHOD_HEAD:
632                 case METHOD_POST:
633                 case METHOD_PUT:
634                         ;
635         }
636
637 #define TUX_SCHEME "http://"
638 #define TUX_SCHEME_LEN (sizeof(TUX_SCHEME)-1)
639
640         if (!memcmp(req->objectname, TUX_SCHEME, TUX_SCHEME_LEN)) {
641
642                 /* http://user:password@host:port/object */
643
644                 const char *head, *tail, *end, *host, *port;
645                 int host_len, objectname_len;
646
647                 head = req->objectname + TUX_SCHEME_LEN;
648                 end = req->objectname + req->objectname_len;
649
650                 tail = memchr(head, '/', end - head);
651                 if (!tail)
652                         GOTO_REDIR;
653                 host = memchr(head, '@', tail - head);
654                 if (!host)
655                         host = head;
656                 else
657                         host++;
658                 if (!*host)
659                         GOTO_REDIR;
660                 port = memchr(host, ':', tail - host);
661                 if (port)
662                         host_len = port - host;
663                 else
664                         host_len = tail - host;
665                 if (host_len >= MAX_HOST_LEN)
666                         GOTO_REDIR;
667                 memcpy(req->host, host, host_len);
668                 req->host_len = host_len;
669                 req->host[host_len] = 0;
670
671                 if (*tail != '/')
672                         TUX_BUG();
673
674                 req->uri_str = tail;
675                 req->uri_len = end - tail;
676
677                 tail++;
678                 while (*tail == '/')
679                         tail++;
680
681                 objectname_len = end - tail;
682                 memcpy(req->objectname, tail, objectname_len);
683                 req->objectname_len = objectname_len;
684                 req->objectname[objectname_len] = 0;
685         } else
686                 if (req->uri_str[0] != '/')
687                         GOTO_REDIR;
688
689         if ((req->version == HTTP_1_1) && !req->host_len)
690                 GOTO_REDIR;
691         if (req->objectname[0] == '/')
692                 GOTO_REDIR;
693         /*
694          * Lets make sure nobody plays games with the host
695          * header in a virtual hosting environment:
696          */
697         if (req->virtual && req->host_len) {
698                 if (memchr(req->host, '/', req->host_len))
699                         GOTO_REDIR;
700                 if (req->host[0] == '.') {
701                         if (req->host_len == 1)
702                                 GOTO_REDIR;
703                         if ((req->host_len == 2) && (req->host[0] == '.'))
704                                 GOTO_REDIR;
705                 }
706         }
707         /*
708          * From this point on the request is for the main TUX engine:
709          */
710         Dprintk("ok, request accepted.\n");
711
712         if (req->keep_alive) {
713                 req->nr_keepalives++;
714                 if (req->nr_keepalives == -1)
715                         req->nr_keepalives--;
716                 INC_STAT(nr_keepalive_reqs);
717         } else
718                 INC_STAT(nr_nonkeepalive_reqs);
719         INC_STAT(keepalive_hist[req->nr_keepalives]);
720
721         PRINT_MESSAGE_LEFT;
722         req->parsed_len = curr-message;
723         if (req->dentry)
724                 TUX_BUG();
725         req->virtual = tux_virtual_server;
726         if (req->virtual)
727                 add_tux_atom(req, http_lookup_vhost);
728         else {
729                 req->docroot_dentry = dget(req->proto->main_docroot.dentry);
730                 req->docroot_mnt = mntget(req->proto->main_docroot.mnt);
731                 add_tux_atom(req, http_process_message);
732         }
733
734         return req->parsed_len;
735
736 incomplete_message:
737         Dprintk("incomplete message!\n");
738         PRINT_MESSAGE_LEFT;
739
740         return 0;
741
742 error:
743         if (total_len > 0)
744                 req->parsed_len = total_len;
745         else
746                 req->parsed_len = 0;
747         PRINT_MESSAGE_LEFT;
748         if (tux_TDprintk) {
749                 TDprintk("redirecting message to secondary server.\n");
750                 print_req(req);
751         }
752         return -1;
753 }
754
755 static int lookup_url (tux_req_t *req, const unsigned int flag)
756 {
757         /*
758          * -1 : no previous checks made
759          *  0 : previous check failed, do not check farther,
760          *  1 : previous check successed, check farther
761          */
762         int not_modified = -1;
763         int perm = 0;
764         struct dentry *dentry = NULL;
765         struct vfsmount *mnt = NULL;
766         struct inode *inode;
767         const char *filename;
768
769         /*
770          * Do not do any etag or last_modified header checking
771          * if both unset.
772          */
773         if (!tux_generate_etags && !tux_generate_last_mod)
774                 not_modified = 0;
775
776 repeat_lookup:
777         if (req->dentry)
778                 TUX_BUG();
779
780         filename = req->objectname;
781         Dprintk("will look up {%s} (%d)\n", filename, req->objectname_len);
782         Dprintk("current->fsuid: %d, current->fsgid: %d, ngroups: %d\n",
783                 current->fsuid, current->fsgid, current->group_info->ngroups);
784
785         dentry = tux_lookup(req, filename, flag, &mnt);
786
787 #define INDEX "/index.html"
788
789         if (!dentry || IS_ERR(dentry)) {
790                 if (PTR_ERR(dentry) == -EWOULDBLOCKIO)
791                         goto cachemiss;
792
793                 if (tux_http_dir_indexing && (req->lookup_dir == 1)) {
794                         // undo the index.html appending:
795                         req->objectname_len -= sizeof(INDEX)-1;
796                         req->objectname[req->objectname_len] = 0;
797                         req->lookup_dir = 2;
798                         goto repeat_lookup;
799                 }
800                 if (!req->lookup_404) {
801                         int len = strlen(tux_404_page);
802                         memcpy(req->objectname, tux_404_page, len);
803                         req->objectname[len] = 0;
804                         req->objectname_len = len;
805                         req->lookup_404 = 1;
806                         req->status = 404;
807                         goto repeat_lookup;
808                 }
809                 TDprintk("abort - lookup error.\n");
810                 goto abort;
811         }
812
813         Dprintk("SUCCESS, looked up {%s} == dentry %p (inode %p, count %d.)\n", filename, dentry, dentry->d_inode, atomic_read(&dentry->d_count));
814         inode = dentry->d_inode;
815
816         /*
817          * At this point we have a real, non-negative dentry.
818          */
819         perm = tux_permission(inode);
820
821         if ((perm < 0) || (!S_ISDIR(dentry->d_inode->i_mode)
822                                 && !S_ISREG(dentry->d_inode->i_mode))) {
823                 Dprintk("FAILED trusted dentry %p permission %d.\n", dentry, perm);
824                 req->status = 403;
825                 goto abort;
826         }
827         if ((req->lookup_dir != 2) && S_ISDIR(dentry->d_inode->i_mode)) {
828                 if (req->lookup_dir || (req->objectname_len +
829                                  sizeof(INDEX) >= MAX_OBJECTNAME_LEN)) {
830                         req->status = 403;
831                         goto abort;
832                 }
833                 if (req->objectname_len && (req->objectname[req->objectname_len-1] != '/')) {
834                         dput(dentry);
835                         mntput(mnt);
836                         req->lookup_dir = 0;
837                         return 2;
838                 }
839                 memcpy(req->objectname + req->objectname_len,
840                                                 INDEX, sizeof(INDEX));
841                 req->objectname_len += sizeof(INDEX)-1;
842                 req->lookup_dir = 1;
843                 dput(dentry);
844                 mntput(mnt);
845                 mnt = NULL;
846                 dentry = NULL;
847                 goto repeat_lookup;
848         }
849         if (tux_max_object_size && (inode->i_size > tux_max_object_size)) {
850                 TDprintk("too big object, %Ld bytes.\n", inode->i_size);
851                 req->status = 403;
852                 goto abort;
853         }
854         req->total_file_len = inode->i_size;
855         req->mtime = inode->i_mtime.tv_sec;
856
857         {
858                 loff_t num = req->total_file_len;
859                 int nr_digits = 0;
860                 unsigned long modulo;
861                 char * etag_p = req->etag;
862                 char digits [30];
863
864                 do {
865                         modulo = do_div(num, 10);
866                         digits[nr_digits++] = '0' + modulo;
867                 } while (num);
868
869                 req->lendigits = nr_digits;
870                 req->etaglen = nr_digits;
871
872                 while (nr_digits)
873                         *etag_p++ = digits[--nr_digits];
874
875                 *etag_p++ = '-';
876                 num = req->mtime;
877                 nr_digits = 0;
878
879                 do {
880                         digits[nr_digits++] = 'a' + num % 16;
881                                 num /= 16;
882                 } while (num);
883                 req->etaglen += nr_digits+1;
884                 while (nr_digits)
885                         *etag_p++ = digits[--nr_digits];
886                 *etag_p = 0;
887         }
888
889         if ((req->if_none_match_len >= req->etaglen) && (abs(not_modified) == 1)) {
890
891                 char * etag_p = req->etag;
892                 const char * match_p = req->if_none_match_str;
893                 int pos = req->etaglen - 1;
894                 int matchpos = req->etaglen - 1;
895
896                 do {
897                         while (etag_p[matchpos--] == match_p[pos--])
898                                 if (matchpos < 0)
899                                         break;
900                         if (matchpos < 0)
901                                 pos = req->if_none_match_len;
902                         else {
903                                 if (match_p[pos+1] == ',')
904                                         pos += req->etaglen + 2;
905                                 else
906                                         pos += req->etaglen-matchpos;
907                                 matchpos = req->etaglen - 1;
908                         }
909                 } while (pos < req->if_none_match_len);
910
911                 if (matchpos < 0) {
912                         not_modified = 1;
913                         TDprintk("Etag matched.\n");
914                 } else
915                         not_modified = 0;
916         }
917
918         if ((req->if_modified_since_len >= 24) && (abs(not_modified) == 1)) {
919                 if (parse_time(req->if_modified_since_str, req->if_modified_since_len) >= req->mtime ) {
920                         not_modified = 1;
921                         Dprintk("Last-Modified matched.\n");
922                 } else
923                         not_modified = 0;
924         }
925
926         if (not_modified == 1) {
927                 req->status = 304;
928                 goto abort;
929         }
930
931         Dprintk("looked up cached dentry %p, (count %d.)\n", dentry, dentry ? atomic_read(&dentry->d_count) : -1 );
932
933         url_hist_hit(req->total_file_len);
934 out:
935         install_req_dentry(req, dentry, mnt);
936         req->lookup_dir = 0;
937         return 0;
938
939 cachemiss:
940         return 1;
941
942 abort:
943         if (dentry) {
944                 if (!IS_ERR(dentry))
945                         dput(dentry);
946                 dentry = NULL;
947         }
948         if (mnt) {
949                 if (!IS_ERR(mnt))
950                         mntput(mnt);
951                 mnt = NULL;
952         }
953 #ifdef CONFIG_TUX_DEBUG
954         if (!not_modified) {
955                 TDprintk("req %p has lookup errors!\n", req);
956                 if (tux_TDprintk)
957                         print_req(req);
958         }
959 #endif
960         req_err(req);
961         goto out;
962 }
963
964 int handle_gzip_req (tux_req_t *req, unsigned int flags)
965 {
966         char *curr = req->objectname + req->objectname_len;
967         struct dentry *dentry;
968         struct vfsmount *mnt = NULL;
969         struct inode *inode, *orig_inode;
970         loff_t size, orig_size;
971
972         *curr++ = '.';
973         *curr++ = 'g';
974         *curr++ = 'z';
975         *curr++ = 0;
976         req->objectname_len += 3;
977
978         dentry = tux_lookup(req, req->objectname, flags, &mnt);
979
980         req->objectname_len -= 3;
981         req->objectname[req->objectname_len] = 0;
982
983         if (!dentry)
984                 return 0;
985         if (IS_ERR(dentry)) {
986                 if (PTR_ERR(dentry) == -EWOULDBLOCKIO) {
987                         release_req_dentry(req);
988                         return 1;
989                 }
990                 return 0;
991         }
992
993         inode = dentry->d_inode;
994         size = inode->i_size;
995         orig_inode = req->dentry->d_inode;
996         orig_size = orig_inode->i_size;
997
998         if (!tux_permission(inode)
999                         && (size < orig_size)
1000                         && (inode->i_mtime.tv_sec >= orig_inode->i_mtime.tv_sec)) {
1001
1002                 release_req_dentry(req);
1003                 install_req_dentry(req, dentry, mnt);
1004                 req->total_file_len = req->output_len = size;
1005                 Dprintk("content WILL be gzipped!\n");
1006                 req->content_gzipped = 1;
1007         } else {
1008                 dput(dentry);
1009                 mntput(mnt);
1010         }
1011
1012         return 0;
1013 }
1014
1015 static DEFINE_SPINLOCK(mimetypes_lock);
1016
1017 static LIST_HEAD(mimetypes_head);
1018
1019 static mimetype_t default_mimetype = { .type = "text/plain", .type_len = 10, .expire_str = "", .expire_str_len = 0 };
1020
1021 #define MAX_MIMETYPE_LEN 128
1022 #define MAX_CACHE_CONTROL_AGE_LEN 30
1023
1024 void add_mimetype (char *new_ext, char *new_type, char *new_expire)
1025 {
1026         int type_len = strlen(new_type);
1027         int ext_len = strlen(new_ext);
1028         int expire_len = strlen(new_expire);
1029         mimetype_t *mime;
1030         char *ext, *type, *expire;
1031
1032         if (type_len > MAX_MIMETYPE_LEN)
1033                 type_len = MAX_MIMETYPE_LEN;
1034         if (ext_len > MAX_URI_LEN)
1035                 ext_len = MAX_URI_LEN;
1036         if (expire_len > MAX_CACHE_CONTROL_AGE_LEN)
1037                 expire_len = MAX_CACHE_CONTROL_AGE_LEN;
1038
1039         mime = tux_kmalloc(sizeof(*mime));
1040         memset(mime, 0, sizeof(*mime));
1041         ext = tux_kmalloc(ext_len + 1);
1042         type = tux_kmalloc(type_len + 1);
1043         expire = tux_kmalloc(expire_len + 1);
1044
1045         strncpy(ext, new_ext, ext_len);
1046         strncpy(type, new_type, type_len);
1047         strncpy(expire, new_expire, expire_len);
1048
1049         // in case one of the above parameters was too long :
1050
1051         ext[ext_len] = '\0';
1052         type[type_len] = '\0';
1053         expire[expire_len] = '\0';
1054
1055         mime->ext = ext;
1056         mime->ext_len = ext_len;
1057
1058         mime->type = type;
1059         mime->type_len = type_len;
1060
1061         mime->expire_str = expire;
1062         mime->expire_str_len = expire_len;
1063
1064         mime->special = NORMAL_MIME_TYPE;
1065         if (!strcmp(type, "TUX/redirect"))
1066                 mime->special = MIME_TYPE_REDIRECT;
1067         if (!strcmp(type, "TUX/CGI"))
1068                 mime->special = MIME_TYPE_CGI;
1069         if (!strcmp(type, "TUX/module"))
1070                 mime->special = MIME_TYPE_MODULE;
1071
1072         spin_lock(&mimetypes_lock);
1073         list_add(&mime->list, &mimetypes_head);
1074         spin_unlock(&mimetypes_lock);
1075 }
1076
1077 static inline int ext_matches (char *file, int len, char *ext, int extlen)
1078 {
1079         int i;
1080         char *tmp = file + len-1;
1081         char *tmp2 = ext + extlen-1;
1082
1083         if (len < extlen)
1084                 return 0;
1085
1086         for (i = 0; i < extlen; i++) {
1087                 if (*tmp != *tmp2)
1088                         return 0;
1089                 tmp--;
1090                 tmp2--;
1091         }
1092         return 1;
1093 }
1094
1095 /*
1096  * Overhead is not a problem, we cache the MIME type
1097  * in the dentry.
1098  */
1099 static mimetype_t * lookup_mimetype (tux_req_t *req)
1100 {
1101         char *objectname = req->objectname;
1102         int len = req->objectname_len;
1103         mimetype_t *mime = NULL;
1104         struct list_head *head, *tmp, *tmp1, *tmp2, *tmp3;
1105
1106         if (!memchr(objectname, '.', len))
1107                 goto out;
1108
1109         spin_lock(&mimetypes_lock);
1110         head = &mimetypes_head;
1111         tmp = head->next;
1112
1113         while (tmp != head) {
1114                 mime = list_entry(tmp, mimetype_t, list);
1115                 if (ext_matches(objectname, len, mime->ext, mime->ext_len)) {
1116                         /*
1117                          * Percolate often-used mimetypes up:
1118                          */
1119                         if (tmp->prev != &mimetypes_head) {
1120                                 tmp1 = tmp;
1121                                 tmp2 = tmp->prev;
1122                                 tmp3 = tmp->prev->prev;
1123                                 list_del(tmp1);
1124                                 list_del(tmp2);
1125                                 list_add(tmp, tmp3);
1126                                 list_add(tmp2, tmp);
1127                         }
1128                         break;
1129                 } else
1130                         mime = NULL;
1131                 tmp = tmp->next;
1132         }
1133         spin_unlock(&mimetypes_lock);
1134
1135 out:
1136         if (!mime)
1137                 mime = &default_mimetype;
1138         return mime;
1139 }
1140
1141 void free_mimetypes (void)
1142 {
1143         struct list_head *head, *tmp, *next;
1144         mimetype_t *mime;
1145
1146         spin_lock(&mimetypes_lock);
1147         head = &mimetypes_head;
1148         tmp = head->next;
1149
1150         while (tmp != head) {
1151                 next = tmp->next;
1152                 mime = list_entry(tmp, mimetype_t, list);
1153                 list_del(tmp);
1154
1155                 kfree(mime->ext);
1156                 mime->ext = NULL;
1157                 kfree(mime->type);
1158                 mime->type = NULL;
1159                 kfree(mime);
1160
1161                 tmp = next;
1162         }
1163         spin_unlock(&mimetypes_lock);
1164 }
1165
1166 /*
1167  * Various constant HTTP responses:
1168  */
1169
1170 static const char forbidden[] =
1171         "HTTP/1.1 403 Forbidden\r\n"
1172         "Connection: Keep-Alive\r\n" \
1173         "Content-Length: 24\r\n\r\n"
1174         "<HTML> Forbidden </HTML>";
1175
1176 static const char not_found[] =
1177         "HTTP/1.1 404 Not Found\r\n"
1178         "Connection: Keep-Alive\r\n" \
1179         "Content-Length: 29\r\n\r\n"
1180         "<HTML> Page Not Found </HTML>";
1181
1182 #define NOTMODIFIED_1 \
1183         "HTTP/1.1 304 Not Modified\r\n" \
1184         "Connection: Keep-Alive\r\n" \
1185         "Date: "
1186
1187 #define NOTMODIFIED_1_LEN (sizeof(NOTMODIFIED_1) - 1)
1188
1189 #define NOTMODIFIED_2 \
1190         "\r\nETag: \""
1191
1192 #define NOTMODIFIED_2_LEN (sizeof(NOTMODIFIED_2) - 1)
1193
1194 #define NOTMODIFIED_3 \
1195         "\"\r\n\r\n"
1196
1197 #define NOTMODIFIED_3_LEN (sizeof(NOTMODIFIED_3) - 1)
1198
1199 #define REDIRECT_1 \
1200         "HTTP/1.1 301 Moved Permanently\r\n" \
1201         "Location: http://"
1202
1203 #define REDIRECT_1_LEN (sizeof(REDIRECT_1) - 1)
1204
1205 #define REDIRECT_2 \
1206         "/\r\nContent-Length: 36\r\n" \
1207         "Connection: Keep-Alive\r\n" \
1208         "Content-Type: text/html\r\n\r\n" \
1209         "<HTML> 301 Moved Permanently </HTML>"
1210
1211 #define REDIRECT_2_LEN (sizeof(REDIRECT_2) - 1)
1212
1213 void send_async_err_forbidden (tux_req_t *req)
1214 {
1215         send_async_message(req, forbidden, 403, 1);
1216 }
1217
1218 void send_async_err_not_found (tux_req_t *req)
1219 {
1220         send_async_message(req, not_found, 404, 1);
1221 }
1222
1223 static void send_ret_notmodified (tux_req_t *req)
1224 {
1225         char *buf;
1226         int size;
1227
1228         size = NOTMODIFIED_1_LEN + DATE_LEN - 1 + NOTMODIFIED_2_LEN + req->etaglen + NOTMODIFIED_3_LEN;
1229         buf = get_abuf(req, size);
1230         memcpy(buf, NOTMODIFIED_1, NOTMODIFIED_1_LEN);
1231         buf += NOTMODIFIED_1_LEN;
1232         memcpy(buf, tux_date, DATE_LEN-1);
1233         buf += DATE_LEN-1;
1234         memcpy(buf, NOTMODIFIED_2, NOTMODIFIED_2_LEN);
1235         buf += NOTMODIFIED_2_LEN;
1236         memcpy(buf, &req->etag, req->etaglen);
1237         buf += req->etaglen;
1238         memcpy(buf, NOTMODIFIED_3, NOTMODIFIED_3_LEN);
1239         buf += NOTMODIFIED_3_LEN;
1240
1241         req->status = 304;
1242         send_abuf(req, size, MSG_DONTWAIT);
1243         add_req_to_workqueue(req);
1244 }
1245
1246 static void send_ret_redirect (tux_req_t *req, int cachemiss)
1247 {
1248         char *buf;
1249         unsigned int size;
1250         unsigned int uts_len = 0;
1251
1252         size = REDIRECT_1_LEN;
1253         if (req->host_len)
1254                 size += req->host_len;
1255         else {
1256                 down_read(&uts_sem);
1257                 uts_len = strlen(system_utsname.nodename);
1258                 size += uts_len;
1259         }
1260         if (req->objectname[0] != '/')
1261                 size++;
1262         size += req->objectname_len;
1263         size += REDIRECT_2_LEN;
1264
1265         if (size > PAGE_SIZE) {
1266                 req->error = TUX_ERROR_CONN_CLOSE;
1267                 zap_request(req, cachemiss);
1268                 return;
1269         }
1270
1271         buf = get_abuf(req, size);
1272
1273         memcpy(buf, REDIRECT_1, REDIRECT_1_LEN);
1274         buf += REDIRECT_1_LEN;
1275
1276         Dprintk("req %p, host: %s, host_len: %d.\n", req, req->host, req->host_len);
1277         if (req->host_len) {
1278                 memcpy(buf, req->host, req->host_len);
1279                 buf += req->host_len;
1280         } else {
1281                 memcpy(buf, system_utsname.nodename, uts_len);
1282                 up_read(&uts_sem);
1283                 buf += uts_len;
1284         }
1285         if (req->objectname[0] != '/') {
1286                 buf[0] = '/';
1287                 buf++;
1288         }
1289
1290         memcpy(buf, req->objectname, req->objectname_len);
1291         buf += req->objectname_len;
1292
1293         memcpy(buf, REDIRECT_2, REDIRECT_2_LEN);
1294         buf += REDIRECT_2_LEN;
1295
1296         req->status = 301;
1297         send_abuf(req, size, MSG_DONTWAIT);
1298         add_req_to_workqueue(req);
1299 }
1300
1301 static void http_got_request (tux_req_t *req)
1302 {
1303         req->host[0] = 0;
1304         req->host_len = 0;
1305         add_tux_atom(req, parse_request);
1306         add_req_to_workqueue(req);
1307 }
1308
1309
1310 tux_attribute_t * lookup_tux_attribute (tux_req_t *req)
1311 {
1312         tux_attribute_t *attr;
1313         struct inode *inode;
1314         mimetype_t *mime;
1315
1316         attr = tux_kmalloc(sizeof(*attr));
1317         memset(attr, 0, sizeof(*attr));
1318
1319         mime = lookup_mimetype(req);
1320
1321         inode = req->dentry->d_inode;
1322         if (!inode->i_uid && !inode->i_gid) {
1323                 if (mime->special == MIME_TYPE_MODULE) {
1324                         attr->tcapi = lookup_tuxmodule(req->objectname);
1325                         if (!attr->tcapi) {
1326                                 req_err(req);
1327                                 mime = &default_mimetype;
1328                         }
1329                 }
1330         } else {
1331                 if (mime->special && (mime->special != MIME_TYPE_REDIRECT))
1332                         mime = &default_mimetype;
1333         }
1334         attr->mime = mime;
1335
1336         return attr;
1337 }
1338
1339 static void handle_range(tux_req_t *req)
1340 {
1341         if (req->if_range_len) {
1342                 time_t range_time;
1343
1344                 range_time = parse_time(req->if_range_str, req->if_range_len);
1345
1346                 /*
1347                  * If the file is newer then we send the whole file.
1348                  */
1349                 if (range_time < req->mtime )
1350                         goto out_no_range;
1351         }
1352         /* if no offset_end was specified then default to 'end of file': */
1353         if (!req->offset_end)
1354                 req->offset_end = req->total_file_len;
1355         /*
1356          * Sanity checks:
1357          *
1358          *  - is the range between 0...file_len-1 ?
1359          *  - is offset_end after offset_start?
1360          *
1361          * (note that offset_end is higher by 1)
1362          */
1363         if ((req->offset_end > req->total_file_len) ||
1364                         (req->offset_start >= req->total_file_len) ||
1365                         (req->offset_end <= req->offset_start))
1366                 goto out_no_range;
1367         /*
1368          * If the range is 0...file_len-1 then send the whole file:
1369          */
1370         if (!req->offset_start && (req->offset_end == req->total_file_len))
1371                 goto out_no_range;
1372
1373         /* ok, the range is valid, use it: */
1374
1375         req->output_len = req->offset_end - req->offset_start;
1376         req->in_file->f_pos = req->offset_start;
1377         return;
1378
1379 out_no_range:
1380         req->offset_start = 0;
1381         req->offset_end = 0;
1382 }
1383
1384 static void http_pre_header (tux_req_t *req, int push);
1385 static void http_post_header (tux_req_t *req, int cachemiss);
1386 static void http_send_body (tux_req_t *req, int cachemiss);
1387
1388 #define DIRLIST_HEAD_1 "\
1389 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\
1390 <HTML><HEAD><TITLE>Index of %s</TITLE></HEAD><BODY>\
1391 <H1>Index of %s </H1><PRE><HR>\n%s"
1392
1393 #define DIRLIST_HEAD_2 "\
1394 <IMG SRC=\"/icons/back.gif\"ALT=\"[DIR]\"> <A HREF=\"../\">Parent Directory</A>\n"
1395
1396 #define DIRLIST_HEAD_SIZE (sizeof(DIRLIST_HEAD_1) + sizeof(DIRLIST_HEAD_2))
1397
1398 static void http_dirlist_head (tux_req_t *req, int cachemiss)
1399 {
1400         char *buf1, *buf2, *path;
1401         int len;
1402
1403         buf1 = (char *)__get_free_page(GFP_KERNEL);
1404         buf2 = (char *)__get_free_page(GFP_KERNEL);
1405         if (!buf1 || !buf2)
1406                 goto out;
1407         path = tux_print_path(req, req->dentry, req->mnt, buf1, PAGE_SIZE);
1408         if (path[0] == '/' && path[1] == '/' && !path[3])
1409                 path = "/";
1410         if (2*strlen(path) + DIRLIST_HEAD_SIZE >= PAGE_SIZE)
1411                 goto out;
1412         len = sprintf(buf2, DIRLIST_HEAD_1, path, path, req->dentry == req->docroot_dentry ? "" : DIRLIST_HEAD_2);
1413         __send_async_message(req, buf2, 200, len, 0);
1414
1415 out:
1416         if (buf1)
1417                 free_page((unsigned long)buf1);
1418         if (buf2)
1419                 free_page((unsigned long)buf2);
1420 }
1421
1422 #define DIRLIST_TAIL "\
1423 </PRE><HR><ADDRESS><IMG SRC=\"/icons/tuxlogo.gif\"ALIGN=\"MIDDLE\"ALT=\"[TUX]\">Powered by Linux/TUX 3.0</ADDRESS>\n</BODY></HTML>"
1424
1425 static void http_dirlist_tail (tux_req_t *req, int cachemiss)
1426 {
1427         __send_async_message(req, DIRLIST_TAIL, 200, sizeof(DIRLIST_TAIL)-1, 1);
1428 }
1429
1430 static void http_dirlist (tux_req_t *req, int cachemiss)
1431 {
1432         int head = (req->method == METHOD_HEAD);
1433
1434         req->lookup_dir = 3;
1435         clear_keepalive(req);
1436         if (!head) {
1437                 add_tux_atom(req, http_dirlist_tail);
1438                 add_tux_atom(req, list_directory);
1439                 add_tux_atom(req, http_dirlist_head);
1440         }
1441         http_pre_header(req, head);
1442         add_req_to_workqueue(req);
1443 }
1444
1445 static char *host_path_hash(tux_req_t *req, char *tmp)
1446 {
1447         if (req->host_len < 2)
1448                 return NULL;
1449
1450         switch (mass_hosting_hash) {
1451                 default:
1452                 case 0:
1453                         return req->host;
1454                 case 1:
1455
1456                         // www.ABCDEFG.com => A/ABCDEFG.com
1457
1458                         tmp[0] = req->host[0];
1459                         tmp[1] = '/';
1460                         memcpy(tmp + 2, req->host, req->host_len);
1461                         tmp[req->host_len + 2] = 0;
1462
1463                         return tmp;
1464                 case 2:
1465                         // www.ABCDEFG.com => A/AB/ABCDEFG.com
1466
1467                         tmp[0] = req->host[0];
1468                         tmp[1] = '/';
1469                         tmp[2] = req->host[0];
1470                         tmp[3] = req->host[1];
1471                         tmp[4] = '/';
1472                         memcpy(tmp + 5, req->host, req->host_len);
1473                         tmp[req->host_len + 5] = 0;
1474
1475                         return tmp;
1476                 case 3:
1477                         // www.ABCDEFG.com => A/AB/ABC/ABCDEFG.com
1478
1479                         tmp[0] = req->host[0];
1480                         tmp[1] = '/';
1481                         tmp[2] = req->host[0];
1482                         tmp[3] = req->host[1];
1483                         tmp[4] = '/';
1484                         tmp[5] = req->host[0];
1485                         tmp[6] = req->host[1];
1486                         tmp[7] = req->host[2];
1487                         tmp[8] = '/';
1488                         memcpy(tmp + 9, req->host, req->host_len);
1489                         tmp[req->host_len + 9] = 0;
1490
1491                         return tmp;
1492         }
1493 }
1494
1495 static struct dentry * vhost_lookup (tux_req_t *req, struct nameidata* base, struct vfsmount **mnt)
1496 {
1497         struct dentry *dentry = NULL;
1498         // 255.255.255.255
1499         char ip [3+1+3+1+3+1+3 + 2];
1500
1501         if (req->virtual >= TUX_VHOST_IP) {
1502                 sprintf(ip, "%d.%d.%d.%d",
1503                                 NIPQUAD(inet_sk(req->sock->sk)->rcv_saddr));
1504                 dentry = __tux_lookup (req, ip, base, mnt);
1505                 if (!dentry || IS_ERR(dentry)) {
1506                         if (PTR_ERR(dentry) == -EWOULDBLOCKIO)
1507                                 return dentry;
1508                         base->dentry = dget(req->proto->main_docroot.dentry);
1509                         base->mnt = mntget(req->proto->main_docroot.mnt);
1510                         goto lookup_default;
1511                 }
1512                 if (req->virtual == TUX_VHOST_IP)
1513                         goto done;
1514
1515                 // fall through in mixed mode:
1516         }
1517
1518         if (!req->host_len) {
1519 lookup_default:
1520                 *mnt = NULL;
1521                 dentry = __tux_lookup (req, tux_default_vhost, base, mnt);
1522         } else {
1523                 char tmp [MAX_HOST_LEN*2];
1524                 char *host_path;
1525
1526                 host_path = host_path_hash(req, tmp);
1527                 Dprintk("host path hash returned: {%s}\n", host_path);
1528
1529                 dentry = NULL;
1530                 if (host_path) {
1531                         *mnt = NULL;
1532                         dentry = __tux_lookup (req, host_path, base, mnt);
1533                 }
1534                 if (!dentry || IS_ERR(dentry)) {
1535                         if (PTR_ERR(dentry) == -EWOULDBLOCKIO)
1536                                 return dentry;
1537                         base->dentry = dget(req->proto->main_docroot.dentry);
1538                         base->mnt = mntget(req->proto->main_docroot.mnt);
1539                         if (req->virtual >= TUX_VHOST_IP) {
1540                                 *mnt = NULL;
1541                                 dentry = __tux_lookup (req, ip, base, mnt);
1542                                 if (!dentry || IS_ERR(dentry)) {
1543                                         if (PTR_ERR(dentry) == -EWOULDBLOCKIO)
1544                                                 return dentry;
1545                                         base->dentry = dget(req->proto->main_docroot.dentry);
1546                                         base->mnt = mntget(req->proto->main_docroot.mnt);
1547                                 }
1548                         }
1549                         goto lookup_default;
1550                 }
1551         }
1552 done:
1553         return dentry;
1554 }
1555
1556 static void http_lookup_vhost (tux_req_t *req, int cachemiss)
1557 {
1558         struct dentry *dentry;
1559         struct nameidata base = { };
1560         struct vfsmount *mnt = NULL;
1561         unsigned int flag = cachemiss ? 0 : LOOKUP_ATOMIC;
1562
1563         Dprintk("http_lookup_vhost(%p, %d, virtual: %d, host: %s (%d).)\n", req, flag, req->virtual, req->host, req->host_len);
1564
1565         base.flags = LOOKUP_FOLLOW|flag;
1566         base.last_type = LAST_ROOT;
1567         base.dentry = dget(req->proto->main_docroot.dentry);
1568         base.mnt = mntget(req->proto->main_docroot.mnt);
1569
1570         dentry = vhost_lookup(req, &base, &mnt);
1571
1572         Dprintk("looked up dentry %p.\n", dentry);
1573
1574         if (dentry && !IS_ERR(dentry) && !dentry->d_inode)
1575                 TUX_BUG();
1576
1577         if (!dentry || IS_ERR(dentry)) {
1578                 if (PTR_ERR(dentry) == -EWOULDBLOCKIO) {
1579                         add_tux_atom(req, http_lookup_vhost);
1580                         queue_cachemiss(req);
1581                         return;
1582                 }
1583                 goto abort;
1584         }
1585
1586         req->docroot_dentry = dentry;
1587         req->docroot_mnt = mnt;
1588
1589         add_tux_atom(req, http_process_message);
1590         add_req_to_workqueue(req);
1591         return;
1592 abort:
1593         if (dentry) {
1594                 if (!IS_ERR(dentry))
1595                         dput(dentry);
1596                 dentry = NULL;
1597         }
1598         if (mnt) {
1599                 if (!IS_ERR(mnt))
1600                         mntput(mnt);
1601                 mnt = NULL;
1602         }
1603         req_err(req);
1604         add_req_to_workqueue(req);
1605 }
1606
1607 static void http_process_message (tux_req_t *req, int cachemiss)
1608 {
1609         tux_attribute_t *attr;
1610         int missed;
1611         unsigned int lookup_flag = cachemiss ? 0 : LOOKUP_ATOMIC;
1612
1613         Dprintk("handling req %p, cachemiss: %d.\n", req, cachemiss);
1614
1615         /*
1616          * URL redirection support - redirect all valid requests
1617          * to the first userspace module.
1618          */
1619         if (tux_all_userspace) {
1620                 tcapi_template_t *tcapi = get_first_usermodule();
1621                 if (tcapi) {
1622                         req->usermode = 1;
1623                         req->usermodule_idx = tcapi->userspace_id;
1624                         goto usermode;
1625                 }
1626         }
1627         missed = lookup_url(req, lookup_flag);
1628         if (missed == 2) {
1629                 if (req->query_str) {
1630                         req->error = TUX_ERROR_REDIRECT;
1631                         goto error;
1632                 }
1633                 send_ret_redirect(req, cachemiss);
1634                 return;
1635         }
1636         if (req->error)
1637                 goto error;
1638         if (missed) {
1639 cachemiss:
1640                 if (cachemiss)
1641                         TUX_BUG();
1642                 Dprintk("uncached request.\n");
1643                 INC_STAT(static_lookup_cachemisses);
1644                 if (req->dentry)
1645                         TUX_BUG();
1646                 add_tux_atom(req, http_process_message);
1647                 queue_cachemiss(req);
1648                 return;
1649         }
1650         /*
1651          * HTML directory indexing.
1652          */
1653         if (S_ISDIR(req->dentry->d_inode->i_mode))
1654                 return http_dirlist(req, cachemiss);
1655         if (!S_ISREG(req->dentry->d_inode->i_mode))
1656                 TUX_BUG();
1657
1658
1659         attr = req->dentry->d_extra_attributes;
1660         if (!attr) {
1661                 attr = lookup_tux_attribute(req);
1662                 if (!attr)
1663                         TUX_BUG();
1664                 req->dentry->d_extra_attributes = attr;
1665         }
1666         if (attr->mime)
1667                 Dprintk("using MIME type %s:%s, %d.\n", attr->mime->type, attr->mime->ext, attr->mime->special);
1668         if (attr->tcapi) {
1669                 req->usermode = 1;
1670                 req->usermodule_idx = attr->tcapi->userspace_id;
1671                 if (req->module_dentry)
1672                         TUX_BUG();
1673                 req->module_dentry = dget(req->dentry);
1674                 release_req_dentry(req);
1675                 goto usermode;
1676         }
1677
1678         switch (attr->mime->special) {
1679                 case MIME_TYPE_MODULE:
1680                         req->usermode = 1;
1681                         goto usermode;
1682
1683                 case MIME_TYPE_REDIRECT:
1684                         req->error = TUX_ERROR_REDIRECT;
1685                         goto error;
1686
1687                 case MIME_TYPE_CGI:
1688 #ifdef CONFIG_TUX_EXTCGI
1689                         Dprintk("CGI request %p.\n", req);
1690                         query_extcgi(req);
1691                         return;
1692 #endif
1693
1694                 default:
1695                         if (req->query_str) {
1696                                 req->error = TUX_ERROR_REDIRECT;
1697                                 goto error;
1698                         }
1699         }
1700         req->attr = attr;
1701         switch (req->method) {
1702                 case METHOD_GET:
1703                 case METHOD_HEAD:
1704                         break;
1705                 default:
1706                         req->error = TUX_ERROR_REDIRECT;
1707                         goto error;
1708         }
1709         if (req->usermode)
1710                 TUX_BUG();
1711
1712         req->output_len = req->total_file_len;
1713         /*
1714          * Do range calculations.
1715          */
1716         if (req->offset_end || req->offset_start)
1717                 handle_range(req);
1718
1719         if (req->may_send_gzip && !req->offset_start && !req->offset_end) {
1720                 if (handle_gzip_req(req, lookup_flag))
1721                         goto cachemiss;
1722                 if ((tux_compression >= 2) && !req->content_gzipped)
1723                         req->content_gzipped = 2;
1724         }
1725         if (req->parsed_len)
1726                 trunc_headers(req);
1727
1728         if (req->error)
1729                 goto error;
1730
1731         add_tux_atom(req, http_send_body);
1732         add_tux_atom(req, http_post_header);
1733
1734         http_pre_header(req, req->method == METHOD_HEAD);
1735
1736         add_req_to_workqueue(req);
1737         return;
1738
1739 error:
1740         if (req->error)
1741                 zap_request(req, cachemiss);
1742         return;
1743
1744 usermode:
1745         add_req_to_workqueue(req);
1746 }
1747
1748 static void http_post_header (tux_req_t *req, int cachemiss)
1749 {
1750 #ifdef CONFIG_TUX_DEBUG
1751         req->bytes_expected = req->output_len;
1752 #endif
1753         req->bytes_sent = 0; // data comes now.
1754
1755         add_req_to_workqueue(req);
1756 }
1757
1758 static void http_send_body (tux_req_t *req, int cachemiss)
1759 {
1760         int ret;
1761
1762         Dprintk("SEND req %p <%p> (sock %p, sk %p) (keepalive: %d, status: %d)\n", req, __builtin_return_address(0), req->sock, req->sock->sk, req->keep_alive, req->status);
1763
1764         SET_TIMESTAMP(req->output_timestamp);
1765
1766         if (req->error) {
1767 #ifdef CONFIG_TUX_DEBUG
1768                 req->bytes_expected = 0;
1769 #endif
1770                 req->in_file->f_pos = 0;
1771                 /*
1772                  * We are in the middle of a file transfer,
1773                  * zap it immediately:
1774                  */
1775                 TDprintk("req->error = TUX_ERROR_CONN_CLOSE.\n");
1776                 req->error = TUX_ERROR_CONN_CLOSE;
1777                 zap_request(req, cachemiss);
1778                 return;
1779         }
1780
1781 repeat:
1782         ret = 0;
1783         if (!req->status)
1784                 req->status = 200;
1785         if (req->method != METHOD_HEAD) {
1786                 ret = generic_send_file(req, req->sock, cachemiss);
1787                 Dprintk("body send-file returned: %d.\n", ret);
1788         } else {
1789 #ifdef CONFIG_TUX_DEBUG
1790                 req->bytes_expected = 0;
1791 #endif
1792         }
1793
1794         switch (ret) {
1795                 case -5:
1796                         add_tux_atom(req, http_send_body);
1797                         output_timeout(req);
1798                         break;
1799                 case -4:
1800                         add_tux_atom(req, http_send_body);
1801                         if (add_output_space_event(req, req->sock)) {
1802                                 del_tux_atom(req);
1803                                 goto repeat;
1804                         }
1805                         break;
1806                 case -3:
1807                         INC_STAT(static_sendfile_cachemisses);
1808                         add_tux_atom(req, http_send_body);
1809                         queue_cachemiss(req);
1810                         break;
1811                 case -1:
1812                         break;
1813                 default:
1814                         req->in_file->f_pos = 0;
1815                         add_req_to_workqueue(req);
1816                         break;
1817         }
1818 }
1819
1820 #define DEFAULT_DATE "Wed, 01 Jan 1970 00:00:01 GMT"
1821
1822 char tux_date [DATE_LEN] = DEFAULT_DATE;
1823
1824 /*
1825  * HTTP header
1826  */
1827
1828 #define HEADER_PART1A \
1829                 "HTTP/1.1 200 OK\r\n" \
1830                 "Content-Type: "
1831
1832 #define HEADER_PART1B \
1833                 "HTTP/1.1 200 OK"
1834
1835 #define HEADER_PART1AP \
1836                 "HTTP/1.1 206 Partial Content\r\n" \
1837                 "Content-Type: "
1838
1839 #define HEADER_PART1BP \
1840                 "HTTP/1.1 206 Partial Content"
1841
1842 #define HEADER_PART1C \
1843                 "HTTP/1.1 404 Page Not Found\r\n" \
1844                 "Content-Type: "
1845
1846 #define HEADER_PART1D \
1847                 "HTTP/1.1 200 OK\r\n" \
1848                 "Content-Type: text/html\r\n" \
1849                 "Connection: close\r\n"
1850
1851 #define HEADER_PART2_keepalive "\r\nConnection: Keep-Alive\r\nDate: "
1852
1853 #define HEADER_PART2_close "\r\nConnection: close\r\nDate: "
1854
1855 #define HEADER_PART2_none "\r\nDate: "
1856
1857 // date "%s"
1858
1859 #define HEADER_PART3A "\r\nContent-Encoding: gzip"
1860 #define HEADER_PART3BX "\r\nContent-Length: "
1861
1862 /*
1863  * Please acknowledge our hard work by not changing this define, or
1864  * at least please acknowledge us by leaving "TUX/2.0 (Linux)" in
1865  * the ID string. Thanks! :-)
1866  */
1867 #define HEADER_PART3BY "\r\nServer: TUX/2.0 (Linux)\r\nContent-Length: "
1868 #define HEADER_PART3C "\r\nETag: \""
1869 #define HEADER_PART3ACC "\r\nAccept-Ranges: bytes"
1870 #define HEADER_PART3L "\r\nLast-Modified: "
1871 #define HEADER_PART3P "\r\nContent-Range: bytes "
1872 #define HEADER_PART3CA "\r\nCache-Control: max-age="
1873 #define HEADER_PART4 "\r\n\r\n"
1874
1875 #define MAX_OUT_HEADER_LEN (sizeof(HEADER_PART1AP) + MAX_MIMETYPE_LEN + \
1876                 sizeof(HEADER_PART2_keepalive) + DATE_LEN + \
1877                 sizeof(HEADER_PART3A) + sizeof(HEADER_PART3BY) + \
1878                 12 + sizeof(HEADER_PART3C) + 21 + sizeof(HEADER_PART3L) + \
1879                 sizeof(HEADER_PART3P) + 32 + \
1880                 DATE_LEN + sizeof(HEADER_PART4) + sizeof(tux_extra_html_header) \
1881                 + sizeof(HEADER_PART3CA) + MAX_CACHE_CONTROL_AGE_LEN)
1882
1883 static void http_pre_header (tux_req_t *req, int head)
1884 {
1885         int partial = req->offset_start | req->offset_end;
1886         unsigned long flags;
1887         char *buf, *curr;
1888         mimetype_t *mime = NULL;
1889         int size;
1890
1891
1892         if (MAX_OUT_HEADER_LEN > PAGE_SIZE)
1893                 TUX_BUG();
1894         if ((req->attr && req->attr->tcapi) || req->usermode)
1895                 TUX_BUG();
1896
1897 #define COPY_STATIC_PART(nr,curr)                                       \
1898         do {    \
1899                 memcpy(curr, HEADER_PART##nr, sizeof(HEADER_PART##nr)-1); \
1900                 curr += sizeof(HEADER_PART##nr)-1;                      \
1901         } while (0)
1902
1903         buf = curr = get_abuf(req, MAX_OUT_HEADER_LEN);
1904
1905         if (req->lookup_dir) {
1906                 COPY_STATIC_PART(1D, curr);
1907                 goto dir_next;
1908         }
1909         mime = req->attr->mime;
1910         if (!mime)
1911                 TUX_BUG();
1912
1913         if (req->status == 404) {
1914                 COPY_STATIC_PART(1C, curr);
1915                 memcpy(curr, mime->type, mime->type_len);
1916                 curr += mime->type_len;
1917         } else {
1918                 if (tux_noid && (mime == &default_mimetype)) {
1919                         if (partial)
1920                                 COPY_STATIC_PART(1BP, curr);
1921                         else
1922                                 COPY_STATIC_PART(1B, curr);
1923                 } else {
1924                         if (partial)
1925                                 COPY_STATIC_PART(1AP, curr);
1926                         else
1927                                 COPY_STATIC_PART(1A, curr);
1928                         memcpy(curr, mime->type, mime->type_len);
1929                         curr += mime->type_len;
1930                 }
1931         }
1932
1933         if (tux_generate_cache_control && mime->expire_str_len) {
1934                 COPY_STATIC_PART(3CA, curr);
1935                 memcpy(curr, mime->expire_str, mime->expire_str_len);
1936                 curr += mime->expire_str_len;
1937         }
1938
1939         if (req->keep_alive /* && (req->version == HTTP_1_0) */)
1940                 COPY_STATIC_PART(2_keepalive, curr);
1941         else if (!req->keep_alive && (req->version == HTTP_1_1))
1942                 COPY_STATIC_PART(2_close, curr);
1943         else
1944                 // HTTP/1.0 default means close
1945                 COPY_STATIC_PART(2_none, curr);
1946
1947 dir_next:
1948         memcpy(curr, tux_date, DATE_LEN-1);
1949         curr += DATE_LEN-1;
1950
1951         if (req->content_gzipped)
1952                 COPY_STATIC_PART(3A, curr);
1953
1954         /*
1955          * Content-Length:
1956          */
1957         if (!req->lookup_dir) {
1958                 if (tux_noid)
1959                         COPY_STATIC_PART(3BX, curr);
1960                 else
1961                         COPY_STATIC_PART(3BY, curr);
1962
1963                 if (partial)
1964                         curr += sprintf(curr, "%Ld", req->output_len);
1965                 else {
1966                         if (req->content_gzipped)
1967                                 curr += sprintf(curr, "%Ld",
1968                                                         req->total_file_len);
1969                         else {
1970                                 memcpy(curr, &req->etag, req->lendigits);
1971                                 curr += req->lendigits;
1972                         }
1973                 }
1974                 if (tux_generate_etags && (req->status != 404)) {
1975                         COPY_STATIC_PART(3C, curr);
1976                         memcpy(curr, &req->etag, req->etaglen);
1977                         curr += req->etaglen;
1978                         curr[0] = '"';
1979                         curr++;
1980                 }
1981                 if (tux_generate_last_mod || tux_generate_etags)
1982                         COPY_STATIC_PART(3ACC, curr);
1983         }
1984         if (tux_generate_last_mod && (req->status != 404)) {
1985                 COPY_STATIC_PART(3L, curr);
1986                 last_mod_time(curr, req->mtime);
1987                 curr += DATE_LEN-1;
1988         }
1989         if (partial) {
1990                 COPY_STATIC_PART(3P, curr);
1991                 curr += sprintf(curr, "%Ld-%Ld/%Ld", req->offset_start,
1992                                 req->offset_end-1, req->total_file_len);
1993         }
1994         COPY_STATIC_PART(4, curr);
1995         /*
1996          * Possibly add an extra HTML header:
1997          */
1998         if (tux_extra_html_header_size && mime && !strcmp(mime->type, "text/html")) {
1999                 unsigned int len = tux_extra_html_header_size;
2000
2001                 memcpy(curr, tux_extra_html_header, len);
2002                 curr += len;
2003         }
2004
2005         size = curr-buf;
2006
2007 #ifdef CONFIG_TUX_DEBUG
2008         *curr = 0;
2009         Dprintk("{%s} [%d/%d]\n", buf, size, strlen(buf));
2010 #endif
2011
2012         flags = MSG_DONTWAIT;
2013         if (!head)
2014                 flags |= MSG_MORE;
2015         send_abuf(req, size, flags);
2016 }
2017
2018 void http_illegal_request (tux_req_t *req, int cachemiss)
2019 {
2020         if (req->status == 304)
2021                 send_ret_notmodified(req);
2022         else {
2023                 if (req->status == 403)
2024                         send_async_err_forbidden(req);
2025                 else
2026                         send_async_err_not_found(req);
2027         }
2028 }
2029
2030 static int http_check_req_err (tux_req_t *req, int cachemiss)
2031 {
2032         if ((req->sock->sk->sk_state <= TCP_SYN_RECV) &&
2033                 !tcp_sk(req->sock->sk)->urg_data)
2034                         return 0;
2035         Dprintk("http_check_req_err(%p,%d): 1 (state: %d, urg: %d)\n",
2036                 req, cachemiss, req->sock->sk->sk_state,
2037                 tcp_sk(req->sock->sk)->urg_data);
2038 #ifdef CONFIG_TUX_DEBUG
2039         req->bytes_expected = 0;
2040 #endif
2041         req->in_file->f_pos = 0;
2042         req->error = TUX_ERROR_CONN_CLOSE;
2043         zap_request(req, cachemiss);
2044
2045         return 1;
2046 }
2047
2048 #define COPY_STR(str) \
2049         do { memcpy(tmp, str, sizeof(str)-1); \
2050         tmp += sizeof(str)-1; } while (0)
2051
2052 static char * http_print_dir_line (tux_req_t *req, char *tmp, char *d_name, int d_len, int d_type, struct dentry *dentry, struct inode *inode)
2053 {
2054         int len, spaces;
2055         loff_t size;
2056
2057         switch (d_type) {
2058         case DT_DIR:
2059                 COPY_STR("<IMG SRC=\"/icons/dir.gif\" ALT=\"[DIR]\">");
2060                 break;
2061         case DT_REG:
2062                 if ((d_len >= 3) &&
2063                         (d_name[d_len-3] == '.') &&
2064                         (d_name[d_len-2] == 'g') &&
2065                         (d_name[d_len-1] == 'z'))
2066                         COPY_STR("<IMG SRC=\"/icons/compressed.gif\" ALT=\"[   ]\">");
2067                 else
2068                 if ((d_len >= 4) &&
2069                         (d_name[d_len-4] == '.') &&
2070                         (d_name[d_len-3] == 't') &&
2071                         (d_name[d_len-2] == 'g') &&
2072                         (d_name[d_len-1] == 'z'))
2073                         COPY_STR("<IMG SRC=\"/icons/compressed.gif\" ALT=\"[   ]\">");
2074                 else
2075                 if ((d_len >= 4) &&
2076                         (d_name[d_len-4] == '.') &&
2077                         (d_name[d_len-3] == 't') &&
2078                         (d_name[d_len-2] == 'x') &&
2079                         (d_name[d_len-1] == 't'))
2080                         COPY_STR("<IMG SRC=\"/icons/text.gif\" ALT=\"[   ]\">");
2081                 else
2082                 if ((d_len >= 4) &&
2083                         (d_name[d_len-4] == '.') &&
2084                         (d_name[d_len-3] == 'b') &&
2085                         (d_name[d_len-2] == 'z') &&
2086                         (d_name[d_len-1] == '2'))
2087                         COPY_STR("<IMG SRC=\"/icons/compressed.gif\" ALT=\"[   ]\">");
2088                 else
2089                 if ((d_len >= 4) &&
2090                         (d_name[d_len-4] == '.') &&
2091                         (d_name[d_len-3] == 'z') &&
2092                         (d_name[d_len-2] == 'i') &&
2093                         (d_name[d_len-1] == 'p'))
2094                         COPY_STR("<IMG SRC=\"/icons/compressed.gif\" ALT=\"[   ]\">");
2095                 else
2096                         COPY_STR("<IMG SRC=\"/icons/file.gif\" ALT=\"[   ]\">");
2097                 break;
2098         case DT_LNK:
2099                 COPY_STR("<IMG SRC=\"/icons/link.gif\" ALT=\"[LNK]\">");
2100                 break;
2101         default:
2102                 if (tux_hide_unreadable)
2103                         goto out_dput;
2104                 COPY_STR("<IMG SRC=\"/icons/unknown.gif\" ALT=\"[   ]\">");
2105                 break;
2106         }
2107
2108 #define LIST_1 " <A HREF=\""
2109 #define LIST_2 "\">"
2110 #define LIST_2_DIR "/\">"
2111 #define LIST_3 "</A> "
2112
2113         COPY_STR(LIST_1);
2114         memcpy(tmp, d_name, d_len);
2115         tmp += d_len;
2116         if (d_type == DT_DIR)
2117                 COPY_STR(LIST_2_DIR);
2118         else
2119                 COPY_STR(LIST_2);
2120         spaces = 0;
2121         len = d_len;
2122
2123         if (len > 25)
2124                 len = 25;
2125         memcpy(tmp, d_name, len);
2126         tmp += len;
2127         if (len != d_len) {
2128                 *tmp++ = '.';
2129                 *tmp++ = '.';
2130         } else {
2131                 if (d_type == DT_DIR)
2132                         *tmp++ = '/';
2133                 else
2134                         spaces++;
2135                 spaces++;
2136         }
2137         COPY_STR(LIST_3);
2138         while (spaces) {
2139                 *tmp++ = ' ';
2140                 spaces--;
2141         }
2142 #define FILL 25
2143         if (d_len < FILL) {
2144                 memset(tmp, ' ', FILL-d_len);
2145                 tmp += FILL-d_len;
2146         }
2147
2148         tmp += time_unix2ls(inode->i_mtime.tv_sec, tmp);
2149         *tmp++ = ' ';
2150
2151         if (d_type != DT_REG) {
2152                 COPY_STR("        - ");
2153                 goto out_size;
2154         }
2155         size = inode->i_size >> 10;
2156         if (size < 1024) {
2157                 tmp += sprintf(tmp, "%8Lik ", size);
2158                 goto out_size;
2159         }
2160         size >>= 10;
2161         if (size < 1024) {
2162                 tmp += sprintf(tmp, "%8LiM ", size);
2163                 goto out_size;
2164         }
2165         size >>= 10;
2166         if (size < 1024) {
2167                 tmp += sprintf(tmp, "%8LiG ", size);
2168                 goto out_size;
2169         }
2170         size >>= 10;
2171         if (size < 1024) {
2172                 tmp += sprintf(tmp, "%8LiT ", size);
2173                 goto out_size;
2174         }
2175         size >>= 10;
2176         tmp += sprintf(tmp, "%8LiT ", size);
2177
2178 out_size:
2179         *tmp++ = '\n';
2180         *tmp = 0;
2181
2182         return tmp;
2183 out_dput:
2184         return NULL;
2185 }
2186
2187 tux_proto_t tux_proto_http = {
2188         .defer_accept = 1,
2189         .can_redirect = 1,
2190         .got_request = http_got_request,
2191         .parse_message = parse_http_message,
2192         .illegal_request = http_illegal_request,
2193         .check_req_err = http_check_req_err,
2194         .print_dir_line = http_print_dir_line,
2195         .name = "http",
2196 };
2197