Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: IAP NTPClient RTC mbed-rtos mbed Socket lwip-sys lwip BurstSPI
Fork of LPC1768_Mini-DK by
httpd.cpp@15:688b3e3958fd, 2013-08-23 (annotated)
- Committer:
- jakeb
- Date:
- Fri Aug 23 21:45:08 2013 +0000
- Revision:
- 15:688b3e3958fd
Initial commit of software v0.2;
Who changed what in which revision?
| User | Revision | Line number | New contents of line |
|---|---|---|---|
| jakeb | 15:688b3e3958fd | 1 | // Copyright (c) 2013, jake (at) allaboutjake (dot) com |
| jakeb | 15:688b3e3958fd | 2 | // All rights reserved. |
| jakeb | 15:688b3e3958fd | 3 | // |
| jakeb | 15:688b3e3958fd | 4 | // Redistribution and use in source and binary forms, with or without |
| jakeb | 15:688b3e3958fd | 5 | // modification, are permitted provided that the following conditions are met: |
| jakeb | 15:688b3e3958fd | 6 | // * Redistributions of source code must retain the above copyright |
| jakeb | 15:688b3e3958fd | 7 | // notice, this list of conditions and the following disclaimer. |
| jakeb | 15:688b3e3958fd | 8 | // * Redistributions in binary form must reproduce the above copyright |
| jakeb | 15:688b3e3958fd | 9 | // notice, this list of conditions and the following disclaimer in the |
| jakeb | 15:688b3e3958fd | 10 | // documentation and/or other materials provided with the distribution. |
| jakeb | 15:688b3e3958fd | 11 | // * The name of the author and/or copyright holder nor the |
| jakeb | 15:688b3e3958fd | 12 | // names of its contributors may be used to endorse or promote products |
| jakeb | 15:688b3e3958fd | 13 | // derived from this software without specific prior written permission. |
| jakeb | 15:688b3e3958fd | 14 | // |
| jakeb | 15:688b3e3958fd | 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
| jakeb | 15:688b3e3958fd | 16 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| jakeb | 15:688b3e3958fd | 17 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| jakeb | 15:688b3e3958fd | 18 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER, AUTHOR, OR ANY CONTRIBUTORS |
| jakeb | 15:688b3e3958fd | 19 | // BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| jakeb | 15:688b3e3958fd | 20 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
| jakeb | 15:688b3e3958fd | 21 | // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| jakeb | 15:688b3e3958fd | 22 | // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| jakeb | 15:688b3e3958fd | 23 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| jakeb | 15:688b3e3958fd | 24 | // OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| jakeb | 15:688b3e3958fd | 25 | |
| jakeb | 15:688b3e3958fd | 26 | // DESCRIPTION OF FILE: |
| jakeb | 15:688b3e3958fd | 27 | // |
| jakeb | 15:688b3e3958fd | 28 | // This file contains the source to the http server. |
| jakeb | 15:688b3e3958fd | 29 | // |
| jakeb | 15:688b3e3958fd | 30 | // The main function is "http_server_task" which is intended to be run in its |
| jakeb | 15:688b3e3958fd | 31 | // own thread. The other functions are helper functions. This function assumes |
| jakeb | 15:688b3e3958fd | 32 | // that the EthernetInterface is already configured and ready for use. |
| jakeb | 15:688b3e3958fd | 33 | // |
| jakeb | 15:688b3e3958fd | 34 | |
| jakeb | 15:688b3e3958fd | 35 | #include "httpd.h" |
| jakeb | 15:688b3e3958fd | 36 | #include "readline.h" |
| jakeb | 15:688b3e3958fd | 37 | #include "main.h" |
| jakeb | 15:688b3e3958fd | 38 | #include "settings.h" |
| jakeb | 15:688b3e3958fd | 39 | |
| jakeb | 15:688b3e3958fd | 40 | // Some Private methods |
| jakeb | 15:688b3e3958fd | 41 | // HTML rendering |
| jakeb | 15:688b3e3958fd | 42 | void sendHTTPHeader(ClientSocket* sock, int response_code, char* response_descr, char* contentType); |
| jakeb | 15:688b3e3958fd | 43 | void sendStatusContent(ClientSocket* sock); |
| jakeb | 15:688b3e3958fd | 44 | void sendPageHeader(ClientSocket* sock); |
| jakeb | 15:688b3e3958fd | 45 | void sendPageFooter(ClientSocket* sock); |
| jakeb | 15:688b3e3958fd | 46 | void sendCommandTable(ClientSocket* sock); |
| jakeb | 15:688b3e3958fd | 47 | void sendHTTPRedirect(ClientSocket* sock, char* redirectTo); |
| jakeb | 15:688b3e3958fd | 48 | void redirectToMessage(ClientSocket* sock, char* message, int isErr); |
| jakeb | 15:688b3e3958fd | 49 | void renderMessageBox(ClientSocket* sock, char* message, int isBadMessage); |
| jakeb | 15:688b3e3958fd | 50 | int handleAuthorizationHeaders(ClientSocket* sock, char* token); |
| jakeb | 15:688b3e3958fd | 51 | void discardHeaders(ClientSocket* socket); |
| jakeb | 15:688b3e3958fd | 52 | |
| jakeb | 15:688b3e3958fd | 53 | //Helper functions |
| jakeb | 15:688b3e3958fd | 54 | int findOccuranceOfChar(char* buffer, char c, int occur, int max_length); |
| jakeb | 15:688b3e3958fd | 55 | char *url_encode(char *str); |
| jakeb | 15:688b3e3958fd | 56 | char *url_decode(char *str); |
| jakeb | 15:688b3e3958fd | 57 | |
| jakeb | 15:688b3e3958fd | 58 | // Network server task: setup and listen for network connections |
| jakeb | 15:688b3e3958fd | 59 | void http_server_task(void const*) { |
| jakeb | 15:688b3e3958fd | 60 | //This thread assumes that the EthernetInterface is connected with an IP address. |
| jakeb | 15:688b3e3958fd | 61 | static int HTTPD_PORT = 80; |
| jakeb | 15:688b3e3958fd | 62 | |
| jakeb | 15:688b3e3958fd | 63 | //Use SimpleSocket library to setup a server socket |
| jakeb | 15:688b3e3958fd | 64 | DISPLAY("Setting up httpd on %d\r\n", HTTPD_PORT); |
| jakeb | 15:688b3e3958fd | 65 | ServerSocket serverSocket(HTTPD_PORT); |
| jakeb | 15:688b3e3958fd | 66 | |
| jakeb | 15:688b3e3958fd | 67 | while (1) { |
| jakeb | 15:688b3e3958fd | 68 | //Debug: DISPLAY("Waiting for connection\r\n"); |
| jakeb | 15:688b3e3958fd | 69 | //Single-threaded: get the next incoming connection |
| jakeb | 15:688b3e3958fd | 70 | ClientSocket socket = serverSocket.accept(); |
| jakeb | 15:688b3e3958fd | 71 | |
| jakeb | 15:688b3e3958fd | 72 | retryAuth: |
| jakeb | 15:688b3e3958fd | 73 | //Get the request line (the first line) of the HTTP request |
| jakeb | 15:688b3e3958fd | 74 | char linebuffer[HTTP_LINE_BUFFER_SIZE]; |
| jakeb | 15:688b3e3958fd | 75 | memset(linebuffer, 0, HTTP_LINE_BUFFER_SIZE); |
| jakeb | 15:688b3e3958fd | 76 | int length = readline(&socket, linebuffer, HTTP_LINE_BUFFER_SIZE); |
| jakeb | 15:688b3e3958fd | 77 | |
| jakeb | 15:688b3e3958fd | 78 | //debug: printf("%s: '%s'\r\n", socket.get_address(), linebuffer); |
| jakeb | 15:688b3e3958fd | 79 | |
| jakeb | 15:688b3e3958fd | 80 | // Make sure its a GET (we only support GET currently) |
| jakeb | 15:688b3e3958fd | 81 | if (memcmp(linebuffer, "GET ", 4) != 0) { |
| jakeb | 15:688b3e3958fd | 82 | printf("Request Line: '%s'\r\n", linebuffer); |
| jakeb | 15:688b3e3958fd | 83 | DISPLAY("SENDING 400 Bad Request\r\n"); |
| jakeb | 15:688b3e3958fd | 84 | sendHTTPHeader(&socket, 400, "Bad Request", "text/html"); |
| jakeb | 15:688b3e3958fd | 85 | sendPageHeader(&socket); |
| jakeb | 15:688b3e3958fd | 86 | socket.printf("400 Bad Request"); |
| jakeb | 15:688b3e3958fd | 87 | sendPageFooter(&socket); |
| jakeb | 15:688b3e3958fd | 88 | socket.close(); |
| jakeb | 15:688b3e3958fd | 89 | continue; |
| jakeb | 15:688b3e3958fd | 90 | } |
| jakeb | 15:688b3e3958fd | 91 | |
| jakeb | 15:688b3e3958fd | 92 | // If we have a password stored in flash, then check to see if we're authorized. |
| jakeb | 15:688b3e3958fd | 93 | char* token = getAuthenticationToken(); |
| jakeb | 15:688b3e3958fd | 94 | if (token != NULL) { |
| jakeb | 15:688b3e3958fd | 95 | //Process the headers, look for an authoirzation header and discard the rest. |
| jakeb | 15:688b3e3958fd | 96 | char* base64token=base64(token); |
| jakeb | 15:688b3e3958fd | 97 | int authState = handleAuthorizationHeaders(&socket, base64token); |
| jakeb | 15:688b3e3958fd | 98 | free(base64token); |
| jakeb | 15:688b3e3958fd | 99 | discardHeaders(&socket); |
| jakeb | 15:688b3e3958fd | 100 | |
| jakeb | 15:688b3e3958fd | 101 | // If authentication was not good, then send out the 401 |
| jakeb | 15:688b3e3958fd | 102 | if (!authState) { |
| jakeb | 15:688b3e3958fd | 103 | //Auth failed or not present. |
| jakeb | 15:688b3e3958fd | 104 | socket.printf("HTTP/1.1 %d %s\r\n", 401, "Not Authorized"); |
| jakeb | 15:688b3e3958fd | 105 | socket.printf("WWW-Authenticate: Basic realm=\"makerbot_server\"\r\n"); |
| jakeb | 15:688b3e3958fd | 106 | socket.printf("\r\n"); |
| jakeb | 15:688b3e3958fd | 107 | Thread::yield(); |
| jakeb | 15:688b3e3958fd | 108 | goto retryAuth; //TODO: goto's bad, but I'm lazy. Can probably be refactored somehow |
| jakeb | 15:688b3e3958fd | 109 | } else { |
| jakeb | 15:688b3e3958fd | 110 | //Authentication OK |
| jakeb | 15:688b3e3958fd | 111 | } |
| jakeb | 15:688b3e3958fd | 112 | } else { |
| jakeb | 15:688b3e3958fd | 113 | //A password wasn't set in flash, we don't need the rest of the headers, discard |
| jakeb | 15:688b3e3958fd | 114 | discardHeaders(&socket); |
| jakeb | 15:688b3e3958fd | 115 | } |
| jakeb | 15:688b3e3958fd | 116 | |
| jakeb | 15:688b3e3958fd | 117 | //Find the path section of the request line. |
| jakeb | 15:688b3e3958fd | 118 | int endOfPath = findOccuranceOfChar(linebuffer, ' ', 2, HTTP_LINE_BUFFER_SIZE); |
| jakeb | 15:688b3e3958fd | 119 | int lengthOfPath = endOfPath - 4; |
| jakeb | 15:688b3e3958fd | 120 | char* path = (char*)malloc(lengthOfPath+1); |
| jakeb | 15:688b3e3958fd | 121 | strncpy(path, linebuffer+4, lengthOfPath); |
| jakeb | 15:688b3e3958fd | 122 | path[lengthOfPath]=0; |
| jakeb | 15:688b3e3958fd | 123 | |
| jakeb | 15:688b3e3958fd | 124 | //debugging: DISPLAY("PATH (%d): '%s'\r\n", lengthOfPath, path); |
| jakeb | 15:688b3e3958fd | 125 | |
| jakeb | 15:688b3e3958fd | 126 | // Process the given path and return the page |
| jakeb | 15:688b3e3958fd | 127 | if (lengthOfPath == 1 && path[0] == '/') { |
| jakeb | 15:688b3e3958fd | 128 | //request for the root (default) page |
| jakeb | 15:688b3e3958fd | 129 | sendHTTPHeader(&socket, 200, "OK", "text/html"); |
| jakeb | 15:688b3e3958fd | 130 | sendPageHeader(&socket); |
| jakeb | 15:688b3e3958fd | 131 | sendStatusContent(&socket); |
| jakeb | 15:688b3e3958fd | 132 | sendCommandTable(&socket); |
| jakeb | 15:688b3e3958fd | 133 | sendPageFooter(&socket); |
| jakeb | 15:688b3e3958fd | 134 | } else if (lengthOfPath > 6 && memcmp(path, "/?msg=",6) == 0) { |
| jakeb | 15:688b3e3958fd | 135 | char* message = url_decode(path+6); |
| jakeb | 15:688b3e3958fd | 136 | sendHTTPHeader(&socket, 200, "OK", "text/html"); |
| jakeb | 15:688b3e3958fd | 137 | sendPageHeader(&socket); |
| jakeb | 15:688b3e3958fd | 138 | renderMessageBox(&socket, message, 0); |
| jakeb | 15:688b3e3958fd | 139 | sendStatusContent(&socket); |
| jakeb | 15:688b3e3958fd | 140 | sendCommandTable(&socket); |
| jakeb | 15:688b3e3958fd | 141 | sendPageFooter(&socket); |
| jakeb | 15:688b3e3958fd | 142 | free(message); |
| jakeb | 15:688b3e3958fd | 143 | } else if (lengthOfPath > 6 && memcmp(path, "/?err=",6) == 0) { |
| jakeb | 15:688b3e3958fd | 144 | char* message = url_decode(path+6); |
| jakeb | 15:688b3e3958fd | 145 | sendHTTPHeader(&socket, 200, "OK", "text/html"); |
| jakeb | 15:688b3e3958fd | 146 | sendPageHeader(&socket); |
| jakeb | 15:688b3e3958fd | 147 | renderMessageBox(&socket, message, 1); |
| jakeb | 15:688b3e3958fd | 148 | sendStatusContent(&socket); |
| jakeb | 15:688b3e3958fd | 149 | sendCommandTable(&socket); |
| jakeb | 15:688b3e3958fd | 150 | sendPageFooter(&socket); |
| jakeb | 15:688b3e3958fd | 151 | free(message); |
| jakeb | 15:688b3e3958fd | 152 | } else if (strncmp(path, "/command?", 9) == 0) { |
| jakeb | 15:688b3e3958fd | 153 | //This is a command, figure out which one |
| jakeb | 15:688b3e3958fd | 154 | if (lengthOfPath >= 9+5 && memcmp(path+9, "pause", 5) == 0) { |
| jakeb | 15:688b3e3958fd | 155 | //pause command |
| jakeb | 15:688b3e3958fd | 156 | //GET /command?pause HTTP/1.1 |
| jakeb | 15:688b3e3958fd | 157 | if (bot) { |
| jakeb | 15:688b3e3958fd | 158 | Makerbot::MakerbotBuildState state = bot->getBuildState(); |
| jakeb | 15:688b3e3958fd | 159 | if (state.build_state == Makerbot::BUILD_STATE_ERROR) { |
| jakeb | 15:688b3e3958fd | 160 | redirectToMessage(&socket, "Error: unable to determine build state. Sending pause/resume command in the blind.", 1); |
| jakeb | 15:688b3e3958fd | 161 | bot->pauseResume(); |
| jakeb | 15:688b3e3958fd | 162 | } else if (state.build_state == Makerbot::BUILD_RUNNING) { |
| jakeb | 15:688b3e3958fd | 163 | bot->pauseResume(); |
| jakeb | 15:688b3e3958fd | 164 | redirectToMessage(&socket, "Pausing build.", 0); |
| jakeb | 15:688b3e3958fd | 165 | } else { |
| jakeb | 15:688b3e3958fd | 166 | redirectToMessage(&socket, "Error: Not currently running. Cannot pause.", 1); |
| jakeb | 15:688b3e3958fd | 167 | } |
| jakeb | 15:688b3e3958fd | 168 | } else { |
| jakeb | 15:688b3e3958fd | 169 | redirectToMessage(&socket, "Error: connection to Makerbot not established.", 1); |
| jakeb | 15:688b3e3958fd | 170 | } |
| jakeb | 15:688b3e3958fd | 171 | } else if (lengthOfPath >= 9+6 && memcmp(path+9, "cancel", 6) == 0) { |
| jakeb | 15:688b3e3958fd | 172 | if (bot) { |
| jakeb | 15:688b3e3958fd | 173 | bot->abortImmediately(); |
| jakeb | 15:688b3e3958fd | 174 | redirectToMessage(&socket, "Cancelling build.", 0); |
| jakeb | 15:688b3e3958fd | 175 | } else { |
| jakeb | 15:688b3e3958fd | 176 | redirectToMessage(&socket, "Error: connection to Makerbot not established.", 1); |
| jakeb | 15:688b3e3958fd | 177 | } |
| jakeb | 15:688b3e3958fd | 178 | } else if (lengthOfPath >= 9+6 && memcmp(path+9, "resume", 6) == 0) { |
| jakeb | 15:688b3e3958fd | 179 | if (bot) { |
| jakeb | 15:688b3e3958fd | 180 | Makerbot::MakerbotBuildState state = bot->getBuildState(); |
| jakeb | 15:688b3e3958fd | 181 | if (state.build_state == Makerbot::BUILD_STATE_ERROR) { |
| jakeb | 15:688b3e3958fd | 182 | redirectToMessage(&socket, "Error: unable to determine build state. Sending pause/resume command in the blind.", 1); |
| jakeb | 15:688b3e3958fd | 183 | bot->pauseResume(); |
| jakeb | 15:688b3e3958fd | 184 | } else if (state.build_state == Makerbot::BUILD_PAUSED) { |
| jakeb | 15:688b3e3958fd | 185 | bot->pauseResume(); |
| jakeb | 15:688b3e3958fd | 186 | redirectToMessage(&socket, "Resuming build.", 0); |
| jakeb | 15:688b3e3958fd | 187 | } else { |
| jakeb | 15:688b3e3958fd | 188 | redirectToMessage(&socket, "Error: Not currently paused. Cannot resume.", 1); |
| jakeb | 15:688b3e3958fd | 189 | } |
| jakeb | 15:688b3e3958fd | 190 | } else { |
| jakeb | 15:688b3e3958fd | 191 | redirectToMessage(&socket, "Error: connection to Makerbot not established.", 1); |
| jakeb | 15:688b3e3958fd | 192 | } |
| jakeb | 15:688b3e3958fd | 193 | } else if (lengthOfPath >= 9+6 && memcmp(path + 9, "build=", 6) == 0) { |
| jakeb | 15:688b3e3958fd | 194 | int startOfFilename = findOccuranceOfChar(path, '=', 1, lengthOfPath)+1; |
| jakeb | 15:688b3e3958fd | 195 | int lengthOfFileName = lengthOfPath - startOfFilename; |
| jakeb | 15:688b3e3958fd | 196 | char* filename = path+startOfFilename; |
| jakeb | 15:688b3e3958fd | 197 | if (lengthOfFileName < 5) { |
| jakeb | 15:688b3e3958fd | 198 | //Minimum filename is of the form "x.x3g" = 5 characters |
| jakeb | 15:688b3e3958fd | 199 | redirectToMessage(&socket, "Filename too short", 1); |
| jakeb | 15:688b3e3958fd | 200 | } else if (lengthOfFileName > 12) { |
| jakeb | 15:688b3e3958fd | 201 | //According to the s3g spec, only 12-character FN's supported |
| jakeb | 15:688b3e3958fd | 202 | redirectToMessage(&socket, "Filename too long", 1); |
| jakeb | 15:688b3e3958fd | 203 | } else if (memcmp(filename+lengthOfFileName-4, ".X3G", 4)==0 || |
| jakeb | 15:688b3e3958fd | 204 | memcmp(filename+lengthOfFileName-4, ".x3g", 4)==0) { |
| jakeb | 15:688b3e3958fd | 205 | //Send command to bot to print |
| jakeb | 15:688b3e3958fd | 206 | if (bot) { |
| jakeb | 15:688b3e3958fd | 207 | bot->playbackCapture(filename); |
| jakeb | 15:688b3e3958fd | 208 | redirectToMessage(&socket, "Beginning build", 0); |
| jakeb | 15:688b3e3958fd | 209 | } else { |
| jakeb | 15:688b3e3958fd | 210 | redirectToMessage(&socket, "Error: connection to Makerbot not established.", 1); |
| jakeb | 15:688b3e3958fd | 211 | } |
| jakeb | 15:688b3e3958fd | 212 | } else { |
| jakeb | 15:688b3e3958fd | 213 | redirectToMessage(&socket, "Error: filename doesn't end with X3G.", 1); |
| jakeb | 15:688b3e3958fd | 214 | } |
| jakeb | 15:688b3e3958fd | 215 | } else { |
| jakeb | 15:688b3e3958fd | 216 | redirectToMessage(&socket, "Unknown command", 1); |
| jakeb | 15:688b3e3958fd | 217 | } |
| jakeb | 15:688b3e3958fd | 218 | } else { |
| jakeb | 15:688b3e3958fd | 219 | sendHTTPHeader(&socket, 404, "Not Found", "text/html"); |
| jakeb | 15:688b3e3958fd | 220 | sendPageHeader(&socket); |
| jakeb | 15:688b3e3958fd | 221 | socket.printf("404 Not Found"); |
| jakeb | 15:688b3e3958fd | 222 | sendPageFooter(&socket); |
| jakeb | 15:688b3e3958fd | 223 | } |
| jakeb | 15:688b3e3958fd | 224 | |
| jakeb | 15:688b3e3958fd | 225 | //DISPLAY("Closing socket\r\n"); |
| jakeb | 15:688b3e3958fd | 226 | socket.close(); |
| jakeb | 15:688b3e3958fd | 227 | free(path); |
| jakeb | 15:688b3e3958fd | 228 | } |
| jakeb | 15:688b3e3958fd | 229 | |
| jakeb | 15:688b3e3958fd | 230 | // never gets here |
| jakeb | 15:688b3e3958fd | 231 | //EthernetInterface::disconnect(); |
| jakeb | 15:688b3e3958fd | 232 | } |
| jakeb | 15:688b3e3958fd | 233 | |
| jakeb | 15:688b3e3958fd | 234 | int findOccuranceOfChar(char* buffer, char c, int occur, int max_length) { |
| jakeb | 15:688b3e3958fd | 235 | int cnt = 0; |
| jakeb | 15:688b3e3958fd | 236 | for (int x=0; x<max_length; x++) { |
| jakeb | 15:688b3e3958fd | 237 | if (buffer[x] == c) { |
| jakeb | 15:688b3e3958fd | 238 | cnt++; |
| jakeb | 15:688b3e3958fd | 239 | if (cnt == occur) { |
| jakeb | 15:688b3e3958fd | 240 | return x; |
| jakeb | 15:688b3e3958fd | 241 | } |
| jakeb | 15:688b3e3958fd | 242 | } |
| jakeb | 15:688b3e3958fd | 243 | } |
| jakeb | 15:688b3e3958fd | 244 | return -1; |
| jakeb | 15:688b3e3958fd | 245 | } |
| jakeb | 15:688b3e3958fd | 246 | |
| jakeb | 15:688b3e3958fd | 247 | void sendPageHeader(ClientSocket* sock) { |
| jakeb | 15:688b3e3958fd | 248 | sock->printf("<html>"); |
| jakeb | 15:688b3e3958fd | 249 | sock->printf("<head><style>"); |
| jakeb | 15:688b3e3958fd | 250 | sock->printf("body {font-family:Helvetica, Arial, sans-serif}"); |
| jakeb | 15:688b3e3958fd | 251 | sock->printf("table { border:1px solid #000; border-collapse:collapse;}"); |
| jakeb | 15:688b3e3958fd | 252 | sock->printf("td {border: 1px solid}"); |
| jakeb | 15:688b3e3958fd | 253 | sock->printf("tr { vertical-align:middle; }"); |
| jakeb | 15:688b3e3958fd | 254 | sock->printf("td.icon { text-align:center; padding:5px; }"); |
| jakeb | 15:688b3e3958fd | 255 | sock->printf("a {color: #000000; text-decoration: none; }"); |
| jakeb | 15:688b3e3958fd | 256 | sock->printf("</style>"); |
| jakeb | 15:688b3e3958fd | 257 | sock->printf("<body>"); |
| jakeb | 15:688b3e3958fd | 258 | } |
| jakeb | 15:688b3e3958fd | 259 | |
| jakeb | 15:688b3e3958fd | 260 | void sendStatusContent(ClientSocket* sock) { |
| jakeb | 15:688b3e3958fd | 261 | if (bot) { |
| jakeb | 15:688b3e3958fd | 262 | Makerbot::MakerbotBuildState state = bot->getBuildState(); |
| jakeb | 15:688b3e3958fd | 263 | sock->printf("<H1>Build Status</H1>"); |
| jakeb | 15:688b3e3958fd | 264 | sock->printf("Machine Name: %s<br>", bot->getMachineName()); |
| jakeb | 15:688b3e3958fd | 265 | sock->printf("Bot firmware version: %0.1f<br><br>", bot->getMakerbotVersion()); |
| jakeb | 15:688b3e3958fd | 266 | sock->printf("<table width=350>"); |
| jakeb | 15:688b3e3958fd | 267 | sock->printf("<tr><td>Build state</td><td>%s</td></tr>", Makerbot::humanReadableBuildState(state)); |
| jakeb | 15:688b3e3958fd | 268 | sock->printf("<tr><Td>Build file name</td><td>%s</td></tr>", bot->getBuildName()); |
| jakeb | 15:688b3e3958fd | 269 | sock->printf("<tr><td>Elapsed Build Time</td><td>%d:%02d</td></tr>", state.hours, state.minutes); |
| jakeb | 15:688b3e3958fd | 270 | sock->printf("<tr><td>Line Number</td><td>%d</td></tr>", state.line); |
| jakeb | 15:688b3e3958fd | 271 | sock->printf("</table>"); |
| jakeb | 15:688b3e3958fd | 272 | |
| jakeb | 15:688b3e3958fd | 273 | sock->printf("<H1>Temperatures</H1>"); |
| jakeb | 15:688b3e3958fd | 274 | sock->printf("<table width=350>"); |
| jakeb | 15:688b3e3958fd | 275 | for (int x=0; x<bot->getToolCount(); x++) { |
| jakeb | 15:688b3e3958fd | 276 | sock->printf("<tr><td>Tool %d</td><td>%d°C</td><td>%d°C</td></tr>", x, bot->getToolTemperature(x), bot->getToolSetPoint(x)); |
| jakeb | 15:688b3e3958fd | 277 | } |
| jakeb | 15:688b3e3958fd | 278 | if (bot->hasPlatform()) { |
| jakeb | 15:688b3e3958fd | 279 | sock->printf("<tr><td>Platform</td><td>%d°C</td><td>%d°C</td></tr>", bot->getPlatformTemperature(0), bot->getPlatformSetPoint(0)); |
| jakeb | 15:688b3e3958fd | 280 | } |
| jakeb | 15:688b3e3958fd | 281 | sock->printf("</table>"); |
| jakeb | 15:688b3e3958fd | 282 | } else { |
| jakeb | 15:688b3e3958fd | 283 | sock->printf("<H1>No connection to Makerbot</H1>"); |
| jakeb | 15:688b3e3958fd | 284 | } |
| jakeb | 15:688b3e3958fd | 285 | } |
| jakeb | 15:688b3e3958fd | 286 | |
| jakeb | 15:688b3e3958fd | 287 | void sendHTTPHeader(ClientSocket* sock, int response_code, char* response_descr, char* contentType) { |
| jakeb | 15:688b3e3958fd | 288 | sock->printf("HTTP/1.1 %d %s\r\n", response_code, response_descr); |
| jakeb | 15:688b3e3958fd | 289 | if (contentType != NULL) |
| jakeb | 15:688b3e3958fd | 290 | sock->printf("Content-Type: %s\r\n", contentType); |
| jakeb | 15:688b3e3958fd | 291 | sock->printf("\r\n"); |
| jakeb | 15:688b3e3958fd | 292 | } |
| jakeb | 15:688b3e3958fd | 293 | |
| jakeb | 15:688b3e3958fd | 294 | void sendHTTPRedirect(ClientSocket* sock, char* redirectTo) { |
| jakeb | 15:688b3e3958fd | 295 | sock->printf("HTTP/1.1 %d %s\r\n", 302, "Found"); |
| jakeb | 15:688b3e3958fd | 296 | sock->printf("Location: %s\r\n", redirectTo); |
| jakeb | 15:688b3e3958fd | 297 | sock->printf("\r\n"); |
| jakeb | 15:688b3e3958fd | 298 | } |
| jakeb | 15:688b3e3958fd | 299 | |
| jakeb | 15:688b3e3958fd | 300 | void redirectToMessage(ClientSocket* sock, char* message, int isErr) { |
| jakeb | 15:688b3e3958fd | 301 | sock->printf("HTTP/1.1 %d %s\r\n", 302, "Found"); |
| jakeb | 15:688b3e3958fd | 302 | char* escapedMsg = url_encode(message); |
| jakeb | 15:688b3e3958fd | 303 | sock->printf("Location: /?%s=%s\r\n", isErr?"err":"msg", escapedMsg); |
| jakeb | 15:688b3e3958fd | 304 | free(escapedMsg); |
| jakeb | 15:688b3e3958fd | 305 | sock->printf("\r\n"); |
| jakeb | 15:688b3e3958fd | 306 | } |
| jakeb | 15:688b3e3958fd | 307 | |
| jakeb | 15:688b3e3958fd | 308 | void renderMessageBox(ClientSocket* sock, char* message, int isBadMessage) { |
| jakeb | 15:688b3e3958fd | 309 | if (isBadMessage) |
| jakeb | 15:688b3e3958fd | 310 | sock->printf("<table><tr><td style='padding: 15px; background: #FF9999'>"); |
| jakeb | 15:688b3e3958fd | 311 | else |
| jakeb | 15:688b3e3958fd | 312 | sock->printf("<table><tr><td style='padding: 15px; background: #99FF99'>"); |
| jakeb | 15:688b3e3958fd | 313 | sock->printf("%s", message); |
| jakeb | 15:688b3e3958fd | 314 | sock->printf("</td><tr></table>"); |
| jakeb | 15:688b3e3958fd | 315 | |
| jakeb | 15:688b3e3958fd | 316 | } |
| jakeb | 15:688b3e3958fd | 317 | |
| jakeb | 15:688b3e3958fd | 318 | |
| jakeb | 15:688b3e3958fd | 319 | void sendCommandTable(ClientSocket* sock) { |
| jakeb | 15:688b3e3958fd | 320 | if (bot) { |
| jakeb | 15:688b3e3958fd | 321 | Makerbot::MakerbotBuildState state = bot->getBuildState(); |
| jakeb | 15:688b3e3958fd | 322 | sock->printf("<H1>Commands</H1>"); |
| jakeb | 15:688b3e3958fd | 323 | sock->printf("<table width=500>"); |
| jakeb | 15:688b3e3958fd | 324 | if (state.build_state == Makerbot::BUILD_RUNNING || |
| jakeb | 15:688b3e3958fd | 325 | state.build_state == Makerbot::BUILD_PAUSED) { |
| jakeb | 15:688b3e3958fd | 326 | sock->printf("<tr><td class=icon><a href='/command?cancel'>█</a></td><td><a href='/command?cancel'>Cancel</a></td><td>Halt build immediately (does not clear build plate)</td></tr>"); |
| jakeb | 15:688b3e3958fd | 327 | } |
| jakeb | 15:688b3e3958fd | 328 | if (state.build_state == Makerbot::BUILD_RUNNING) { |
| jakeb | 15:688b3e3958fd | 329 | sock->printf("<tr><td class=icon><a href='/command?pause'>❚ ❚</a></td><td><a hred='/command?pause'>Pause</a></td><td>Pause build and clear build area</td></tr>"); |
| jakeb | 15:688b3e3958fd | 330 | } |
| jakeb | 15:688b3e3958fd | 331 | if (state.build_state == Makerbot::BUILD_PAUSED) { |
| jakeb | 15:688b3e3958fd | 332 | sock->printf("<tr><td class=icon><a href='/command?resume'>►</a></td><td><a href='/command?resume'>Resume</a></td><td>Resume from pause</td></tr>"); |
| jakeb | 15:688b3e3958fd | 333 | } |
| jakeb | 15:688b3e3958fd | 334 | if (state.build_state == Makerbot::NO_BUILD || |
| jakeb | 15:688b3e3958fd | 335 | state.build_state == Makerbot::BUILD_FINISHED_NORMALLY || |
| jakeb | 15:688b3e3958fd | 336 | state.build_state == Makerbot::BUILD_CANCELLED || |
| jakeb | 15:688b3e3958fd | 337 | state.build_state == Makerbot::BUILD_SLEEPING) { |
| jakeb | 15:688b3e3958fd | 338 | sock->printf("<tr><td class=icon>►</td><td>Play</TD><TD><form action='/command' method=get><input length=12 maxlength=12 name=build><input type=submit></form></td></tr>"); |
| jakeb | 15:688b3e3958fd | 339 | } |
| jakeb | 15:688b3e3958fd | 340 | sock->printf("</table>"); |
| jakeb | 15:688b3e3958fd | 341 | } |
| jakeb | 15:688b3e3958fd | 342 | } |
| jakeb | 15:688b3e3958fd | 343 | |
| jakeb | 15:688b3e3958fd | 344 | void sendPageFooter(ClientSocket* sock) { |
| jakeb | 15:688b3e3958fd | 345 | sock->printf("<br><hr><br>Server firmware version: %s<br>", FIRMWARE_VERSION); |
| jakeb | 15:688b3e3958fd | 346 | time_t ctTime = time(NULL); |
| jakeb | 15:688b3e3958fd | 347 | sock->printf("Page generated at %s\r\n", ctime(&ctTime)); |
| jakeb | 15:688b3e3958fd | 348 | sock->printf("</body>"); |
| jakeb | 15:688b3e3958fd | 349 | sock->printf("</html>"); |
| jakeb | 15:688b3e3958fd | 350 | } |
| jakeb | 15:688b3e3958fd | 351 | |
| jakeb | 15:688b3e3958fd | 352 | // Public domain code from http://www.geekhideout.com/urlcode.shtml |
| jakeb | 15:688b3e3958fd | 353 | // To the extent possible under law, Fred Bulback has waived all copyright and related or neighboring rights to URL Encoding/Decoding in C. This work is published from: United States. |
| jakeb | 15:688b3e3958fd | 354 | bool isalnum(char c) { |
| jakeb | 15:688b3e3958fd | 355 | if (c >= 48 && c <= 57) return 1; //numbers |
| jakeb | 15:688b3e3958fd | 356 | if (c >= 65 && c <= 90) return 1; //upercase |
| jakeb | 15:688b3e3958fd | 357 | if (c >= 97 && c <= 122) return 1; //lowercase |
| jakeb | 15:688b3e3958fd | 358 | return 0; |
| jakeb | 15:688b3e3958fd | 359 | } |
| jakeb | 15:688b3e3958fd | 360 | |
| jakeb | 15:688b3e3958fd | 361 | bool isdigit(char c) { |
| jakeb | 15:688b3e3958fd | 362 | if (c >= 48 && c <= 57) return 1; //numbers |
| jakeb | 15:688b3e3958fd | 363 | return 0; |
| jakeb | 15:688b3e3958fd | 364 | } |
| jakeb | 15:688b3e3958fd | 365 | |
| jakeb | 15:688b3e3958fd | 366 | char tolower(char c) { |
| jakeb | 15:688b3e3958fd | 367 | if (c >= 65 && c <= 90) { |
| jakeb | 15:688b3e3958fd | 368 | return c+32; |
| jakeb | 15:688b3e3958fd | 369 | } else { |
| jakeb | 15:688b3e3958fd | 370 | return c; |
| jakeb | 15:688b3e3958fd | 371 | } |
| jakeb | 15:688b3e3958fd | 372 | } |
| jakeb | 15:688b3e3958fd | 373 | |
| jakeb | 15:688b3e3958fd | 374 | |
| jakeb | 15:688b3e3958fd | 375 | /* Converts a hex character to its integer value */ |
| jakeb | 15:688b3e3958fd | 376 | char from_hex(char ch) { |
| jakeb | 15:688b3e3958fd | 377 | return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10; |
| jakeb | 15:688b3e3958fd | 378 | } |
| jakeb | 15:688b3e3958fd | 379 | |
| jakeb | 15:688b3e3958fd | 380 | /* Converts an integer value to its hex character*/ |
| jakeb | 15:688b3e3958fd | 381 | char to_hex(char code) { |
| jakeb | 15:688b3e3958fd | 382 | static char hex[] = "0123456789abcdef"; |
| jakeb | 15:688b3e3958fd | 383 | return hex[code & 15]; |
| jakeb | 15:688b3e3958fd | 384 | } |
| jakeb | 15:688b3e3958fd | 385 | |
| jakeb | 15:688b3e3958fd | 386 | /* Returns a url-encoded version of str */ |
| jakeb | 15:688b3e3958fd | 387 | /* IMPORTANT: be sure to free() the returned string after use */ |
| jakeb | 15:688b3e3958fd | 388 | char *url_encode(char *str) { |
| jakeb | 15:688b3e3958fd | 389 | char *pstr = str, *buf = (char*)malloc(strlen(str) * 3 + 1), *pbuf = buf; |
| jakeb | 15:688b3e3958fd | 390 | while (*pstr) { |
| jakeb | 15:688b3e3958fd | 391 | if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~') |
| jakeb | 15:688b3e3958fd | 392 | *pbuf++ = *pstr; |
| jakeb | 15:688b3e3958fd | 393 | else if (*pstr == ' ') |
| jakeb | 15:688b3e3958fd | 394 | *pbuf++ = '+'; |
| jakeb | 15:688b3e3958fd | 395 | else |
| jakeb | 15:688b3e3958fd | 396 | *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15); |
| jakeb | 15:688b3e3958fd | 397 | pstr++; |
| jakeb | 15:688b3e3958fd | 398 | } |
| jakeb | 15:688b3e3958fd | 399 | *pbuf = '\0'; |
| jakeb | 15:688b3e3958fd | 400 | return buf; |
| jakeb | 15:688b3e3958fd | 401 | } |
| jakeb | 15:688b3e3958fd | 402 | |
| jakeb | 15:688b3e3958fd | 403 | /* Returns a url-decoded version of str */ |
| jakeb | 15:688b3e3958fd | 404 | /* IMPORTANT: be sure to free() the returned string after use */ |
| jakeb | 15:688b3e3958fd | 405 | char *url_decode(char *str) { |
| jakeb | 15:688b3e3958fd | 406 | char *pstr = str, *buf = (char*)malloc(strlen(str) + 1), *pbuf = buf; |
| jakeb | 15:688b3e3958fd | 407 | while (*pstr) { |
| jakeb | 15:688b3e3958fd | 408 | if (*pstr == '%') { |
| jakeb | 15:688b3e3958fd | 409 | if (pstr[1] && pstr[2]) { |
| jakeb | 15:688b3e3958fd | 410 | *pbuf++ = from_hex(pstr[1]) << 4 | from_hex(pstr[2]); |
| jakeb | 15:688b3e3958fd | 411 | pstr += 2; |
| jakeb | 15:688b3e3958fd | 412 | } |
| jakeb | 15:688b3e3958fd | 413 | } else if (*pstr == '+') { |
| jakeb | 15:688b3e3958fd | 414 | *pbuf++ = ' '; |
| jakeb | 15:688b3e3958fd | 415 | } else { |
| jakeb | 15:688b3e3958fd | 416 | *pbuf++ = *pstr; |
| jakeb | 15:688b3e3958fd | 417 | } |
| jakeb | 15:688b3e3958fd | 418 | pstr++; |
| jakeb | 15:688b3e3958fd | 419 | } |
| jakeb | 15:688b3e3958fd | 420 | *pbuf = '\0'; |
| jakeb | 15:688b3e3958fd | 421 | return buf; |
| jakeb | 15:688b3e3958fd | 422 | } |
| jakeb | 15:688b3e3958fd | 423 | |
| jakeb | 15:688b3e3958fd | 424 | // Search through the HTTP headers and if there is an authentication line, process it |
| jakeb | 15:688b3e3958fd | 425 | // Discard headers that are not relevant. |
| jakeb | 15:688b3e3958fd | 426 | int handleAuthorizationHeaders(ClientSocket* socket, char* desiredToken) { |
| jakeb | 15:688b3e3958fd | 427 | char line [HTTP_LINE_BUFFER_SIZE]; |
| jakeb | 15:688b3e3958fd | 428 | int length=0; |
| jakeb | 15:688b3e3958fd | 429 | char* authToken = NULL; |
| jakeb | 15:688b3e3958fd | 430 | while ((length = readline(socket, line, HTTP_LINE_BUFFER_SIZE)) > 0) { |
| jakeb | 15:688b3e3958fd | 431 | if (memcmp(line, "Authorization: Basic ", 21) == 0) { |
| jakeb | 15:688b3e3958fd | 432 | //we found the authorization line. |
| jakeb | 15:688b3e3958fd | 433 | authToken = (char*)malloc(length - 21 + 1); |
| jakeb | 15:688b3e3958fd | 434 | memcpy(authToken, line+21, length-21); |
| jakeb | 15:688b3e3958fd | 435 | authToken[length-21] = 0; //terminator |
| jakeb | 15:688b3e3958fd | 436 | break; |
| jakeb | 15:688b3e3958fd | 437 | } else { |
| jakeb | 15:688b3e3958fd | 438 | //nothing |
| jakeb | 15:688b3e3958fd | 439 | } |
| jakeb | 15:688b3e3958fd | 440 | Thread::yield(); |
| jakeb | 15:688b3e3958fd | 441 | } |
| jakeb | 15:688b3e3958fd | 442 | |
| jakeb | 15:688b3e3958fd | 443 | if (authToken != NULL) { |
| jakeb | 15:688b3e3958fd | 444 | int match = memcmp(desiredToken, authToken, strlen(authToken)); |
| jakeb | 15:688b3e3958fd | 445 | free(authToken); |
| jakeb | 15:688b3e3958fd | 446 | if (match == 0) return 1; |
| jakeb | 15:688b3e3958fd | 447 | } |
| jakeb | 15:688b3e3958fd | 448 | |
| jakeb | 15:688b3e3958fd | 449 | return 0; |
| jakeb | 15:688b3e3958fd | 450 | } |
| jakeb | 15:688b3e3958fd | 451 | |
| jakeb | 15:688b3e3958fd | 452 | // Discard line by line until a blank line |
| jakeb | 15:688b3e3958fd | 453 | // (the separator between the header and the body of the HTTP request) |
| jakeb | 15:688b3e3958fd | 454 | void discardHeaders(ClientSocket* socket) { |
| jakeb | 15:688b3e3958fd | 455 | if (!socket->available()) { |
| jakeb | 15:688b3e3958fd | 456 | //printf("nothing available, so not doing anything about the headers\r\n"); |
| jakeb | 15:688b3e3958fd | 457 | return; |
| jakeb | 15:688b3e3958fd | 458 | } |
| jakeb | 15:688b3e3958fd | 459 | |
| jakeb | 15:688b3e3958fd | 460 | char line [HTTP_LINE_BUFFER_SIZE]; |
| jakeb | 15:688b3e3958fd | 461 | while (readline(socket, line, HTTP_LINE_BUFFER_SIZE) > 0) { |
| jakeb | 15:688b3e3958fd | 462 | Thread::yield(); |
| jakeb | 15:688b3e3958fd | 463 | } |
| jakeb | 15:688b3e3958fd | 464 | } |
| jakeb | 15:688b3e3958fd | 465 | |
| jakeb | 15:688b3e3958fd | 466 | // Return the base64 representation of the input |
| jakeb | 15:688b3e3958fd | 467 | // IMPORTANT: caller must call free() on the result when done with it. |
| jakeb | 15:688b3e3958fd | 468 | char* base64(char* input) { |
| jakeb | 15:688b3e3958fd | 469 | int inputLen = strlen(input); |
| jakeb | 15:688b3e3958fd | 470 | int padLength = (3-(inputLen % 3))%3; |
| jakeb | 15:688b3e3958fd | 471 | |
| jakeb | 15:688b3e3958fd | 472 | int outNdx=0; |
| jakeb | 15:688b3e3958fd | 473 | char* output = (char*)malloc(((4*inputLen)/3)+1); |
| jakeb | 15:688b3e3958fd | 474 | |
| jakeb | 15:688b3e3958fd | 475 | for (int i=0; i < inputLen; i+=3) { |
| jakeb | 15:688b3e3958fd | 476 | //Assemble the 24 bit number |
| jakeb | 15:688b3e3958fd | 477 | uint32_t a = input[i] << 16; |
| jakeb | 15:688b3e3958fd | 478 | if (i+1 < inputLen) a += input[i+1] << 8; |
| jakeb | 15:688b3e3958fd | 479 | if (i+2 < inputLen) a += input[i+2]; |
| jakeb | 15:688b3e3958fd | 480 | |
| jakeb | 15:688b3e3958fd | 481 | //Split the 24 bit number into 4 parts |
| jakeb | 15:688b3e3958fd | 482 | uint8_t p1 = (uint8_t)(a >> 18) & 0x3F; |
| jakeb | 15:688b3e3958fd | 483 | uint8_t p2 = (uint8_t)(a >> 12) & 0x3F; |
| jakeb | 15:688b3e3958fd | 484 | uint8_t p3 = (uint8_t)(a >> 6) & 0x3F; |
| jakeb | 15:688b3e3958fd | 485 | uint8_t p4 = (uint8_t)a & 0x3F; |
| jakeb | 15:688b3e3958fd | 486 | |
| jakeb | 15:688b3e3958fd | 487 | //write the data to the output |
| jakeb | 15:688b3e3958fd | 488 | output[outNdx++] = base64table[p1]; |
| jakeb | 15:688b3e3958fd | 489 | output[outNdx++] = base64table[p2]; |
| jakeb | 15:688b3e3958fd | 490 | output[outNdx++] = base64table[p3]; |
| jakeb | 15:688b3e3958fd | 491 | output[outNdx++] = base64table[p4]; |
| jakeb | 15:688b3e3958fd | 492 | } |
| jakeb | 15:688b3e3958fd | 493 | |
| jakeb | 15:688b3e3958fd | 494 | // Put the padding in at the end |
| jakeb | 15:688b3e3958fd | 495 | for (int i=0; i<padLength; i++) { |
| jakeb | 15:688b3e3958fd | 496 | output[outNdx-i-1]='='; |
| jakeb | 15:688b3e3958fd | 497 | } |
| jakeb | 15:688b3e3958fd | 498 | |
| jakeb | 15:688b3e3958fd | 499 | // Terminating \0 |
| jakeb | 15:688b3e3958fd | 500 | output[outNdx]='\0'; |
| jakeb | 15:688b3e3958fd | 501 | |
| jakeb | 15:688b3e3958fd | 502 | return output; |
| jakeb | 15:688b3e3958fd | 503 | } |
