]> git.0d.be Git - PanikSwitch.git/blob - WebServer.h
webserver: update with latest upstream code
[PanikSwitch.git] / WebServer.h
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil;  c-file-style: "k&r"; c-basic-offset: 2; -*-
2
3    Webduino, a simple Arduino web server
4    Copyright 2009-2012 Ben Combee, Ran Talbott, Christopher Lee, Martin Lormes
5
6    Permission is hereby granted, free of charge, to any person obtaining a copy
7    of this software and associated documentation files (the "Software"), to deal
8    in the Software without restriction, including without limitation the rights
9    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10    copies of the Software, and to permit persons to whom the Software is
11    furnished to do so, subject to the following conditions:
12
13    The above copyright notice and this permission notice shall be included in
14    all copies or substantial portions of the Software.
15
16    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22    THE SOFTWARE.
23 */
24
25 #ifndef WEBDUINO_H_
26 #define WEBDUINO_H_
27
28 #include <string.h>
29 #include <stdlib.h>
30
31 #include <EthernetClient.h>
32 #include <EthernetServer.h>
33
34 /********************************************************************
35  * CONFIGURATION
36  ********************************************************************/
37
38 #define WEBDUINO_VERSION 1007
39 #define WEBDUINO_VERSION_STRING "1.7"
40
41 #if WEBDUINO_SUPRESS_SERVER_HEADER
42 #define WEBDUINO_SERVER_HEADER ""
43 #else
44 #define WEBDUINO_SERVER_HEADER "Server: Webduino/" WEBDUINO_VERSION_STRING CRLF
45 #endif
46
47 // standard END-OF-LINE marker in HTTP
48 #define CRLF "\r\n"
49
50 // If processConnection is called without a buffer, it allocates one
51 // of 32 bytes
52 #define WEBDUINO_DEFAULT_REQUEST_LENGTH 32
53
54 // How long to wait before considering a connection as dead when
55 // reading the HTTP request.  Used to avoid DOS attacks.
56 #ifndef WEBDUINO_READ_TIMEOUT_IN_MS
57 #define WEBDUINO_READ_TIMEOUT_IN_MS 1000
58 #endif
59
60 #ifndef WEBDUINO_URL_PATH_COMMAND_LENGTH
61 #define WEBDUINO_URL_PATH_COMMAND_LENGTH 8
62 #endif
63
64 #ifndef WEBDUINO_FAIL_MESSAGE
65 #define WEBDUINO_FAIL_MESSAGE "<h1>EPIC FAIL</h1>"
66 #endif
67
68 #ifndef WEBDUINO_AUTH_REALM
69 #define WEBDUINO_AUTH_REALM "Webduino"
70 #endif // #ifndef WEBDUINO_AUTH_REALM
71
72 #ifndef WEBDUINO_AUTH_MESSAGE
73 #define WEBDUINO_AUTH_MESSAGE "<h1>401 Unauthorized</h1>"
74 #endif // #ifndef WEBDUINO_AUTH_MESSAGE
75
76 #ifndef WEBDUINO_SERVER_ERROR_MESSAGE
77 #define WEBDUINO_SERVER_ERROR_MESSAGE "<h1>500 Internal Server Error</h1>"
78 #endif // WEBDUINO_SERVER_ERROR_MESSAGE
79
80 #ifndef WEBDUINO_OUTPUT_BUFFER_SIZE
81 #define WEBDUINO_OUTPUT_BUFFER_SIZE 32
82 #endif // WEBDUINO_OUTPUT_BUFFER_SIZE
83
84 // add '#define WEBDUINO_FAVICON_DATA ""' to your application
85 // before including WebServer.h to send a null file as the favicon.ico file
86 // otherwise this defaults to a 16x16 px black diode on blue ground
87 // (or include your own icon if you like)
88 #ifndef WEBDUINO_FAVICON_DATA
89 #define WEBDUINO_FAVICON_DATA { 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x10, \
90                                 0x10, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, \
91                                 0xb0, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, \
92                                 0x00, 0x28, 0x00, 0x00, 0x00, 0x10, 0x00, \
93                                 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, \
94                                 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, \
95                                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
96                                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
97                                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
98                                 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, \
99                                 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, \
100                                 0x00, 0xff, 0xff, 0x00, 0x00, 0xcf, 0xbf, \
101                                 0x00, 0x00, 0xc7, 0xbf, 0x00, 0x00, 0xc3, \
102                                 0xbf, 0x00, 0x00, 0xc1, 0xbf, 0x00, 0x00, \
103                                 0xc0, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, \
104                                 0x00, 0xc0, 0xbf, 0x00, 0x00, 0xc1, 0xbf, \
105                                 0x00, 0x00, 0xc3, 0xbf, 0x00, 0x00, 0xc7, \
106                                 0xbf, 0x00, 0x00, 0xcf, 0xbf, 0x00, 0x00, \
107                                 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, \
108                                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
109                                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
110                                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
111                                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
112                                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
113                                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
114                                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
115                                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
116                                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
117                                 0x00, 0x00 }
118 #endif // #ifndef WEBDUINO_FAVICON_DATA
119
120 // add "#define WEBDUINO_SERIAL_DEBUGGING 1" to your application
121 // before including WebServer.h to have incoming requests logged to
122 // the serial port.
123 #ifndef WEBDUINO_SERIAL_DEBUGGING
124 #define WEBDUINO_SERIAL_DEBUGGING 0
125 #endif
126 #if WEBDUINO_SERIAL_DEBUGGING
127 #include <HardwareSerial.h>
128 #endif
129
130 // declared in wiring.h
131 extern "C" unsigned long millis(void);
132
133 // declare a static string
134 #ifdef __AVR__
135 #define P(name)   static const unsigned char name[] PROGMEM
136 #else
137 #define P(name)   static const unsigned char name[]
138 #endif
139
140 // returns the number of elements in the array
141 #define SIZE(array) (sizeof(array) / sizeof(*array))
142
143 #ifdef _VARIANT_ARDUINO_DUE_X_
144 #define pgm_read_byte(ptr) (unsigned char)(* ptr)
145 #endif
146 /********************************************************************
147  * DECLARATIONS
148  ********************************************************************/
149
150 /* Return codes from nextURLparam.  NOTE: URLPARAM_EOS is returned
151  * when you call nextURLparam AFTER the last parameter is read.  The
152  * last actual parameter gets an "OK" return code. */
153
154 enum URLPARAM_RESULT { URLPARAM_OK,
155                        URLPARAM_NAME_OFLO,
156                        URLPARAM_VALUE_OFLO,
157                        URLPARAM_BOTH_OFLO,
158                        URLPARAM_EOS         // No params left
159 };
160
161 class WebServer: public Print
162 {
163 public:
164   // passed to a command to indicate what kind of request was received
165   enum ConnectionType { INVALID, GET, HEAD, POST, PUT, DELETE, PATCH };
166
167   // any commands registered with the web server have to follow
168   // this prototype.
169   // url_tail contains the part of the URL that wasn't matched against
170   //          the registered command table.
171   // tail_complete is true if the complete URL fit in url_tail,  false if
172   //          part of it was lost because the buffer was too small.
173   typedef void Command(WebServer &server, ConnectionType type,
174                        char *url_tail, bool tail_complete);
175
176   // Prototype for the optional function which consumes the URL path itself.
177   // url_path contains pointers to the seperate parts of the URL path where '/'
178   //          was used as the delimiter.
179   typedef void UrlPathCommand(WebServer &server, ConnectionType type,
180                               char **url_path, char *url_tail,
181                               bool tail_complete);
182
183   // constructor for webserver object
184   WebServer(const char *urlPrefix = "", int port = 80);
185
186   // start listening for connections
187   void begin();
188
189   // check for an incoming connection, and if it exists, process it
190   // by reading its request and calling the appropriate command
191   // handler.  This version is for compatibility with apps written for
192   // version 1.1,  and allocates the URL "tail" buffer internally.
193   void processConnection();
194
195   // check for an incoming connection, and if it exists, process it
196   // by reading its request and calling the appropriate command
197   // handler.  This version saves the "tail" of the URL in buff.
198   void processConnection(char *buff, int *bufflen);
199
200   // set command that's run when you access the root of the server
201   void setDefaultCommand(Command *cmd);
202
203   // set command run for undefined pages
204   void setFailureCommand(Command *cmd);
205
206   // add a new command to be run at the URL specified by verb
207   void addCommand(const char *verb, Command *cmd);
208
209   // Set command that's run if default command or URL specified commands do
210   // not run, uses extra url_path parameter to allow resolving the URL in the
211   // function.
212   void setUrlPathCommand(UrlPathCommand *cmd);
213
214   // utility function to output CRLF pair
215   void printCRLF();
216
217   // output a string stored in program memory, usually one defined
218   // with the P macro
219   void printP(const unsigned char *str);
220
221   // inline overload for printP to handle signed char strings
222   void printP(const char *str) { printP((unsigned char*)str); }
223
224   // output raw data stored in program memory
225   void writeP(const unsigned char *data, size_t length);
226
227   // output HTML for a radio button
228   void radioButton(const char *name, const char *val,
229                    const char *label, bool selected);
230
231   // output HTML for a checkbox
232   void checkBox(const char *name, const char *val,
233                 const char *label, bool selected);
234
235   // returns next character or -1 if we're at end-of-stream
236   int read();
237
238   // put a character that's been read back into the input pool
239   void push(int ch);
240
241   // returns true if the string is next in the stream.  Doesn't
242   // consume any character if false, so can be used to try out
243   // different expected values.
244   bool expect(const char *expectedStr);
245
246   // returns true if a number, with possible whitespace in front, was
247   // read from the server stream.  number will be set with the new
248   // value or 0 if nothing was read.
249   bool readInt(int &number);
250
251   // reads a header value, stripped of possible whitespace in front,
252   // from the server stream
253   void readHeader(char *value, int valueLen);
254
255   // Read the next keyword parameter from the socket.  Assumes that other
256   // code has already skipped over the headers,  and the next thing to
257   // be read will be the start of a keyword.
258   //
259   // returns true if we're not at end-of-stream
260   bool readPOSTparam(char *name, int nameLen, char *value, int valueLen);
261
262   // Read the next keyword parameter from the buffer filled by getRequest.
263   //
264   // returns 0 if everything weent okay,  non-zero if not
265   // (see the typedef for codes)
266   URLPARAM_RESULT nextURLparam(char **tail, char *name, int nameLen,
267                                char *value, int valueLen);
268
269   // compare string against credentials in current request
270   //
271   // authCredentials must be Base64 encoded outside of Webduino
272   // (I wanted to be easy on the resources)
273   //
274   // returns true if strings match, false otherwise
275   bool checkCredentials(const char authCredentials[45]);
276
277   // output headers and a message indicating a server error
278   void httpFail();
279
280   // output headers and a message indicating "401 Unauthorized"
281   void httpUnauthorized();
282
283   // output headers and a message indicating "500 Internal Server Error"
284   void httpServerError();
285
286   // output headers indicating "204 No Content" and no further message
287   void httpNoContent();
288
289   // output standard headers indicating "200 Success".  You can change the
290   // type of the data you're outputting or also add extra headers like
291   // "Refresh: 1".  Extra headers should each be terminated with CRLF.
292   void httpSuccess(const char *contentType = "text/html; charset=utf-8",
293                    const char *extraHeaders = NULL);
294
295   // used with POST to output a redirect to another URL.  This is
296   // preferable to outputting HTML from a post because you can then
297   // refresh the page without getting a "resubmit form" dialog.
298   void httpSeeOther(const char *otherURL);
299
300   // implementation of write used to implement Print interface
301   virtual size_t write(uint8_t);
302
303   // tells if there is anything to process
304   uint8_t available();
305
306 private:
307   EthernetServer m_server;
308   EthernetClient m_client;
309   const char *m_urlPrefix;
310
311   unsigned char m_pushback[32];
312   unsigned char m_pushbackDepth;
313
314   int m_contentLength;
315   char m_authCredentials[51];
316   bool m_readingContent;
317
318   Command *m_failureCmd;
319   Command *m_defaultCmd;
320   struct CommandMap
321   {
322     const char *verb;
323     Command *cmd;
324   } m_commands[8];
325   unsigned char m_cmdCount;
326   UrlPathCommand *m_urlPathCmd;
327
328   uint8_t m_buffer[WEBDUINO_OUTPUT_BUFFER_SIZE];
329   uint8_t m_bufFill;
330
331   void reset();
332   void getRequest(WebServer::ConnectionType &type, char *request, int *length);
333   bool dispatchCommand(ConnectionType requestType, char *verb,
334                        bool tail_complete);
335   void processHeaders();
336   void outputCheckboxOrRadio(const char *element, const char *name,
337                              const char *val, const char *label,
338                              bool selected);
339
340   static void defaultFailCmd(WebServer &server, ConnectionType type,
341                              char *url_tail, bool tail_complete);
342   void noRobots(ConnectionType type);
343   void favicon(ConnectionType type);
344   void flushBuf();
345 };
346
347 /* define this macro if you want to include the header in a sketch source
348    file but not define any of the implementation. This is useful if
349    multiple source files are using the Webduino class. */
350 #ifndef WEBDUINO_NO_IMPLEMENTATION
351
352 /********************************************************************
353  * IMPLEMENTATION
354  ********************************************************************/
355
356 WebServer::WebServer(const char *urlPrefix, int port) :
357   m_server(port),
358   m_client(MAX_SOCK_NUM),
359   m_urlPrefix(urlPrefix),
360   m_pushbackDepth(0),
361   m_contentLength(0),
362   m_failureCmd(&defaultFailCmd),
363   m_defaultCmd(&defaultFailCmd),
364   m_cmdCount(0),
365   m_urlPathCmd(NULL),
366   m_bufFill(0)
367 {
368 }
369
370 void WebServer::begin()
371 {
372   m_server.begin();
373 }
374
375 void WebServer::setDefaultCommand(Command *cmd)
376 {
377   m_defaultCmd = cmd;
378 }
379
380 void WebServer::setFailureCommand(Command *cmd)
381 {
382   m_failureCmd = cmd;
383 }
384
385 void WebServer::addCommand(const char *verb, Command *cmd)
386 {
387   if (m_cmdCount < SIZE(m_commands))
388   {
389     m_commands[m_cmdCount].verb = verb;
390     m_commands[m_cmdCount++].cmd = cmd;
391   }
392 }
393
394 void WebServer::setUrlPathCommand(UrlPathCommand *cmd)
395 {
396   m_urlPathCmd = cmd;
397 }
398
399 size_t WebServer::write(uint8_t ch)
400 {
401   m_buffer[m_bufFill++] = ch;
402
403   if(m_bufFill == sizeof(m_buffer))
404   {
405     m_client.write(m_buffer, sizeof(m_buffer));
406     m_bufFill = 0;
407   }
408
409   return sizeof(ch);
410 }
411
412 void WebServer::flushBuf()
413 {
414   if(m_bufFill > 0)
415   {
416     m_client.write(m_buffer, m_bufFill);
417     m_bufFill = 0;
418   }
419 }
420
421 void WebServer::writeP(const unsigned char *data, size_t length)
422 {
423   // copy data out of program memory into local storage
424
425   while (length--)
426   {
427     write(pgm_read_byte(data++));
428   }
429 }
430
431 void WebServer::printP(const unsigned char *str)
432 {
433   // copy data out of program memory into local storage
434
435   while (uint8_t value = pgm_read_byte(str++))
436   {
437     write(value);
438   }
439 }
440
441 void WebServer::printCRLF()
442 {
443   print(CRLF);
444 }
445
446 bool WebServer::dispatchCommand(ConnectionType requestType, char *verb,
447         bool tail_complete)
448 {
449   // if there is no URL, i.e. we have a prefix and it's requested without a
450   // trailing slash or if the URL is just the slash
451   if ((verb[0] == 0) || ((verb[0] == '/') && (verb[1] == 0)))
452   {
453     m_defaultCmd(*this, requestType, (char*)"", tail_complete);
454     return true;
455   }
456   // if the URL is just a slash followed by a question mark
457   // we're looking at the default command with GET parameters passed
458   if ((verb[0] == '/') && (verb[1] == '?'))
459   {
460     verb+=2; // skip over the "/?" part of the url
461     m_defaultCmd(*this, requestType, verb, tail_complete);
462     return true;
463   }
464   // We now know that the URL contains at least one character.  And,
465   // if the first character is a slash,  there's more after it.
466   if (verb[0] == '/')
467   {
468     unsigned char i;
469     char *qm_loc;
470     unsigned int verb_len;
471     int qm_offset;
472     // Skip over the leading "/",  because it makes the code more
473     // efficient and easier to understand.
474     verb++;
475     // Look for a "?" separating the filename part of the URL from the
476     // parameters.  If it's not there, compare to the whole URL.
477     qm_loc = strchr(verb, '?');
478     verb_len = (qm_loc == NULL) ? strlen(verb) : (qm_loc - verb);
479     qm_offset = (qm_loc == NULL) ? 0 : 1;
480     for (i = 0; i < m_cmdCount; ++i)
481     {
482       if ((verb_len == strlen(m_commands[i].verb))
483           && (strncmp(verb, m_commands[i].verb, verb_len) == 0))
484       {
485         // Skip over the "verb" part of the URL (and the question
486         // mark, if present) when passing it to the "action" routine
487         m_commands[i].cmd(*this, requestType,
488         verb + verb_len + qm_offset,
489         tail_complete);
490         return true;
491       }
492     }
493     // Check if UrlPathCommand is assigned.
494     if (m_urlPathCmd != NULL)
495     {
496       // Initialize with null bytes, so number of parts can be determined.
497       char *url_path[WEBDUINO_URL_PATH_COMMAND_LENGTH] = {0};
498       int part = 0;
499
500       // URL path should be terminated with null byte.
501       *(verb + verb_len) = 0;
502
503       // First URL path part is at the start of verb.
504       url_path[part++] = verb;
505       // Replace all slashes ('/') with a null byte so every part of the URL
506       // path is a seperate string. Add every char following a '/' as a new
507       // part of the URL, even if that char is a '/' (which will be replaced
508       // with a null byte).
509       for (char * p = verb; p < verb + verb_len; p++)
510       {
511         if (*p == '/')
512         {
513           *p = 0;
514           url_path[part++] = p + 1;
515           // Don't try to assign out of array bounds.
516           if (part == WEBDUINO_URL_PATH_COMMAND_LENGTH) break;
517         }
518       }
519       m_urlPathCmd(*this, requestType, url_path,
520                    verb + verb_len + qm_offset, tail_complete);
521       return true;
522     }
523   }
524   return false;
525 }
526
527 // processConnection with a default buffer
528 void WebServer::processConnection()
529 {
530   char request[WEBDUINO_DEFAULT_REQUEST_LENGTH];
531   int  request_len = WEBDUINO_DEFAULT_REQUEST_LENGTH;
532   processConnection(request, &request_len);
533 }
534
535 void WebServer::processConnection(char *buff, int *bufflen)
536 {
537   int urlPrefixLen = strlen(m_urlPrefix);
538
539   m_client = m_server.available();
540
541   if (m_client) {
542     m_readingContent = false;
543     buff[0] = 0;
544     ConnectionType requestType = INVALID;
545 #if WEBDUINO_SERIAL_DEBUGGING > 1
546     Serial.println("*** checking request ***");
547 #endif
548     getRequest(requestType, buff, bufflen);
549 #if WEBDUINO_SERIAL_DEBUGGING > 1
550     Serial.print("*** requestType = ");
551     Serial.print((int)requestType);
552     Serial.print(", request = \"");
553     Serial.print(buff);
554     Serial.println("\" ***");
555 #endif
556
557     // don't even look further at invalid requests.
558     // this is done to prevent Webduino from hanging
559     // - when there are illegal requests,
560     // - when someone contacts it through telnet rather than proper HTTP,
561     // - etc.
562     if (requestType != INVALID)
563     {
564       processHeaders();
565 #if WEBDUINO_SERIAL_DEBUGGING > 1
566       Serial.println("*** headers complete ***");
567 #endif
568
569       if (strcmp(buff, "/robots.txt") == 0)
570       {
571         noRobots(requestType);
572       }
573       else if (strcmp(buff, "/favicon.ico") == 0)
574       {
575         favicon(requestType);
576       }
577     }
578     // Only try to dispatch command if request type and prefix are correct.
579     // Fix by quarencia.
580     if (requestType == INVALID ||
581         strncmp(buff, m_urlPrefix, urlPrefixLen) != 0)
582     {
583       m_failureCmd(*this, requestType, buff, (*bufflen) >= 0);
584     }
585     else if (!dispatchCommand(requestType, buff + urlPrefixLen,
586              (*bufflen) >= 0))
587     {
588       m_failureCmd(*this, requestType, buff, (*bufflen) >= 0);
589     }
590
591     flushBuf();
592
593 #if WEBDUINO_SERIAL_DEBUGGING > 1
594     Serial.println("*** stopping connection ***");
595 #endif
596     reset();
597   }
598 }
599
600 bool WebServer::checkCredentials(const char authCredentials[45])
601 {
602   char basic[7] = "Basic ";
603   if((0 == strncmp(m_authCredentials,basic,6)) &&
604      (0 == strcmp(authCredentials, m_authCredentials + 6))) return true;
605   return false;
606 }
607
608 void WebServer::httpFail()
609 {
610   P(failMsg) =
611     "HTTP/1.0 400 Bad Request" CRLF
612     WEBDUINO_SERVER_HEADER
613     "Content-Type: text/html" CRLF
614     CRLF
615     WEBDUINO_FAIL_MESSAGE;
616
617   printP(failMsg);
618 }
619
620 void WebServer::defaultFailCmd(WebServer &server,
621                                WebServer::ConnectionType type,
622                                char *url_tail,
623                                bool tail_complete)
624 {
625   server.httpFail();
626 }
627
628 void WebServer::noRobots(ConnectionType type)
629 {
630   httpSuccess("text/plain");
631   if (type != HEAD)
632   {
633     P(allowNoneMsg) = "User-agent: *" CRLF "Disallow: /" CRLF;
634     printP(allowNoneMsg);
635   }
636 }
637
638 void WebServer::favicon(ConnectionType type)
639 {
640   httpSuccess("image/x-icon","Cache-Control: max-age=31536000\r\n");
641   if (type != HEAD)
642   {
643     P(faviconIco) = WEBDUINO_FAVICON_DATA;
644     writeP(faviconIco, sizeof(faviconIco));
645   }
646 }
647
648 void WebServer::httpUnauthorized()
649 {
650   P(failMsg) =
651     "HTTP/1.0 401 Authorization Required" CRLF
652     WEBDUINO_SERVER_HEADER
653     "Content-Type: text/html" CRLF
654     "WWW-Authenticate: Basic realm=\"" WEBDUINO_AUTH_REALM "\"" CRLF
655     CRLF
656     WEBDUINO_AUTH_MESSAGE;
657
658   printP(failMsg);
659 }
660
661 void WebServer::httpServerError()
662 {
663   P(failMsg) =
664     "HTTP/1.0 500 Internal Server Error" CRLF
665     WEBDUINO_SERVER_HEADER
666     "Content-Type: text/html" CRLF
667     CRLF
668     WEBDUINO_SERVER_ERROR_MESSAGE;
669
670   printP(failMsg);
671 }
672
673 void WebServer::httpNoContent()
674 {
675   P(noContentMsg) =
676     "HTTP/1.0 204 NO CONTENT" CRLF
677     WEBDUINO_SERVER_HEADER
678     CRLF
679     CRLF;
680
681   printP(noContentMsg);
682 }
683
684 void WebServer::httpSuccess(const char *contentType,
685                             const char *extraHeaders)
686 {
687   P(successMsg1) =
688     "HTTP/1.0 200 OK" CRLF
689     WEBDUINO_SERVER_HEADER
690     "Access-Control-Allow-Origin: *" CRLF
691     "Content-Type: ";
692
693   printP(successMsg1);
694   print(contentType);
695   printCRLF();
696   if (extraHeaders)
697     print(extraHeaders);
698   printCRLF();
699 }
700
701 void WebServer::httpSeeOther(const char *otherURL)
702 {
703   P(seeOtherMsg) =
704     "HTTP/1.0 303 See Other" CRLF
705     WEBDUINO_SERVER_HEADER
706     "Location: ";
707
708   printP(seeOtherMsg);
709   print(otherURL);
710   printCRLF();
711   printCRLF();
712 }
713
714 int WebServer::read()
715 {
716   if (!m_client)
717     return -1;
718
719   if (m_pushbackDepth == 0)
720   {
721     unsigned long timeoutTime = millis() + WEBDUINO_READ_TIMEOUT_IN_MS;
722
723     while (m_client.connected())
724     {
725       // stop reading the socket early if we get to content-length
726       // characters in the POST.  This is because some clients leave
727       // the socket open because they assume HTTP keep-alive.
728       if (m_readingContent)
729       {
730         if (m_contentLength == 0)
731         {
732 #if WEBDUINO_SERIAL_DEBUGGING > 1
733           Serial.println("\n*** End of content, terminating connection");
734 #endif
735           return -1;
736         }
737       }
738
739       int ch = m_client.read();
740
741       // if we get a character, return it, otherwise continue in while
742       // loop, checking connection status
743       if (ch != -1)
744       {
745         // count character against content-length
746         if (m_readingContent)
747         {
748           --m_contentLength;
749         }
750
751 #if WEBDUINO_SERIAL_DEBUGGING
752         if (ch == '\r')
753           Serial.print("<CR>");
754         else if (ch == '\n')
755           Serial.println("<LF>");
756         else
757           Serial.print((char)ch);
758 #endif
759         return ch;
760       }
761       else
762       {
763         unsigned long now = millis();
764         if (now > timeoutTime)
765         {
766           // connection timed out, destroy client, return EOF
767 #if WEBDUINO_SERIAL_DEBUGGING
768           Serial.println("*** Connection timed out");
769 #endif
770           reset();
771           return -1;
772         }
773       }
774     }
775
776     // connection lost, return EOF
777 #if WEBDUINO_SERIAL_DEBUGGING
778     Serial.println("*** Connection lost");
779 #endif
780     return -1;
781   }
782   else
783     return m_pushback[--m_pushbackDepth];
784 }
785
786 void WebServer::push(int ch)
787 {
788   // don't allow pushing EOF
789   if (ch == -1)
790     return;
791
792   m_pushback[m_pushbackDepth++] = ch;
793   // can't raise error here, so just replace last char over and over
794   if (m_pushbackDepth == SIZE(m_pushback))
795     m_pushbackDepth = SIZE(m_pushback) - 1;
796 }
797
798 void WebServer::reset()
799 {
800   m_pushbackDepth = 0;
801   m_client.flush();
802   m_client.stop();
803 }
804
805 bool WebServer::expect(const char *str)
806 {
807   const char *curr = str;
808   while (*curr != 0)
809   {
810     int ch = read();
811     if (ch != *curr++)
812     {
813       // push back ch and the characters we accepted
814       push(ch);
815       while (--curr != str)
816         push(curr[-1]);
817       return false;
818     }
819   }
820   return true;
821 }
822
823 bool WebServer::readInt(int &number)
824 {
825   bool negate = false;
826   bool gotNumber = false;
827   int ch;
828   number = 0;
829
830   // absorb whitespace
831   do
832   {
833     ch = read();
834   } while (ch == ' ' || ch == '\t');
835
836   // check for leading minus sign
837   if (ch == '-')
838   {
839     negate = true;
840     ch = read();
841   }
842
843   // read digits to update number, exit when we find non-digit
844   while (ch >= '0' && ch <= '9')
845   {
846     gotNumber = true;
847     number = number * 10 + ch - '0';
848     ch = read();
849   }
850
851   push(ch);
852   if (negate)
853     number = -number;
854   return gotNumber;
855 }
856
857 void WebServer::readHeader(char *value, int valueLen)
858 {
859   int ch;
860   memset(value, 0, valueLen);
861   --valueLen;
862
863   // absorb whitespace
864   do
865   {
866     ch = read();
867   } while (ch == ' ' || ch == '\t');
868
869   // read rest of line
870   do
871   {
872     if (valueLen > 1)
873     {
874       *value++=ch;
875       --valueLen;
876     }
877     ch = read();
878   } while (ch != '\r');
879   push(ch);
880 }
881
882 bool WebServer::readPOSTparam(char *name, int nameLen,
883                               char *value, int valueLen)
884 {
885   // assume name is at current place in stream
886   int ch;
887   // to not to miss the last parameter
888   bool foundSomething = false;
889
890   // clear out name and value so they'll be NUL terminated
891   memset(name, 0, nameLen);
892   memset(value, 0, valueLen);
893
894   // decrement length so we don't write into NUL terminator
895   --nameLen;
896   --valueLen;
897
898   while ((ch = read()) != -1)
899   {
900     foundSomething = true;
901     if (ch == '+')
902     {
903       ch = ' ';
904     }
905     else if (ch == '=')
906     {
907       /* that's end of name, so switch to storing in value */
908       nameLen = 0;
909       continue;
910     }
911     else if (ch == '&')
912     {
913       /* that's end of pair, go away */
914       return true;
915     }
916     else if (ch == '%')
917     {
918       /* handle URL encoded characters by converting back to original form */
919       int ch1 = read();
920       int ch2 = read();
921       if (ch1 == -1 || ch2 == -1)
922         return false;
923       char hex[3] = { ch1, ch2, 0 };
924       ch = strtoul(hex, NULL, 16);
925     }
926
927     // output the new character into the appropriate buffer or drop it if
928     // there's no room in either one.  This code will malfunction in the
929     // case where the parameter name is too long to fit into the name buffer,
930     // but in that case, it will just overflow into the value buffer so
931     // there's no harm.
932     if (nameLen > 0)
933     {
934       *name++ = ch;
935       --nameLen;
936     }
937     else if (valueLen > 0)
938     {
939       *value++ = ch;
940       --valueLen;
941     }
942   }
943
944   if (foundSomething)
945   {
946     // if we get here, we have one last parameter to serve
947     return true;
948   }
949   else
950   {
951     // if we get here, we hit the end-of-file, so POST is over and there
952     // are no more parameters
953     return false;
954   }
955 }
956
957 /* Retrieve a parameter that was encoded as part of the URL, stored in
958  * the buffer pointed to by *tail.  tail is updated to point just past
959  * the last character read from the buffer. */
960 URLPARAM_RESULT WebServer::nextURLparam(char **tail, char *name, int nameLen,
961                                         char *value, int valueLen)
962 {
963   // assume name is at current place in stream
964   char ch, hex[3];
965   URLPARAM_RESULT result = URLPARAM_OK;
966   char *s = *tail;
967   bool keep_scanning = true;
968   bool need_value = true;
969
970   // clear out name and value so they'll be NUL terminated
971   memset(name, 0, nameLen);
972   memset(value, 0, valueLen);
973
974   if (*s == 0)
975     return URLPARAM_EOS;
976   // Read the keyword name
977   while (keep_scanning)
978   {
979     ch = *s++;
980     switch (ch)
981     {
982     case 0:
983       s--;  // Back up to point to terminating NUL
984       // Fall through to "stop the scan" code
985     case '&':
986       /* that's end of pair, go away */
987       keep_scanning = false;
988       need_value = false;
989       break;
990     case '+':
991       ch = ' ';
992       break;
993     case '%':
994       /* handle URL encoded characters by converting back
995        * to original form */
996       if ((hex[0] = *s++) == 0)
997       {
998         s--;        // Back up to NUL
999         keep_scanning = false;
1000         need_value = false;
1001       }
1002       else
1003       {
1004         if ((hex[1] = *s++) == 0)
1005         {
1006           s--;  // Back up to NUL
1007           keep_scanning = false;
1008           need_value = false;
1009         }
1010         else
1011         {
1012           hex[2] = 0;
1013           ch = strtoul(hex, NULL, 16);
1014         }
1015       }
1016       break;
1017     case '=':
1018       /* that's end of name, so switch to storing in value */
1019       keep_scanning = false;
1020       break;
1021     }
1022
1023
1024     // check against 1 so we don't overwrite the final NUL
1025     if (keep_scanning && (nameLen > 1))
1026     {
1027       *name++ = ch;
1028       --nameLen;
1029     }
1030     else if(keep_scanning)
1031       result = URLPARAM_NAME_OFLO;
1032   }
1033
1034   if (need_value && (*s != 0))
1035   {
1036     keep_scanning = true;
1037     while (keep_scanning)
1038     {
1039       ch = *s++;
1040       switch (ch)
1041       {
1042       case 0:
1043         s--;  // Back up to point to terminating NUL
1044               // Fall through to "stop the scan" code
1045       case '&':
1046         /* that's end of pair, go away */
1047         keep_scanning = false;
1048         need_value = false;
1049         break;
1050       case '+':
1051         ch = ' ';
1052         break;
1053       case '%':
1054         /* handle URL encoded characters by converting back to original form */
1055         if ((hex[0] = *s++) == 0)
1056         {
1057           s--;  // Back up to NUL
1058           keep_scanning = false;
1059           need_value = false;
1060         }
1061         else
1062         {
1063           if ((hex[1] = *s++) == 0)
1064           {
1065             s--;  // Back up to NUL
1066             keep_scanning = false;
1067             need_value = false;
1068           }
1069           else
1070           {
1071             hex[2] = 0;
1072             ch = strtoul(hex, NULL, 16);
1073           }
1074
1075         }
1076         break;
1077       }
1078
1079
1080       // check against 1 so we don't overwrite the final NUL
1081       if (keep_scanning && (valueLen > 1))
1082       {
1083         *value++ = ch;
1084         --valueLen;
1085       }
1086       else if(keep_scanning)
1087         result = (result == URLPARAM_OK) ?
1088           URLPARAM_VALUE_OFLO :
1089           URLPARAM_BOTH_OFLO;
1090     }
1091   }
1092   *tail = s;
1093   return result;
1094 }
1095
1096
1097
1098 // Read and parse the first line of the request header.
1099 // The "command" (GET/HEAD/POST) is translated into a numeric value in type.
1100 // The URL is stored in request,  up to the length passed in length
1101 // NOTE 1: length must include one byte for the terminating NUL.
1102 // NOTE 2: request is NOT checked for NULL,  nor length for a value < 1.
1103 // Reading stops when the code encounters a space, CR, or LF.  If the HTTP
1104 // version was supplied by the client,  it will still be waiting in the input
1105 // stream when we exit.
1106 //
1107 // On return, length contains the amount of space left in request.  If it's
1108 // less than 0,  the URL was longer than the buffer,  and part of it had to
1109 // be discarded.
1110
1111 void WebServer::getRequest(WebServer::ConnectionType &type,
1112                            char *request, int *length)
1113 {
1114   --*length; // save room for NUL
1115
1116   type = INVALID;
1117
1118   // store the HTTP method line of the request
1119   if (expect("GET "))
1120     type = GET;
1121   else if (expect("HEAD "))
1122     type = HEAD;
1123   else if (expect("POST "))
1124     type = POST;
1125   else if (expect("PUT "))
1126     type = PUT;
1127   else if (expect("DELETE "))
1128     type = DELETE;
1129   else if (expect("PATCH "))
1130     type = PATCH;
1131
1132   // if it doesn't start with any of those, we have an unknown method
1133   // so just get out of here
1134   else
1135     return;
1136
1137   int ch;
1138   while ((ch = read()) != -1)
1139   {
1140     // stop storing at first space or end of line
1141     if (ch == ' ' || ch == '\n' || ch == '\r')
1142     {
1143       break;
1144     }
1145     if (*length > 0)
1146     {
1147       *request = ch;
1148       ++request;
1149     }
1150     --*length;
1151   }
1152   // NUL terminate
1153   *request = 0;
1154 }
1155
1156 void WebServer::processHeaders()
1157 {
1158   // look for three things: the Content-Length header, the Authorization
1159   // header, and the double-CRLF that ends the headers.
1160
1161   // empty the m_authCredentials before every run of this function.
1162   // otherwise users who don't send an Authorization header would be treated
1163   // like the last user who tried to authenticate (possibly successful)
1164   m_authCredentials[0]=0;
1165
1166   while (1)
1167   {
1168     if (expect("Content-Length:"))
1169     {
1170       readInt(m_contentLength);
1171 #if WEBDUINO_SERIAL_DEBUGGING > 1
1172       Serial.print("\n*** got Content-Length of ");
1173       Serial.print(m_contentLength);
1174       Serial.print(" ***");
1175 #endif
1176       continue;
1177     }
1178
1179     if (expect("Authorization:"))
1180     {
1181       readHeader(m_authCredentials,51);
1182 #if WEBDUINO_SERIAL_DEBUGGING > 1
1183       Serial.print("\n*** got Authorization: of ");
1184       Serial.print(m_authCredentials);
1185       Serial.print(" ***");
1186 #endif
1187       continue;
1188     }
1189
1190     if (expect(CRLF CRLF))
1191     {
1192       m_readingContent = true;
1193       return;
1194     }
1195
1196     // no expect checks hit, so just absorb a character and try again
1197     if (read() == -1)
1198     {
1199       return;
1200     }
1201   }
1202 }
1203
1204 void WebServer::outputCheckboxOrRadio(const char *element, const char *name,
1205                                       const char *val, const char *label,
1206                                       bool selected)
1207 {
1208   P(cbPart1a) = "<label><input type='";
1209   P(cbPart1b) = "' name='";
1210   P(cbPart2) = "' value='";
1211   P(cbPart3) = "' ";
1212   P(cbChecked) = "checked ";
1213   P(cbPart4) = "/> ";
1214   P(cbPart5) = "</label>";
1215
1216   printP(cbPart1a);
1217   print(element);
1218   printP(cbPart1b);
1219   print(name);
1220   printP(cbPart2);
1221   print(val);
1222   printP(cbPart3);
1223   if (selected)
1224     printP(cbChecked);
1225   printP(cbPart4);
1226   print(label);
1227   printP(cbPart5);
1228 }
1229
1230 void WebServer::checkBox(const char *name, const char *val,
1231                          const char *label, bool selected)
1232 {
1233   outputCheckboxOrRadio("checkbox", name, val, label, selected);
1234 }
1235
1236 void WebServer::radioButton(const char *name, const char *val,
1237                             const char *label, bool selected)
1238 {
1239   outputCheckboxOrRadio("radio", name, val, label, selected);
1240 }
1241
1242 uint8_t WebServer::available(){
1243   return m_server.available();
1244 }
1245
1246 #endif // WEBDUINO_NO_IMPLEMENTATION
1247
1248 #endif // WEBDUINO_H_