adamdunkels | 3023dee | 2003-07-04 10:54:51 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright (c) 2001, Adam Dunkels. |
| 3 | * All rights reserved. |
| 4 | * |
| 5 | * Redistribution and use in source and binary forms, with or without |
| 6 | * modification, are permitted provided that the following conditions |
| 7 | * are met: |
| 8 | * 1. Redistributions of source code must retain the above copyright |
| 9 | * notice, this list of conditions and the following disclaimer. |
| 10 | * 2. Redistributions in binary form must reproduce the above copyright |
| 11 | * notice, this list of conditions and the following disclaimer in the |
| 12 | * documentation and/or other materials provided with the distribution. |
| 13 | * 3. All advertising materials mentioning features or use of this software |
| 14 | * must display the following acknowledgement: |
| 15 | * This product includes software developed by Adam Dunkels. |
| 16 | * 4. The name of the author may not be used to endorse or promote |
| 17 | * products derived from this software without specific prior |
| 18 | * written permission. |
| 19 | * |
| 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS |
| 21 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 22 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
| 24 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
| 26 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| 27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| 28 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| 29 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| 30 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 31 | * |
| 32 | * This file is part of the uIP TCP/IP stack. |
| 33 | * |
| 34 | * $Id: httpd.c,v 1.1 2003/07/04 10:54:51 adamdunkels Exp $ |
| 35 | * |
| 36 | */ |
| 37 | |
| 38 | |
| 39 | #include "uip.h" |
| 40 | #include "httpd.h" |
| 41 | #include "httpd-fs.h" |
| 42 | #include "httpd-fsdata.h" |
| 43 | #include "httpd-cgi.h" |
| 44 | |
| 45 | #include "webserver.h" |
| 46 | |
| 47 | #include <avr/pgmspace.h> |
| 48 | |
| 49 | /* The HTTP server states: */ |
| 50 | #define HTTP_DEALLOCATED 0 |
| 51 | #define HTTP_NOGET 1 |
| 52 | #define HTTP_FILE 2 |
| 53 | #define HTTP_TEXT 3 |
| 54 | #define HTTP_FUNC 4 |
| 55 | #define HTTP_END 5 |
| 56 | |
| 57 | #ifdef DEBUG |
| 58 | #include <stdio.h> |
| 59 | #define PRINT(x) printf("%s", x) |
| 60 | #define PRINTLN(x) printf("%s\n", x) |
| 61 | #else /* DEBUG */ |
| 62 | #define PRINT(x) |
| 63 | #define PRINTLN(x) |
| 64 | #endif /* DEBUG */ |
| 65 | |
| 66 | struct httpd_state *hs; |
| 67 | |
| 68 | extern const struct httpd_fsdata_file file_index_html; |
| 69 | extern const struct httpd_fsdata_file file_404_html; |
| 70 | |
| 71 | static void next_scriptline(void); |
| 72 | static void next_scriptstate(void); |
| 73 | |
| 74 | #define ISO_G 0x47 |
| 75 | #define ISO_E 0x45 |
| 76 | #define ISO_T 0x54 |
| 77 | #define ISO_slash 0x2f |
| 78 | #define ISO_c 0x63 |
| 79 | #define ISO_g 0x67 |
| 80 | #define ISO_i 0x69 |
| 81 | #define ISO_space 0x20 |
| 82 | #define ISO_nl 0x0a |
| 83 | #define ISO_cr 0x0d |
| 84 | #define ISO_a 0x61 |
| 85 | #define ISO_t 0x74 |
| 86 | #define ISO_hash 0x23 |
| 87 | #define ISO_period 0x2e |
| 88 | |
| 89 | #define HTTPD_CONF_NUMCONNS UIP_CONNS |
| 90 | static struct httpd_state conns[HTTPD_CONF_NUMCONNS]; |
| 91 | static u8_t i; |
| 92 | |
| 93 | static char tmp[40]; |
| 94 | /*-----------------------------------------------------------------------------------*/ |
| 95 | static struct httpd_state * |
| 96 | alloc_state(void) |
| 97 | { |
| 98 | |
| 99 | for(i = 0; i < HTTPD_CONF_NUMCONNS; ++i) { |
| 100 | if(conns[i].state == HTTP_DEALLOCATED) { |
| 101 | return &conns[i]; |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | /* We are overloaded! XXX: we'll just kick all other connections! */ |
| 106 | for(i = 0; i < HTTPD_CONF_NUMCONNS; ++i) { |
| 107 | conns[i].state = HTTP_DEALLOCATED; |
| 108 | } |
| 109 | |
| 110 | return NULL; |
| 111 | } |
| 112 | /*-----------------------------------------------------------------------------------*/ |
| 113 | static void |
| 114 | dealloc_state(struct httpd_state *s) |
| 115 | { |
| 116 | s->state = HTTP_DEALLOCATED; |
| 117 | } |
| 118 | /*-----------------------------------------------------------------------------------*/ |
| 119 | void |
| 120 | httpd_init(void) |
| 121 | { |
| 122 | httpd_fs_init(); |
| 123 | |
| 124 | /* Listen to port 80. */ |
| 125 | dispatcher_uiplisten(80); |
| 126 | |
| 127 | for(i = 0; i < HTTPD_CONF_NUMCONNS; ++i) { |
| 128 | conns[i].state = HTTP_DEALLOCATED; |
| 129 | } |
| 130 | } |
| 131 | /*-----------------------------------------------------------------------------------*/ |
| 132 | DISPATCHER_UIPCALL(httpd_appcall, state) |
| 133 | { |
| 134 | struct httpd_fs_file fsfile; |
| 135 | u8_t i; |
| 136 | char c; |
| 137 | DISPATCHER_UIPCALL_ARG(state); |
| 138 | |
| 139 | hs = (struct httpd_state *)(state); |
| 140 | |
| 141 | /* We use the uip_ test functions to deduce why we were |
| 142 | called. If uip_connected() is non-zero, we were called |
| 143 | because a remote host has connected to us. If |
| 144 | uip_newdata() is non-zero, we were called because the |
| 145 | remote host has sent us new data, and if uip_acked() is |
| 146 | non-zero, the remote host has acknowledged the data we |
| 147 | previously sent to it. */ |
| 148 | if(uip_connected()) { |
| 149 | |
| 150 | /* Since we've just been connected, the state pointer should be |
| 151 | NULL and we need to allocate a new state object. If we have run |
| 152 | out of memory for state objects, we'll have to abort the |
| 153 | connection and return. */ |
| 154 | if(hs == NULL) { |
| 155 | hs = alloc_state(); |
| 156 | if(hs == NULL) { |
| 157 | uip_close(); |
| 158 | return; |
| 159 | } |
| 160 | dispatcher_markconn(uip_conn, (void *)hs); |
| 161 | } |
| 162 | /* Since we have just been connected with the remote host, we |
| 163 | reset the state for this connection. The ->count variable |
| 164 | contains the amount of data that is yet to be sent to the |
| 165 | remote host, and the ->state is set to HTTP_NOGET to signal |
| 166 | that we haven't received any HTTP GET request for this |
| 167 | connection yet. */ |
| 168 | hs->state = HTTP_NOGET; |
| 169 | hs->count = 0; |
| 170 | hs->poll = 0; |
| 171 | } else if(uip_closed() || uip_aborted()) { |
| 172 | if(hs != NULL) { |
| 173 | dealloc_state(hs); |
| 174 | } |
| 175 | return; |
| 176 | } else if(uip_poll()) { |
| 177 | /* If we are polled ten times, we abort the connection. This is |
| 178 | because we don't want connections lingering indefinately in |
| 179 | the system. */ |
| 180 | if(hs != NULL) { |
| 181 | if(hs->state == HTTP_DEALLOCATED) { |
| 182 | uip_abort(); |
| 183 | } else if(hs->poll++ >= 100) { |
| 184 | uip_abort(); |
| 185 | dealloc_state(hs); |
| 186 | } |
| 187 | } |
| 188 | return; |
| 189 | } |
| 190 | |
| 191 | |
| 192 | if(uip_newdata() && hs->state == HTTP_NOGET) { |
| 193 | hs->poll = 0; |
| 194 | /* This is the first data we receive, and it should contain a |
| 195 | GET. */ |
| 196 | |
| 197 | /* Check for GET. */ |
| 198 | if(uip_appdata[0] != ISO_G || |
| 199 | uip_appdata[1] != ISO_E || |
| 200 | uip_appdata[2] != ISO_T || |
| 201 | uip_appdata[3] != ISO_space) { |
| 202 | /* If it isn't a GET, we abort the connection. */ |
| 203 | uip_abort(); |
| 204 | dealloc_state(hs); |
| 205 | return; |
| 206 | } |
| 207 | |
| 208 | /* Find the file we are looking for. */ |
| 209 | for(i = 4; i < 40; ++i) { |
| 210 | if(uip_appdata[i] == ISO_space || |
| 211 | uip_appdata[i] == ISO_cr || |
| 212 | uip_appdata[i] == ISO_nl) { |
| 213 | uip_appdata[i] = 0; |
| 214 | break; |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | PRINT("request for file "); |
| 219 | PRINTLN(&uip_appdata[4]); |
| 220 | webserver_log_file(uip_conn->ripaddr, &uip_appdata[4]); |
| 221 | /* Check for a request for "/". */ |
| 222 | if(uip_appdata[4] == ISO_slash && |
| 223 | uip_appdata[5] == 0) { |
| 224 | memcpy_P(tmp, file_index_html.name, sizeof(tmp)); |
| 225 | httpd_fs_open((const char *)tmp, &fsfile); |
| 226 | } else { |
| 227 | if(!httpd_fs_open((const char *)&uip_appdata[4], &fsfile)) { |
| 228 | PRINTLN("couldn't open file"); |
| 229 | memcpy_P(tmp, file_404_html.name, sizeof(tmp)); |
| 230 | httpd_fs_open((const char *)tmp, &fsfile); |
| 231 | } |
| 232 | } |
| 233 | httpd_fs_inc(); |
| 234 | |
| 235 | if(uip_appdata[4] == ISO_slash && |
| 236 | uip_appdata[5] == ISO_c && |
| 237 | uip_appdata[6] == ISO_g && |
| 238 | uip_appdata[7] == ISO_i && |
| 239 | uip_appdata[8] == ISO_slash) { |
| 240 | /* If the request is for a file that starts with "/cgi/", we |
| 241 | prepare for invoking a script. */ |
| 242 | hs->script = fsfile.data; |
| 243 | next_scriptstate(); |
| 244 | } else { |
| 245 | hs->script = NULL; |
| 246 | /* The web server is now no longer in the HTTP_NOGET state, but |
| 247 | in the HTTP_FILE state since is has now got the GET from |
| 248 | the client and will start transmitting the file. */ |
| 249 | hs->state = HTTP_FILE; |
| 250 | |
| 251 | /* Point the file pointers in the connection state to point to |
| 252 | the first byte of the file. */ |
| 253 | hs->dataptr = fsfile.data; |
| 254 | hs->count = fsfile.len; |
| 255 | } |
| 256 | } |
| 257 | |
| 258 | |
| 259 | if(hs->state != HTTP_FUNC) { |
| 260 | /* Check if the client (remote end) has acknowledged any data that |
| 261 | we've previously sent. If so, we move the file pointer further |
| 262 | into the file and send back more data. If we are out of data to |
| 263 | send, we close the connection. */ |
| 264 | if(uip_acked()) { |
| 265 | hs->poll = 0; |
| 266 | if(hs->count >= uip_mss()) { |
| 267 | hs->count -= uip_mss(); |
| 268 | hs->dataptr += uip_mss(); |
| 269 | } else { |
| 270 | hs->count = 0; |
| 271 | } |
| 272 | |
| 273 | if(hs->count == 0) { |
| 274 | if(hs->script != NULL) { |
| 275 | next_scriptline(); |
| 276 | next_scriptstate(); |
| 277 | } else { |
| 278 | uip_close(); |
| 279 | dealloc_state(hs); |
| 280 | } |
| 281 | } |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | if(hs->state == HTTP_FUNC) { |
| 286 | /* Call the CGI function. */ |
| 287 | memcpy_P(&c, &hs->script[2], 1); |
| 288 | if(httpd_cgitab[c - ISO_a]()) { |
| 289 | /* If the function returns non-zero, we jump to the next line |
| 290 | in the script. */ |
| 291 | next_scriptline(); |
| 292 | next_scriptstate(); |
| 293 | } |
| 294 | } |
| 295 | |
| 296 | if(hs->state != HTTP_FUNC && !uip_poll()) { |
| 297 | hs->poll = 0; |
| 298 | /* Send a piece of data, but not more than the MSS of the |
| 299 | connection. */ |
| 300 | memcpy_P(uip_appdata, hs->dataptr, uip_mss()); |
| 301 | uip_send(uip_appdata, |
| 302 | hs->count > uip_mss()? uip_mss(): hs->count); |
| 303 | } |
| 304 | |
| 305 | /* Finally, return to uIP. Our outgoing packet will soon be on its |
| 306 | way... */ |
| 307 | } |
| 308 | /*-----------------------------------------------------------------------------------*/ |
| 309 | /* next_scriptline(): |
| 310 | * |
| 311 | * Reads the script until it finds a newline. */ |
| 312 | static void |
| 313 | next_scriptline(void) |
| 314 | { |
| 315 | char c; |
| 316 | /* Loop until we find a newline character. */ |
| 317 | do { |
| 318 | ++(hs->script); |
| 319 | memcpy_P(&c, hs->script, 1); |
| 320 | } while(c != ISO_nl); |
| 321 | |
| 322 | /* Eat up the newline as well. */ |
| 323 | ++(hs->script); |
| 324 | } |
| 325 | /*-----------------------------------------------------------------------------------*/ |
| 326 | /* next_sciptstate: |
| 327 | * |
| 328 | * Reads one line of script and decides what to do next. |
| 329 | */ |
| 330 | static void |
| 331 | next_scriptstate(void) |
| 332 | { |
| 333 | struct httpd_fs_file fsfile; |
| 334 | u8_t i; |
| 335 | char c; |
| 336 | |
| 337 | again: |
| 338 | memcpy_P(&c, hs->script, 1); |
| 339 | switch(c) { |
| 340 | case ISO_t: |
| 341 | /* Send a text string. */ |
| 342 | hs->state = HTTP_TEXT; |
| 343 | hs->dataptr = hs->script + 2; |
| 344 | |
| 345 | /* Calculate length of string. */ |
| 346 | i = 0; |
| 347 | do { |
| 348 | memcpy_P(&c, &hs->dataptr[i], 1); |
| 349 | ++i; |
| 350 | } while(c != ISO_nl); |
| 351 | hs->count = i; |
| 352 | break; |
| 353 | case ISO_c: |
| 354 | /* Call a function. */ |
| 355 | hs->state = HTTP_FUNC; |
| 356 | hs->dataptr = NULL; |
| 357 | hs->count = 0; |
| 358 | uip_reset_acked(); |
| 359 | break; |
| 360 | case ISO_i: |
| 361 | /* Include a file. */ |
| 362 | hs->state = HTTP_FILE; |
| 363 | memcpy_P(tmp, &hs->script[2], sizeof(tmp)); |
| 364 | if(!httpd_fs_open(tmp, &fsfile)) { |
| 365 | uip_abort(); |
| 366 | dealloc_state(hs); |
| 367 | } |
| 368 | hs->dataptr = fsfile.data; |
| 369 | hs->count = fsfile.len; |
| 370 | break; |
| 371 | case ISO_hash: |
| 372 | /* Comment line. */ |
| 373 | next_scriptline(); |
| 374 | goto again; |
| 375 | break; |
| 376 | case ISO_period: |
| 377 | /* End of script. */ |
| 378 | hs->state = HTTP_END; |
| 379 | uip_close(); |
| 380 | dealloc_state(hs); |
| 381 | break; |
| 382 | default: |
| 383 | uip_abort(); |
| 384 | dealloc_state(hs); |
| 385 | break; |
| 386 | } |
| 387 | } |
| 388 | /*-----------------------------------------------------------------------------------*/ |