adamdunkels | c7cc92a | 2003-05-28 05:21:49 +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 | * |
adamdunkels | f1d1965 | 2003-06-30 20:42:49 +0000 | [diff] [blame^] | 34 | * $Id: httpd.c,v 1.2 2003/06/30 20:42:49 adamdunkels Exp $ |
adamdunkels | c7cc92a | 2003-05-28 05:21:49 +0000 | [diff] [blame] | 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 | /* The HTTP server states: */ |
| 46 | #define HTTP_DEALLOCATED 0 |
| 47 | #define HTTP_NOGET 1 |
| 48 | #define HTTP_FILE 2 |
| 49 | #define HTTP_TEXT 3 |
| 50 | #define HTTP_FUNC 4 |
| 51 | #define HTTP_END 5 |
| 52 | |
| 53 | #ifdef DEBUG |
| 54 | #include <stdio.h> |
| 55 | #define PRINT(x) printf("%s", x) |
| 56 | #define PRINTLN(x) printf("%s\n", x) |
| 57 | #else /* DEBUG */ |
| 58 | #define PRINT(x) |
| 59 | #define PRINTLN(x) |
| 60 | #endif /* DEBUG */ |
| 61 | |
| 62 | struct httpd_state *hs; |
| 63 | |
| 64 | extern const struct httpd_fsdata_file file_index_html; |
| 65 | extern const struct httpd_fsdata_file file_404_html; |
| 66 | |
| 67 | static void next_scriptline(void); |
| 68 | static void next_scriptstate(void); |
| 69 | |
| 70 | #define ISO_G 0x47 |
| 71 | #define ISO_E 0x45 |
| 72 | #define ISO_T 0x54 |
| 73 | #define ISO_slash 0x2f |
| 74 | #define ISO_c 0x63 |
| 75 | #define ISO_g 0x67 |
| 76 | #define ISO_i 0x69 |
| 77 | #define ISO_space 0x20 |
| 78 | #define ISO_nl 0x0a |
| 79 | #define ISO_cr 0x0d |
| 80 | #define ISO_a 0x61 |
| 81 | #define ISO_t 0x74 |
| 82 | #define ISO_hash 0x23 |
| 83 | #define ISO_period 0x2e |
| 84 | |
adamdunkels | f1d1965 | 2003-06-30 20:42:49 +0000 | [diff] [blame^] | 85 | #define HTTPD_CONF_NUMCONNS UIP_CONNS |
adamdunkels | c7cc92a | 2003-05-28 05:21:49 +0000 | [diff] [blame] | 86 | static struct httpd_state conns[HTTPD_CONF_NUMCONNS]; |
adamdunkels | f1d1965 | 2003-06-30 20:42:49 +0000 | [diff] [blame^] | 87 | static u8_t i; |
adamdunkels | c7cc92a | 2003-05-28 05:21:49 +0000 | [diff] [blame] | 88 | /*-----------------------------------------------------------------------------------*/ |
| 89 | static struct httpd_state * |
| 90 | alloc_state(void) |
| 91 | { |
adamdunkels | f1d1965 | 2003-06-30 20:42:49 +0000 | [diff] [blame^] | 92 | |
adamdunkels | c7cc92a | 2003-05-28 05:21:49 +0000 | [diff] [blame] | 93 | for(i = 0; i < HTTPD_CONF_NUMCONNS; ++i) { |
| 94 | if(conns[i].state == HTTP_DEALLOCATED) { |
| 95 | return &conns[i]; |
| 96 | } |
| 97 | } |
adamdunkels | f1d1965 | 2003-06-30 20:42:49 +0000 | [diff] [blame^] | 98 | |
| 99 | /* We are overloaded! XXX: we'll just kick all other connections! */ |
| 100 | for(i = 0; i < HTTPD_CONF_NUMCONNS; ++i) { |
| 101 | conns[i].state = HTTP_DEALLOCATED; |
| 102 | } |
| 103 | |
adamdunkels | c7cc92a | 2003-05-28 05:21:49 +0000 | [diff] [blame] | 104 | return NULL; |
| 105 | } |
| 106 | /*-----------------------------------------------------------------------------------*/ |
| 107 | static void |
| 108 | dealloc_state(struct httpd_state *s) |
| 109 | { |
| 110 | s->state = HTTP_DEALLOCATED; |
| 111 | } |
| 112 | /*-----------------------------------------------------------------------------------*/ |
| 113 | void |
| 114 | httpd_init(void) |
| 115 | { |
| 116 | httpd_fs_init(); |
| 117 | |
| 118 | /* Listen to port 80. */ |
| 119 | dispatcher_uiplisten(80); |
adamdunkels | f1d1965 | 2003-06-30 20:42:49 +0000 | [diff] [blame^] | 120 | |
| 121 | for(i = 0; i < HTTPD_CONF_NUMCONNS; ++i) { |
| 122 | conns[i].state = HTTP_DEALLOCATED; |
| 123 | } |
adamdunkels | c7cc92a | 2003-05-28 05:21:49 +0000 | [diff] [blame] | 124 | } |
| 125 | /*-----------------------------------------------------------------------------------*/ |
| 126 | DISPATCHER_UIPCALL(httpd_appcall, state) |
| 127 | { |
| 128 | struct httpd_fs_file fsfile; |
| 129 | u8_t i; |
| 130 | DISPATCHER_UIPCALL_ARG(state); |
| 131 | |
| 132 | hs = (struct httpd_state *)(state); |
| 133 | |
| 134 | /* We use the uip_ test functions to deduce why we were |
| 135 | called. If uip_connected() is non-zero, we were called |
| 136 | because a remote host has connected to us. If |
| 137 | uip_newdata() is non-zero, we were called because the |
| 138 | remote host has sent us new data, and if uip_acked() is |
| 139 | non-zero, the remote host has acknowledged the data we |
| 140 | previously sent to it. */ |
| 141 | if(uip_connected()) { |
| 142 | |
| 143 | /* Since we've just been connected, the state pointer should be |
| 144 | NULL and we need to allocate a new state object. If we have run |
| 145 | out of memory for state objects, we'll have to abort the |
| 146 | connection and return. */ |
| 147 | if(hs == NULL) { |
| 148 | hs = alloc_state(); |
| 149 | if(hs == NULL) { |
adamdunkels | f1d1965 | 2003-06-30 20:42:49 +0000 | [diff] [blame^] | 150 | uip_close(); |
adamdunkels | c7cc92a | 2003-05-28 05:21:49 +0000 | [diff] [blame] | 151 | return; |
| 152 | } |
| 153 | dispatcher_markconn(uip_conn, (void *)hs); |
| 154 | } |
| 155 | /* Since we have just been connected with the remote host, we |
| 156 | reset the state for this connection. The ->count variable |
| 157 | contains the amount of data that is yet to be sent to the |
| 158 | remote host, and the ->state is set to HTTP_NOGET to signal |
| 159 | that we haven't received any HTTP GET request for this |
| 160 | connection yet. */ |
| 161 | hs->state = HTTP_NOGET; |
| 162 | hs->count = 0; |
adamdunkels | f1d1965 | 2003-06-30 20:42:49 +0000 | [diff] [blame^] | 163 | hs->poll = 0; |
| 164 | } else if(uip_closed() || uip_aborted()) { |
| 165 | if(hs != NULL) { |
| 166 | dealloc_state(hs); |
| 167 | } |
adamdunkels | c7cc92a | 2003-05-28 05:21:49 +0000 | [diff] [blame] | 168 | return; |
adamdunkels | c7cc92a | 2003-05-28 05:21:49 +0000 | [diff] [blame] | 169 | } else if(uip_poll()) { |
| 170 | /* If we are polled ten times, we abort the connection. This is |
| 171 | because we don't want connections lingering indefinately in |
| 172 | the system. */ |
adamdunkels | f1d1965 | 2003-06-30 20:42:49 +0000 | [diff] [blame^] | 173 | if(hs != NULL) { |
| 174 | if(hs->state == HTTP_DEALLOCATED) { |
| 175 | uip_abort(); |
| 176 | } else if(hs->poll++ >= 100) { |
| 177 | uip_abort(); |
| 178 | dealloc_state(hs); |
| 179 | } |
adamdunkels | c7cc92a | 2003-05-28 05:21:49 +0000 | [diff] [blame] | 180 | } |
| 181 | return; |
adamdunkels | f1d1965 | 2003-06-30 20:42:49 +0000 | [diff] [blame^] | 182 | } |
| 183 | |
| 184 | |
| 185 | if(uip_newdata() && hs->state == HTTP_NOGET) { |
| 186 | hs->poll = 0; |
adamdunkels | c7cc92a | 2003-05-28 05:21:49 +0000 | [diff] [blame] | 187 | /* This is the first data we receive, and it should contain a |
| 188 | GET. */ |
| 189 | |
| 190 | /* Check for GET. */ |
| 191 | if(uip_appdata[0] != ISO_G || |
| 192 | uip_appdata[1] != ISO_E || |
| 193 | uip_appdata[2] != ISO_T || |
| 194 | uip_appdata[3] != ISO_space) { |
| 195 | /* If it isn't a GET, we abort the connection. */ |
| 196 | uip_abort(); |
| 197 | dealloc_state(hs); |
| 198 | return; |
| 199 | } |
| 200 | |
| 201 | /* Find the file we are looking for. */ |
| 202 | for(i = 4; i < 40; ++i) { |
| 203 | if(uip_appdata[i] == ISO_space || |
| 204 | uip_appdata[i] == ISO_cr || |
| 205 | uip_appdata[i] == ISO_nl) { |
| 206 | uip_appdata[i] = 0; |
| 207 | break; |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | PRINT("request for file "); |
| 212 | PRINTLN(&uip_appdata[4]); |
adamdunkels | f1d1965 | 2003-06-30 20:42:49 +0000 | [diff] [blame^] | 213 | webserver_log_file(uip_conn->ripaddr, &uip_appdata[4]); |
adamdunkels | c7cc92a | 2003-05-28 05:21:49 +0000 | [diff] [blame] | 214 | /* Check for a request for "/". */ |
| 215 | if(uip_appdata[4] == ISO_slash && |
| 216 | uip_appdata[5] == 0) { |
| 217 | httpd_fs_open(file_index_html.name, &fsfile); |
| 218 | } else { |
| 219 | if(!httpd_fs_open((const char *)&uip_appdata[4], &fsfile)) { |
| 220 | PRINTLN("couldn't open file"); |
| 221 | httpd_fs_open(file_404_html.name, &fsfile); |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | if(uip_appdata[4] == ISO_slash && |
| 226 | uip_appdata[5] == ISO_c && |
| 227 | uip_appdata[6] == ISO_g && |
| 228 | uip_appdata[7] == ISO_i && |
| 229 | uip_appdata[8] == ISO_slash) { |
| 230 | /* If the request is for a file that starts with "/cgi/", we |
| 231 | prepare for invoking a script. */ |
| 232 | hs->script = fsfile.data; |
| 233 | next_scriptstate(); |
| 234 | } else { |
| 235 | hs->script = NULL; |
| 236 | /* The web server is now no longer in the HTTP_NOGET state, but |
| 237 | in the HTTP_FILE state since is has now got the GET from |
| 238 | the client and will start transmitting the file. */ |
| 239 | hs->state = HTTP_FILE; |
| 240 | |
| 241 | /* Point the file pointers in the connection state to point to |
| 242 | the first byte of the file. */ |
| 243 | hs->dataptr = fsfile.data; |
| 244 | hs->count = fsfile.len; |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | |
| 249 | if(hs->state != HTTP_FUNC) { |
| 250 | /* Check if the client (remote end) has acknowledged any data that |
| 251 | we've previously sent. If so, we move the file pointer further |
| 252 | into the file and send back more data. If we are out of data to |
| 253 | send, we close the connection. */ |
| 254 | if(uip_acked()) { |
adamdunkels | f1d1965 | 2003-06-30 20:42:49 +0000 | [diff] [blame^] | 255 | hs->poll = 0; |
adamdunkels | c7cc92a | 2003-05-28 05:21:49 +0000 | [diff] [blame] | 256 | if(hs->count >= uip_mss()) { |
| 257 | hs->count -= uip_mss(); |
| 258 | hs->dataptr += uip_mss(); |
| 259 | } else { |
| 260 | hs->count = 0; |
| 261 | } |
| 262 | |
| 263 | if(hs->count == 0) { |
| 264 | if(hs->script != NULL) { |
| 265 | next_scriptline(); |
| 266 | next_scriptstate(); |
| 267 | } else { |
| 268 | uip_close(); |
| 269 | dealloc_state(hs); |
| 270 | } |
| 271 | } |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | if(hs->state == HTTP_FUNC) { |
| 276 | /* Call the CGI function. */ |
| 277 | if(httpd_cgitab[hs->script[2] - ISO_a]()) { |
| 278 | /* If the function returns non-zero, we jump to the next line |
| 279 | in the script. */ |
| 280 | next_scriptline(); |
| 281 | next_scriptstate(); |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | if(hs->state != HTTP_FUNC && !uip_poll()) { |
adamdunkels | f1d1965 | 2003-06-30 20:42:49 +0000 | [diff] [blame^] | 286 | hs->poll = 0; |
adamdunkels | c7cc92a | 2003-05-28 05:21:49 +0000 | [diff] [blame] | 287 | /* Send a piece of data, but not more than the MSS of the |
| 288 | connection. */ |
| 289 | uip_send(hs->dataptr, |
| 290 | hs->count > uip_mss()? uip_mss(): hs->count); |
| 291 | } |
| 292 | |
| 293 | /* Finally, return to uIP. Our outgoing packet will soon be on its |
| 294 | way... */ |
| 295 | } |
| 296 | /*-----------------------------------------------------------------------------------*/ |
| 297 | /* next_scriptline(): |
| 298 | * |
| 299 | * Reads the script until it finds a newline. */ |
| 300 | static void |
| 301 | next_scriptline(void) |
| 302 | { |
| 303 | /* Loop until we find a newline character. */ |
| 304 | do { |
| 305 | ++(hs->script); |
| 306 | } while(hs->script[0] != ISO_nl); |
| 307 | |
| 308 | /* Eat up the newline as well. */ |
| 309 | ++(hs->script); |
| 310 | } |
| 311 | /*-----------------------------------------------------------------------------------*/ |
| 312 | /* next_sciptstate: |
| 313 | * |
| 314 | * Reads one line of script and decides what to do next. |
| 315 | */ |
| 316 | static void |
| 317 | next_scriptstate(void) |
| 318 | { |
| 319 | struct httpd_fs_file fsfile; |
| 320 | u8_t i; |
| 321 | |
| 322 | again: |
| 323 | switch(hs->script[0]) { |
| 324 | case ISO_t: |
| 325 | /* Send a text string. */ |
| 326 | hs->state = HTTP_TEXT; |
| 327 | hs->dataptr = &hs->script[2]; |
| 328 | |
| 329 | /* Calculate length of string. */ |
| 330 | for(i = 0; hs->dataptr[i] != ISO_nl; ++i); |
| 331 | hs->count = i; |
| 332 | break; |
| 333 | case ISO_c: |
| 334 | /* Call a function. */ |
| 335 | hs->state = HTTP_FUNC; |
| 336 | hs->dataptr = NULL; |
| 337 | hs->count = 0; |
| 338 | uip_reset_acked(); |
| 339 | break; |
| 340 | case ISO_i: |
| 341 | /* Include a file. */ |
| 342 | hs->state = HTTP_FILE; |
| 343 | if(!httpd_fs_open(&hs->script[2], &fsfile)) { |
| 344 | uip_abort(); |
| 345 | dealloc_state(hs); |
| 346 | } |
| 347 | hs->dataptr = fsfile.data; |
| 348 | hs->count = fsfile.len; |
| 349 | break; |
| 350 | case ISO_hash: |
| 351 | /* Comment line. */ |
| 352 | next_scriptline(); |
| 353 | goto again; |
| 354 | break; |
| 355 | case ISO_period: |
| 356 | /* End of script. */ |
| 357 | hs->state = HTTP_END; |
| 358 | uip_close(); |
| 359 | dealloc_state(hs); |
| 360 | break; |
| 361 | default: |
| 362 | uip_abort(); |
| 363 | dealloc_state(hs); |
| 364 | break; |
| 365 | } |
| 366 | } |
| 367 | /*-----------------------------------------------------------------------------------*/ |