This commit was manufactured by cvs2svn to create branch 'fedora'.
[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, i;
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         for (i = 0; i < current->group_info->ngroups; i++)
785                 Dprintk(".. group #%d: %d.\n", i, current->groups[i]);
786
787         dentry = tux_lookup(req, filename, flag, &mnt);
788
789 #define INDEX "/index.html"
790
791         if (!dentry || IS_ERR(dentry)) {
792                 if (PTR_ERR(dentry) == -EWOULDBLOCKIO)
793                         goto cachemiss;
794
795                 if (tux_http_dir_indexing && (req->lookup_dir == 1)) {
796                         // undo the index.html appending:
797                         req->objectname_len -= sizeof(INDEX)-1;
798                         req->objectname[req->objectname_len] = 0;
799                         req->lookup_dir = 2;
800                         goto repeat_lookup;
801                 }
802                 if (!req->lookup_404) {
803                         int len = strlen(tux_404_page);
804                         memcpy(req->objectname, tux_404_page, len);
805                         req->objectname[len] = 0;
806                         req->objectname_len = len;
807                         req->lookup_404 = 1;
808                         req->status = 404;
809                         goto repeat_lookup;
810                 }
811                 TDprintk("abort - lookup error.\n");
812                 goto abort;
813         }
814
815         Dprintk("SUCCESS, looked up {%s} == dentry %p (inode %p, count %d.)\n", filename, dentry, dentry->d_inode, atomic_read(&dentry->d_count));
816         inode = dentry->d_inode;
817
818         /*
819          * At this point we have a real, non-negative dentry.
820          */
821         perm = tux_permission(inode);
822
823         if ((perm < 0) || (!S_ISDIR(dentry->d_inode->i_mode)
824                                 && !S_ISREG(dentry->d_inode->i_mode))) {
825                 Dprintk("FAILED trusted dentry %p permission %d.\n", dentry, perm);
826                 req->status = 403;
827                 goto abort;
828         }
829         if ((req->lookup_dir != 2) && S_ISDIR(dentry->d_inode->i_mode)) {
830                 if (req->lookup_dir || (req->objectname_len +
831                                  sizeof(INDEX) >= MAX_OBJECTNAME_LEN)) {
832                         req->status = 403;
833                         goto abort;
834                 }
835                 if (req->objectname_len && (req->objectname[req->objectname_len-1] != '/')) {
836                         dput(dentry);
837                         mntput(mnt);
838                         req->lookup_dir = 0;
839                         return 2;
840                 }
841                 memcpy(req->objectname + req->objectname_len,
842                                                 INDEX, sizeof(INDEX));
843                 req->objectname_len += sizeof(INDEX)-1;
844                 req->lookup_dir = 1;
845                 dput(dentry);
846                 mntput(mnt);
847                 mnt = NULL;
848                 dentry = NULL;
849                 goto repeat_lookup;
850         }
851         if (tux_max_object_size && (inode->i_size > tux_max_object_size)) {
852                 TDprintk("too big object, %Ld bytes.\n", inode->i_size);
853                 req->status = 403;
854                 goto abort;
855         }
856         req->total_file_len = inode->i_size;
857         req->mtime = inode->i_mtime.tv_sec;
858
859         {
860                 loff_t num = req->total_file_len;
861                 int nr_digits = 0;
862                 unsigned long modulo;
863                 char * etag_p = req->etag;
864                 char digits [30];
865
866                 do {
867                         modulo = do_div(num, 10);
868                         digits[nr_digits++] = '0' + modulo;
869                 } while (num);
870
871                 req->lendigits = nr_digits;
872                 req->etaglen = nr_digits;
873
874                 while (nr_digits)
875                         *etag_p++ = digits[--nr_digits];
876
877                 *etag_p++ = '-';
878                 num = req->mtime;
879                 nr_digits = 0;
880
881                 do {
882                         digits[nr_digits++] = 'a' + num % 16;
883                                 num /= 16;
884                 } while (num);
885                 req->etaglen += nr_digits+1;
886                 while (nr_digits)
887                         *etag_p++ = digits[--nr_digits];
888                 *etag_p = 0;
889         }
890
891         if ((req->if_none_match_len >= req->etaglen) && (abs(not_modified) == 1)) {
892
893                 char * etag_p = req->etag;
894                 const char * match_p = req->if_none_match_str;
895                 int pos = req->etaglen - 1;
896                 int matchpos = req->etaglen - 1;
897
898                 do {
899                         while (etag_p[matchpos--] == match_p[pos--])
900                                 if (matchpos < 0)
901                                         break;
902                         if (matchpos < 0)
903                                 pos = req->if_none_match_len;
904                         else {
905                                 if (match_p[pos+1] == ',')
906                                         pos += req->etaglen + 2;
907                                 else
908                                         pos += req->etaglen-matchpos;
909                                 matchpos = req->etaglen - 1;
910                         }
911                 } while (pos < req->if_none_match_len);
912
913                 if (matchpos < 0) {
914                         not_modified = 1;
915                         TDprintk("Etag matched.\n");
916                 } else
917                         not_modified = 0; 
918         }
919
920         if ((req->if_modified_since_len >= 24) && (abs(not_modified) == 1)) {
921                 if (parse_time(req->if_modified_since_str, req->if_modified_since_len) >= req->mtime ) {
922                         not_modified = 1;
923                         Dprintk("Last-Modified matched.\n");
924                 } else
925                         not_modified = 0;
926         }
927
928         if (not_modified == 1) {
929                 req->status = 304;
930                 goto abort;
931         }
932
933         Dprintk("looked up cached dentry %p, (count %d.)\n", dentry, dentry ? atomic_read(&dentry->d_count) : -1 );
934
935         url_hist_hit(req->total_file_len);
936 out:
937         install_req_dentry(req, dentry, mnt);
938         req->lookup_dir = 0;
939         return 0;
940
941 cachemiss:
942         return 1;
943
944 abort:
945         if (dentry) {
946                 if (!IS_ERR(dentry))
947                         dput(dentry);
948                 dentry = NULL;
949         }
950         if (mnt) {
951                 if (!IS_ERR(mnt))
952                         mntput(mnt);
953                 mnt = NULL;
954         }
955 #if CONFIG_TUX_DEBUG
956         if (!not_modified) {
957                 TDprintk("req %p has lookup errors!\n", req);
958                 if (tux_TDprintk)
959                         print_req(req);
960         }
961 #endif
962         req_err(req);
963         goto out;
964 }
965
966 int handle_gzip_req (tux_req_t *req, unsigned int flags)
967 {
968         char *curr = req->objectname + req->objectname_len;
969         struct dentry *dentry;
970         struct vfsmount *mnt = NULL;
971         struct inode *inode, *orig_inode;
972         loff_t size, orig_size;
973
974         *curr++ = '.';
975         *curr++ = 'g';
976         *curr++ = 'z';
977         *curr++ = 0;
978         req->objectname_len += 3;
979
980         dentry = tux_lookup(req, req->objectname, flags, &mnt);
981
982         req->objectname_len -= 3;
983         req->objectname[req->objectname_len] = 0;
984
985         if (!dentry)
986                 return 0;
987         if (IS_ERR(dentry)) {
988                 if (PTR_ERR(dentry) == -EWOULDBLOCKIO) {
989                         release_req_dentry(req);
990                         return 1;
991                 }
992                 return 0;
993         }
994
995         inode = dentry->d_inode;
996         size = inode->i_size;
997         orig_inode = req->dentry->d_inode;
998         orig_size = orig_inode->i_size;
999
1000         if (!tux_permission(inode)
1001                         && (size < orig_size)
1002                         && (inode->i_mtime.tv_sec >= orig_inode->i_mtime.tv_sec)) {
1003
1004                 release_req_dentry(req);
1005                 install_req_dentry(req, dentry, mnt);
1006                 req->total_file_len = req->output_len = size;
1007                 Dprintk("content WILL be gzipped!\n");
1008                 req->content_gzipped = 1;
1009         } else {
1010                 dput(dentry);
1011                 mntput(mnt);
1012         }
1013
1014         return 0;
1015 }
1016
1017 static spinlock_t mimetypes_lock = SPIN_LOCK_UNLOCKED;
1018
1019 static LIST_HEAD(mimetypes_head);
1020
1021 static mimetype_t default_mimetype = { type: "text/plain", type_len: 10, expire_str: "", expire_str_len: 0 };
1022
1023 #define MAX_MIMETYPE_LEN 128
1024 #define MAX_CACHE_CONTROL_AGE_LEN 30
1025
1026 void add_mimetype (char *new_ext, char *new_type, char *new_expire)
1027 {
1028         int type_len = strlen(new_type);
1029         int ext_len = strlen(new_ext);
1030         int expire_len = strlen(new_expire);
1031         mimetype_t *mime;
1032         char *ext, *type, *expire;
1033
1034         if (type_len > MAX_MIMETYPE_LEN)
1035                 type_len = MAX_MIMETYPE_LEN;
1036         if (ext_len > MAX_URI_LEN)
1037                 ext_len = MAX_URI_LEN;
1038         if (expire_len > MAX_CACHE_CONTROL_AGE_LEN)
1039                 expire_len = MAX_CACHE_CONTROL_AGE_LEN;
1040
1041         mime = tux_kmalloc(sizeof(*mime));
1042         memset(mime, 0, sizeof(*mime));
1043         ext = tux_kmalloc(ext_len + 1);
1044         type = tux_kmalloc(type_len + 1);
1045         expire = tux_kmalloc(expire_len + 1);
1046
1047         strncpy(ext, new_ext, ext_len);
1048         strncpy(type, new_type, type_len);
1049         strncpy(expire, new_expire, expire_len);
1050         
1051         // in case one of the above parameters was too long :
1052
1053         ext[ext_len] = '\0';
1054         type[type_len] = '\0';
1055         expire[expire_len] = '\0';
1056
1057         mime->ext = ext;
1058         mime->ext_len = ext_len;
1059
1060         mime->type = type;
1061         mime->type_len = type_len;
1062
1063         mime->expire_str = expire;
1064         mime->expire_str_len = expire_len;
1065
1066         mime->special = NORMAL_MIME_TYPE;
1067         if (!strcmp(type, "TUX/redirect"))
1068                 mime->special = MIME_TYPE_REDIRECT;
1069         if (!strcmp(type, "TUX/CGI"))
1070                 mime->special = MIME_TYPE_CGI;
1071         if (!strcmp(type, "TUX/module"))
1072                 mime->special = MIME_TYPE_MODULE;
1073
1074         spin_lock(&mimetypes_lock);
1075         list_add(&mime->list, &mimetypes_head);
1076         spin_unlock(&mimetypes_lock);
1077 }
1078
1079 static inline int ext_matches (char *file, int len, char *ext, int extlen)
1080 {
1081         int i;
1082         char *tmp = file + len-1;
1083         char *tmp2 = ext + extlen-1;
1084
1085         if (len < extlen)
1086                 return 0;
1087
1088         for (i = 0; i < extlen; i++) {
1089                 if (*tmp != *tmp2)
1090                         return 0;
1091                 tmp--;
1092                 tmp2--;
1093         }
1094         return 1;
1095 }
1096
1097 /*
1098  * Overhead is not a problem, we cache the MIME type
1099  * in the dentry.
1100  */
1101 static mimetype_t * lookup_mimetype (tux_req_t *req)
1102 {
1103         char *objectname = req->objectname;
1104         int len = req->objectname_len;
1105         mimetype_t *mime = NULL;
1106         struct list_head *head, *tmp, *tmp1, *tmp2, *tmp3;
1107
1108         if (!memchr(objectname, '.', len))
1109                 goto out;
1110
1111         spin_lock(&mimetypes_lock);
1112         head = &mimetypes_head;
1113         tmp = head->next;
1114
1115         while (tmp != head) {
1116                 mime = list_entry(tmp, mimetype_t, list);
1117                 if (ext_matches(objectname, len, mime->ext, mime->ext_len)) {
1118                         /*
1119                          * Percolate often-used mimetypes up:
1120                          */
1121                         if (tmp->prev != &mimetypes_head) {
1122                                 tmp1 = tmp;
1123                                 tmp2 = tmp->prev;
1124                                 tmp3 = tmp->prev->prev;
1125                                 list_del(tmp1);
1126                                 list_del(tmp2);
1127                                 list_add(tmp, tmp3);
1128                                 list_add(tmp2, tmp);
1129                         }
1130                         break;
1131                 } else
1132                         mime = NULL;
1133                 tmp = tmp->next;
1134         }
1135         spin_unlock(&mimetypes_lock);
1136
1137 out:
1138         if (!mime)
1139                 mime = &default_mimetype;
1140         return mime;
1141 }
1142
1143 void free_mimetypes (void)
1144 {
1145         struct list_head *head, *tmp, *next;
1146         mimetype_t *mime;
1147
1148         spin_lock(&mimetypes_lock);
1149         head = &mimetypes_head;
1150         tmp = head->next;
1151
1152         while (tmp != head) {
1153                 next = tmp->next;
1154                 mime = list_entry(tmp, mimetype_t, list);
1155                 list_del(tmp);
1156
1157                 kfree(mime->ext);
1158                 mime->ext = NULL;
1159                 kfree(mime->type);
1160                 mime->type = NULL;
1161                 kfree(mime);
1162
1163                 tmp = next;
1164         }
1165         spin_unlock(&mimetypes_lock);
1166 }
1167
1168 /*
1169  * Various constant HTTP responses:
1170  */
1171
1172 static const char forbidden[] =
1173         "HTTP/1.1 403 Forbidden\r\n"
1174         "Connection: Keep-Alive\r\n" \
1175         "Content-Length: 24\r\n\r\n"
1176         "<HTML> Forbidden </HTML>";
1177
1178 static const char not_found[] =
1179         "HTTP/1.1 404 Not Found\r\n"
1180         "Connection: Keep-Alive\r\n" \
1181         "Content-Length: 29\r\n\r\n"
1182         "<HTML> Page Not Found </HTML>";
1183
1184 #define NOTMODIFIED_1 \
1185         "HTTP/1.1 304 Not Modified\r\n" \
1186         "Connection: Keep-Alive\r\n" \
1187         "Date: "
1188
1189 #define NOTMODIFIED_1_LEN (sizeof(NOTMODIFIED_1) - 1)
1190
1191 #define NOTMODIFIED_2 \
1192         "\r\nETag: \""
1193
1194 #define NOTMODIFIED_2_LEN (sizeof(NOTMODIFIED_2) - 1)
1195
1196 #define NOTMODIFIED_3 \
1197         "\"\r\n\r\n"
1198
1199 #define NOTMODIFIED_3_LEN (sizeof(NOTMODIFIED_3) - 1)
1200
1201 #define REDIRECT_1 \
1202         "HTTP/1.1 301 Moved Permanently\r\n" \
1203         "Location: http://"
1204
1205 #define REDIRECT_1_LEN (sizeof(REDIRECT_1) - 1)
1206
1207 #define REDIRECT_2 \
1208         "/\r\nContent-Length: 36\r\n" \
1209         "Connection: Keep-Alive\r\n" \
1210         "Content-Type: text/html\r\n\r\n" \
1211         "<HTML> 301 Moved Permanently </HTML>"
1212
1213 #define REDIRECT_2_LEN (sizeof(REDIRECT_2) - 1)
1214
1215 void send_async_err_forbidden (tux_req_t *req)
1216 {
1217         send_async_message(req, forbidden, 403, 1);
1218 }
1219
1220 void send_async_err_not_found (tux_req_t *req)
1221 {
1222         send_async_message(req, not_found, 404, 1);
1223 }
1224
1225 static void send_ret_notmodified (tux_req_t *req)
1226 {
1227         char *buf;
1228         int size;
1229
1230         size = NOTMODIFIED_1_LEN + DATE_LEN - 1 + NOTMODIFIED_2_LEN + req->etaglen + NOTMODIFIED_3_LEN;
1231         buf = get_abuf(req, size); 
1232         memcpy(buf, NOTMODIFIED_1, NOTMODIFIED_1_LEN);
1233         buf += NOTMODIFIED_1_LEN;
1234         memcpy(buf, tux_date, DATE_LEN-1);
1235         buf += DATE_LEN-1; 
1236         memcpy(buf, NOTMODIFIED_2, NOTMODIFIED_2_LEN);
1237         buf += NOTMODIFIED_2_LEN;
1238         memcpy(buf, &req->etag, req->etaglen);
1239         buf += req->etaglen;
1240         memcpy(buf, NOTMODIFIED_3, NOTMODIFIED_3_LEN);
1241         buf += NOTMODIFIED_3_LEN;
1242
1243         req->status = 304;
1244         send_abuf(req, size, MSG_DONTWAIT);
1245         add_req_to_workqueue(req);
1246 }
1247
1248 static void send_ret_redirect (tux_req_t *req, int cachemiss)
1249 {
1250         char *buf;
1251         unsigned int size;
1252         unsigned int uts_len = 0;
1253
1254         size = REDIRECT_1_LEN;
1255         if (req->host_len)
1256                 size += req->host_len;
1257         else {
1258                 down_read(&uts_sem);
1259                 uts_len = strlen(system_utsname.nodename);
1260                 size += uts_len;
1261         }
1262         if (req->objectname[0] != '/')
1263                 size++;
1264         size += req->objectname_len;
1265         size += REDIRECT_2_LEN;
1266
1267         if (size > PAGE_SIZE) {
1268                 req->error = TUX_ERROR_CONN_CLOSE;
1269                 zap_request(req, cachemiss);
1270                 return;
1271         }
1272
1273         buf = get_abuf(req, size);
1274
1275         memcpy(buf, REDIRECT_1, REDIRECT_1_LEN);
1276         buf += REDIRECT_1_LEN;
1277
1278         Dprintk("req %p, host: %s, host_len: %d.\n", req, req->host, req->host_len);
1279         if (req->host_len) {
1280                 memcpy(buf, req->host, req->host_len);
1281                 buf += req->host_len;
1282         } else {
1283                 memcpy(buf, system_utsname.nodename, uts_len);
1284                 up_read(&uts_sem);
1285                 buf += uts_len;
1286         }
1287         if (req->objectname[0] != '/') {
1288                 buf[0] = '/';
1289                 buf++;
1290         }
1291
1292         memcpy(buf, req->objectname, req->objectname_len);
1293         buf += req->objectname_len;
1294
1295         memcpy(buf, REDIRECT_2, REDIRECT_2_LEN);
1296         buf += REDIRECT_2_LEN;
1297
1298         req->status = 301;
1299         send_abuf(req, size, MSG_DONTWAIT);
1300         add_req_to_workqueue(req);
1301 }
1302
1303 static void http_got_request (tux_req_t *req)
1304 {
1305         req->host[0] = 0;
1306         req->host_len = 0;
1307         add_tux_atom(req, parse_request);
1308         add_req_to_workqueue(req);
1309 }
1310
1311
1312 tux_attribute_t * lookup_tux_attribute (tux_req_t *req)
1313 {
1314         tux_attribute_t *attr;
1315         struct inode *inode;
1316         mimetype_t *mime;
1317
1318         attr = tux_kmalloc(sizeof(*attr));
1319         memset(attr, 0, sizeof(*attr));
1320
1321         mime = lookup_mimetype(req);
1322
1323         inode = req->dentry->d_inode;
1324         if (!inode->i_uid && !inode->i_gid) {
1325                 if (mime->special == MIME_TYPE_MODULE) {
1326                         attr->tcapi = lookup_tuxmodule(req->objectname);
1327                         if (!attr->tcapi) {
1328                                 req_err(req);
1329                                 mime = &default_mimetype;
1330                         }
1331                 }
1332         } else {
1333                 if (mime->special && (mime->special != MIME_TYPE_REDIRECT))
1334                         mime = &default_mimetype;
1335         }
1336         attr->mime = mime;
1337
1338         return attr;
1339 }
1340
1341 static void handle_range(tux_req_t *req)
1342 {
1343         if (req->if_range_len) {
1344                 time_t range_time;
1345
1346                 range_time = parse_time(req->if_range_str, req->if_range_len);
1347
1348                 /*
1349                  * If the file is newer then we send the whole file.
1350                  */
1351                 if (range_time < req->mtime )
1352                         goto out_no_range;
1353         }
1354         /* if no offset_end was specified then default to 'end of file': */
1355         if (!req->offset_end)
1356                 req->offset_end = req->total_file_len;
1357         /*
1358          * Sanity checks:
1359          *
1360          *  - is the range between 0...file_len-1 ?
1361          *  - is offset_end after offset_start?
1362          *
1363          * (note that offset_end is higher by 1)
1364          */
1365         if ((req->offset_end > req->total_file_len) ||
1366                         (req->offset_start >= req->total_file_len) ||
1367                         (req->offset_end <= req->offset_start))
1368                 goto out_no_range;
1369         /*
1370          * If the range is 0...file_len-1 then send the whole file:
1371          */
1372         if (!req->offset_start && (req->offset_end == req->total_file_len))
1373                 goto out_no_range;
1374
1375         /* ok, the range is valid, use it: */
1376
1377         req->output_len = req->offset_end - req->offset_start;
1378         req->in_file.f_pos = req->offset_start;
1379         return;
1380
1381 out_no_range:
1382         req->offset_start = 0;
1383         req->offset_end = 0;
1384 }
1385
1386 static void http_pre_header (tux_req_t *req, int push);
1387 static void http_post_header (tux_req_t *req, int cachemiss);
1388 static void http_send_body (tux_req_t *req, int cachemiss);
1389
1390 #define DIRLIST_HEAD_1 "\
1391 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\
1392 <HTML><HEAD><TITLE>Index of %s</TITLE></HEAD><BODY>\
1393 <H1>Index of %s </H1><PRE><HR>\n%s"
1394
1395 #define DIRLIST_HEAD_2 "\
1396 <IMG SRC=\"/icons/back.gif\"ALT=\"[DIR]\"> <A HREF=\"../\">Parent Directory</A>\n"
1397
1398 #define DIRLIST_HEAD_SIZE (sizeof(DIRLIST_HEAD_1) + sizeof(DIRLIST_HEAD_2))
1399
1400 static void http_dirlist_head (tux_req_t *req, int cachemiss)
1401 {
1402         char *buf1, *buf2, *path;
1403         int len;
1404
1405         buf1 = (char *)__get_free_page(GFP_KERNEL);
1406         buf2 = (char *)__get_free_page(GFP_KERNEL);
1407         if (!buf1 || !buf2)
1408                 goto out;
1409         path = tux_print_path(req, req->dentry, req->mnt, buf1, PAGE_SIZE);
1410         if (path[0] == '/' && path[1] == '/' && !path[3])
1411                 path = "/";
1412         if (2*strlen(path) + DIRLIST_HEAD_SIZE >= PAGE_SIZE)
1413                 goto out;
1414         len = sprintf(buf2, DIRLIST_HEAD_1, path, path, req->dentry == req->docroot_dentry ? "" : DIRLIST_HEAD_2);
1415         __send_async_message(req, buf2, 200, len, 0);
1416
1417 out:
1418         if (buf1)
1419                 free_page((unsigned long)buf1);
1420         if (buf2)
1421                 free_page((unsigned long)buf2);
1422 }
1423
1424 #define DIRLIST_TAIL "\
1425 </PRE><HR><ADDRESS><IMG SRC=\"/icons/tuxlogo.gif\"ALIGN=\"MIDDLE\"ALT=\"[TUX]\">Powered by Linux/TUX 3.0</ADDRESS>\n</BODY></HTML>"
1426
1427 static void http_dirlist_tail (tux_req_t *req, int cachemiss)
1428 {
1429         __send_async_message(req, DIRLIST_TAIL, 200, sizeof(DIRLIST_TAIL)-1, 1);
1430 }
1431
1432 static void http_dirlist (tux_req_t *req, int cachemiss)
1433 {
1434         int head = (req->method == METHOD_HEAD);
1435
1436         req->lookup_dir = 3;
1437         clear_keepalive(req);
1438         if (!head) {
1439                 add_tux_atom(req, http_dirlist_tail);
1440                 add_tux_atom(req, list_directory);
1441                 add_tux_atom(req, http_dirlist_head);
1442         }
1443         http_pre_header(req, head);
1444         add_req_to_workqueue(req);
1445 }
1446
1447 static char *host_path_hash(tux_req_t *req, char *tmp)
1448 {
1449         if (req->host_len < 2)
1450                 return NULL;
1451
1452         switch (mass_hosting_hash) {
1453                 default:
1454                 case 0:
1455                         return req->host;
1456                 case 1:
1457
1458                         // www.ABCDEFG.com => A/ABCDEFG.com
1459
1460                         tmp[0] = req->host[0];
1461                         tmp[1] = '/';
1462                         memcpy(tmp + 2, req->host, req->host_len);
1463                         tmp[req->host_len + 2] = 0;
1464
1465                         return tmp;
1466                 case 2:
1467                         // www.ABCDEFG.com => A/AB/ABCDEFG.com
1468
1469                         tmp[0] = req->host[0];
1470                         tmp[1] = '/';
1471                         tmp[2] = req->host[0];
1472                         tmp[3] = req->host[1];
1473                         tmp[4] = '/';
1474                         memcpy(tmp + 5, req->host, req->host_len);
1475                         tmp[req->host_len + 5] = 0;
1476
1477                         return tmp;
1478                 case 3:
1479                         // www.ABCDEFG.com => A/AB/ABC/ABCDEFG.com
1480
1481                         tmp[0] = req->host[0];
1482                         tmp[1] = '/';
1483                         tmp[2] = req->host[0];
1484                         tmp[3] = req->host[1];
1485                         tmp[4] = '/';
1486                         tmp[5] = req->host[0];
1487                         tmp[6] = req->host[1];
1488                         tmp[7] = req->host[2];
1489                         tmp[8] = '/';
1490                         memcpy(tmp + 9, req->host, req->host_len);
1491                         tmp[req->host_len + 9] = 0;
1492
1493                         return tmp;
1494         }
1495 }
1496
1497 static struct dentry * vhost_lookup (tux_req_t *req, struct nameidata* base, struct vfsmount **mnt)
1498 {
1499         struct dentry *dentry = NULL;
1500         // 255.255.255.255
1501         char ip [3+1+3+1+3+1+3 + 2];
1502
1503         if (req->virtual >= TUX_VHOST_IP) {
1504                 sprintf(ip, "%d.%d.%d.%d",
1505                                 NIPQUAD(inet_sk(req->sock->sk)->rcv_saddr));
1506                 dentry = __tux_lookup (req, ip, base, mnt);
1507                 if (!dentry || IS_ERR(dentry)) {
1508                         if (PTR_ERR(dentry) == -EWOULDBLOCKIO)
1509                                 return dentry;
1510                         base->dentry = dget(req->proto->main_docroot.dentry);
1511                         base->mnt = mntget(req->proto->main_docroot.mnt);
1512                         goto lookup_default;
1513                 }
1514                 if (req->virtual == TUX_VHOST_IP)
1515                         goto done;
1516
1517                 // fall through in mixed mode:
1518         }
1519
1520         if (!req->host_len) {
1521 lookup_default:
1522                 *mnt = NULL;
1523                 dentry = __tux_lookup (req, tux_default_vhost, base, mnt);
1524         } else {
1525                 char tmp [MAX_HOST_LEN*2];
1526                 char *host_path;
1527
1528                 host_path = host_path_hash(req, tmp);
1529                 Dprintk("host path hash returned: {%s}\n", host_path);
1530
1531                 dentry = NULL;
1532                 if (host_path) {
1533                         *mnt = NULL;
1534                         dentry = __tux_lookup (req, host_path, base, mnt);
1535                 }
1536                 if (!dentry || IS_ERR(dentry)) {
1537                         if (PTR_ERR(dentry) == -EWOULDBLOCKIO)
1538                                 return dentry;
1539                         base->dentry = dget(req->proto->main_docroot.dentry);
1540                         base->mnt = mntget(req->proto->main_docroot.mnt);
1541                         if (req->virtual >= TUX_VHOST_IP) {
1542                                 *mnt = NULL;
1543                                 dentry = __tux_lookup (req, ip, base, mnt);
1544                                 if (!dentry || IS_ERR(dentry)) {
1545                                         if (PTR_ERR(dentry) == -EWOULDBLOCKIO)
1546                                                 return dentry;
1547                                         base->dentry = dget(req->proto->main_docroot.dentry);
1548                                         base->mnt = mntget(req->proto->main_docroot.mnt);
1549                                 }
1550                         }
1551                         goto lookup_default;
1552                 }
1553         }
1554 done:
1555         return dentry;
1556 }
1557
1558 static void http_lookup_vhost (tux_req_t *req, int cachemiss)
1559 {
1560         struct dentry *dentry;
1561         struct nameidata base;
1562         struct vfsmount *mnt = NULL;
1563         unsigned int flag = cachemiss ? 0 : LOOKUP_ATOMIC;
1564         
1565         Dprintk("http_lookup_vhost(%p, %d, virtual: %d, host: %s (%d).)\n", req, flag, req->virtual, req->host, req->host_len);
1566
1567         base.flags = LOOKUP_FOLLOW|flag;
1568         base.last_type = LAST_ROOT;
1569         base.dentry = dget(req->proto->main_docroot.dentry);
1570         base.mnt = mntget(req->proto->main_docroot.mnt);
1571
1572         dentry = vhost_lookup(req, &base, &mnt);
1573
1574         Dprintk("looked up dentry %p.\n", dentry);
1575
1576         if (dentry && !IS_ERR(dentry) && !dentry->d_inode)
1577                 TUX_BUG();
1578
1579         if (!dentry || IS_ERR(dentry)) {
1580                 if (PTR_ERR(dentry) == -EWOULDBLOCKIO) {
1581                         add_tux_atom(req, http_lookup_vhost);
1582                         queue_cachemiss(req);
1583                         return;
1584                 }
1585                 goto abort;
1586         }
1587
1588         req->docroot_dentry = dentry;
1589         req->docroot_mnt = mnt;
1590
1591         add_tux_atom(req, http_process_message);
1592         add_req_to_workqueue(req);
1593         return;
1594 abort:
1595         if (dentry) {
1596                 if (!IS_ERR(dentry))
1597                         dput(dentry);
1598                 dentry = NULL;
1599         }
1600         if (mnt) {
1601                 if (!IS_ERR(mnt))
1602                         mntput(mnt);
1603                 mnt = NULL;
1604         }
1605         req_err(req);
1606         add_req_to_workqueue(req);
1607 }
1608
1609 static void http_process_message (tux_req_t *req, int cachemiss)
1610 {
1611         tux_attribute_t *attr;
1612         int missed;
1613         unsigned int lookup_flag = cachemiss ? 0 : LOOKUP_ATOMIC;
1614
1615         Dprintk("handling req %p, cachemiss: %d.\n", req, cachemiss);
1616
1617         /*
1618          * URL redirection support - redirect all valid requests
1619          * to the first userspace module.
1620          */
1621         if (tux_all_userspace) {
1622                 tcapi_template_t *tcapi = get_first_usermodule();
1623                 if (tcapi) {
1624                         req->usermode = 1;
1625                         req->usermodule_idx = tcapi->userspace_id;
1626                         goto usermode;
1627                 }
1628         }
1629         missed = lookup_url(req, lookup_flag);
1630         if (missed == 2) {
1631                 if (req->query_str) {
1632                         req->error = TUX_ERROR_REDIRECT;
1633                         goto error;
1634                 }
1635                 send_ret_redirect(req, cachemiss);
1636                 return;
1637         }
1638         if (req->error)
1639                 goto error;
1640         if (missed) {
1641 cachemiss:
1642                 if (cachemiss)
1643                         TUX_BUG();
1644                 Dprintk("uncached request.\n");
1645                 INC_STAT(static_lookup_cachemisses);
1646                 if (req->dentry)
1647                         TUX_BUG();
1648                 add_tux_atom(req, http_process_message);
1649                 queue_cachemiss(req);
1650                 return;
1651         }
1652         /*
1653          * HTML directory indexing.
1654          */
1655         if (S_ISDIR(req->dentry->d_inode->i_mode))
1656                 return http_dirlist(req, cachemiss);
1657         if (!S_ISREG(req->dentry->d_inode->i_mode))
1658                 TUX_BUG();
1659
1660
1661         attr = req->dentry->d_extra_attributes;
1662         if (!attr) {
1663                 attr = lookup_tux_attribute(req);
1664                 if (!attr)
1665                         TUX_BUG();
1666                 req->dentry->d_extra_attributes = attr;
1667         }
1668         if (attr->mime)
1669                 Dprintk("using MIME type %s:%s, %d.\n", attr->mime->type, attr->mime->ext, attr->mime->special);
1670         if (attr->tcapi) {
1671                 req->usermode = 1;
1672                 req->usermodule_idx = attr->tcapi->userspace_id;
1673                 if (req->module_dentry)
1674                         TUX_BUG();
1675                 req->module_dentry = dget(req->dentry);
1676                 release_req_dentry(req);
1677                 goto usermode;
1678         }
1679
1680         switch (attr->mime->special) {
1681                 case MIME_TYPE_MODULE:
1682                         req->usermode = 1;
1683                         goto usermode;
1684
1685                 case MIME_TYPE_REDIRECT:
1686                         req->error = TUX_ERROR_REDIRECT;
1687                         goto error;
1688
1689                 case MIME_TYPE_CGI:
1690 #if CONFIG_TUX_EXTCGI
1691                         Dprintk("CGI request %p.\n", req);
1692                         query_extcgi(req);
1693                         return;
1694 #endif
1695
1696                 default:
1697                         if (req->query_str) {
1698                                 req->error = TUX_ERROR_REDIRECT;
1699                                 goto error;
1700                         }
1701         }
1702         req->attr = attr;
1703         switch (req->method) {
1704                 case METHOD_GET:
1705                 case METHOD_HEAD:
1706                         break;
1707                 default:
1708                         req->error = TUX_ERROR_REDIRECT;
1709                         goto error;
1710         }
1711         if (req->usermode)
1712                 TUX_BUG();
1713
1714         req->output_len = req->total_file_len;
1715         /*
1716          * Do range calculations.
1717          */
1718         if (req->offset_end || req->offset_start)
1719                 handle_range(req);
1720
1721         if (req->may_send_gzip && !req->offset_start && !req->offset_end) {
1722                 if (handle_gzip_req(req, lookup_flag))
1723                         goto cachemiss;
1724                 if ((tux_compression >= 2) && !req->content_gzipped)
1725                         req->content_gzipped = 2;
1726         }
1727         if (req->parsed_len)
1728                 trunc_headers(req);
1729
1730         if (req->error)
1731                 goto error;
1732
1733         add_tux_atom(req, http_send_body);
1734         add_tux_atom(req, http_post_header);
1735
1736         http_pre_header(req, req->method == METHOD_HEAD);
1737
1738         add_req_to_workqueue(req);
1739         return;
1740
1741 error:
1742         if (req->error)
1743                 zap_request(req, cachemiss);
1744         return;
1745
1746 usermode:
1747         add_req_to_workqueue(req);
1748 }
1749
1750 static void http_post_header (tux_req_t *req, int cachemiss)
1751 {
1752 #if CONFIG_TUX_DEBUG
1753         req->bytes_expected = req->output_len;
1754 #endif
1755         req->bytes_sent = 0; // data comes now.
1756
1757         add_req_to_workqueue(req);
1758 }
1759
1760 static void http_send_body (tux_req_t *req, int cachemiss)
1761 {
1762         int ret;
1763
1764         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);
1765
1766         SET_TIMESTAMP(req->output_timestamp);
1767
1768         if (req->error) {
1769 #if CONFIG_TUX_DEBUG
1770                 req->bytes_expected = 0;
1771 #endif
1772                 req->in_file.f_pos = 0;
1773                 /*
1774                  * We are in the middle of a file transfer,
1775                  * zap it immediately:
1776                  */
1777                 TDprintk("req->error = TUX_ERROR_CONN_CLOSE.\n");
1778                 req->error = TUX_ERROR_CONN_CLOSE;
1779                 zap_request(req, cachemiss);
1780                 return;
1781         }
1782
1783 repeat:
1784         ret = 0;
1785         if (!req->status)
1786                 req->status = 200;
1787         if (req->method != METHOD_HEAD) {
1788                 ret = generic_send_file(req, req->sock, cachemiss);
1789                 Dprintk("body send-file returned: %d.\n", ret);
1790         } else {
1791 #if CONFIG_TUX_DEBUG
1792                 req->bytes_expected = 0;
1793 #endif
1794         }
1795
1796         switch (ret) {
1797                 case -5:
1798                         add_tux_atom(req, http_send_body);
1799                         output_timeout(req);
1800                         break;
1801                 case -4:
1802                         add_tux_atom(req, http_send_body);
1803                         if (add_output_space_event(req, req->sock)) {
1804                                 del_tux_atom(req);
1805                                 goto repeat;
1806                         }
1807                         break;
1808                 case -3:
1809                         INC_STAT(static_sendfile_cachemisses);
1810                         add_tux_atom(req, http_send_body);
1811                         queue_cachemiss(req);
1812                         break;
1813                 case -1:
1814                         break;
1815                 default:
1816                         req->in_file.f_pos = 0;
1817                         add_req_to_workqueue(req);
1818                         break;
1819         }
1820 }
1821
1822 #define DEFAULT_DATE "Wed, 01 Jan 1970 00:00:01 GMT"
1823
1824 char tux_date [DATE_LEN] = DEFAULT_DATE;
1825
1826 /*
1827  * HTTP header
1828  */
1829
1830 #define HEADER_PART1A \
1831                 "HTTP/1.1 200 OK\r\n" \
1832                 "Content-Type: "
1833
1834 #define HEADER_PART1B \
1835                 "HTTP/1.1 200 OK" 
1836
1837 #define HEADER_PART1AP \
1838                 "HTTP/1.1 206 Partial Content\r\n" \
1839                 "Content-Type: "
1840
1841 #define HEADER_PART1BP \
1842                 "HTTP/1.1 206 Partial Content" 
1843
1844 #define HEADER_PART1C \
1845                 "HTTP/1.1 404 Page Not Found\r\n" \
1846                 "Content-Type: "
1847
1848 #define HEADER_PART1D \
1849                 "HTTP/1.1 200 OK\r\n" \
1850                 "Content-Type: text/html\r\n" \
1851                 "Connection: close\r\n"
1852
1853 #define HEADER_PART2_keepalive "\r\nConnection: Keep-Alive\r\nDate: "
1854
1855 #define HEADER_PART2_close "\r\nConnection: close\r\nDate: "
1856
1857 #define HEADER_PART2_none "\r\nDate: "
1858
1859 // date "%s"
1860
1861 #define HEADER_PART3A "\r\nContent-Encoding: gzip"
1862 #define HEADER_PART3BX "\r\nContent-Length: "
1863
1864 /*
1865  * Please acknowledge our hard work by not changing this define, or
1866  * at least please acknowledge us by leaving "TUX/2.0 (Linux)" in
1867  * the ID string. Thanks! :-)
1868  */
1869 #define HEADER_PART3BY "\r\nServer: TUX/2.0 (Linux)\r\nContent-Length: "
1870 #define HEADER_PART3C "\r\nETag: \""
1871 #define HEADER_PART3ACC "\r\nAccept-Ranges: bytes"
1872 #define HEADER_PART3L "\r\nLast-Modified: "
1873 #define HEADER_PART3P "\r\nContent-Range: bytes "
1874 #define HEADER_PART3CA "\r\nCache-Control: max-age="
1875 #define HEADER_PART4 "\r\n\r\n"
1876
1877 #define MAX_OUT_HEADER_LEN (sizeof(HEADER_PART1AP) + MAX_MIMETYPE_LEN + \
1878                 sizeof(HEADER_PART2_keepalive) + DATE_LEN + \
1879                 sizeof(HEADER_PART3A) + sizeof(HEADER_PART3BY) + \
1880                 12 + sizeof(HEADER_PART3C) + 21 + sizeof(HEADER_PART3L) + \
1881                 sizeof(HEADER_PART3P) + 32 + \
1882                 DATE_LEN + sizeof(HEADER_PART4) + sizeof(tux_extra_html_header) \
1883                 + sizeof(HEADER_PART3CA) + MAX_CACHE_CONTROL_AGE_LEN)
1884
1885 static void http_pre_header (tux_req_t *req, int head)
1886 {
1887         int partial = req->offset_start | req->offset_end;
1888         unsigned long flags;
1889         char *buf, *curr;
1890         mimetype_t *mime = NULL;
1891         int size;
1892
1893
1894         if (MAX_OUT_HEADER_LEN > PAGE_SIZE)
1895                 TUX_BUG();
1896         if ((req->attr && req->attr->tcapi) || req->usermode)
1897                 TUX_BUG();
1898
1899 #define COPY_STATIC_PART(nr,curr)                                       \
1900         do {    \
1901                 memcpy(curr, HEADER_PART##nr, sizeof(HEADER_PART##nr)-1); \
1902                 curr += sizeof(HEADER_PART##nr)-1;                      \
1903         } while (0)
1904
1905         buf = curr = get_abuf(req, MAX_OUT_HEADER_LEN);
1906
1907         if (req->lookup_dir) {
1908                 COPY_STATIC_PART(1D, curr);
1909                 goto dir_next;
1910         }
1911         mime = req->attr->mime;
1912         if (!mime)
1913                 TUX_BUG();
1914
1915         if (req->status == 404) {
1916                 COPY_STATIC_PART(1C, curr);
1917                 memcpy(curr, mime->type, mime->type_len);
1918                 curr += mime->type_len;
1919         } else {
1920                 if (tux_noid && (mime == &default_mimetype)) {
1921                         if (partial)
1922                                 COPY_STATIC_PART(1BP, curr);
1923                         else
1924                                 COPY_STATIC_PART(1B, curr);
1925                 } else {
1926                         if (partial)
1927                                 COPY_STATIC_PART(1AP, curr);
1928                         else
1929                                 COPY_STATIC_PART(1A, curr);
1930                         memcpy(curr, mime->type, mime->type_len);
1931                         curr += mime->type_len;
1932                 }
1933         }
1934
1935         if (tux_generate_cache_control && mime->expire_str_len) {
1936                 COPY_STATIC_PART(3CA, curr);
1937                 memcpy(curr, mime->expire_str, mime->expire_str_len);
1938                 curr += mime->expire_str_len;
1939         }
1940
1941         if (req->keep_alive /* && (req->version == HTTP_1_0) */)
1942                 COPY_STATIC_PART(2_keepalive, curr);
1943         else if (!req->keep_alive && (req->version == HTTP_1_1))
1944                 COPY_STATIC_PART(2_close, curr);
1945         else
1946                 // HTTP/1.0 default means close
1947                 COPY_STATIC_PART(2_none, curr);
1948
1949 dir_next:
1950         memcpy(curr, tux_date, DATE_LEN-1);
1951         curr += DATE_LEN-1;
1952
1953         if (req->content_gzipped)
1954                 COPY_STATIC_PART(3A, curr);
1955
1956         /*
1957          * Content-Length:
1958          */
1959         if (!req->lookup_dir) {
1960                 if (tux_noid)
1961                         COPY_STATIC_PART(3BX, curr);
1962                 else
1963                         COPY_STATIC_PART(3BY, curr);
1964
1965                 if (partial)
1966                         curr += sprintf(curr, "%Ld", req->output_len);
1967                 else {
1968                         if (req->content_gzipped)
1969                                 curr += sprintf(curr, "%Ld",
1970                                                         req->total_file_len);
1971                         else {
1972                                 memcpy(curr, &req->etag, req->lendigits);
1973                                 curr += req->lendigits;
1974                         }
1975                 }
1976                 if (tux_generate_etags && (req->status != 404)) {
1977                         COPY_STATIC_PART(3C, curr);
1978                         memcpy(curr, &req->etag, req->etaglen);
1979                         curr += req->etaglen;
1980                         curr[0] = '"';
1981                         curr++;
1982                 }
1983                 if (tux_generate_last_mod || tux_generate_etags)
1984                         COPY_STATIC_PART(3ACC, curr);
1985         }
1986         if (tux_generate_last_mod && (req->status != 404)) {
1987                 COPY_STATIC_PART(3L, curr);
1988                 last_mod_time(curr, req->mtime);
1989                 curr += DATE_LEN-1;
1990         }
1991         if (partial) {
1992                 COPY_STATIC_PART(3P, curr);
1993                 curr += sprintf(curr, "%Ld-%Ld/%Ld", req->offset_start,
1994                                 req->offset_end-1, req->total_file_len);
1995         }
1996         COPY_STATIC_PART(4, curr);
1997         /*
1998          * Possibly add an extra HTML header:
1999          */
2000         if (tux_extra_html_header_size && mime && !strcmp(mime->type, "text/html")) {
2001                 unsigned int len = tux_extra_html_header_size;
2002
2003                 memcpy(curr, tux_extra_html_header, len);
2004                 curr += len;
2005         }
2006
2007         size = curr-buf;
2008
2009 #if CONFIG_TUX_DEBUG
2010         *curr = 0;
2011         Dprintk("{%s} [%d/%d]\n", buf, size, strlen(buf));
2012 #endif
2013
2014         flags = MSG_DONTWAIT;
2015         if (!head)
2016                 flags |= MSG_MORE;
2017         send_abuf(req, size, flags);
2018 }
2019
2020 void http_illegal_request (tux_req_t *req, int cachemiss)
2021 {
2022         if (req->status == 304)
2023                 send_ret_notmodified(req);
2024         else {
2025                 if (req->status == 403)
2026                         send_async_err_forbidden(req);
2027                 else
2028                         send_async_err_not_found(req);
2029         }
2030 }
2031
2032 static int http_check_req_err (tux_req_t *req, int cachemiss)
2033 {
2034         if ((req->sock->sk->sk_state <= TCP_SYN_RECV) &&
2035                 !tcp_sk(req->sock->sk)->urg_data)
2036                         return 0;
2037         Dprintk("http_check_req_err(%p,%d): 1 (state: %d, urg: %d)\n",
2038                 req, cachemiss, req->sock->sk->sk_state,
2039                 tcp_sk(req->sock->sk)->urg_data);
2040 #if CONFIG_TUX_DEBUG
2041         req->bytes_expected = 0;
2042 #endif
2043         req->in_file.f_pos = 0;
2044         req->error = TUX_ERROR_CONN_CLOSE;
2045         zap_request(req, cachemiss);
2046
2047         return 1;
2048 }
2049
2050 #define COPY_STR(str) \
2051         do { memcpy(tmp, str, sizeof(str)-1); \
2052         tmp += sizeof(str)-1; } while (0)
2053
2054 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)
2055 {
2056         int len, spaces;
2057         loff_t size;
2058
2059         switch (d_type) {
2060         case DT_DIR:
2061                 COPY_STR("<IMG SRC=\"/icons/dir.gif\" ALT=\"[DIR]\">");
2062                 break;
2063         case DT_REG:
2064                 if ((d_len >= 3) &&
2065                         (d_name[d_len-3] == '.') &&
2066                         (d_name[d_len-2] == 'g') &&
2067                         (d_name[d_len-1] == 'z'))
2068                         COPY_STR("<IMG SRC=\"/icons/compressed.gif\" ALT=\"[   ]\">");
2069                 else
2070                 if ((d_len >= 4) &&
2071                         (d_name[d_len-4] == '.') &&
2072                         (d_name[d_len-3] == 't') &&
2073                         (d_name[d_len-2] == 'g') &&
2074                         (d_name[d_len-1] == 'z'))
2075                         COPY_STR("<IMG SRC=\"/icons/compressed.gif\" ALT=\"[   ]\">");
2076                 else
2077                 if ((d_len >= 4) &&
2078                         (d_name[d_len-4] == '.') &&
2079                         (d_name[d_len-3] == 't') &&
2080                         (d_name[d_len-2] == 'x') &&
2081                         (d_name[d_len-1] == 't'))
2082                         COPY_STR("<IMG SRC=\"/icons/text.gif\" ALT=\"[   ]\">");
2083                 else
2084                 if ((d_len >= 4) &&
2085                         (d_name[d_len-4] == '.') &&
2086                         (d_name[d_len-3] == 'b') &&
2087                         (d_name[d_len-2] == 'z') &&
2088                         (d_name[d_len-1] == '2'))
2089                         COPY_STR("<IMG SRC=\"/icons/compressed.gif\" ALT=\"[   ]\">");
2090                 else
2091                 if ((d_len >= 4) &&
2092                         (d_name[d_len-4] == '.') &&
2093                         (d_name[d_len-3] == 'z') &&
2094                         (d_name[d_len-2] == 'i') &&
2095                         (d_name[d_len-1] == 'p'))
2096                         COPY_STR("<IMG SRC=\"/icons/compressed.gif\" ALT=\"[   ]\">");
2097                 else
2098                         COPY_STR("<IMG SRC=\"/icons/file.gif\" ALT=\"[   ]\">");
2099                 break;
2100         case DT_LNK:
2101                 COPY_STR("<IMG SRC=\"/icons/link.gif\" ALT=\"[LNK]\">");
2102                 break;
2103         default:
2104                 if (tux_hide_unreadable)
2105                         goto out_dput;
2106                 COPY_STR("<IMG SRC=\"/icons/unknown.gif\" ALT=\"[   ]\">");
2107                 break;
2108         }
2109
2110 #define LIST_1 " <A HREF=\""
2111 #define LIST_2 "\">"
2112 #define LIST_2_DIR "/\">"
2113 #define LIST_3 "</A> "
2114
2115         COPY_STR(LIST_1);
2116         memcpy(tmp, d_name, d_len);
2117         tmp += d_len;
2118         if (d_type == DT_DIR)
2119                 COPY_STR(LIST_2_DIR);
2120         else
2121                 COPY_STR(LIST_2);
2122         spaces = 0;
2123         len = d_len;
2124
2125         if (len > 25)
2126                 len = 25;
2127         memcpy(tmp, d_name, len);
2128         tmp += len;
2129         if (len != d_len) {
2130                 *tmp++ = '.';
2131                 *tmp++ = '.';
2132         } else {
2133                 if (d_type == DT_DIR)
2134                         *tmp++ = '/';
2135                 else
2136                         spaces++;
2137                 spaces++;
2138         }
2139         COPY_STR(LIST_3);
2140         while (spaces) {
2141                 *tmp++ = ' ';
2142                 spaces--;
2143         }
2144 #define FILL 25
2145         if (d_len < FILL) {
2146                 memset(tmp, ' ', FILL-d_len);
2147                 tmp += FILL-d_len;
2148         }
2149
2150         tmp += time_unix2ls(inode->i_mtime.tv_sec, tmp);
2151         *tmp++ = ' ';
2152
2153         if (d_type != DT_REG) {
2154                 COPY_STR("        - ");
2155                 goto out_size;
2156         }
2157         size = inode->i_size >> 10;
2158         if (size < 1024) {
2159                 tmp += sprintf(tmp, "%8Lik ", size);
2160                 goto out_size;
2161         }
2162         size >>= 10;
2163         if (size < 1024) {
2164                 tmp += sprintf(tmp, "%8LiM ", size);
2165                 goto out_size;
2166         }
2167         size >>= 10;
2168         if (size < 1024) {
2169                 tmp += sprintf(tmp, "%8LiG ", size);
2170                 goto out_size;
2171         }
2172         size >>= 10;
2173         if (size < 1024) {
2174                 tmp += sprintf(tmp, "%8LiT ", size);
2175                 goto out_size;
2176         }
2177         size >>= 10;
2178         tmp += sprintf(tmp, "%8LiT ", size);
2179
2180 out_size:
2181         *tmp++ = '\n';
2182         *tmp = 0;
2183
2184         return tmp;
2185 out_dput:
2186         return NULL;
2187 }
2188
2189 tux_proto_t tux_proto_http = {
2190         defer_accept: 1,
2191         can_redirect: 1,
2192         got_request: http_got_request,
2193         parse_message: parse_http_message,
2194         illegal_request: http_illegal_request,
2195         check_req_err: http_check_req_err,
2196         print_dir_line: http_print_dir_line,
2197         name: "http",
2198 };
2199