diff options
Diffstat (limited to 'opentracker.c')
-rw-r--r-- | opentracker.c | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/opentracker.c b/opentracker.c new file mode 100644 index 0000000..bd47a24 --- /dev/null +++ b/opentracker.c | |||
@@ -0,0 +1,288 @@ | |||
1 | #include "socket.h" | ||
2 | #include "io.h" | ||
3 | #include "buffer.h" | ||
4 | #include "ip6.h" | ||
5 | #include "array.h" | ||
6 | #include "case.h" | ||
7 | #include "fmt.h" | ||
8 | #include "iob.h" | ||
9 | #include "str.h" | ||
10 | #include <sys/types.h> | ||
11 | #include <sys/stat.h> | ||
12 | #include <unistd.h> | ||
13 | #include <stdlib.h> | ||
14 | #include <errno.h> | ||
15 | |||
16 | static void carp(const char* routine) { | ||
17 | buffer_puts(buffer_2,routine); | ||
18 | buffer_puts(buffer_2,": "); | ||
19 | buffer_puterror(buffer_2); | ||
20 | buffer_putnlflush(buffer_2); | ||
21 | } | ||
22 | |||
23 | static void panic(const char* routine) { | ||
24 | carp(routine); | ||
25 | exit(111); | ||
26 | } | ||
27 | |||
28 | struct http_data { | ||
29 | array r; | ||
30 | io_batch iob; | ||
31 | char* hdrbuf; | ||
32 | int hlen; | ||
33 | int keepalive; | ||
34 | }; | ||
35 | |||
36 | int header_complete(struct http_data* r) { | ||
37 | long i; | ||
38 | long l=array_bytes(&r->r); | ||
39 | const char* c=array_start(&r->r); | ||
40 | for (i=0; i+1<l; ++i) { | ||
41 | if (c[i]=='\n' && c[i+1]=='\n') | ||
42 | return i+2; | ||
43 | if (i+3<l && | ||
44 | c[i]=='\r' && c[i+1]=='\n' && | ||
45 | c[i+2]=='\r' && c[i+3]=='\n') | ||
46 | return i+4; | ||
47 | } | ||
48 | return 0; | ||
49 | } | ||
50 | |||
51 | void httperror(struct http_data* r,const char* title,const char* message) { | ||
52 | char* c; | ||
53 | c=r->hdrbuf=(char*)malloc(strlen(message)+strlen(title)+200); | ||
54 | if (!c) { | ||
55 | r->hdrbuf="HTTP/1.0 500 internal error\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nout of memory\n"; | ||
56 | r->hlen=strlen(r->hdrbuf); | ||
57 | } else { | ||
58 | c+=fmt_str(c,"HTTP/1.0 "); | ||
59 | c+=fmt_str(c,title); | ||
60 | c+=fmt_str(c,"\r\nContent-Type: text/html\r\nConnection: "); | ||
61 | c+=fmt_str(c,r->keepalive?"keep-alive":"close"); | ||
62 | c+=fmt_str(c,"\r\nContent-Length: "); | ||
63 | c+=fmt_ulong(c,strlen(message)+strlen(title)+16-4); | ||
64 | c+=fmt_str(c,"\r\n\r\n<title>"); | ||
65 | c+=fmt_str(c,title+4); | ||
66 | c+=fmt_str(c,"</title>\n"); | ||
67 | r->hlen=c - r->hdrbuf; | ||
68 | } | ||
69 | iob_addbuf(&r->iob,r->hdrbuf,r->hlen); | ||
70 | } | ||
71 | |||
72 | static struct mimeentry { const char* name, *type; } mimetab[] = { | ||
73 | { "html", "text/html" }, | ||
74 | { "css", "text/css" }, | ||
75 | { "dvi", "application/x-dvi" }, | ||
76 | { "ps", "application/postscript" }, | ||
77 | { "pdf", "application/pdf" }, | ||
78 | { "gif", "image/gif" }, | ||
79 | { "png", "image/png" }, | ||
80 | { "jpeg", "image/jpeg" }, | ||
81 | { "jpg", "image/jpeg" }, | ||
82 | { "mpeg", "video/mpeg" }, | ||
83 | { "mpg", "video/mpeg" }, | ||
84 | { "avi", "video/x-msvideo" }, | ||
85 | { "mov", "video/quicktime" }, | ||
86 | { "qt", "video/quicktime" }, | ||
87 | { "mp3", "audio/mpeg" }, | ||
88 | { "ogg", "audio/x-oggvorbis" }, | ||
89 | { "wav", "audio/x-wav" }, | ||
90 | { "pac", "application/x-ns-proxy-autoconfig" }, | ||
91 | { "sig", "application/pgp-signature" }, | ||
92 | { "torrent", "application/x-bittorrent" }, | ||
93 | { "class", "application/octet-stream" }, | ||
94 | { "js", "application/x-javascript" }, | ||
95 | { "tar", "application/x-tar" }, | ||
96 | { "zip", "application/zip" }, | ||
97 | { "dtd", "text/xml" }, | ||
98 | { "xml", "text/xml" }, | ||
99 | { "xbm", "image/x-xbitmap" }, | ||
100 | { "xpm", "image/x-xpixmap" }, | ||
101 | { "xwd", "image/x-xwindowdump" }, | ||
102 | { 0,0 } }; | ||
103 | |||
104 | const char* mimetype(const char* filename) { | ||
105 | int i,e=str_rchr(filename,'.'); | ||
106 | if (filename[e]==0) return "text/plain"; | ||
107 | ++e; | ||
108 | for (i=0; mimetab[i].name; ++i) | ||
109 | if (str_equal(mimetab[i].name,filename+e)) | ||
110 | return mimetab[i].type; | ||
111 | return "application/octet-stream"; | ||
112 | } | ||
113 | |||
114 | const char* http_header(struct http_data* r,const char* h) { | ||
115 | long i; | ||
116 | long l=array_bytes(&r->r); | ||
117 | long sl=strlen(h); | ||
118 | const char* c=array_start(&r->r); | ||
119 | for (i=0; i+sl+2<l; ++i) | ||
120 | if (c[i]=='\n' && case_equalb(c+i+1,sl,h) && c[i+sl+1]==':') { | ||
121 | c+=i+sl+1; | ||
122 | if (*c==' ' || *c=='\t') ++c; | ||
123 | return c; | ||
124 | } | ||
125 | return 0; | ||
126 | } | ||
127 | |||
128 | void httpresponse(struct http_data* h,int64 s) { | ||
129 | char* c; | ||
130 | const char* m; | ||
131 | array_cat0(&h->r); | ||
132 | c=array_start(&h->r); | ||
133 | if (byte_diff(c,4,"GET ")) { | ||
134 | e400: | ||
135 | httperror(h,"400 Invalid Request","This server only understands GET."); | ||
136 | } else { | ||
137 | char *d; | ||
138 | int64 fd; | ||
139 | struct stat s; | ||
140 | c+=4; | ||
141 | for (d=c; *d!=' '&&*d!='\t'&&*d!='\n'&&*d!='\r'; ++d) ; | ||
142 | if (*d!=' ') goto e400; | ||
143 | *d=0; | ||
144 | if (c[0]!='/') goto e404; | ||
145 | while (c[1]=='/') ++c; | ||
146 | if (!io_readfile(&fd,c+1)) { | ||
147 | e404: | ||
148 | httperror(h,"404 Not Found","No such file or directory."); | ||
149 | } else { | ||
150 | if (fstat(fd,&s)==-1) { | ||
151 | io_close(fd); | ||
152 | goto e404; | ||
153 | } | ||
154 | if ((m=http_header(h,"Connection"))) { | ||
155 | if (str_equal(m,"keep-alive")) | ||
156 | h->keepalive=1; | ||
157 | else | ||
158 | h->keepalive=0; | ||
159 | } else { | ||
160 | if (byte_equal(d+1,8,"HTTP/1.0")) | ||
161 | h->keepalive=0; | ||
162 | else | ||
163 | h->keepalive=1; | ||
164 | } | ||
165 | m=mimetype(c); | ||
166 | c=h->hdrbuf=(char*)malloc(500); | ||
167 | c+=fmt_str(c,"HTTP/1.1 Coming Up\r\nContent-Type: "); | ||
168 | c+=fmt_str(c,m); | ||
169 | c+=fmt_str(c,"\r\nContent-Length: "); | ||
170 | c+=fmt_ulonglong(c,s.st_size); | ||
171 | c+=fmt_str(c,"\r\nLast-Modified: "); | ||
172 | c+=fmt_httpdate(c,s.st_mtime); | ||
173 | c+=fmt_str(c,"\r\nConnection: "); | ||
174 | c+=fmt_str(c,h->keepalive?"keep-alive":"close"); | ||
175 | c+=fmt_str(c,"\r\n\r\n"); | ||
176 | iob_addbuf(&h->iob,h->hdrbuf,c - h->hdrbuf); | ||
177 | iob_addfile(&h->iob,fd,0,s.st_size); | ||
178 | } | ||
179 | } | ||
180 | io_dontwantread(s); | ||
181 | io_wantwrite(s); | ||
182 | } | ||
183 | |||
184 | int main() { | ||
185 | int s=socket_tcp6b(); | ||
186 | uint32 scope_id; | ||
187 | char ip[16]; | ||
188 | uint16 port; | ||
189 | if (socket_bind6_reuse(s,V6any,8000,0)==-1) | ||
190 | panic("socket_bind6_reuse"); | ||
191 | if (socket_listen(s,16)==-1) | ||
192 | panic("socket_listen"); | ||
193 | if (!io_fd(s)) | ||
194 | panic("io_fd"); | ||
195 | io_wantread(s); | ||
196 | for (;;) { | ||
197 | int64 i; | ||
198 | io_wait(); | ||
199 | while ((i=io_canread())!=-1) { | ||
200 | if (i==s) { | ||
201 | int n; | ||
202 | while ((n=socket_accept6(s,ip,&port,&scope_id))!=-1) { | ||
203 | char buf[IP6_FMT]; | ||
204 | buffer_puts(buffer_2,"accepted new connection from "); | ||
205 | buffer_put(buffer_2,buf,fmt_ip6(buf,ip)); | ||
206 | buffer_puts(buffer_2,":"); | ||
207 | buffer_putulong(buffer_2,port); | ||
208 | buffer_puts(buffer_2," (fd "); | ||
209 | buffer_putulong(buffer_2,n); | ||
210 | buffer_puts(buffer_2,")"); | ||
211 | if (io_fd(n)) { | ||
212 | struct http_data* h=(struct http_data*)malloc(sizeof(struct http_data)); | ||
213 | io_wantread(n); | ||
214 | if (h) { | ||
215 | byte_zero(h,sizeof(struct http_data)); | ||
216 | io_setcookie(n,h); | ||
217 | } else | ||
218 | io_close(n); | ||
219 | } else { | ||
220 | buffer_puts(buffer_2,", but io_fd failed."); | ||
221 | io_close(n); | ||
222 | } | ||
223 | buffer_putnlflush(buffer_2); | ||
224 | } | ||
225 | if (errno==EAGAIN) | ||
226 | io_eagain(s); | ||
227 | else | ||
228 | carp("socket_accept6"); | ||
229 | } else { | ||
230 | char buf[8192]; | ||
231 | struct http_data* h=io_getcookie(i); | ||
232 | int l=io_tryread(i,buf,sizeof buf); | ||
233 | if (l==-3) { | ||
234 | if (h) { | ||
235 | array_reset(&h->r); | ||
236 | iob_reset(&h->iob); | ||
237 | free(h->hdrbuf); h->hdrbuf=0; | ||
238 | } | ||
239 | buffer_puts(buffer_2,"io_tryread("); | ||
240 | buffer_putulong(buffer_2,i); | ||
241 | buffer_puts(buffer_2,"): "); | ||
242 | buffer_puterror(buffer_2); | ||
243 | buffer_putnlflush(buffer_2); | ||
244 | io_close(i); | ||
245 | } else if (l==0) { | ||
246 | if (h) { | ||
247 | array_reset(&h->r); | ||
248 | iob_reset(&h->iob); | ||
249 | free(h->hdrbuf); h->hdrbuf=0; | ||
250 | } | ||
251 | buffer_puts(buffer_2,"eof on fd #"); | ||
252 | buffer_putulong(buffer_2,i); | ||
253 | buffer_putnlflush(buffer_2); | ||
254 | io_close(i); | ||
255 | } else if (l>0) { | ||
256 | array_catb(&h->r,buf,l); | ||
257 | if (array_failed(&h->r)) { | ||
258 | httperror(h,"500 Server Error","request too long."); | ||
259 | emerge: | ||
260 | io_dontwantread(i); | ||
261 | io_wantwrite(i); | ||
262 | } else if (array_bytes(&h->r)>8192) { | ||
263 | httperror(h,"500 request too long","You sent too much headers"); | ||
264 | goto emerge; | ||
265 | } else if ((l=header_complete(h))) | ||
266 | httpresponse(h,i); | ||
267 | } | ||
268 | } | ||
269 | } | ||
270 | while ((i=io_canwrite())!=-1) { | ||
271 | struct http_data* h=io_getcookie(i); | ||
272 | int64 r=iob_send(i,&h->iob); | ||
273 | /* printf("iob_send returned %lld\n",r); */ | ||
274 | if (r==-1) io_eagain(i); else | ||
275 | if (r<=0) { | ||
276 | array_trunc(&h->r); | ||
277 | iob_reset(&h->iob); | ||
278 | free(h->hdrbuf); h->hdrbuf=0; | ||
279 | if (h->keepalive) { | ||
280 | io_dontwantwrite(i); | ||
281 | io_wantread(i); | ||
282 | } else | ||
283 | io_close(i); | ||
284 | } | ||
285 | } | ||
286 | } | ||
287 | return 0; | ||
288 | } | ||