2 * TUX - Integrated Application Protocols Layer and Object Cache
4 * Copyright (C) 2000, 2001, Ingo Molnar <mingo@redhat.com>
6 * proto_http.c: HTTP application protocol support
8 * Right now we detect simple GET headers, anything more
9 * subtle gets redirected to secondary server port.
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)
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.
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.
30 ****************************************************************/
33 * Parse the HTTP message and put results into the request structure.
34 * CISAPI extensions do not see the actual message buffer.
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.
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.
47 static inline int TOHEX (char c)
50 case '0' ... '9': c -= '0'; break;
51 case 'a' ... 'f': c -= 'a'-10; break;
52 case 'A' ... 'F': c -= 'A'-10; break;
60 * This function determines whether the client supports
61 * gzip-type content-encoding.
63 static int may_gzip (const char *str, int len)
65 const char *tmp, *curr;
71 for (i = 0; i <= len-6; i++) {
72 Dprintk("gzip-checking: {%s}\n", tmp);
73 if (memcmp(tmp, " gzip", 5)) {
79 if (*curr == ',' || *curr == '\r')
81 if (memcmp(curr, ";q=", 3))
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.
93 if (*curr == ' ' || *curr == '\r')
97 if (*curr == ' ' || *curr == '\r')
114 * This function strips off 'strip_host_tail' number of hostname
115 * components from the tail of the hostname.
117 * Eg. with a value of '1', the "somesite.hosting.com" hostname gets
118 * transformed into the "somesite" string.
120 static void strip_hostname(tux_req_t *req)
122 int strip = strip_host_tail;
123 int left = req->host_len;
130 if (req->host[left] != '.')
132 if (++component == strip)
138 req->host_len = left;
141 static void http_lookup_vhost (tux_req_t *req, int cachemiss);
142 static void http_process_message (tux_req_t *req, int cachemiss);
144 int parse_http_message (tux_req_t *req, const int total_len)
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;
153 message = req->headers;
154 Dprintk("parsing request:\n---\n%s\n---\n", message);
158 * Request-Line = Method SP Request-URI SP HTTP-Version CRLF
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)
169 #define PRINT_MESSAGE_LEFT \
170 Dprintk("message left (%d) at %s:%d:\n--->{%s}<---\n", left, __FILE__, __LINE__, curr)
174 if (PARSE_METHOD(req,curr,GET,left))
179 if (PARSE_METHOD(req,curr,HEAD,left))
184 if (PARSE_METHOD(req,curr,POST,left))
186 if (PARSE_METHOD(req,curr,PUT,left))
194 req->method_str = message;
195 req->method_len = curr-message-1;
197 Dprintk("got method %d\n", req->method);
202 * Ok, we got one of the methods we can handle, parse
207 // Do not allow leading "../" and intermediate "/../"
209 char *tmp = req->objectname;
212 req->uri_str = uri = curr;
215 c = get_c(curr,left);
223 if (c == ' ' || ((c == '?') && (tux_ignore_query != 1)) || c == '\r' || c == '\n')
228 Dprintk("hexhex: %d.\n", hexhex);
230 * First handle HEX HEX encoding
236 goto continue_parsing;
240 hex_val_0 = TOHEX(c);
244 goto continue_parsing;
246 hex_val_1 = TOHEX(c);
249 c = (hex_val_0 << 4) | hex_val_1;
284 if (!dotdot && (c == '/'))
289 if (curr - uri >= MAX_OBJECTNAME_LEN)
295 // handle trailing "/.."
299 objectname_len = tmp - req->objectname;
300 req->objectname_len = objectname_len;
302 Dprintk("got filename %s (%d)\n", req->objectname, req->objectname_len);
307 * Parse optional query string. Copy until end-of-string or space.
313 req->query_str = query = curr;
316 c = get_c(curr,left);
322 if (unlikely(tux_ignore_query == 2))
323 req->query_str = NULL;
325 query_len = curr-query-1;
326 req->query_len = query_len;
330 Dprintk("got query string %s (%d)\n", req->query_str, req->query_len);
331 req->uri_len = curr-uri-1;
334 Dprintk("got URI %s (%d)\n", req->uri_str, req->uri_len);
338 * Parse the HTTP version field:
340 req->version_str = curr;
341 if (!PARSE_TOKEN(curr,"HTTP/1.",left))
344 switch (get_c(curr,left)) {
346 req->version = HTTP_1_0;
349 req->version = HTTP_1_1;
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.
359 clear_keepalive(req);
360 if (tux_max_keepalives && (req->version == HTTP_1_1))
362 req->version_len = curr - req->version_str;
364 if (get_c(curr,left) != '\r')
366 if (get_c(curr,left) != '\n')
369 Dprintk("got version %d [%d]\n", req->version, req->version_len);
373 * Now parse (optional) request header fields:
378 c = get_c(curr,left);
394 #define PARSE_STR_FIELD(char,field,str,len) \
395 if (PARSE_TOKEN(curr,field,left)) { \
397 SKIP_LINE(curr,left); \
398 req->len = curr - req->str - 2; \
399 Dprintk(char field "field: %s.\n", req->str); \
403 #define ALLOW_UNKNOWN_FIELDS 1
404 #ifdef ALLOW_UNKNOWN_FIELDS
405 # define UNKNOWN_FIELD { SKIP_LINE(curr,left); break; }
407 # define UNKNOWN_FIELD GOTO_REDIR
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;
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);
422 if (tux_compression && may_gzip(str,curr-str)) {
423 Dprintk("client accepts gzip!.\n");
424 req->may_send_gzip = 1;
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);
435 if (PARSE_TOKEN(curr,"onnection: ",left)) {
437 switch (get_c(curr,left)) {
439 if (!PARSE_TOKEN(curr,"eep-Alive",left))
441 if (tux_max_keepalives)
447 if (!PARSE_TOKEN(curr,"lose",left))
449 clear_keepalive(req);
453 if (!PARSE_TOKEN(curr,"eep-alive",left))
455 if (tux_max_keepalives)
459 if (PARSE_TOKEN(curr,"E",left))
461 if (PARSE_TOKEN(curr,"railers",left))
463 if (PARSE_TOKEN(curr,"ransfer-Encoding",left))
467 if (PARSE_TOKEN(curr,"roxy-Authenticate",left))
469 if (PARSE_TOKEN(curr,"roxy-Authorization",left))
473 if (!PARSE_TOKEN(curr,"pgrade",left))
488 // allow other tokens.
489 SKIP_LINE(curr,left);
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);
498 if (PARSE_TOKEN(curr,"ontent-Length: ",left) ||
499 PARSE_TOKEN(curr,"ontent-length: ",left)) {
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);
508 Dprintk("Content-Length field: %s [%d].\n", req->contentlen_str, req->contentlen_len);
509 Dprintk("Content-Length value: %d.\n", req->content_len);
512 PARSE_STR_FIELD("C","ache-Control: ",
513 cache_control_str,cache_control_len);
517 if (PARSE_TOKEN(curr,"ost: ",left)) {
518 const char *tmp = curr;
519 char *tmp2 = req->host;
522 * canonize the hostname:
524 * 1) strip off preceding 'www.' variants,
525 * 2) transform it to lowercase.
526 * 3) strip trailing dots
527 * 4) potentially strip off tail
530 #define is_w(n) ((curr[n] == 'w') || (curr[n] == 'W'))
532 if ((left > 4) && is_w(0) && is_w(1) &&
533 is_w(2) && curr[3] == '.') {
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] == '.') {
546 req->host[req->host_len] = 0;
549 Dprintk("Host field: %s [%d].\n", req->host, req->host_len);
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);
564 PARSE_STR_FIELD("N","egotiate: ",
565 negotiate_str,negotiate_len);
569 PARSE_STR_FIELD("P","ragma: ",
570 pragma_str,pragma_len);
575 PARSE_STR_FIELD("R","eferer: ",
576 referer_str,referer_len);
577 if (!PARSE_TOKEN(curr,"ange: bytes=",left))
580 const char *tmp = curr;
581 char *tmp2 = (char *)curr;
582 unsigned int offset_start = 0, offset_end = 0;
585 offset_start = simple_strtoul(tmp2, &tmp2, 10);
589 offset_end = simple_strtoul(tmp2, &tmp2, 10) +1;
594 req->offset_start = offset_start;
595 req->offset_end = offset_end;
597 SKIP_LINE(curr,left);
598 Dprintk("Range field: %s [%d] (%d-%d).\n", tmp, curr-tmp, offset_start, offset_end);
603 PARSE_STR_FIELD("U","ser-Agent: ",
604 user_agent_str,user_agent_len);
616 if ((req->method == METHOD_POST) && req->content_len) {
618 if (curr + req->content_len > message + total_len)
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);
627 switch (req->method) {
637 #define TUX_SCHEME "http://"
638 #define TUX_SCHEME_LEN (sizeof(TUX_SCHEME)-1)
640 if (!memcmp(req->objectname, TUX_SCHEME, TUX_SCHEME_LEN)) {
642 /* http://user:password@host:port/object */
644 const char *head, *tail, *end, *host, *port;
645 int host_len, objectname_len;
647 head = req->objectname + TUX_SCHEME_LEN;
648 end = req->objectname + req->objectname_len;
650 tail = memchr(head, '/', end - head);
653 host = memchr(head, '@', tail - head);
660 port = memchr(host, ':', tail - host);
662 host_len = port - host;
664 host_len = tail - host;
665 if (host_len >= MAX_HOST_LEN)
667 memcpy(req->host, host, host_len);
668 req->host_len = host_len;
669 req->host[host_len] = 0;
675 req->uri_len = end - tail;
681 objectname_len = end - tail;
682 memcpy(req->objectname, tail, objectname_len);
683 req->objectname_len = objectname_len;
684 req->objectname[objectname_len] = 0;
686 if (req->uri_str[0] != '/')
689 if ((req->version == HTTP_1_1) && !req->host_len)
691 if (req->objectname[0] == '/')
694 * Lets make sure nobody plays games with the host
695 * header in a virtual hosting environment:
697 if (req->virtual && req->host_len) {
698 if (memchr(req->host, '/', req->host_len))
700 if (req->host[0] == '.') {
701 if (req->host_len == 1)
703 if ((req->host_len == 2) && (req->host[0] == '.'))
708 * From this point on the request is for the main TUX engine:
710 Dprintk("ok, request accepted.\n");
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);
718 INC_STAT(nr_nonkeepalive_reqs);
719 INC_STAT(keepalive_hist[req->nr_keepalives]);
722 req->parsed_len = curr-message;
725 req->virtual = tux_virtual_server;
727 add_tux_atom(req, http_lookup_vhost);
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);
734 return req->parsed_len;
737 Dprintk("incomplete message!\n");
744 req->parsed_len = total_len;
749 TDprintk("redirecting message to secondary server.\n");
755 static int lookup_url (tux_req_t *req, const unsigned int flag)
758 * -1 : no previous checks made
759 * 0 : previous check failed, do not check farther,
760 * 1 : previous check successed, check farther
762 int not_modified = -1;
764 struct dentry *dentry = NULL;
765 struct vfsmount *mnt = NULL;
767 const char *filename;
770 * Do not do any etag or last_modified header checking
773 if (!tux_generate_etags && !tux_generate_last_mod)
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]);
787 dentry = tux_lookup(req, filename, flag, &mnt);
789 #define INDEX "/index.html"
791 if (!dentry || IS_ERR(dentry)) {
792 if (PTR_ERR(dentry) == -EWOULDBLOCKIO)
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;
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;
811 TDprintk("abort - lookup error.\n");
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;
819 * At this point we have a real, non-negative dentry.
821 perm = tux_permission(inode);
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);
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)) {
835 if (req->objectname_len && (req->objectname[req->objectname_len-1] != '/')) {
841 memcpy(req->objectname + req->objectname_len,
842 INDEX, sizeof(INDEX));
843 req->objectname_len += sizeof(INDEX)-1;
851 if (tux_max_object_size && (inode->i_size > tux_max_object_size)) {
852 TDprintk("too big object, %Ld bytes.\n", inode->i_size);
856 req->total_file_len = inode->i_size;
857 req->mtime = inode->i_mtime.tv_sec;
860 loff_t num = req->total_file_len;
862 unsigned long modulo;
863 char * etag_p = req->etag;
867 modulo = do_div(num, 10);
868 digits[nr_digits++] = '0' + modulo;
871 req->lendigits = nr_digits;
872 req->etaglen = nr_digits;
875 *etag_p++ = digits[--nr_digits];
882 digits[nr_digits++] = 'a' + num % 16;
885 req->etaglen += nr_digits+1;
887 *etag_p++ = digits[--nr_digits];
891 if ((req->if_none_match_len >= req->etaglen) && (abs(not_modified) == 1)) {
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;
899 while (etag_p[matchpos--] == match_p[pos--])
903 pos = req->if_none_match_len;
905 if (match_p[pos+1] == ',')
906 pos += req->etaglen + 2;
908 pos += req->etaglen-matchpos;
909 matchpos = req->etaglen - 1;
911 } while (pos < req->if_none_match_len);
915 TDprintk("Etag matched.\n");
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 ) {
923 Dprintk("Last-Modified matched.\n");
928 if (not_modified == 1) {
933 Dprintk("looked up cached dentry %p, (count %d.)\n", dentry, dentry ? atomic_read(&dentry->d_count) : -1 );
935 url_hist_hit(req->total_file_len);
937 install_req_dentry(req, dentry, mnt);
957 TDprintk("req %p has lookup errors!\n", req);
966 int handle_gzip_req (tux_req_t *req, unsigned int flags)
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;
978 req->objectname_len += 3;
980 dentry = tux_lookup(req, req->objectname, flags, &mnt);
982 req->objectname_len -= 3;
983 req->objectname[req->objectname_len] = 0;
987 if (IS_ERR(dentry)) {
988 if (PTR_ERR(dentry) == -EWOULDBLOCKIO) {
989 release_req_dentry(req);
995 inode = dentry->d_inode;
996 size = inode->i_size;
997 orig_inode = req->dentry->d_inode;
998 orig_size = orig_inode->i_size;
1000 if (!tux_permission(inode)
1001 && (size < orig_size)
1002 && (inode->i_mtime.tv_sec >= orig_inode->i_mtime.tv_sec)) {
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;
1017 static spinlock_t mimetypes_lock = SPIN_LOCK_UNLOCKED;
1019 static LIST_HEAD(mimetypes_head);
1021 static mimetype_t default_mimetype = { type: "text/plain", type_len: 10, expire_str: "", expire_str_len: 0 };
1023 #define MAX_MIMETYPE_LEN 128
1024 #define MAX_CACHE_CONTROL_AGE_LEN 30
1026 void add_mimetype (char *new_ext, char *new_type, char *new_expire)
1028 int type_len = strlen(new_type);
1029 int ext_len = strlen(new_ext);
1030 int expire_len = strlen(new_expire);
1032 char *ext, *type, *expire;
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;
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);
1047 strncpy(ext, new_ext, ext_len);
1048 strncpy(type, new_type, type_len);
1049 strncpy(expire, new_expire, expire_len);
1051 // in case one of the above parameters was too long :
1053 ext[ext_len] = '\0';
1054 type[type_len] = '\0';
1055 expire[expire_len] = '\0';
1058 mime->ext_len = ext_len;
1061 mime->type_len = type_len;
1063 mime->expire_str = expire;
1064 mime->expire_str_len = expire_len;
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;
1074 spin_lock(&mimetypes_lock);
1075 list_add(&mime->list, &mimetypes_head);
1076 spin_unlock(&mimetypes_lock);
1079 static inline int ext_matches (char *file, int len, char *ext, int extlen)
1082 char *tmp = file + len-1;
1083 char *tmp2 = ext + extlen-1;
1088 for (i = 0; i < extlen; i++) {
1098 * Overhead is not a problem, we cache the MIME type
1101 static mimetype_t * lookup_mimetype (tux_req_t *req)
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;
1108 if (!memchr(objectname, '.', len))
1111 spin_lock(&mimetypes_lock);
1112 head = &mimetypes_head;
1115 while (tmp != head) {
1116 mime = list_entry(tmp, mimetype_t, list);
1117 if (ext_matches(objectname, len, mime->ext, mime->ext_len)) {
1119 * Percolate often-used mimetypes up:
1121 if (tmp->prev != &mimetypes_head) {
1124 tmp3 = tmp->prev->prev;
1127 list_add(tmp, tmp3);
1128 list_add(tmp2, tmp);
1135 spin_unlock(&mimetypes_lock);
1139 mime = &default_mimetype;
1143 void free_mimetypes (void)
1145 struct list_head *head, *tmp, *next;
1148 spin_lock(&mimetypes_lock);
1149 head = &mimetypes_head;
1152 while (tmp != head) {
1154 mime = list_entry(tmp, mimetype_t, list);
1165 spin_unlock(&mimetypes_lock);
1169 * Various constant HTTP responses:
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>";
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>";
1184 #define NOTMODIFIED_1 \
1185 "HTTP/1.1 304 Not Modified\r\n" \
1186 "Connection: Keep-Alive\r\n" \
1189 #define NOTMODIFIED_1_LEN (sizeof(NOTMODIFIED_1) - 1)
1191 #define NOTMODIFIED_2 \
1194 #define NOTMODIFIED_2_LEN (sizeof(NOTMODIFIED_2) - 1)
1196 #define NOTMODIFIED_3 \
1199 #define NOTMODIFIED_3_LEN (sizeof(NOTMODIFIED_3) - 1)
1201 #define REDIRECT_1 \
1202 "HTTP/1.1 301 Moved Permanently\r\n" \
1205 #define REDIRECT_1_LEN (sizeof(REDIRECT_1) - 1)
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>"
1213 #define REDIRECT_2_LEN (sizeof(REDIRECT_2) - 1)
1215 void send_async_err_forbidden (tux_req_t *req)
1217 send_async_message(req, forbidden, 403, 1);
1220 void send_async_err_not_found (tux_req_t *req)
1222 send_async_message(req, not_found, 404, 1);
1225 static void send_ret_notmodified (tux_req_t *req)
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);
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;
1244 send_abuf(req, size, MSG_DONTWAIT);
1245 add_req_to_workqueue(req);
1248 static void send_ret_redirect (tux_req_t *req, int cachemiss)
1252 unsigned int uts_len = 0;
1254 size = REDIRECT_1_LEN;
1256 size += req->host_len;
1258 down_read(&uts_sem);
1259 uts_len = strlen(system_utsname.nodename);
1262 if (req->objectname[0] != '/')
1264 size += req->objectname_len;
1265 size += REDIRECT_2_LEN;
1267 if (size > PAGE_SIZE) {
1268 req->error = TUX_ERROR_CONN_CLOSE;
1269 zap_request(req, cachemiss);
1273 buf = get_abuf(req, size);
1275 memcpy(buf, REDIRECT_1, REDIRECT_1_LEN);
1276 buf += REDIRECT_1_LEN;
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;
1283 memcpy(buf, system_utsname.nodename, uts_len);
1287 if (req->objectname[0] != '/') {
1292 memcpy(buf, req->objectname, req->objectname_len);
1293 buf += req->objectname_len;
1295 memcpy(buf, REDIRECT_2, REDIRECT_2_LEN);
1296 buf += REDIRECT_2_LEN;
1299 send_abuf(req, size, MSG_DONTWAIT);
1300 add_req_to_workqueue(req);
1303 static void http_got_request (tux_req_t *req)
1307 add_tux_atom(req, parse_request);
1308 add_req_to_workqueue(req);
1312 tux_attribute_t * lookup_tux_attribute (tux_req_t *req)
1314 tux_attribute_t *attr;
1315 struct inode *inode;
1318 attr = tux_kmalloc(sizeof(*attr));
1319 memset(attr, 0, sizeof(*attr));
1321 mime = lookup_mimetype(req);
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);
1329 mime = &default_mimetype;
1333 if (mime->special && (mime->special != MIME_TYPE_REDIRECT))
1334 mime = &default_mimetype;
1341 static void handle_range(tux_req_t *req)
1343 if (req->if_range_len) {
1346 range_time = parse_time(req->if_range_str, req->if_range_len);
1349 * If the file is newer then we send the whole file.
1351 if (range_time < req->mtime )
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;
1360 * - is the range between 0...file_len-1 ?
1361 * - is offset_end after offset_start?
1363 * (note that offset_end is higher by 1)
1365 if ((req->offset_end > req->total_file_len) ||
1366 (req->offset_start >= req->total_file_len) ||
1367 (req->offset_end <= req->offset_start))
1370 * If the range is 0...file_len-1 then send the whole file:
1372 if (!req->offset_start && (req->offset_end == req->total_file_len))
1375 /* ok, the range is valid, use it: */
1377 req->output_len = req->offset_end - req->offset_start;
1378 req->in_file.f_pos = req->offset_start;
1382 req->offset_start = 0;
1383 req->offset_end = 0;
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);
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"
1395 #define DIRLIST_HEAD_2 "\
1396 <IMG SRC=\"/icons/back.gif\"ALT=\"[DIR]\"> <A HREF=\"../\">Parent Directory</A>\n"
1398 #define DIRLIST_HEAD_SIZE (sizeof(DIRLIST_HEAD_1) + sizeof(DIRLIST_HEAD_2))
1400 static void http_dirlist_head (tux_req_t *req, int cachemiss)
1402 char *buf1, *buf2, *path;
1405 buf1 = (char *)__get_free_page(GFP_KERNEL);
1406 buf2 = (char *)__get_free_page(GFP_KERNEL);
1409 path = tux_print_path(req, req->dentry, req->mnt, buf1, PAGE_SIZE);
1410 if (path[0] == '/' && path[1] == '/' && !path[3])
1412 if (2*strlen(path) + DIRLIST_HEAD_SIZE >= PAGE_SIZE)
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);
1419 free_page((unsigned long)buf1);
1421 free_page((unsigned long)buf2);
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>"
1427 static void http_dirlist_tail (tux_req_t *req, int cachemiss)
1429 __send_async_message(req, DIRLIST_TAIL, 200, sizeof(DIRLIST_TAIL)-1, 1);
1432 static void http_dirlist (tux_req_t *req, int cachemiss)
1434 int head = (req->method == METHOD_HEAD);
1436 req->lookup_dir = 3;
1437 clear_keepalive(req);
1439 add_tux_atom(req, http_dirlist_tail);
1440 add_tux_atom(req, list_directory);
1441 add_tux_atom(req, http_dirlist_head);
1443 http_pre_header(req, head);
1444 add_req_to_workqueue(req);
1447 static char *host_path_hash(tux_req_t *req, char *tmp)
1449 if (req->host_len < 2)
1452 switch (mass_hosting_hash) {
1458 // www.ABCDEFG.com => A/ABCDEFG.com
1460 tmp[0] = req->host[0];
1462 memcpy(tmp + 2, req->host, req->host_len);
1463 tmp[req->host_len + 2] = 0;
1467 // www.ABCDEFG.com => A/AB/ABCDEFG.com
1469 tmp[0] = req->host[0];
1471 tmp[2] = req->host[0];
1472 tmp[3] = req->host[1];
1474 memcpy(tmp + 5, req->host, req->host_len);
1475 tmp[req->host_len + 5] = 0;
1479 // www.ABCDEFG.com => A/AB/ABC/ABCDEFG.com
1481 tmp[0] = req->host[0];
1483 tmp[2] = req->host[0];
1484 tmp[3] = req->host[1];
1486 tmp[5] = req->host[0];
1487 tmp[6] = req->host[1];
1488 tmp[7] = req->host[2];
1490 memcpy(tmp + 9, req->host, req->host_len);
1491 tmp[req->host_len + 9] = 0;
1497 static struct dentry * vhost_lookup (tux_req_t *req, struct nameidata* base, struct vfsmount **mnt)
1499 struct dentry *dentry = NULL;
1501 char ip [3+1+3+1+3+1+3 + 2];
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)
1510 base->dentry = dget(req->proto->main_docroot.dentry);
1511 base->mnt = mntget(req->proto->main_docroot.mnt);
1512 goto lookup_default;
1514 if (req->virtual == TUX_VHOST_IP)
1517 // fall through in mixed mode:
1520 if (!req->host_len) {
1523 dentry = __tux_lookup (req, tux_default_vhost, base, mnt);
1525 char tmp [MAX_HOST_LEN*2];
1528 host_path = host_path_hash(req, tmp);
1529 Dprintk("host path hash returned: {%s}\n", host_path);
1534 dentry = __tux_lookup (req, host_path, base, mnt);
1536 if (!dentry || IS_ERR(dentry)) {
1537 if (PTR_ERR(dentry) == -EWOULDBLOCKIO)
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) {
1543 dentry = __tux_lookup (req, ip, base, mnt);
1544 if (!dentry || IS_ERR(dentry)) {
1545 if (PTR_ERR(dentry) == -EWOULDBLOCKIO)
1547 base->dentry = dget(req->proto->main_docroot.dentry);
1548 base->mnt = mntget(req->proto->main_docroot.mnt);
1551 goto lookup_default;
1558 static void http_lookup_vhost (tux_req_t *req, int cachemiss)
1560 struct dentry *dentry;
1561 struct nameidata base;
1562 struct vfsmount *mnt = NULL;
1563 unsigned int flag = cachemiss ? 0 : LOOKUP_ATOMIC;
1565 Dprintk("http_lookup_vhost(%p, %d, virtual: %d, host: %s (%d).)\n", req, flag, req->virtual, req->host, req->host_len);
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);
1572 dentry = vhost_lookup(req, &base, &mnt);
1574 Dprintk("looked up dentry %p.\n", dentry);
1576 if (dentry && !IS_ERR(dentry) && !dentry->d_inode)
1579 if (!dentry || IS_ERR(dentry)) {
1580 if (PTR_ERR(dentry) == -EWOULDBLOCKIO) {
1581 add_tux_atom(req, http_lookup_vhost);
1582 queue_cachemiss(req);
1588 req->docroot_dentry = dentry;
1589 req->docroot_mnt = mnt;
1591 add_tux_atom(req, http_process_message);
1592 add_req_to_workqueue(req);
1596 if (!IS_ERR(dentry))
1606 add_req_to_workqueue(req);
1609 static void http_process_message (tux_req_t *req, int cachemiss)
1611 tux_attribute_t *attr;
1613 unsigned int lookup_flag = cachemiss ? 0 : LOOKUP_ATOMIC;
1615 Dprintk("handling req %p, cachemiss: %d.\n", req, cachemiss);
1618 * URL redirection support - redirect all valid requests
1619 * to the first userspace module.
1621 if (tux_all_userspace) {
1622 tcapi_template_t *tcapi = get_first_usermodule();
1625 req->usermodule_idx = tcapi->userspace_id;
1629 missed = lookup_url(req, lookup_flag);
1631 if (req->query_str) {
1632 req->error = TUX_ERROR_REDIRECT;
1635 send_ret_redirect(req, cachemiss);
1644 Dprintk("uncached request.\n");
1645 INC_STAT(static_lookup_cachemisses);
1648 add_tux_atom(req, http_process_message);
1649 queue_cachemiss(req);
1653 * HTML directory indexing.
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))
1661 attr = req->dentry->d_extra_attributes;
1663 attr = lookup_tux_attribute(req);
1666 req->dentry->d_extra_attributes = attr;
1669 Dprintk("using MIME type %s:%s, %d.\n", attr->mime->type, attr->mime->ext, attr->mime->special);
1672 req->usermodule_idx = attr->tcapi->userspace_id;
1673 if (req->module_dentry)
1675 req->module_dentry = dget(req->dentry);
1676 release_req_dentry(req);
1680 switch (attr->mime->special) {
1681 case MIME_TYPE_MODULE:
1685 case MIME_TYPE_REDIRECT:
1686 req->error = TUX_ERROR_REDIRECT;
1690 #if CONFIG_TUX_EXTCGI
1691 Dprintk("CGI request %p.\n", req);
1697 if (req->query_str) {
1698 req->error = TUX_ERROR_REDIRECT;
1703 switch (req->method) {
1708 req->error = TUX_ERROR_REDIRECT;
1714 req->output_len = req->total_file_len;
1716 * Do range calculations.
1718 if (req->offset_end || req->offset_start)
1721 if (req->may_send_gzip && !req->offset_start && !req->offset_end) {
1722 if (handle_gzip_req(req, lookup_flag))
1724 if ((tux_compression >= 2) && !req->content_gzipped)
1725 req->content_gzipped = 2;
1727 if (req->parsed_len)
1733 add_tux_atom(req, http_send_body);
1734 add_tux_atom(req, http_post_header);
1736 http_pre_header(req, req->method == METHOD_HEAD);
1738 add_req_to_workqueue(req);
1743 zap_request(req, cachemiss);
1747 add_req_to_workqueue(req);
1750 static void http_post_header (tux_req_t *req, int cachemiss)
1752 #if CONFIG_TUX_DEBUG
1753 req->bytes_expected = req->output_len;
1755 req->bytes_sent = 0; // data comes now.
1757 add_req_to_workqueue(req);
1760 static void http_send_body (tux_req_t *req, int cachemiss)
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);
1766 SET_TIMESTAMP(req->output_timestamp);
1769 #if CONFIG_TUX_DEBUG
1770 req->bytes_expected = 0;
1772 req->in_file.f_pos = 0;
1774 * We are in the middle of a file transfer,
1775 * zap it immediately:
1777 TDprintk("req->error = TUX_ERROR_CONN_CLOSE.\n");
1778 req->error = TUX_ERROR_CONN_CLOSE;
1779 zap_request(req, cachemiss);
1787 if (req->method != METHOD_HEAD) {
1788 ret = generic_send_file(req, req->sock, cachemiss);
1789 Dprintk("body send-file returned: %d.\n", ret);
1791 #if CONFIG_TUX_DEBUG
1792 req->bytes_expected = 0;
1798 add_tux_atom(req, http_send_body);
1799 output_timeout(req);
1802 add_tux_atom(req, http_send_body);
1803 if (add_output_space_event(req, req->sock)) {
1809 INC_STAT(static_sendfile_cachemisses);
1810 add_tux_atom(req, http_send_body);
1811 queue_cachemiss(req);
1816 req->in_file.f_pos = 0;
1817 add_req_to_workqueue(req);
1822 #define DEFAULT_DATE "Wed, 01 Jan 1970 00:00:01 GMT"
1824 char tux_date [DATE_LEN] = DEFAULT_DATE;
1830 #define HEADER_PART1A \
1831 "HTTP/1.1 200 OK\r\n" \
1834 #define HEADER_PART1B \
1837 #define HEADER_PART1AP \
1838 "HTTP/1.1 206 Partial Content\r\n" \
1841 #define HEADER_PART1BP \
1842 "HTTP/1.1 206 Partial Content"
1844 #define HEADER_PART1C \
1845 "HTTP/1.1 404 Page Not Found\r\n" \
1848 #define HEADER_PART1D \
1849 "HTTP/1.1 200 OK\r\n" \
1850 "Content-Type: text/html\r\n" \
1851 "Connection: close\r\n"
1853 #define HEADER_PART2_keepalive "\r\nConnection: Keep-Alive\r\nDate: "
1855 #define HEADER_PART2_close "\r\nConnection: close\r\nDate: "
1857 #define HEADER_PART2_none "\r\nDate: "
1861 #define HEADER_PART3A "\r\nContent-Encoding: gzip"
1862 #define HEADER_PART3BX "\r\nContent-Length: "
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! :-)
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"
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)
1885 static void http_pre_header (tux_req_t *req, int head)
1887 int partial = req->offset_start | req->offset_end;
1888 unsigned long flags;
1890 mimetype_t *mime = NULL;
1894 if (MAX_OUT_HEADER_LEN > PAGE_SIZE)
1896 if ((req->attr && req->attr->tcapi) || req->usermode)
1899 #define COPY_STATIC_PART(nr,curr) \
1901 memcpy(curr, HEADER_PART##nr, sizeof(HEADER_PART##nr)-1); \
1902 curr += sizeof(HEADER_PART##nr)-1; \
1905 buf = curr = get_abuf(req, MAX_OUT_HEADER_LEN);
1907 if (req->lookup_dir) {
1908 COPY_STATIC_PART(1D, curr);
1911 mime = req->attr->mime;
1915 if (req->status == 404) {
1916 COPY_STATIC_PART(1C, curr);
1917 memcpy(curr, mime->type, mime->type_len);
1918 curr += mime->type_len;
1920 if (tux_noid && (mime == &default_mimetype)) {
1922 COPY_STATIC_PART(1BP, curr);
1924 COPY_STATIC_PART(1B, curr);
1927 COPY_STATIC_PART(1AP, curr);
1929 COPY_STATIC_PART(1A, curr);
1930 memcpy(curr, mime->type, mime->type_len);
1931 curr += mime->type_len;
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;
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);
1946 // HTTP/1.0 default means close
1947 COPY_STATIC_PART(2_none, curr);
1950 memcpy(curr, tux_date, DATE_LEN-1);
1953 if (req->content_gzipped)
1954 COPY_STATIC_PART(3A, curr);
1959 if (!req->lookup_dir) {
1961 COPY_STATIC_PART(3BX, curr);
1963 COPY_STATIC_PART(3BY, curr);
1966 curr += sprintf(curr, "%Ld", req->output_len);
1968 if (req->content_gzipped)
1969 curr += sprintf(curr, "%Ld",
1970 req->total_file_len);
1972 memcpy(curr, &req->etag, req->lendigits);
1973 curr += req->lendigits;
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;
1983 if (tux_generate_last_mod || tux_generate_etags)
1984 COPY_STATIC_PART(3ACC, curr);
1986 if (tux_generate_last_mod && (req->status != 404)) {
1987 COPY_STATIC_PART(3L, curr);
1988 last_mod_time(curr, req->mtime);
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);
1996 COPY_STATIC_PART(4, curr);
1998 * Possibly add an extra HTML header:
2000 if (tux_extra_html_header_size && mime && !strcmp(mime->type, "text/html")) {
2001 unsigned int len = tux_extra_html_header_size;
2003 memcpy(curr, tux_extra_html_header, len);
2009 #if CONFIG_TUX_DEBUG
2011 Dprintk("{%s} [%d/%d]\n", buf, size, strlen(buf));
2014 flags = MSG_DONTWAIT;
2017 send_abuf(req, size, flags);
2020 void http_illegal_request (tux_req_t *req, int cachemiss)
2022 if (req->status == 304)
2023 send_ret_notmodified(req);
2025 if (req->status == 403)
2026 send_async_err_forbidden(req);
2028 send_async_err_not_found(req);
2032 static int http_check_req_err (tux_req_t *req, int cachemiss)
2034 if ((req->sock->sk->sk_state <= TCP_SYN_RECV) &&
2035 !tcp_sk(req->sock->sk)->urg_data)
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;
2043 req->in_file.f_pos = 0;
2044 req->error = TUX_ERROR_CONN_CLOSE;
2045 zap_request(req, cachemiss);
2050 #define COPY_STR(str) \
2051 do { memcpy(tmp, str, sizeof(str)-1); \
2052 tmp += sizeof(str)-1; } while (0)
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)
2061 COPY_STR("<IMG SRC=\"/icons/dir.gif\" ALT=\"[DIR]\">");
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=\"[ ]\">");
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=\"[ ]\">");
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=\"[ ]\">");
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=\"[ ]\">");
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=\"[ ]\">");
2098 COPY_STR("<IMG SRC=\"/icons/file.gif\" ALT=\"[ ]\">");
2101 COPY_STR("<IMG SRC=\"/icons/link.gif\" ALT=\"[LNK]\">");
2104 if (tux_hide_unreadable)
2106 COPY_STR("<IMG SRC=\"/icons/unknown.gif\" ALT=\"[ ]\">");
2110 #define LIST_1 " <A HREF=\""
2111 #define LIST_2 "\">"
2112 #define LIST_2_DIR "/\">"
2113 #define LIST_3 "</A> "
2116 memcpy(tmp, d_name, d_len);
2118 if (d_type == DT_DIR)
2119 COPY_STR(LIST_2_DIR);
2127 memcpy(tmp, d_name, len);
2133 if (d_type == DT_DIR)
2146 memset(tmp, ' ', FILL-d_len);
2150 tmp += time_unix2ls(inode->i_mtime.tv_sec, tmp);
2153 if (d_type != DT_REG) {
2157 size = inode->i_size >> 10;
2159 tmp += sprintf(tmp, "%8Lik ", size);
2164 tmp += sprintf(tmp, "%8LiM ", size);
2169 tmp += sprintf(tmp, "%8LiG ", size);
2174 tmp += sprintf(tmp, "%8LiT ", size);
2178 tmp += sprintf(tmp, "%8LiT ", size);
2189 tux_proto_t tux_proto_http = {
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,