Bringing WiFi to the Cidco Mailstation
at main 274 lines 6.6 kB view raw
1/* 2 * WiFiStation 3 * Copyright (c) 2021 joshua stein <jcs@jcs.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18#include <ESP8266WebServer.h> 19#include "wifistation.h" 20 21std::unique_ptr<ESP8266WebServer> http = NULL; 22 23static const char html_wrapper[] PROGMEM = R"END(<!doctype html> 24<html> 25<head> 26 <meta http-equiv="content-type" content="text/html; charset=utf-8" /> 27 <title>WiFiStation at %s</title> 28 <style type="text/css"> 29 body { 30 background-color: #fffff8; 31 font-family: sans-serif; 32 font-size: 12pt; 33 padding: 1em; 34 } 35 h3 { 36 margin: 0; 37 } 38 </style> 39</head> 40<body> 41<h3>WiFiStation %s</h3> 42<p> 43%s 44</body> 45</html> 46)END"; 47 48static unsigned int upload_size = 0; 49static unsigned int delivered_bytes = 0; 50 51void 52http_process(void) 53{ 54 if (!settings->http_server) 55 return; 56 57 http->handleClient(); 58} 59 60void 61http_send_result(int status, bool include_home, const char *body, ...) 62{ 63 va_list arg; 64 const char *accept = http->header("Accept").c_str(); 65 int len; 66 char *doc = NULL; 67 68 /* expand body format */ 69 va_start(arg, body); 70 len = vasprintf(&doc, body, arg); 71 va_end(arg); 72 73 if (len == -1) 74 goto fail; 75 76 /* if Accept header starts with text/html, client prefers html */ 77 if (accept && strncmp(accept, "text/html", 9) == 0) { 78 char *tmp; 79 80 /* append home link to body */ 81 if (include_home) { 82 len = asprintf(&tmp, "%s" 83 "<p>" 84 "<a href=\"/\">Home</a>", doc); 85 if (len == -1) 86 goto fail; 87 free(doc); 88 doc = tmp; 89 } 90 91 /* insert body into html template */ 92 len = asprintf(&tmp, html_wrapper, 93 WiFi.localIP().toString().c_str(), WIFISTATION_VERSION, 94 doc); 95 if (len == -1) 96 goto fail; 97 free(doc); 98 doc = tmp; 99 100 http->send(status, "text/html", doc); 101 } else { 102 /* append newline since this is probably going to a terminal */ 103 doc = (char *)realloc(doc, len + 2); 104 doc[len] = '\n'; 105 doc[len + 1] = '\0'; 106 http->send(status, "text/plain", doc); 107 } 108 109 free(doc); 110 return; 111 112fail: 113 if (doc != NULL) 114 free(doc); 115 http->send(500, "text/plain", "out of memory :("); 116 return; 117} 118 119void 120http_setup(void) 121{ 122 if (settings->http_server) { 123 if (http) 124 return; 125 126 http.reset(new ESP8266WebServer(80)); 127 } else { 128 if (http) 129 http = NULL; 130 131 return; 132 } 133 134 http->collectHeaders(F("Accept")); 135 136 http->on("/", HTTP_GET, []() { 137 http_send_result(200, false, R"END( 138<form action="/upload" method="POST" enctype="multipart/form-data"> 139<p> 140Binary to execute on MailStation (<em>maximum size is %d bytes</em>): 141<p> 142<input type="file" name="file" maxlength="%d"> 143<br> 144<p> 145Ensure the WSLoader application is running on the MailStation ready to accept 146the upload. 147<p> 148<input type="submit" value="Upload"> 149</form> 150)END", 151 MAX_UPLOAD_SIZE, MAX_UPLOAD_SIZE); 152 }); 153 154 /* 155 * This measure step is because we don't get the total size of the 156 * upload until we read the whole thing, but we need to send the file 157 * size to the MailStation before sending any data. So to avoid 158 * caching the entire upload, we process the upload and just count the 159 * bytes being passed through, then send a 307 redirect to the actual 160 * upload endpoint with the total size. Browsers follow a 307 with a 161 * POST, so at /upload we'll have the actual size before it sends us 162 * the same data. 163 */ 164 http->on("/upload", HTTP_POST, []() { 165 char tmp[32]; 166 167 if (upload_size == 0) { 168 http_send_result(400, true, 169 "Failed receiving file (size 0)."); 170 } else { 171 snprintf(tmp, sizeof(tmp), "/upload_measured?size=%d", 172 upload_size); 173 http->sendHeader("Location", tmp); 174 http->send(307, "text/plain", "Ok, follow me!"); 175 } 176 }, []() { 177 HTTPUpload& upload = http->upload(); 178 179 switch (upload.status) { 180 case UPLOAD_FILE_START: 181 upload_size = 0; 182 break; 183 case UPLOAD_FILE_WRITE: 184 upload_size += upload.currentSize; 185 break; 186 case UPLOAD_FILE_ABORTED: 187 upload_size = 0; 188 /* FALLTHROUGH */ 189 case UPLOAD_FILE_END: 190 if (upload_size == 0) { 191 http_send_result(400, true, 192 "Failed receiving file (size 0)."); 193 return; 194 } 195 196 if (upload_size > MAX_UPLOAD_SIZE) { 197 http_send_result(400, true, 198 "File upload cannot be larger than %d " 199 "bytes.", MAX_UPLOAD_SIZE); 200 upload_size = 0; 201 return; 202 } 203 204 break; 205 } 206 }); 207 208 http->on("/upload_measured", HTTP_POST, []() { 209 http_send_result(200, true, 210 "Successfully uploaded %d byte%s to MailStation.", 211 delivered_bytes, delivered_bytes == 1 ? "" : "s"); 212 }, []() { 213 HTTPUpload& upload = http->upload(); 214 215 switch (upload.status) { 216 case UPLOAD_FILE_START: 217 delivered_bytes = 0; 218 219 if (!(upload_size = atoi(http->arg("size").c_str()))) { 220 http_send_result(400, true, "No size " 221 "parameter passed. Perhaps your browser " 222 "failed to follow the 307 redirect " 223 "properly."); 224 return; 225 } 226 227 if (upload_size > MAX_UPLOAD_SIZE) { 228 http_send_result(400, true, 229 "File upload cannot be larger than %d " 230 "bytes.", MAX_UPLOAD_SIZE); 231 return; 232 } 233 234 if (ms_write(upload_size & 0xff) != 0 || 235 ms_write((upload_size >> 8) & 0xff) != 0) { 236 http_send_result(400, true, 237 "Failed sending size bytes " 238 "(<tt>0x%x</tt>, <tt>0x%x</tt>) to " 239 "MailStation. Is the WSLoader program " 240 "running?", 241 upload_size & 0xff, 242 (upload_size >> 8) & 0xff); 243 return; 244 } 245 break; 246 case UPLOAD_FILE_WRITE: 247 for (int i = 0; i < (int)upload.currentSize; i++) { 248 delivered_bytes++; 249 if (ms_write(upload.buf[i]) == -1) { 250 http_send_result(400, true, 251 "Failed uploading to MailStation " 252 "at byte %d/%d.", 253 delivered_bytes, upload_size); 254 return; 255 } 256 yield(); 257 delayMicroseconds(500); 258 } 259 break; 260 case UPLOAD_FILE_END: 261 break; 262 case UPLOAD_FILE_ABORTED: 263 http_send_result(400, true, 264 "Aborted upload to MailStation."); 265 return; 266 } 267 }); 268 269 http->onNotFound([]() { 270 http_send_result(404, true, "404"); 271 }); 272 273 http->begin(); 274}