summaryrefslogtreecommitdiff
path: root/libraries/WebServer
diff options
context:
space:
mode:
Diffstat (limited to 'libraries/WebServer')
-rw-r--r--libraries/WebServer/WebServer.h1159
-rw-r--r--libraries/WebServer/keywords.txt34
-rw-r--r--libraries/WebServer/readme.md103
3 files changed, 1296 insertions, 0 deletions
diff --git a/libraries/WebServer/WebServer.h b/libraries/WebServer/WebServer.h
new file mode 100644
index 0000000..0bbbf35
--- /dev/null
+++ b/libraries/WebServer/WebServer.h
@@ -0,0 +1,1159 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-file-style: "k&r"; c-basic-offset: 2; -*-
+
+ Webduino, a simple Arduino web server
+ Copyright 2009-2012 Ben Combee, Ran Talbott, Christopher Lee, Martin Lormes
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+
+#ifndef WEBDUINO_H_
+#define WEBDUINO_H_
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <EthernetClient.h>
+#include <EthernetServer.h>
+
+/********************************************************************
+ * CONFIGURATION
+ ********************************************************************/
+
+#define WEBDUINO_VERSION 1007
+#define WEBDUINO_VERSION_STRING "1.7"
+
+#if WEBDUINO_SUPRESS_SERVER_HEADER
+#define WEBDUINO_SERVER_HEADER ""
+#else
+#define WEBDUINO_SERVER_HEADER "Server: Webduino/" WEBDUINO_VERSION_STRING CRLF
+#endif
+
+// standard END-OF-LINE marker in HTTP
+#define CRLF "\r\n"
+
+// If processConnection is called without a buffer, it allocates one
+// of 32 bytes
+#define WEBDUINO_DEFAULT_REQUEST_LENGTH 32
+
+// How long to wait before considering a connection as dead when
+// reading the HTTP request. Used to avoid DOS attacks.
+#ifndef WEBDUINO_READ_TIMEOUT_IN_MS
+#define WEBDUINO_READ_TIMEOUT_IN_MS 1000
+#endif
+
+#ifndef WEBDUINO_FAIL_MESSAGE
+#define WEBDUINO_FAIL_MESSAGE "<h1>EPIC FAIL</h1>"
+#endif
+
+#ifndef WEBDUINO_AUTH_REALM
+#define WEBDUINO_AUTH_REALM "Webduino"
+#endif // #ifndef WEBDUINO_AUTH_REALM
+
+#ifndef WEBDUINO_AUTH_MESSAGE
+#define WEBDUINO_AUTH_MESSAGE "<h1>401 Unauthorized</h1>"
+#endif // #ifndef WEBDUINO_AUTH_MESSAGE
+
+#ifndef WEBDUINO_SERVER_ERROR_MESSAGE
+#define WEBDUINO_SERVER_ERROR_MESSAGE "<h1>500 Internal Server Error</h1>"
+#endif // WEBDUINO_SERVER_ERROR_MESSAGE
+
+// add '#define WEBDUINO_FAVICON_DATA ""' to your application
+// before including WebServer.h to send a null file as the favicon.ico file
+// otherwise this defaults to a 16x16 px black diode on blue ground
+// (or include your own icon if you like)
+#ifndef WEBDUINO_FAVICON_DATA
+#define WEBDUINO_FAVICON_DATA { 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x10, \
+ 0x10, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, \
+ 0xb0, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, \
+ 0x00, 0x28, 0x00, 0x00, 0x00, 0x10, 0x00, \
+ 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, \
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, \
+ 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, \
+ 0x00, 0xff, 0xff, 0x00, 0x00, 0xcf, 0xbf, \
+ 0x00, 0x00, 0xc7, 0xbf, 0x00, 0x00, 0xc3, \
+ 0xbf, 0x00, 0x00, 0xc1, 0xbf, 0x00, 0x00, \
+ 0xc0, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0xc0, 0xbf, 0x00, 0x00, 0xc1, 0xbf, \
+ 0x00, 0x00, 0xc3, 0xbf, 0x00, 0x00, 0xc7, \
+ 0xbf, 0x00, 0x00, 0xcf, 0xbf, 0x00, 0x00, \
+ 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00 }
+#endif // #ifndef WEBDUINO_FAVICON_DATA
+
+// add "#define WEBDUINO_SERIAL_DEBUGGING 1" to your application
+// before including WebServer.h to have incoming requests logged to
+// the serial port.
+#ifndef WEBDUINO_SERIAL_DEBUGGING
+#define WEBDUINO_SERIAL_DEBUGGING 0
+#endif
+#if WEBDUINO_SERIAL_DEBUGGING
+#include <HardwareSerial.h>
+#endif
+
+// declared in wiring.h
+extern "C" unsigned long millis(void);
+
+// declare a static string
+#define P(name) static const prog_uchar name[] PROGMEM
+
+// returns the number of elements in the array
+#define SIZE(array) (sizeof(array) / sizeof(*array))
+
+/********************************************************************
+ * DECLARATIONS
+ ********************************************************************/
+
+/* Return codes from nextURLparam. NOTE: URLPARAM_EOS is returned
+ * when you call nextURLparam AFTER the last parameter is read. The
+ * last actual parameter gets an "OK" return code. */
+
+typedef enum URLPARAM_RESULT { URLPARAM_OK,
+ URLPARAM_NAME_OFLO,
+ URLPARAM_VALUE_OFLO,
+ URLPARAM_BOTH_OFLO,
+ URLPARAM_EOS // No params left
+};
+
+class WebServer: public Print
+{
+public:
+ // passed to a command to indicate what kind of request was received
+ enum ConnectionType { INVALID, GET, HEAD, POST, PUT, DELETE, PATCH };
+
+ // any commands registered with the web server have to follow
+ // this prototype.
+ // url_tail contains the part of the URL that wasn't matched against
+ // the registered command table.
+ // tail_complete is true if the complete URL fit in url_tail, false if
+ // part of it was lost because the buffer was too small.
+ typedef void Command(WebServer &server, ConnectionType type,
+ char *url_tail, bool tail_complete);
+
+ // constructor for webserver object
+ WebServer(const char *urlPrefix = "", int port = 80);
+
+ // start listening for connections
+ void begin();
+
+ // check for an incoming connection, and if it exists, process it
+ // by reading its request and calling the appropriate command
+ // handler. This version is for compatibility with apps written for
+ // version 1.1, and allocates the URL "tail" buffer internally.
+ void processConnection();
+
+ // check for an incoming connection, and if it exists, process it
+ // by reading its request and calling the appropriate command
+ // handler. This version saves the "tail" of the URL in buff.
+ void processConnection(char *buff, int *bufflen);
+
+ // set command that's run when you access the root of the server
+ void setDefaultCommand(Command *cmd);
+
+ // set command run for undefined pages
+ void setFailureCommand(Command *cmd);
+
+ // add a new command to be run at the URL specified by verb
+ void addCommand(const char *verb, Command *cmd);
+
+ // utility function to output CRLF pair
+ void printCRLF();
+
+ // output a string stored in program memory, usually one defined
+ // with the P macro
+ void printP(const prog_uchar *str);
+
+ // inline overload for printP to handle signed char strings
+ void printP(const prog_char *str) { printP((prog_uchar*)str); }
+
+ // output raw data stored in program memory
+ void writeP(const prog_uchar *data, size_t length);
+
+ // output HTML for a radio button
+ void radioButton(const char *name, const char *val,
+ const char *label, bool selected);
+
+ // output HTML for a checkbox
+ void checkBox(const char *name, const char *val,
+ const char *label, bool selected);
+
+ // returns next character or -1 if we're at end-of-stream
+ int read();
+
+ // put a character that's been read back into the input pool
+ void push(int ch);
+
+ // returns true if the string is next in the stream. Doesn't
+ // consume any character if false, so can be used to try out
+ // different expected values.
+ bool expect(const char *expectedStr);
+
+ // returns true if a number, with possible whitespace in front, was
+ // read from the server stream. number will be set with the new
+ // value or 0 if nothing was read.
+ bool readInt(int &number);
+
+ // reads a header value, stripped of possible whitespace in front,
+ // from the server stream
+ void readHeader(char *value, int valueLen);
+
+ // Read the next keyword parameter from the socket. Assumes that other
+ // code has already skipped over the headers, and the next thing to
+ // be read will be the start of a keyword.
+ //
+ // returns true if we're not at end-of-stream
+ bool readPOSTparam(char *name, int nameLen, char *value, int valueLen);
+
+ // Read the next keyword parameter from the buffer filled by getRequest.
+ //
+ // returns 0 if everything weent okay, non-zero if not
+ // (see the typedef for codes)
+ URLPARAM_RESULT nextURLparam(char **tail, char *name, int nameLen,
+ char *value, int valueLen);
+
+ // compare string against credentials in current request
+ //
+ // authCredentials must be Base64 encoded outside of Webduino
+ // (I wanted to be easy on the resources)
+ //
+ // returns true if strings match, false otherwise
+ bool checkCredentials(const char authCredentials[45]);
+
+ // output headers and a message indicating a server error
+ void httpFail();
+
+ // output headers and a message indicating "401 Unauthorized"
+ void httpUnauthorized();
+
+ // output headers and a message indicating "500 Internal Server Error"
+ void httpServerError();
+
+ // output standard headers indicating "200 Success". You can change the
+ // type of the data you're outputting or also add extra headers like
+ // "Refresh: 1". Extra headers should each be terminated with CRLF.
+ void httpSuccess(const char *contentType = "text/html; charset=utf-8",
+ const char *extraHeaders = NULL);
+
+ // used with POST to output a redirect to another URL. This is
+ // preferable to outputting HTML from a post because you can then
+ // refresh the page without getting a "resubmit form" dialog.
+ void httpSeeOther(const char *otherURL);
+
+ // implementation of write used to implement Print interface
+ virtual size_t write(uint8_t);
+ virtual size_t write(const char *str);
+ virtual size_t write(const uint8_t *buffer, size_t size);
+ size_t write(const char *data, size_t length);
+
+private:
+ EthernetServer m_server;
+ EthernetClient m_client;
+ const char *m_urlPrefix;
+
+ unsigned char m_pushback[32];
+ char m_pushbackDepth;
+
+ int m_contentLength;
+ char m_authCredentials[51];
+ bool m_readingContent;
+
+ Command *m_failureCmd;
+ Command *m_defaultCmd;
+ struct CommandMap
+ {
+ const char *verb;
+ Command *cmd;
+ } m_commands[8];
+ char m_cmdCount;
+
+ void reset();
+ void getRequest(WebServer::ConnectionType &type, char *request, int *length);
+ bool dispatchCommand(ConnectionType requestType, char *verb,
+ bool tail_complete);
+ void processHeaders();
+ void outputCheckboxOrRadio(const char *element, const char *name,
+ const char *val, const char *label,
+ bool selected);
+
+ static void defaultFailCmd(WebServer &server, ConnectionType type,
+ char *url_tail, bool tail_complete);
+ void noRobots(ConnectionType type);
+ void favicon(ConnectionType type);
+};
+
+/* define this macro if you want to include the header in a sketch source
+ file but not define any of the implementation. This is useful if
+ multiple source files are using the Webduino class. */
+#ifndef WEBDUINO_NO_IMPLEMENTATION
+
+/********************************************************************
+ * IMPLEMENTATION
+ ********************************************************************/
+
+WebServer::WebServer(const char *urlPrefix, int port) :
+ m_server(port),
+ m_client(255),
+ m_urlPrefix(urlPrefix),
+ m_pushbackDepth(0),
+ m_cmdCount(0),
+ m_contentLength(0),
+ m_failureCmd(&defaultFailCmd),
+ m_defaultCmd(&defaultFailCmd)
+{
+}
+
+void WebServer::begin()
+{
+ m_server.begin();
+}
+
+void WebServer::setDefaultCommand(Command *cmd)
+{
+ m_defaultCmd = cmd;
+}
+
+void WebServer::setFailureCommand(Command *cmd)
+{
+ m_failureCmd = cmd;
+}
+
+void WebServer::addCommand(const char *verb, Command *cmd)
+{
+ if (m_cmdCount < SIZE(m_commands))
+ {
+ m_commands[m_cmdCount].verb = verb;
+ m_commands[m_cmdCount++].cmd = cmd;
+ }
+}
+
+size_t WebServer::write(uint8_t ch)
+{
+ return m_client.write(ch);
+}
+
+size_t WebServer::write(const char *str)
+{
+ return m_client.write(str);
+}
+
+size_t WebServer::write(const uint8_t *buffer, size_t size)
+{
+ return m_client.write(buffer, size);
+}
+
+size_t WebServer::write(const char *buffer, size_t length)
+{
+ return m_client.write((const uint8_t *)buffer, length);
+}
+
+void WebServer::writeP(const prog_uchar *data, size_t length)
+{
+ // copy data out of program memory into local storage, write out in
+ // chunks of 32 bytes to avoid extra short TCP/IP packets
+ uint8_t buffer[32];
+ size_t bufferEnd = 0;
+
+ while (length--)
+ {
+ if (bufferEnd == 32)
+ {
+ m_client.write(buffer, 32);
+ bufferEnd = 0;
+ }
+
+ buffer[bufferEnd++] = pgm_read_byte(data++);
+ }
+
+ if (bufferEnd > 0)
+ m_client.write(buffer, bufferEnd);
+}
+
+void WebServer::printP(const prog_uchar *str)
+{
+ // copy data out of program memory into local storage, write out in
+ // chunks of 32 bytes to avoid extra short TCP/IP packets
+ uint8_t buffer[32];
+ size_t bufferEnd = 0;
+
+ while (buffer[bufferEnd++] = pgm_read_byte(str++))
+ {
+ if (bufferEnd == 32)
+ {
+ m_client.write(buffer, 32);
+ bufferEnd = 0;
+ }
+ }
+
+ // write out everything left but trailing NUL
+ if (bufferEnd > 1)
+ m_client.write(buffer, bufferEnd - 1);
+}
+
+void WebServer::printCRLF()
+{
+ m_client.write((const uint8_t *)"\r\n", 2);
+}
+
+bool WebServer::dispatchCommand(ConnectionType requestType, char *verb,
+ bool tail_complete)
+{
+ // if there is no URL, i.e. we have a prefix and it's requested without a
+ // trailing slash or if the URL is just the slash
+ if ((verb[0] == 0) || ((verb[0] == '/') && (verb[1] == 0)))
+ {
+ m_defaultCmd(*this, requestType, "", tail_complete);
+ return true;
+ }
+ // if the URL is just a slash followed by a question mark
+ // we're looking at the default command with GET parameters passed
+ if ((verb[0] == '/') && (verb[1] == '?'))
+ {
+ verb+=2; // skip over the "/?" part of the url
+ m_defaultCmd(*this, requestType, verb, tail_complete);
+ return true;
+ }
+ // We now know that the URL contains at least one character. And,
+ // if the first character is a slash, there's more after it.
+ if (verb[0] == '/')
+ {
+ char i;
+ char *qm_loc;
+ int verb_len;
+ int qm_offset;
+ // Skip over the leading "/", because it makes the code more
+ // efficient and easier to understand.
+ verb++;
+ // Look for a "?" separating the filename part of the URL from the
+ // parameters. If it's not there, compare to the whole URL.
+ qm_loc = strchr(verb, '?');
+ verb_len = (qm_loc == NULL) ? strlen(verb) : (qm_loc - verb);
+ qm_offset = (qm_loc == NULL) ? 0 : 1;
+ for (i = 0; i < m_cmdCount; ++i)
+ {
+ if ((verb_len == strlen(m_commands[i].verb))
+ && (strncmp(verb, m_commands[i].verb, verb_len) == 0))
+ {
+ // Skip over the "verb" part of the URL (and the question
+ // mark, if present) when passing it to the "action" routine
+ m_commands[i].cmd(*this, requestType,
+ verb + verb_len + qm_offset,
+ tail_complete);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// processConnection with a default buffer
+void WebServer::processConnection()
+{
+ char request[WEBDUINO_DEFAULT_REQUEST_LENGTH];
+ int request_len = WEBDUINO_DEFAULT_REQUEST_LENGTH;
+ processConnection(request, &request_len);
+}
+
+void WebServer::processConnection(char *buff, int *bufflen)
+{
+ int urlPrefixLen = strlen(m_urlPrefix);
+
+ m_client = m_server.available();
+
+ if (m_client) {
+ m_readingContent = false;
+ buff[0] = 0;
+ ConnectionType requestType = INVALID;
+#if WEBDUINO_SERIAL_DEBUGGING > 1
+ Serial.println("*** checking request ***");
+#endif
+ getRequest(requestType, buff, bufflen);
+#if WEBDUINO_SERIAL_DEBUGGING > 1
+ Serial.print("*** requestType = ");
+ Serial.print((int)requestType);
+ Serial.print(", request = \"");
+ Serial.print(buff);
+ Serial.println("\" ***");
+#endif
+
+ // don't even look further at invalid requests.
+ // this is done to prevent Webduino from hanging
+ // - when there are illegal requests,
+ // - when someone contacts it through telnet rather than proper HTTP,
+ // - etc.
+ if (requestType != INVALID)
+ {
+ processHeaders();
+#if WEBDUINO_SERIAL_DEBUGGING > 1
+ Serial.println("*** headers complete ***");
+#endif
+
+ if (strcmp(buff, "/robots.txt") == 0)
+ {
+ noRobots(requestType);
+ }
+ else if (strcmp(buff, "/favicon.ico") == 0)
+ {
+ favicon(requestType);
+ }
+ }
+ if (requestType == INVALID ||
+ strncmp(buff, m_urlPrefix, urlPrefixLen) != 0 ||
+ !dispatchCommand(requestType, buff + urlPrefixLen,
+ (*bufflen) >= 0))
+ {
+ m_failureCmd(*this, requestType, buff, (*bufflen) >= 0);
+ }
+
+#if WEBDUINO_SERIAL_DEBUGGING > 1
+ Serial.println("*** stopping connection ***");
+#endif
+ reset();
+ }
+}
+
+bool WebServer::checkCredentials(const char authCredentials[45])
+{
+ char basic[7] = "Basic ";
+ if((0 == strncmp(m_authCredentials,basic,6)) &&
+ (0 == strcmp(authCredentials, m_authCredentials + 6))) return true;
+ return false;
+}
+
+void WebServer::httpFail()
+{
+ P(failMsg) =
+ "HTTP/1.0 400 Bad Request" CRLF
+ WEBDUINO_SERVER_HEADER
+ "Content-Type: text/html" CRLF
+ CRLF
+ WEBDUINO_FAIL_MESSAGE;
+
+ printP(failMsg);
+}
+
+void WebServer::defaultFailCmd(WebServer &server,
+ WebServer::ConnectionType type,
+ char *url_tail,
+ bool tail_complete)
+{
+ server.httpFail();
+}
+
+void WebServer::noRobots(ConnectionType type)
+{
+ httpSuccess("text/plain");
+ if (type != HEAD)
+ {
+ P(allowNoneMsg) = "User-agent: *" CRLF "Disallow: /" CRLF;
+ printP(allowNoneMsg);
+ }
+}
+
+void WebServer::favicon(ConnectionType type)
+{
+ httpSuccess("image/x-icon","Cache-Control: max-age=31536000\r\n");
+ if (type != HEAD)
+ {
+ P(faviconIco) = WEBDUINO_FAVICON_DATA;
+ writeP(faviconIco, sizeof(faviconIco));
+ }
+}
+
+void WebServer::httpUnauthorized()
+{
+ P(failMsg) =
+ "HTTP/1.0 401 Authorization Required" CRLF
+ WEBDUINO_SERVER_HEADER
+ "Content-Type: text/html" CRLF
+ "WWW-Authenticate: Basic realm=\"" WEBDUINO_AUTH_REALM "\"" CRLF
+ CRLF
+ WEBDUINO_AUTH_MESSAGE;
+
+ printP(failMsg);
+}
+
+void WebServer::httpServerError()
+{
+ P(failMsg) =
+ "HTTP/1.0 500 Internal Server Error" CRLF
+ WEBDUINO_SERVER_HEADER
+ "Content-Type: text/html" CRLF
+ CRLF
+ WEBDUINO_SERVER_ERROR_MESSAGE;
+
+ printP(failMsg);
+}
+
+void WebServer::httpSuccess(const char *contentType,
+ const char *extraHeaders)
+{
+ P(successMsg1) =
+ "HTTP/1.0 200 OK" CRLF
+ WEBDUINO_SERVER_HEADER
+ "Access-Control-Allow-Origin: *" CRLF
+ "Content-Type: ";
+
+ printP(successMsg1);
+ print(contentType);
+ printCRLF();
+ if (extraHeaders)
+ print(extraHeaders);
+ printCRLF();
+}
+
+void WebServer::httpSeeOther(const char *otherURL)
+{
+ P(seeOtherMsg) =
+ "HTTP/1.0 303 See Other" CRLF
+ WEBDUINO_SERVER_HEADER
+ "Location: ";
+
+ printP(seeOtherMsg);
+ print(otherURL);
+ printCRLF();
+ printCRLF();
+}
+
+int WebServer::read()
+{
+ if (m_client == NULL)
+ return -1;
+
+ if (m_pushbackDepth == 0)
+ {
+ unsigned long timeoutTime = millis() + WEBDUINO_READ_TIMEOUT_IN_MS;
+
+ while (m_client.connected())
+ {
+ // stop reading the socket early if we get to content-length
+ // characters in the POST. This is because some clients leave
+ // the socket open because they assume HTTP keep-alive.
+ if (m_readingContent)
+ {
+ if (m_contentLength == 0)
+ {
+#if WEBDUINO_SERIAL_DEBUGGING > 1
+ Serial.println("\n*** End of content, terminating connection");
+#endif
+ return -1;
+ }
+ --m_contentLength;
+ }
+
+ int ch = m_client.read();
+
+ // if we get a character, return it, otherwise continue in while
+ // loop, checking connection status
+ if (ch != -1)
+ {
+#if WEBDUINO_SERIAL_DEBUGGING
+ if (ch == '\r')
+ Serial.print("<CR>");
+ else if (ch == '\n')
+ Serial.println("<LF>");
+ else
+ Serial.print((char)ch);
+#endif
+ return ch;
+ }
+ else
+ {
+ unsigned long now = millis();
+ if (now > timeoutTime)
+ {
+ // connection timed out, destroy client, return EOF
+#if WEBDUINO_SERIAL_DEBUGGING
+ Serial.println("*** Connection timed out");
+#endif
+ reset();
+ return -1;
+ }
+ }
+ }
+
+ // connection lost, return EOF
+#if WEBDUINO_SERIAL_DEBUGGING
+ Serial.println("*** Connection lost");
+#endif
+ return -1;
+ }
+ else
+ return m_pushback[--m_pushbackDepth];
+}
+
+void WebServer::push(int ch)
+{
+ // don't allow pushing EOF
+ if (ch == -1)
+ return;
+
+ m_pushback[m_pushbackDepth++] = ch;
+ // can't raise error here, so just replace last char over and over
+ if (m_pushbackDepth == SIZE(m_pushback))
+ m_pushbackDepth = SIZE(m_pushback) - 1;
+}
+
+void WebServer::reset()
+{
+ m_pushbackDepth = 0;
+ m_client.flush();
+ m_client.stop();
+}
+
+bool WebServer::expect(const char *str)
+{
+ const char *curr = str;
+ while (*curr != 0)
+ {
+ int ch = read();
+ if (ch != *curr++)
+ {
+ // push back ch and the characters we accepted
+ push(ch);
+ while (--curr != str)
+ push(curr[-1]);
+ return false;
+ }
+ }
+ return true;
+}
+
+bool WebServer::readInt(int &number)
+{
+ bool negate = false;
+ bool gotNumber = false;
+ int ch;
+ number = 0;
+
+ // absorb whitespace
+ do
+ {
+ ch = read();
+ } while (ch == ' ' || ch == '\t');
+
+ // check for leading minus sign
+ if (ch == '-')
+ {
+ negate = true;
+ ch = read();
+ }
+
+ // read digits to update number, exit when we find non-digit
+ while (ch >= '0' && ch <= '9')
+ {
+ gotNumber = true;
+ number = number * 10 + ch - '0';
+ ch = read();
+ }
+
+ push(ch);
+ if (negate)
+ number = -number;
+ return gotNumber;
+}
+
+void WebServer::readHeader(char *value, int valueLen)
+{
+ int ch;
+ memset(value, 0, valueLen);
+ --valueLen;
+
+ // absorb whitespace
+ do
+ {
+ ch = read();
+ } while (ch == ' ' || ch == '\t');
+
+ // read rest of line
+ do
+ {
+ if (valueLen > 1)
+ {
+ *value++=ch;
+ --valueLen;
+ ch = read();
+ }
+ } while (ch != '\r');
+ push(ch);
+}
+
+bool WebServer::readPOSTparam(char *name, int nameLen,
+ char *value, int valueLen)
+{
+ // assume name is at current place in stream
+ int ch;
+
+ // clear out name and value so they'll be NUL terminated
+ memset(name, 0, nameLen);
+ memset(value, 0, valueLen);
+
+ // decrement length so we don't write into NUL terminator
+ --nameLen;
+ --valueLen;
+
+ while ((ch = read()) != -1)
+ {
+ if (ch == '+')
+ {
+ ch = ' ';
+ }
+ else if (ch == '=')
+ {
+ /* that's end of name, so switch to storing in value */
+ nameLen = 0;
+ continue;
+ }
+ else if (ch == '&')
+ {
+ /* that's end of pair, go away */
+ return true;
+ }
+ else if (ch == '%')
+ {
+ /* handle URL encoded characters by converting back to original form */
+ int ch1 = read();
+ int ch2 = read();
+ if (ch1 == -1 || ch2 == -1)
+ return false;
+ char hex[3] = { ch1, ch2, 0 };
+ ch = strtoul(hex, NULL, 16);
+ }
+
+ // output the new character into the appropriate buffer or drop it if
+ // there's no room in either one. This code will malfunction in the
+ // case where the parameter name is too long to fit into the name buffer,
+ // but in that case, it will just overflow into the value buffer so
+ // there's no harm.
+ if (nameLen > 0)
+ {
+ *name++ = ch;
+ --nameLen;
+ }
+ else if (valueLen > 0)
+ {
+ *value++ = ch;
+ --valueLen;
+ }
+ }
+
+ // if we get here, we hit the end-of-file, so POST is over and there
+ // are no more parameters
+ return false;
+}
+
+/* Retrieve a parameter that was encoded as part of the URL, stored in
+ * the buffer pointed to by *tail. tail is updated to point just past
+ * the last character read from the buffer. */
+URLPARAM_RESULT WebServer::nextURLparam(char **tail, char *name, int nameLen,
+ char *value, int valueLen)
+{
+ // assume name is at current place in stream
+ char ch, hex[3];
+ URLPARAM_RESULT result = URLPARAM_OK;
+ char *s = *tail;
+ bool keep_scanning = true;
+ bool need_value = true;
+
+ // clear out name and value so they'll be NUL terminated
+ memset(name, 0, nameLen);
+ memset(value, 0, valueLen);
+
+ if (*s == 0)
+ return URLPARAM_EOS;
+ // Read the keyword name
+ while (keep_scanning)
+ {
+ ch = *s++;
+ switch (ch)
+ {
+ case 0:
+ s--; // Back up to point to terminating NUL
+ // Fall through to "stop the scan" code
+ case '&':
+ /* that's end of pair, go away */
+ keep_scanning = false;
+ need_value = false;
+ break;
+ case '+':
+ ch = ' ';
+ break;
+ case '%':
+ /* handle URL encoded characters by converting back
+ * to original form */
+ if ((hex[0] = *s++) == 0)
+ {
+ s--; // Back up to NUL
+ keep_scanning = false;
+ need_value = false;
+ }
+ else
+ {
+ if ((hex[1] = *s++) == 0)
+ {
+ s--; // Back up to NUL
+ keep_scanning = false;
+ need_value = false;
+ }
+ else
+ {
+ hex[2] = 0;
+ ch = strtoul(hex, NULL, 16);
+ }
+ }
+ break;
+ case '=':
+ /* that's end of name, so switch to storing in value */
+ keep_scanning = false;
+ break;
+ }
+
+
+ // check against 1 so we don't overwrite the final NUL
+ if (keep_scanning && (nameLen > 1))
+ {
+ *name++ = ch;
+ --nameLen;
+ }
+ else
+ result = URLPARAM_NAME_OFLO;
+ }
+
+ if (need_value && (*s != 0))
+ {
+ keep_scanning = true;
+ while (keep_scanning)
+ {
+ ch = *s++;
+ switch (ch)
+ {
+ case 0:
+ s--; // Back up to point to terminating NUL
+ // Fall through to "stop the scan" code
+ case '&':
+ /* that's end of pair, go away */
+ keep_scanning = false;
+ need_value = false;
+ break;
+ case '+':
+ ch = ' ';
+ break;
+ case '%':
+ /* handle URL encoded characters by converting back to original form */
+ if ((hex[0] = *s++) == 0)
+ {
+ s--; // Back up to NUL
+ keep_scanning = false;
+ need_value = false;
+ }
+ else
+ {
+ if ((hex[1] = *s++) == 0)
+ {
+ s--; // Back up to NUL
+ keep_scanning = false;
+ need_value = false;
+ }
+ else
+ {
+ hex[2] = 0;
+ ch = strtoul(hex, NULL, 16);
+ }
+
+ }
+ break;
+ }
+
+
+ // check against 1 so we don't overwrite the final NUL
+ if (keep_scanning && (valueLen > 1))
+ {
+ *value++ = ch;
+ --valueLen;
+ }
+ else
+ result = (result == URLPARAM_OK) ?
+ URLPARAM_VALUE_OFLO :
+ URLPARAM_BOTH_OFLO;
+ }
+ }
+ *tail = s;
+ return result;
+}
+
+
+
+// Read and parse the first line of the request header.
+// The "command" (GET/HEAD/POST) is translated into a numeric value in type.
+// The URL is stored in request, up to the length passed in length
+// NOTE 1: length must include one byte for the terminating NUL.
+// NOTE 2: request is NOT checked for NULL, nor length for a value < 1.
+// Reading stops when the code encounters a space, CR, or LF. If the HTTP
+// version was supplied by the client, it will still be waiting in the input
+// stream when we exit.
+//
+// On return, length contains the amount of space left in request. If it's
+// less than 0, the URL was longer than the buffer, and part of it had to
+// be discarded.
+
+void WebServer::getRequest(WebServer::ConnectionType &type,
+ char *request, int *length)
+{
+ --*length; // save room for NUL
+
+ type = INVALID;
+
+ // store the HTTP method line of the request
+ if (expect("GET "))
+ type = GET;
+ else if (expect("HEAD "))
+ type = HEAD;
+ else if (expect("POST "))
+ type = POST;
+ else if (expect("PUT "))
+ type = PUT;
+ else if (expect("DELETE "))
+ type = DELETE;
+ else if (expect("PATCH "))
+ type = PATCH;
+
+ // if it doesn't start with any of those, we have an unknown method
+ // so just get out of here
+ else
+ return;
+
+ int ch;
+ while ((ch = read()) != -1)
+ {
+ // stop storing at first space or end of line
+ if (ch == ' ' || ch == '\n' || ch == '\r')
+ {
+ break;
+ }
+ if (*length > 0)
+ {
+ *request = ch;
+ ++request;
+ }
+ --*length;
+ }
+ // NUL terminate
+ *request = 0;
+}
+
+void WebServer::processHeaders()
+{
+ // look for three things: the Content-Length header, the Authorization
+ // header, and the double-CRLF that ends the headers.
+
+ // empty the m_authCredentials before every run of this function.
+ // otherwise users who don't send an Authorization header would be treated
+ // like the last user who tried to authenticate (possibly successful)
+ m_authCredentials[0]=0;
+
+ while (1)
+ {
+ if (expect("Content-Length:"))
+ {
+ readInt(m_contentLength);
+#if WEBDUINO_SERIAL_DEBUGGING > 1
+ Serial.print("\n*** got Content-Length of ");
+ Serial.print(m_contentLength);
+ Serial.print(" ***");
+#endif
+ continue;
+ }
+
+ if (expect("Authorization:"))
+ {
+ readHeader(m_authCredentials,51);
+#if WEBDUINO_SERIAL_DEBUGGING > 1
+ Serial.print("\n*** got Authorization: of ");
+ Serial.print(m_authCredentials);
+ Serial.print(" ***");
+#endif
+ continue;
+ }
+
+ if (expect(CRLF CRLF))
+ {
+ m_readingContent = true;
+ return;
+ }
+
+ // no expect checks hit, so just absorb a character and try again
+ if (read() == -1)
+ {
+ return;
+ }
+ }
+}
+
+void WebServer::outputCheckboxOrRadio(const char *element, const char *name,
+ const char *val, const char *label,
+ bool selected)
+{
+ P(cbPart1a) = "<label><input type='";
+ P(cbPart1b) = "' name='";
+ P(cbPart2) = "' value='";
+ P(cbPart3) = "' ";
+ P(cbChecked) = "checked ";
+ P(cbPart4) = "/> ";
+ P(cbPart5) = "</label>";
+
+ printP(cbPart1a);
+ print(element);
+ printP(cbPart1b);
+ print(name);
+ printP(cbPart2);
+ print(val);
+ printP(cbPart3);
+ if (selected)
+ printP(cbChecked);
+ printP(cbPart4);
+ print(label);
+ printP(cbPart5);
+}
+
+void WebServer::checkBox(const char *name, const char *val,
+ const char *label, bool selected)
+{
+ outputCheckboxOrRadio("checkbox", name, val, label, selected);
+}
+
+void WebServer::radioButton(const char *name, const char *val,
+ const char *label, bool selected)
+{
+ outputCheckboxOrRadio("radio", name, val, label, selected);
+}
+
+#endif // WEBDUINO_NO_IMPLEMENTATION
+
+#endif // WEBDUINO_H_
diff --git a/libraries/WebServer/keywords.txt b/libraries/WebServer/keywords.txt
new file mode 100644
index 0000000..f72c5a9
--- /dev/null
+++ b/libraries/WebServer/keywords.txt
@@ -0,0 +1,34 @@
+WebServer KEYWORD1
+ConnectionType KEYWORD1
+INVALID KEYWORD2
+GET KEYWORD2
+HEAD KEYWORD2
+POST KEYWORD2
+PUT KEYWORD2
+DELETE KEYWORD2
+PATCH KEYWORD2
+begin KEYWORD2
+processConnection KEYWORD2
+setDefaultCommand KEYWORD2
+setFailureCommand KEYWORD2
+addCommand KEYWORD2
+printCRLF KEYWORD2
+printP KEYWORD2
+writeP KEYWORD2
+radioButton KEYWORD2
+checkBox KEYWORD2
+read KEYWORD2
+push KEYWORD2
+expect KEYWORD2
+readInt KEYWORD2
+readHeader KEYWORD2
+readPOSTparam KEYWORD2
+nextURLparam KEYWORD2
+checkCredentials KEYWORD2
+httpFail KEYWORD2
+httpUnauthorized KEYWORD2
+httpServerError KEYWORD2
+httpSuccess KEYWORD2
+httpSeeOther KEYWORD2
+write KEYWORD2
+P KEYWORD2
diff --git a/libraries/WebServer/readme.md b/libraries/WebServer/readme.md
new file mode 100644
index 0000000..6e4c6a1
--- /dev/null
+++ b/libraries/WebServer/readme.md
@@ -0,0 +1,103 @@
+# Webduino
+
+This is an Arduino-based Web Server library, originally developed for a class at NYC Resistor. It's called Webduino, and it's an extensible web server library for the Arduino using the Wiznet-based Ethernet shields. It's released under the MIT license allowing all sorts of reuse.
+
+## Features
+
+- URL parameter parsing
+- Handle the following HTTP Methods: GET, HEAD, POST, PUT, DELETE, PATCH
+- Web Forms
+- Images
+- JSON/RESTful interface
+- HTTP Basic Authentication
+
+## Installation Notes
+
+With Arduino 1.0, add the Webduino folder to the "libraries" folder of your sketchbook directory.
+
+You can put the examples in your own sketchbook directory, or in hardware/libraries/webduino/examples, as you prefer.
+
+If you get an error message when building the examples similar to "WebServer.h not found", it's a problem with where you put the Webduino folder. The server won't work if the header is directly in the libraries folder.
+
+## Presentation
+
+[Wedbuino Presentation on Google Docs](http://docs.google.com/present/view?id=dd8gqxt8_5c8w9qfg3)
+
+## Compatible Ethernet Shields
+
+These have all been tested with the library successfully:
+
+- [Freetronics Etherten](http://www.freetronics.com/products/etherten)
+- [Freetronics Ethernet Shield](http://www.freetronics.com/products/ethernet-shield-with-poe)
+- [Arduino Ethernet](http://arduino.cc/en/Main/ArduinoBoardEthernet)
+- [Arduino Ethernet Shield, both original and updated microSD version](http://arduino.cc/en/Main/ArduinoEthernetShield)
+- [Adafruit Ethernet Shield w/ Wiznet 811MJ module](http://www.ladyada.net/make/eshield/)
+- [NKC Electronics Ethernet Shield DIY Kit](http://store.nkcelectronics.com/nkc-ethernet-shield-diy-kit-without-wiz812mj-mod812.html)
+
+Shields using the Microchip ENC28J60 chip won't work with the library as that requires more software support for implementating
+the TCP/IP stack.
+
+## Version history
+
+### 1.7 released in Jan 2012
+
+- fixed Google Code issue [4](http://code.google.com/p/webduino/issues/detail?id=4) where expect fails with high-ASCII characters due to sign issues
+- fixed Google Code issue [8](http://code.google.com/p/webduino/issues/detail?id=8) by adding WEBDUINO_NO_IMPLEMENTATION macro that allows including the class definition without the implementation code
+- fixed Google Code issue [9](http://code.google.com/p/webduino/issues/detail?id=9): allowing prog_char* strings for printP
+- added httpServerError() method to output 500 Internal Server Error message
+- added support for HTTP PUT, DELETE, and PATCH methods (see Google Code issue [11](http://code.google.com/p/webduino/issues/detail?id=11)
+- fixed Google Code issue [12](http://code.google.com/p/webduino/issues/detail?id=12): off-by-one error in name/value parser (readPOSTparam) where the buffer wouldn't ever be completely filled
+- updated copyright string for 2012 and major authors
+- GitHub fork now the official version; all open issues on Google Code site fixed or closed and moved to GitHub
+
+### 1.6 released in Jan 2012
+
+- added [checkCredentials](http://ten-fingers-and-a-brain.com/arduino-projects/webduino/checkcredentials/) and [httpUnauthorized](http://ten-fingers-and-a-brain.com/arduino-projects/webduino/httpunauthorized/) methods as well as readHeader method for HTTP Basic Authentication; currently users will have to do the Base64 encoding outside of Webduino and I'm uncertain whether I ever want this inside the library or not...
+- fixed the request parser: previously the command dispatcher would always pass true for tail_complete, even if the tail was incomplete
+- fixed the command dispatcher: previously the default command could not have a tail, but the EPIC FAIL was returned
+
+### 1.5 released in Dec 2011
+
+- added a default favicon.ico based on the led.png from the Image example to save resources on Firefox trying to load this file on every request if it can't be loaded
+- added keywords.txt for syntax highlighting in Arduino IDE
+- bumped the version number up in response headers and compiler variables
+- added version history to readme
+- fixed default value for prefix
+- fixed /index.html in Hello World example
+
+### releases between Jul 2011 and Dec 2011
+
+- project forked on GitHub by Chris Lee
+- JSON/RESTful interface
+- header Access-Control-Allow-Origin added
+- code split in .h and .cpp files
+- Arduino 1.0 changes by Dave Falkenburg and others
+- adding CRLF after extraHeaders
+
+### 1.4.1 released in Nov 2009
+
+- fix examples to use readPOSTparam method
+
+### 1.4 released in Sep 2009
+
+- bug fix for multple connections
+
+### 1.3.1 released in Aug 2009
+
+- major bug fix for 1.3 for posts where Content-Length is last header sent
+
+### 1.3 released in Aug 2009
+
+- mostly robustness fixes (beta)
+
+### 1.2.1 released in Aug 2009
+
+- fixed HelloWorld example
+
+### 1.2 released in Jul 2009
+
+- now with GET parameter handling
+
+### 1.1 released in Apr 2009
+
+### 1.0 released in Mar 2009