xref: /aosp_15_r20/external/toybox/toys/net/httpd.c (revision cf5a6c84e2b8763fc1a7db14496fd4742913b199)
1*cf5a6c84SAndroid Build Coastguard Worker /* httpd.c - Web server.
2*cf5a6c84SAndroid Build Coastguard Worker  *
3*cf5a6c84SAndroid Build Coastguard Worker  * Copyright 2022 Rob Landley <[email protected]>
4*cf5a6c84SAndroid Build Coastguard Worker  *
5*cf5a6c84SAndroid Build Coastguard Worker  * See https://www.ietf.org/rfc/rfc2616.txt
6*cf5a6c84SAndroid Build Coastguard Worker  *
7*cf5a6c84SAndroid Build Coastguard Worker  * TODO: multiple domains, https, actual inetd with ratelimit...
8*cf5a6c84SAndroid Build Coastguard Worker  * range, gzip, ETag (If-None-Match:, Last-Modified:), Date:
9*cf5a6c84SAndroid Build Coastguard Worker  * "Accept-Ranges: bytes"/"Range: bytes=xxx-[yyy]"
10*cf5a6c84SAndroid Build Coastguard Worker  * .htaccess (auth, forward)
11*cf5a6c84SAndroid Build Coastguard Worker  * optional conf file, error pages
12*cf5a6c84SAndroid Build Coastguard Worker  * -ifv -p [IP:]PORT -u [USER][:GRP] -c CFGFILE
13*cf5a6c84SAndroid Build Coastguard Worker  * cgi: SERVER_PORT SERVER_NAME REMOTE_ADDR REMOTE_HOST REQUEST_METHOD
14*cf5a6c84SAndroid Build Coastguard Worker 
15*cf5a6c84SAndroid Build Coastguard Worker USE_HTTPD(NEWTOY(httpd, ">1v", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LINEBUF))
16*cf5a6c84SAndroid Build Coastguard Worker 
17*cf5a6c84SAndroid Build Coastguard Worker config HTTPD
18*cf5a6c84SAndroid Build Coastguard Worker   bool "httpd"
19*cf5a6c84SAndroid Build Coastguard Worker   default y
20*cf5a6c84SAndroid Build Coastguard Worker   help
21*cf5a6c84SAndroid Build Coastguard Worker     usage: httpd [-de STR] [-v] [DIR]
22*cf5a6c84SAndroid Build Coastguard Worker 
23*cf5a6c84SAndroid Build Coastguard Worker     Serve contents of directory as static web pages.
24*cf5a6c84SAndroid Build Coastguard Worker 
25*cf5a6c84SAndroid Build Coastguard Worker     -e	Escape STR as URL, printing result and exiting.
26*cf5a6c84SAndroid Build Coastguard Worker     -d	Decode escaped STR, printing result and exiting.
27*cf5a6c84SAndroid Build Coastguard Worker     -v	Verbose
28*cf5a6c84SAndroid Build Coastguard Worker */
29*cf5a6c84SAndroid Build Coastguard Worker 
30*cf5a6c84SAndroid Build Coastguard Worker #define FOR_httpd
31*cf5a6c84SAndroid Build Coastguard Worker #include "toys.h"
32*cf5a6c84SAndroid Build Coastguard Worker 
rfc1123(char * buf,time_t t)33*cf5a6c84SAndroid Build Coastguard Worker char *rfc1123(char *buf, time_t t)
34*cf5a6c84SAndroid Build Coastguard Worker {
35*cf5a6c84SAndroid Build Coastguard Worker   strftime(buf, 64, "%a, %d %b %Y %T GMT", gmtime(&t));
36*cf5a6c84SAndroid Build Coastguard Worker 
37*cf5a6c84SAndroid Build Coastguard Worker   return buf;
38*cf5a6c84SAndroid Build Coastguard Worker }
39*cf5a6c84SAndroid Build Coastguard Worker 
40*cf5a6c84SAndroid Build Coastguard Worker // She never told me...
mime(char * file)41*cf5a6c84SAndroid Build Coastguard Worker char *mime(char *file)
42*cf5a6c84SAndroid Build Coastguard Worker {
43*cf5a6c84SAndroid Build Coastguard Worker   char *s = strrchr(file, '.'), *types[] = {
44*cf5a6c84SAndroid Build Coastguard Worker     "asc\0text/plain", "bin\0application/octet-stream", "bmp\0image/bmp",
45*cf5a6c84SAndroid Build Coastguard Worker     "cpio\0application/x-cpio", "css\0text/css", "doc\0application/msword",
46*cf5a6c84SAndroid Build Coastguard Worker     "dtd\0text/xml", "dvi\0application/x-dvi", "gif\0image/gif",
47*cf5a6c84SAndroid Build Coastguard Worker     "htm\0text/html", "html\0text/html", "jar\0applicat/x-java-archive",
48*cf5a6c84SAndroid Build Coastguard Worker     "jpeg\0image/jpeg", "jpg\0image/jpeg", "js\0application/x-javascript",
49*cf5a6c84SAndroid Build Coastguard Worker     "mp3\0audio/mpeg", "mp4\0video/mp4", "mpg\0video/mpeg",
50*cf5a6c84SAndroid Build Coastguard Worker     "ogg\0application/ogg", "pbm\0image/x-portable-bitmap",
51*cf5a6c84SAndroid Build Coastguard Worker     "pdf\0application/pdf", "png\0image/png",
52*cf5a6c84SAndroid Build Coastguard Worker     "ppt\0application/vnd.ms-powerpoint", "ps\0application/postscript",
53*cf5a6c84SAndroid Build Coastguard Worker     "rtf\0text/rtf", "sgml\0text/sgml", "svg\0image/svg+xml",
54*cf5a6c84SAndroid Build Coastguard Worker     "tar\0application/x-tar", "tex\0application/x-tex", "tiff\0image/tiff",
55*cf5a6c84SAndroid Build Coastguard Worker     "txt\0text/plain", "wav\0audio/x-wav", "xls\0application/vnd.ms-excel",
56*cf5a6c84SAndroid Build Coastguard Worker     "xml\0tet/xml", "zip\0application/zip"
57*cf5a6c84SAndroid Build Coastguard Worker   };
58*cf5a6c84SAndroid Build Coastguard Worker   int i;
59*cf5a6c84SAndroid Build Coastguard Worker 
60*cf5a6c84SAndroid Build Coastguard Worker   strcpy(toybuf, "text/plain");
61*cf5a6c84SAndroid Build Coastguard Worker   if (s++) for (i = 0; i<ARRAY_LEN(types); i++) {
62*cf5a6c84SAndroid Build Coastguard Worker     if (strcasecmp(s, types[i])) continue;
63*cf5a6c84SAndroid Build Coastguard Worker     strcpy(toybuf, types[i]+strlen(types[i])+1);
64*cf5a6c84SAndroid Build Coastguard Worker     break;
65*cf5a6c84SAndroid Build Coastguard Worker   }
66*cf5a6c84SAndroid Build Coastguard Worker   if (!strncmp(toybuf, "text/", 5)) strcat(toybuf, "; charset=UTF-8");
67*cf5a6c84SAndroid Build Coastguard Worker 
68*cf5a6c84SAndroid Build Coastguard Worker   return toybuf;
69*cf5a6c84SAndroid Build Coastguard Worker }
70*cf5a6c84SAndroid Build Coastguard Worker 
71*cf5a6c84SAndroid Build Coastguard Worker // Stop: header time.
header_time(int stat,char * str,char * more)72*cf5a6c84SAndroid Build Coastguard Worker static void header_time(int stat, char *str, char *more)
73*cf5a6c84SAndroid Build Coastguard Worker {
74*cf5a6c84SAndroid Build Coastguard Worker   char buf[64];
75*cf5a6c84SAndroid Build Coastguard Worker 
76*cf5a6c84SAndroid Build Coastguard Worker   if (!more) more = "";
77*cf5a6c84SAndroid Build Coastguard Worker   if (FLAG(v)) dprintf(2, "REPLY: %d %s\n%s\n", stat, str, more);
78*cf5a6c84SAndroid Build Coastguard Worker   xprintf("HTTP/1.1 %d %s\r\nServer: toybox httpd/%s\r\nDate: %s\r\n%s"
79*cf5a6c84SAndroid Build Coastguard Worker     "Connection: close\r\n\r\n", stat, str, TOYBOX_VERSION,
80*cf5a6c84SAndroid Build Coastguard Worker     rfc1123(buf, time(0)), more);
81*cf5a6c84SAndroid Build Coastguard Worker }
82*cf5a6c84SAndroid Build Coastguard Worker 
error_time(int stat,char * str)83*cf5a6c84SAndroid Build Coastguard Worker static void error_time(int stat, char *str)
84*cf5a6c84SAndroid Build Coastguard Worker {
85*cf5a6c84SAndroid Build Coastguard Worker   header_time(stat, str, 0);
86*cf5a6c84SAndroid Build Coastguard Worker   xprintf("<html><head><title>%d %s</title></head>"
87*cf5a6c84SAndroid Build Coastguard Worker     "<body><h3>%d %s</h3></body></html>", stat, str, stat, str);
88*cf5a6c84SAndroid Build Coastguard Worker }
89*cf5a6c84SAndroid Build Coastguard Worker 
isunder(char * file,char * dir)90*cf5a6c84SAndroid Build Coastguard Worker static int isunder(char *file, char *dir)
91*cf5a6c84SAndroid Build Coastguard Worker {
92*cf5a6c84SAndroid Build Coastguard Worker   char *s1 = xabspath(dir, ABS_FILE), *s2 = xabspath(file, 0), *ss = s2;
93*cf5a6c84SAndroid Build Coastguard Worker   int rc = s1 && s2 && strstart(&ss, s1) && (!*ss || *ss=='/' || ss[-1]=='/');
94*cf5a6c84SAndroid Build Coastguard Worker 
95*cf5a6c84SAndroid Build Coastguard Worker   free(s2);
96*cf5a6c84SAndroid Build Coastguard Worker   free(s1);
97*cf5a6c84SAndroid Build Coastguard Worker 
98*cf5a6c84SAndroid Build Coastguard Worker   return rc;
99*cf5a6c84SAndroid Build Coastguard Worker }
100*cf5a6c84SAndroid Build Coastguard Worker 
101*cf5a6c84SAndroid Build Coastguard Worker // Handle a connection on fd
handle(int infd,int outfd)102*cf5a6c84SAndroid Build Coastguard Worker void handle(int infd, int outfd)
103*cf5a6c84SAndroid Build Coastguard Worker {
104*cf5a6c84SAndroid Build Coastguard Worker   FILE *fp = fdopen(infd, "r");
105*cf5a6c84SAndroid Build Coastguard Worker   char *s = xgetline(fp), *cut, *ss, *esc, *path, *word[3];
106*cf5a6c84SAndroid Build Coastguard Worker   int i = sizeof(toybuf), fd;
107*cf5a6c84SAndroid Build Coastguard Worker 
108*cf5a6c84SAndroid Build Coastguard Worker   if (!s) return;
109*cf5a6c84SAndroid Build Coastguard Worker 
110*cf5a6c84SAndroid Build Coastguard Worker   if (!getsockname(0, (void *)&toybuf, &i)) {
111*cf5a6c84SAndroid Build Coastguard Worker     if (FLAG(v))
112*cf5a6c84SAndroid Build Coastguard Worker       dprintf(2, "Hello %s\n%s\n", ntop((void *)toybuf), s);
113*cf5a6c84SAndroid Build Coastguard Worker   }
114*cf5a6c84SAndroid Build Coastguard Worker 
115*cf5a6c84SAndroid Build Coastguard Worker   // Split line into method/path/protocol
116*cf5a6c84SAndroid Build Coastguard Worker   for (i = 0, ss = s;;) {
117*cf5a6c84SAndroid Build Coastguard Worker     word[i++] = ss;
118*cf5a6c84SAndroid Build Coastguard Worker     while (*ss && !strchr(" \r\n", *ss)) ss++;
119*cf5a6c84SAndroid Build Coastguard Worker     while (*ss && strchr(" \r\n", *ss)) *(ss++) = 0;
120*cf5a6c84SAndroid Build Coastguard Worker     if (i==3) break;
121*cf5a6c84SAndroid Build Coastguard Worker     if (!*ss) return header_time(400, "Bad Request", 0);
122*cf5a6c84SAndroid Build Coastguard Worker   }
123*cf5a6c84SAndroid Build Coastguard Worker 
124*cf5a6c84SAndroid Build Coastguard Worker   // Process additional http/1.1 lines
125*cf5a6c84SAndroid Build Coastguard Worker   while ((ss = xgetline(fp))) {
126*cf5a6c84SAndroid Build Coastguard Worker     i = *chomp(ss);
127*cf5a6c84SAndroid Build Coastguard Worker     if (FLAG(v)) dprintf(2, "%s\n", ss);
128*cf5a6c84SAndroid Build Coastguard Worker // TODO: any of
129*cf5a6c84SAndroid Build Coastguard Worker //User-Agent: Wget/1.20.1 (linux-gnu) - do we want to log anything?
130*cf5a6c84SAndroid Build Coastguard Worker //Accept: */* - 406 Too Snobbish
131*cf5a6c84SAndroid Build Coastguard Worker //Accept-Encoding: identity - we can gzip?
132*cf5a6c84SAndroid Build Coastguard Worker //Host: landley.net  - we could handle multiple domains?
133*cf5a6c84SAndroid Build Coastguard Worker //Connection: Keep-Alive - probably don't care
134*cf5a6c84SAndroid Build Coastguard Worker 
135*cf5a6c84SAndroid Build Coastguard Worker     free(ss);
136*cf5a6c84SAndroid Build Coastguard Worker     if (!i) break;
137*cf5a6c84SAndroid Build Coastguard Worker   }
138*cf5a6c84SAndroid Build Coastguard Worker 
139*cf5a6c84SAndroid Build Coastguard Worker   if (!strcasecmp(word[0], "get")) {
140*cf5a6c84SAndroid Build Coastguard Worker     struct stat st;
141*cf5a6c84SAndroid Build Coastguard Worker 
142*cf5a6c84SAndroid Build Coastguard Worker     if (*(ss = word[1])!='/') error_time(400, "Bad Request");
143*cf5a6c84SAndroid Build Coastguard Worker     while (*ss=='/') ss++;
144*cf5a6c84SAndroid Build Coastguard Worker     if (!*ss) ss = "./";
145*cf5a6c84SAndroid Build Coastguard Worker     else if ((cut = unescape_url(ss, 1))) setenv("QUERY_STRING", cut, 1);
146*cf5a6c84SAndroid Build Coastguard Worker 
147*cf5a6c84SAndroid Build Coastguard Worker     // TODO domain.com:/path/to/blah domain2.com:/path/to/that
148*cf5a6c84SAndroid Build Coastguard Worker     // TODO cgi PATH_INFO /path/to/filename.cgi/and/more/stuff?path&info
149*cf5a6c84SAndroid Build Coastguard Worker     if (!isunder(ss, ".") || stat(ss, &st)) error_time(404, "Not Found");
150*cf5a6c84SAndroid Build Coastguard Worker     else if (-1 == (fd = open(ss, O_RDONLY))) error_time(403, "Forbidden");
151*cf5a6c84SAndroid Build Coastguard Worker     else if (!S_ISDIR(st.st_mode)) {
152*cf5a6c84SAndroid Build Coastguard Worker       char buf[64];
153*cf5a6c84SAndroid Build Coastguard Worker file:
154*cf5a6c84SAndroid Build Coastguard Worker       header_time(200, "Ok", ss = xmprintf("Content-Type: %s\r\n"
155*cf5a6c84SAndroid Build Coastguard Worker         "Content-Length: %lld\r\nLast-Modified: %s\r\n",
156*cf5a6c84SAndroid Build Coastguard Worker         mime(ss), (long long)st.st_size, rfc1123(buf, st.st_mtime)));
157*cf5a6c84SAndroid Build Coastguard Worker       free(ss);
158*cf5a6c84SAndroid Build Coastguard Worker       xsendfile(fd, outfd);
159*cf5a6c84SAndroid Build Coastguard Worker     } else if (ss[strlen(ss)-1]!='/') {
160*cf5a6c84SAndroid Build Coastguard Worker       header_time(302, "Found", path = xmprintf("Location: %s/\r\n", word[1]));
161*cf5a6c84SAndroid Build Coastguard Worker       free(path);
162*cf5a6c84SAndroid Build Coastguard Worker     } else {
163*cf5a6c84SAndroid Build Coastguard Worker       DIR *dd;
164*cf5a6c84SAndroid Build Coastguard Worker       struct dirent *dir;
165*cf5a6c84SAndroid Build Coastguard Worker 
166*cf5a6c84SAndroid Build Coastguard Worker       // Do we have an index.html?
167*cf5a6c84SAndroid Build Coastguard Worker       path = ss;
168*cf5a6c84SAndroid Build Coastguard Worker       ss = "index.html";
169*cf5a6c84SAndroid Build Coastguard Worker       path = xmprintf("%s%s", path, ss);
170*cf5a6c84SAndroid Build Coastguard Worker       if (stat(path, &st) || !S_ISREG(st.st_mode)) i = -1;
171*cf5a6c84SAndroid Build Coastguard Worker       else if (-1 == (i = open(path, O_RDONLY))) error_time(403, "Forbidden");
172*cf5a6c84SAndroid Build Coastguard Worker       free(path);
173*cf5a6c84SAndroid Build Coastguard Worker       if (i != -1) {
174*cf5a6c84SAndroid Build Coastguard Worker         close(fd);
175*cf5a6c84SAndroid Build Coastguard Worker         fd = i;
176*cf5a6c84SAndroid Build Coastguard Worker 
177*cf5a6c84SAndroid Build Coastguard Worker         goto file;
178*cf5a6c84SAndroid Build Coastguard Worker       }
179*cf5a6c84SAndroid Build Coastguard Worker 
180*cf5a6c84SAndroid Build Coastguard Worker       // List directory contents
181*cf5a6c84SAndroid Build Coastguard Worker       header_time(200, "Ok", "Content-Type: text/html\r\n");
182*cf5a6c84SAndroid Build Coastguard Worker       dprintf(outfd, "<html><head><title>Index of %s</title></head>\n"
183*cf5a6c84SAndroid Build Coastguard Worker         "<body><h3>Index of %s</h3></body>\n", word[1], word[1]);
184*cf5a6c84SAndroid Build Coastguard Worker       for (dd = fdopendir(fd); (dir = readdir(dd));) {
185*cf5a6c84SAndroid Build Coastguard Worker         esc = escape_url(dir->d_name, "<>&\"");
186*cf5a6c84SAndroid Build Coastguard Worker         dprintf(outfd, "<a href=\"%s\">%s</a><br />\n", esc, esc);
187*cf5a6c84SAndroid Build Coastguard Worker         free(esc);
188*cf5a6c84SAndroid Build Coastguard Worker       }
189*cf5a6c84SAndroid Build Coastguard Worker       dprintf(outfd, "</body></html>\n");
190*cf5a6c84SAndroid Build Coastguard Worker     }
191*cf5a6c84SAndroid Build Coastguard Worker   } else error_time(501, "Not Implemented");
192*cf5a6c84SAndroid Build Coastguard Worker   free(s);
193*cf5a6c84SAndroid Build Coastguard Worker }
194*cf5a6c84SAndroid Build Coastguard Worker 
httpd_main(void)195*cf5a6c84SAndroid Build Coastguard Worker void httpd_main(void)
196*cf5a6c84SAndroid Build Coastguard Worker {
197*cf5a6c84SAndroid Build Coastguard Worker   if (toys.optc && chdir(*toys.optargs))
198*cf5a6c84SAndroid Build Coastguard Worker     return error_time(500, "Internal Error");
199*cf5a6c84SAndroid Build Coastguard Worker   // inetd only at the moment
200*cf5a6c84SAndroid Build Coastguard Worker   handle(0, 1);
201*cf5a6c84SAndroid Build Coastguard Worker }
202