├── GarageDoorOpener ├── .gitignore ├── WebPages.h ├── WebPages.cpp ├── GarageDoorOpener.ino └── Webduino.h ├── Parts ├── EtherTen.blend ├── Sensor Mount.stl ├── Magnet Protector.stl └── EtherTen Enclosure.zip ├── .gitignore ├── README.md ├── LICENSE └── Garage Door Password Manager.mlx /GarageDoorOpener/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /obj/ 3 | *.user 4 | -------------------------------------------------------------------------------- /Parts/EtherTen.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Megunolink/GarageDoorOpener/HEAD/Parts/EtherTen.blend -------------------------------------------------------------------------------- /Parts/Sensor Mount.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Megunolink/GarageDoorOpener/HEAD/Parts/Sensor Mount.stl -------------------------------------------------------------------------------- /Parts/Magnet Protector.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Megunolink/GarageDoorOpener/HEAD/Parts/Magnet Protector.stl -------------------------------------------------------------------------------- /Parts/EtherTen Enclosure.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Megunolink/GarageDoorOpener/HEAD/Parts/EtherTen Enclosure.zip -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | *.dylib 9 | 10 | # Compiled Static libraries 11 | *.lai 12 | *.la 13 | *.a 14 | 15 | # Intellisense file 16 | *.sdf 17 | *.suo 18 | *.opensdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Arduino Garage Door Opener 2 | ========================== 3 | 4 | Arduino program to provide web access control for activating a garage door from a Smart-Phone 5 | 6 | For more information see [http://www.MegunoLink.com/articles/arduino-garage-door-opener/] 7 | 8 | This repository contains 9 | * `Garage Door Password Manager.mlx` [MegunoLink](http://www.MegunoLink.com) project for setting passwords 10 | * `GarageDoorOpener\` Arduino source code 11 | * `Parts` STL files for 3D printed parts 12 | 13 | -------------------------------------------------------------------------------- /GarageDoorOpener/WebPages.h: -------------------------------------------------------------------------------- 1 | /* ************************************************************************************************ 2 | ** Write web page content to the webserver. 3 | ** ************************************************************************************************ */ 4 | 5 | #pragma once 6 | #include 7 | 8 | class WebServer; 9 | 10 | void SendLoginPage(WebServer &rServer); 11 | void SendAccessGrantedPage(WebServer &rServer); 12 | void SendAccessDeniedPage(WebServer &rServer); 13 | void SendErrorPage(WebServer &rServer); 14 | void SendPageNotFound(WebServer &rServer); 15 | void SendStyles(WebServer &rServer); 16 | void SendDoorState(WebServer &rServer, const char *Description); 17 | -------------------------------------------------------------------------------- /GarageDoorOpener/WebPages.cpp: -------------------------------------------------------------------------------- 1 | #include "WebPages.h" 2 | 3 | // Implementation for webserver class is in header. Need WEBDUINO_NO_IMPLEMENTATION defined 4 | // so we don't get multiple instances of it. 5 | #define WEBDUINO_NO_IMPLEMENTATION 6 | #include "Webduino.h" 7 | 8 | void SendHeader(WebServer &rServer) 9 | { 10 | rServer.print(F("")); 11 | rServer.print(F("")); 12 | rServer.print(F("")); 13 | rServer.print(F("")); 14 | rServer.print(F("")); 15 | rServer.print(F("Gateway")); 16 | 17 | // Bootstrap core. 18 | rServer.print(F("")); 19 | 20 | // Bootstrap theme 21 | rServer.print(F("")); 22 | 23 | // Site Styles 24 | rServer.print(F("")); 25 | 26 | // jQuery 27 | rServer.print(F("")); 28 | 29 | // Latest compiled and minified JavaScript 30 | rServer.print(F("")); 31 | rServer.print(F("")); 32 | } 33 | 34 | void SendLoginPage(WebServer &rServer) 35 | { 36 | Serial.println(F("Login page")); 37 | SendHeader(rServer); 38 | rServer.println(F("
")); 39 | rServer.print(F("
")); 40 | rServer.print(F("

Speak Friend & Enter

")); 41 | rServer.print(F(" ")); 42 | rServer.print(F("")); 43 | rServer.print(F("
")); 44 | rServer.print(F("
")); 45 | } 46 | 47 | void SendAccessGrantedPage(WebServer &rServer) 48 | { 49 | SendHeader(rServer); 50 | rServer.println(F("
")); 51 | rServer.print(F("
")); 52 | rServer.print(F("

Welcome Friend

")); 53 | rServer.print(F("")); 54 | rServer.print(F("Lock")); 55 | rServer.print(F("
The way is unresolved
")); 56 | rServer.print(F("
")); 57 | rServer.print(F("")); 62 | rServer.print(F("
")); 63 | } 64 | 65 | void SendAccessDeniedPage(WebServer &rServer) 66 | { 67 | SendHeader(rServer); 68 | rServer.println(F("
")); 69 | rServer.print(F("

None shall pass!

")); 70 | rServer.print(F("
")); 71 | } 72 | 73 | void SendErrorPage(WebServer &rServer) 74 | { 75 | SendHeader(rServer); 76 | rServer.println(F("

Something has gone wrong. Please try later

")); 77 | } 78 | 79 | void SendPageNotFound(WebServer &rServer) 80 | { 81 | SendHeader(rServer); 82 | rServer.println(F("

Can't find that here

")); 83 | } 84 | 85 | void SendStyles(WebServer &rServer) 86 | { 87 | Serial.println(F("Styles")); 88 | rServer.println(F("body {padding-top: 40px;padding-bottom: 40px;background-color: #eee;}")); 89 | rServer.println(F("form {max-width: 350px;padding: 5px;margin: 0 auto;}")); 90 | rServer.println(F("h2 {margin-bottom: 10px;}")); 91 | rServer.println(F(".form-control {position: relative;height: auto;-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;padding: 10px;font-size: 16px;}")); 92 | rServer.println(F(".form-control:focus {z-index: 2;}")); 93 | rServer.println(F("input {margin-bottom: 10px;border-top-left-radius: 0;border-top-right-radius: 0;}")); 94 | rServer.println(F(".alert {margin-top: 20px; text-align: center;}")); 95 | } 96 | 97 | void SendDoorState(WebServer &rServer, const char *DoorStateDescription) 98 | { 99 | Serial.println(F("Get door state")); 100 | rServer.print(F("{ \"DoorState\" : \"")); 101 | rServer.print(DoorStateDescription); 102 | rServer.println(F("\"}")); 103 | } 104 | 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /GarageDoorOpener/GarageDoorOpener.ino: -------------------------------------------------------------------------------- 1 | /* ************************************************************************* 2 | * Garage door opener. Provides a web interface to trigger a garage door to 3 | * open/close. Passwords can be updated using a MegunoLink interface panel. 4 | */ 5 | #include "Ethernet.h" 6 | #include "Webduino.h" 7 | 8 | #include "CommandHandler.h" // see: http://www.megunolink.com/documentation/arduino-libraries/serial-command-handler/ 9 | #include "EEPROMStore.h" // see: http://www.megunolink.com/documentation/arduino-libraries/eepromstore/ 10 | #include "ArduinoTimer.h" // see: http://www.megunolink.com/documentation/arduino-libraries/arduino-timer/ 11 | 12 | #include "WebPages.h" 13 | 14 | /* ----------------------------------------------------- 15 | * Hardware configuration 16 | */ 17 | 18 | // Digital input for door states. Inputs have pull-ups and are active low. 19 | const uint8_t DoorOpenPin = 6; 20 | const uint8_t DoorClosedPin = 5; 21 | 22 | // Digital output connected to a relay that activates the door 23 | const uint8_t DoorControlPin = 7; 24 | 25 | // Digital output for LED that signals door activation. 26 | const uint8_t IndicatorPin = 13; 27 | 28 | // Duration to close the switch on the door opener. This should be long 29 | // enough for the mechanism to start; typically it doesn't to remain 30 | // activated for the door to complete its motion. It is the same as the 31 | // time you'd hold down the button to start the door moving. 32 | const uint8_t DoorActivationPeriod = 600; // [ms] 33 | 34 | // MAC address for the ethernet controller. Preferably globally unique, but 35 | // at least unique for the local network. 36 | byte MyMACAddress[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xDE }; 37 | 38 | // IP address for the arduino. Either gobally unique, or at least unique 39 | // on the local network. Should be a statically allocated address (that is 40 | // not using DHCP so we know how to connect to the arduino when we want 41 | // to open the door. 42 | IPAddress MyIPAddress( 192, 168, 15, 22); 43 | 44 | 45 | /* ----------------------------------------------------- 46 | * Password and setting storage 47 | */ 48 | const uint8_t MaxPasswordLength = 15; 49 | const uint8_t PasswordSlots = 5; 50 | 51 | bool HadValidPasswordRecently = false; 52 | 53 | // Expire the users password after a little while 54 | const uint32_t MaxPasswordAge = 5*60; // seconds 55 | ArduinoTimer AgeOfValidPassword; 56 | 57 | struct PasswordData 58 | { 59 | char Passwords[PasswordSlots][MaxPasswordLength + 1]; 60 | 61 | void Reset() 62 | { 63 | memset(Passwords, 0, sizeof(Passwords)); 64 | } 65 | }; 66 | 67 | EEPROMStore PasswordStore; 68 | 69 | // Check the supplied password against all the ones we know of. Return true 70 | // if the password is valid and false otherwise. 71 | bool IsPasswordValid(const char *TestPassword) 72 | { 73 | for(int i = 0; i < PasswordSlots; ++i) 74 | { 75 | if (strcmp(PasswordStore.Data.Passwords[i], TestPassword) == 0) 76 | { 77 | Serial.println(F("Matched valid password")); 78 | HadValidPasswordRecently = true; 79 | AgeOfValidPassword.Reset(); 80 | return true; 81 | } 82 | } 83 | 84 | Serial.println(F("Invalid password")); 85 | return false; 86 | } 87 | 88 | // Returns true if a valid password has been entered recently. 89 | bool HasValidPassword() 90 | { 91 | if (HadValidPasswordRecently && AgeOfValidPassword.TimePassed_Seconds(MaxPasswordAge)) 92 | { 93 | HadValidPasswordRecently = false; 94 | } 95 | 96 | return HadValidPasswordRecently; 97 | } 98 | 99 | /* ----------------------------------------------------- 100 | * Hardware Support 101 | */ 102 | 103 | 104 | enum DoorState 105 | { 106 | // Garage door open sensor is triggered 107 | Open, 108 | 109 | // Door closed sensor is activated 110 | Closed, 111 | 112 | // Neither sensor is active. 113 | Unknown, 114 | }; 115 | 116 | void InitializeHardware(); 117 | DoorState GetDoorState(); 118 | void ActivateGarageDoor(); 119 | 120 | void InitializeHardware() 121 | { 122 | pinMode(DoorControlPin, OUTPUT); 123 | digitalWrite(DoorControlPin, LOW); 124 | 125 | pinMode(IndicatorPin, OUTPUT); 126 | digitalWrite(IndicatorPin, LOW); 127 | 128 | pinMode(DoorOpenPin, INPUT_PULLUP); 129 | pinMode(DoorClosedPin, INPUT_PULLUP); 130 | } 131 | 132 | void ActivateGarageDoor() 133 | { 134 | Serial.println(F("Door activated")); 135 | 136 | digitalWrite(DoorControlPin, HIGH); 137 | digitalWrite(IndicatorPin, HIGH); 138 | delay(DoorActivationPeriod); 139 | digitalWrite(DoorControlPin, LOW); 140 | digitalWrite(IndicatorPin, LOW); 141 | } 142 | 143 | 144 | DoorState GetDoorState() 145 | { 146 | if (digitalRead(DoorOpenPin) == 0) 147 | { 148 | return Open; 149 | } 150 | else if (digitalRead(DoorClosedPin) == 0) 151 | { 152 | return Closed; 153 | } 154 | 155 | return Unknown; 156 | } 157 | 158 | /* ----------------------------------------------------- 159 | * Handling serial commands 160 | */ 161 | CommandHandler<> SerialCommands; 162 | 163 | void InitializeSerialCommands() 164 | { 165 | SerialCommands.AddCommand(F("SetPass"), Cmd_SetPassword); 166 | SerialCommands.AddCommand(F("ListPass"), Cmd_ListPasswords); 167 | SerialCommands.AddCommand(F("ActivateDoor"), Cmd_ActivateDoor); 168 | } 169 | 170 | void Cmd_ListPasswords(CommandParameter &p) 171 | { 172 | Serial.println(F("Passwords")); 173 | Serial.println(F("---------")); 174 | for(int i = 0; i < PasswordSlots; ++i) 175 | { 176 | Serial.print(i); 177 | Serial.print(F(": ")); 178 | if (PasswordStore.Data.Passwords[i][0] == '\0') 179 | { 180 | Serial.println(F("(empty)")); 181 | } 182 | else 183 | { 184 | Serial.println(PasswordStore.Data.Passwords[i]); 185 | } 186 | } 187 | } 188 | 189 | void Cmd_SetPassword(CommandParameter &p) 190 | { 191 | unsigned Slot = p.NextParameterAsInteger(-1); 192 | 193 | if (Slot >= PasswordSlots) 194 | { 195 | Serial.print(F("Invalid password slot. Slots available: 0..")); 196 | Serial.println(PasswordSlots - 1); 197 | return; 198 | } 199 | 200 | char *Password = p.NextParameter(); 201 | unsigned Length = strlen(Password); 202 | if (Length > MaxPasswordLength) 203 | { 204 | Serial.print(F("Password is too long. Maximum length is: ")); 205 | Serial.println(MaxPasswordLength); 206 | return; 207 | } 208 | 209 | // Clear old password & make sure null terminator is present then 210 | // save new password. 211 | memset(PasswordStore.Data.Passwords[Slot], 0, MaxPasswordLength + 1); 212 | memcpy(PasswordStore.Data.Passwords[Slot], Password, Length); 213 | PasswordStore.Save(); 214 | 215 | Serial.print(F("Password slot ")); 216 | Serial.print(Slot); 217 | Serial.println(F(" updated")); 218 | } 219 | 220 | void Cmd_ActivateDoor(CommandParameter &p) 221 | { 222 | Serial.println(F("Activating door")); 223 | ActivateGarageDoor(); 224 | } 225 | 226 | /* ----------------------------------------------------- 227 | * Handle commands for webserver 228 | */ 229 | WebServer AccessControlServer("", 80); 230 | 231 | void InitializeWebserver() 232 | { 233 | AccessControlServer.setDefaultCommand(ShowWebRoot); 234 | AccessControlServer.addCommand("LetMeIn.html", CheckLogin); 235 | AccessControlServer.addCommand("index.html", ShowWebRoot); 236 | AccessControlServer.addCommand("style.css", SendStyles); 237 | AccessControlServer.addCommand("doorstate.json", GetDoorState); 238 | AccessControlServer.setFailureCommand(ShowPageNotFound); 239 | 240 | Ethernet.begin(MyMACAddress, MyIPAddress); 241 | AccessControlServer.begin(); 242 | } 243 | 244 | void ProcessWebserver() 245 | { 246 | char achBuffer[64]; 247 | int nBufferLength = sizeof(achBuffer); 248 | 249 | AccessControlServer.processConnection(achBuffer, &nBufferLength); 250 | } 251 | 252 | // --------------- Page request handlers 253 | void ShowWebRoot(WebServer &rServer, WebServer::ConnectionType Type, char *pchUrlTail, bool bTailComplete) 254 | { 255 | Serial.println(F("Show web root")); 256 | 257 | // Expire grace login period when the home page is shown. 258 | HadValidPasswordRecently = false; 259 | 260 | // We show the password entry page as the web root. Presents a form that asks for a password. 261 | rServer.httpSuccess(); 262 | 263 | switch (Type) 264 | { 265 | case WebServer::GET: 266 | SendLoginPage(rServer); 267 | break; 268 | 269 | case WebServer::POST: 270 | SendErrorPage(rServer); 271 | break; 272 | 273 | // None of these are expected, so we don't respond. 274 | case WebServer::INVALID: 275 | case WebServer::HEAD: 276 | case WebServer::PUT: 277 | case WebServer::DELETE: 278 | case WebServer::PATCH: 279 | default: 280 | break; 281 | } 282 | } 283 | 284 | void SendStyles(WebServer &rServer, WebServer::ConnectionType Type, char *pchUrlTail, bool bTailComplete) 285 | { 286 | rServer.httpSuccess("text/css; charset=utf-8"); 287 | 288 | switch (Type) 289 | { 290 | case WebServer::GET: 291 | case WebServer::POST: 292 | SendStyles(rServer); 293 | break; 294 | 295 | // None of these are expected, so we don't respond. 296 | case WebServer::INVALID: 297 | case WebServer::HEAD: 298 | case WebServer::PUT: 299 | case WebServer::DELETE: 300 | case WebServer::PATCH: 301 | default: 302 | break; 303 | } 304 | } 305 | 306 | void GetDoorState(WebServer &rServer, WebServer::ConnectionType Type, char *pchUrlTail, bool bTailComplete) 307 | { 308 | rServer.httpSuccess("application/json; charset=utf-8", "Pragma: no-cache\r\nCache-Control: no-cache\r\n"); 309 | 310 | switch (Type) 311 | { 312 | case WebServer::GET: 313 | case WebServer::POST: 314 | { 315 | const char *DoorStateDescription = "unknown"; 316 | DoorState CurrentState = GetDoorState(); 317 | if (CurrentState == Open) 318 | { 319 | DoorStateDescription = "open"; 320 | } 321 | else if (CurrentState == Closed) 322 | { 323 | DoorStateDescription = "closed"; 324 | } 325 | SendDoorState(rServer, DoorStateDescription); 326 | } 327 | break; 328 | 329 | // None of these are expected, so we don't respond. 330 | case WebServer::INVALID: 331 | case WebServer::HEAD: 332 | case WebServer::PUT: 333 | case WebServer::DELETE: 334 | case WebServer::PATCH: 335 | default: 336 | break; 337 | } 338 | } 339 | 340 | void CheckLogin(WebServer &rServer, WebServer::ConnectionType Type, char *pchUrlTail, bool bTailComplete) 341 | { 342 | 343 | rServer.httpSuccess(); 344 | 345 | switch (Type) 346 | { 347 | case WebServer::GET: 348 | SendErrorPage(rServer); 349 | break; 350 | 351 | case WebServer::POST: 352 | if (IsValidLogin(rServer)) 353 | { 354 | ActivateGarageDoor(); 355 | SendAccessGrantedPage(rServer); 356 | } 357 | else 358 | { 359 | SendAccessDeniedPage(rServer); 360 | } 361 | break; 362 | 363 | // None of these are expected, so we don't respond. 364 | case WebServer::INVALID: 365 | case WebServer::HEAD: 366 | case WebServer::PUT: 367 | case WebServer::DELETE: 368 | case WebServer::PATCH: 369 | default: 370 | break; 371 | } 372 | } 373 | 374 | bool IsValidLogin(WebServer &rServer) 375 | { 376 | const int nMaxParameterBuffer = 20; 377 | char achName[nMaxParameterBuffer]; 378 | char achValue[nMaxParameterBuffer]; 379 | 380 | // Check if there is a recent password & we are still within grace period. 381 | if (HasValidPassword()) 382 | return true; 383 | 384 | // Work through all the parameters supplied until we find the one with the 385 | // password in it. Then check to see if the password is valid. 386 | while (rServer.readPOSTparam(achName, sizeof(achName), achValue, sizeof(achValue))) 387 | if (strcmp(achName, "message") == 0) // we have the password parameter. 388 | return IsPasswordValid(achValue); 389 | 390 | return false; // didn't find the password parameter. 391 | } 392 | 393 | void ShowPageNotFound(WebServer &rServer, WebServer::ConnectionType Type, char *pchUrlTail, bool bTailComplete) 394 | { 395 | // Expire grace login period when invalid page requested 396 | HadValidPasswordRecently = false; 397 | 398 | // We show the password entry page as the web root. Presents a form that asks for a password. 399 | rServer.httpSuccess(); 400 | 401 | switch (Type) 402 | { 403 | case WebServer::GET: 404 | case WebServer::POST: 405 | SendPageNotFound(rServer); 406 | break; 407 | 408 | // None of these are expected, so we don't respond. 409 | case WebServer::INVALID: 410 | case WebServer::HEAD: 411 | case WebServer::PUT: 412 | case WebServer::DELETE: 413 | case WebServer::PATCH: 414 | default: 415 | break; 416 | } 417 | } 418 | 419 | /* ----------------------------------------------------- 420 | * Program entry points. 421 | */ 422 | void setup() 423 | { 424 | Serial.begin(9600); 425 | Serial.println(F("Garage Door Controller")); 426 | Serial.print(F("Built: ")); Serial.println(F(__TIMESTAMP__)); 427 | 428 | InitializeHardware(); 429 | InitializeSerialCommands(); 430 | PasswordStore.Load(); 431 | InitializeWebserver(); 432 | } 433 | 434 | void loop() 435 | { 436 | SerialCommands.Process(); 437 | ProcessWebserver(); 438 | } 439 | -------------------------------------------------------------------------------- /Garage Door Password Manager.mlx: -------------------------------------------------------------------------------- 1 | 2 | 3 | en-NZ 4 | 5 | 6 | 7 | true 8 | true 9 | true 10 | false 11 | true 12 | false 13 | 14 | false 15 | 16 | 17 | 18 | 19 | 20 | false 21 | 22 | 23 | 0 24 | d:\Users\Paul\Documents 25 | true 26 | Report {Seq} 27 | true 28 | PT1H 29 | 0001-01-01T00:00:00 30 | 31 | 32 | 33 | 34 | 0 35 | 36 | 37 | 0 38 | Garage Door Controller 39 | 40 | Fixed 41 | COM2 42 | 43 | 9600 44 | 8 45 | None 46 | None 47 | One 48 | true 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | !ActivateDoor\r\n 64 | Activate Door 65 | 16, 209 66 | IPButton6 67 | 127, 23 68 | 16 69 | 70 | 71 | 72 | 73 | !ListPass\r\n 74 | List Passwords 75 | 189, 209 76 | IPButton5 77 | 127, 23 78 | 16 79 | 80 | 81 | 82 | 83 | !SetPass 4 [txtPass4.Text]\r\n 84 | Set 85 | 241, 151 86 | IPButton4 87 | 75, 23 88 | 15 89 | 90 | 91 | 92 | 93 | !SetPass 3 [txtPass3.Text]\r\n 94 | Set 95 | 241, 125 96 | IPButton3 97 | 75, 23 98 | 12 99 | 100 | 101 | 102 | 103 | !SetPass 2 [txtPass2.Text]\r\n 104 | Set 105 | 241, 99 106 | IPButton2 107 | 75, 23 108 | 9 109 | 110 | 111 | 112 | 113 | !SetPass 1 [txtPass1.Text]\r\n 114 | Set 115 | 241, 73 116 | IPButton1 117 | 75, 23 118 | 6 119 | 120 | 121 | 122 | 123 | !SetPass 0 [txtPass0.Text]\r\n 124 | Set 125 | 241, 47 126 | btnSetPass0 127 | 75, 23 128 | 3 129 | 130 | 131 | 132 | 133 | 134 | 15 135 | 87, 152 136 | txtPass4 137 | 148, 20 138 | 14 139 | 140 | 141 | 142 | 143 | True 144 | Password 4: 145 | 16, 156 146 | Label6 147 | 65, 13 148 | 13 149 | 150 | 151 | 152 | 153 | 154 | 15 155 | 87, 126 156 | txtPass3 157 | 148, 20 158 | 11 159 | 160 | 161 | 162 | 163 | True 164 | Password 3: 165 | 16, 130 166 | Label5 167 | 65, 13 168 | 10 169 | 170 | 171 | 172 | 173 | 174 | 15 175 | 87, 100 176 | txtPass2 177 | 148, 20 178 | 8 179 | 180 | 181 | 182 | 183 | True 184 | Password 2: 185 | 16, 104 186 | Label4 187 | 65, 13 188 | 7 189 | 190 | 191 | 192 | 193 | 194 | 15 195 | 87, 74 196 | txtPass1 197 | 148, 20 198 | 5 199 | 200 | 201 | 202 | 203 | True 204 | Password 1: 205 | 16, 78 206 | Label3 207 | 65, 13 208 | 4 209 | 210 | 211 | 212 | 213 | 214 | 15 215 | 87, 48 216 | txtPass0 217 | 148, 20 218 | 2 219 | 220 | 221 | 222 | 223 | True 224 | Password 0: 225 | 16, 52 226 | Label2 227 | 65, 13 228 | 1 229 | 230 | 231 | 232 | 233 | True 234 | Password Manager 235 | Microsoft Sans Serif, 15.75pt, style=Bold 236 | 0, 0, 192 237 | 3, 0 238 | Label1 239 | 213, 25 240 | 0 241 | 242 | 243 | 244 | 245 | UserControl1 246 | 632, 1014 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | false 268 | false 269 | 0 270 | Interface Panel 271 | 272 | 273 | false 274 | true 275 | false 276 | true 277 | false 278 | 279 | false 280 | false 281 | false 282 | false 283 | false 284 | false 285 | 0 286 | Monitor 287 | 288 | 289 | false 290 | false 291 | -1 292 | Connection Manager 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | -------------------------------------------------------------------------------- /GarageDoorOpener/Webduino.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-2014 Ben Combee, Ran Talbott, Christopher Lee, Martin Lormes 5 | Francisco M Cuenca-Acuna 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | */ 25 | 26 | #ifndef WEBDUINO_H_ 27 | #define WEBDUINO_H_ 28 | 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | /******************************************************************** 37 | * CONFIGURATION 38 | ********************************************************************/ 39 | 40 | #define WEBDUINO_VERSION 1007 41 | #define WEBDUINO_VERSION_STRING "1.7" 42 | 43 | // standard END-OF-LINE marker in HTTP 44 | #define CRLF "\r\n" 45 | 46 | // If processConnection is called without a buffer, it allocates one 47 | // of 32 bytes 48 | #define WEBDUINO_DEFAULT_REQUEST_LENGTH 32 49 | 50 | // How long to wait before considering a connection as dead when 51 | // reading the HTTP request. Used to avoid DOS attacks. 52 | #ifndef WEBDUINO_READ_TIMEOUT_IN_MS 53 | #define WEBDUINO_READ_TIMEOUT_IN_MS 1000 54 | #endif 55 | 56 | #ifndef WEBDUINO_COMMANDS_COUNT 57 | #define WEBDUINO_COMMANDS_COUNT 8 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 "

EPIC FAIL

" 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 "

401 Unauthorized

" 74 | #endif // #ifndef WEBDUINO_AUTH_MESSAGE 75 | 76 | #ifndef WEBDUINO_SERVER_ERROR_MESSAGE 77 | #define WEBDUINO_SERVER_ERROR_MESSAGE "

500 Internal Server Error

" 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 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[] __attribute__(( section(".progmem." #name) )) 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 = "", uint16_t 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 | // support for C style formating 225 | void printf(char *fmt, ... ); 226 | #ifdef F 227 | void printf(const __FlashStringHelper *format, ... ); 228 | #endif 229 | 230 | // output raw data stored in program memory 231 | void writeP(const unsigned char *data, size_t length); 232 | 233 | // output HTML for a radio button 234 | void radioButton(const char *name, const char *val, 235 | const char *label, bool selected); 236 | 237 | // output HTML for a checkbox 238 | void checkBox(const char *name, const char *val, 239 | const char *label, bool selected); 240 | 241 | // returns next character or -1 if we're at end-of-stream 242 | int read(); 243 | 244 | // put a character that's been read back into the input pool 245 | void push(int ch); 246 | 247 | // returns true if the string is next in the stream. Doesn't 248 | // consume any character if false, so can be used to try out 249 | // different expected values. 250 | bool expect(const char *expectedStr); 251 | 252 | // returns true if a number, with possible whitespace in front, was 253 | // read from the server stream. number will be set with the new 254 | // value or 0 if nothing was read. 255 | bool readInt(int &number); 256 | 257 | // reads a header value, stripped of possible whitespace in front, 258 | // from the server stream 259 | void readHeader(char *value, int valueLen); 260 | 261 | // Read the next keyword parameter from the socket. Assumes that other 262 | // code has already skipped over the headers, and the next thing to 263 | // be read will be the start of a keyword. 264 | // 265 | // returns true if we're not at end-of-stream 266 | bool readPOSTparam(char *name, int nameLen, char *value, int valueLen); 267 | 268 | // Read the next keyword parameter from the buffer filled by getRequest. 269 | // 270 | // returns 0 if everything weent okay, non-zero if not 271 | // (see the typedef for codes) 272 | URLPARAM_RESULT nextURLparam(char **tail, char *name, int nameLen, 273 | char *value, int valueLen); 274 | 275 | // compare string against credentials in current request 276 | // 277 | // authCredentials must be Base64 encoded outside of Webduino 278 | // (I wanted to be easy on the resources) 279 | // 280 | // returns true if strings match, false otherwise 281 | bool checkCredentials(const char authCredentials[45]); 282 | 283 | // output headers and a message indicating a server error 284 | void httpFail(); 285 | 286 | // output headers and a message indicating "401 Unauthorized" 287 | void httpUnauthorized(); 288 | 289 | // output headers and a message indicating "500 Internal Server Error" 290 | void httpServerError(); 291 | 292 | // output headers indicating "204 No Content" and no further message 293 | void httpNoContent(); 294 | 295 | // output standard headers indicating "200 Success". You can change the 296 | // type of the data you're outputting or also add extra headers like 297 | // "Refresh: 1". Extra headers should each be terminated with CRLF. 298 | void httpSuccess(const char *contentType = "text/html; charset=utf-8", 299 | const char *extraHeaders = NULL); 300 | 301 | // used with POST to output a redirect to another URL. This is 302 | // preferable to outputting HTML from a post because you can then 303 | // refresh the page without getting a "resubmit form" dialog. 304 | void httpSeeOther(const char *otherURL); 305 | 306 | // implementation of write used to implement Print interface 307 | virtual size_t write(uint8_t); 308 | virtual size_t write(const uint8_t *buffer, size_t size); 309 | 310 | // tells if there is anything to process 311 | uint8_t available(); 312 | 313 | // Flush the send buffer 314 | void flushBuf(); 315 | 316 | // Close the current connection and flush ethernet buffers 317 | void reset(); 318 | private: 319 | EthernetServer m_server; 320 | EthernetClient m_client; 321 | const char *m_urlPrefix; 322 | 323 | unsigned char m_pushback[32]; 324 | unsigned char m_pushbackDepth; 325 | 326 | int m_contentLength; 327 | char m_authCredentials[51]; 328 | bool m_readingContent; 329 | 330 | Command *m_failureCmd; 331 | Command *m_defaultCmd; 332 | struct CommandMap 333 | { 334 | const char *verb; 335 | Command *cmd; 336 | } m_commands[WEBDUINO_COMMANDS_COUNT]; 337 | unsigned char m_cmdCount; 338 | UrlPathCommand *m_urlPathCmd; 339 | 340 | uint8_t m_buffer[WEBDUINO_OUTPUT_BUFFER_SIZE]; 341 | uint8_t m_bufFill; 342 | 343 | void getRequest(WebServer::ConnectionType &type, char *request, int *length); 344 | bool dispatchCommand(ConnectionType requestType, char *verb, 345 | bool tail_complete); 346 | void processHeaders(); 347 | void outputCheckboxOrRadio(const char *element, const char *name, 348 | const char *val, const char *label, 349 | bool selected); 350 | 351 | static void defaultFailCmd(WebServer &server, ConnectionType type, 352 | char *url_tail, bool tail_complete); 353 | void noRobots(ConnectionType type); 354 | void favicon(ConnectionType type); 355 | }; 356 | 357 | /* define this macro if you want to include the header in a sketch source 358 | file but not define any of the implementation. This is useful if 359 | multiple source files are using the Webduino class. */ 360 | #ifndef WEBDUINO_NO_IMPLEMENTATION 361 | 362 | /******************************************************************** 363 | * IMPLEMENTATION 364 | ********************************************************************/ 365 | 366 | WebServer::WebServer(const char *urlPrefix, uint16_t port) : 367 | m_server(port), 368 | m_client(), 369 | m_urlPrefix(urlPrefix), 370 | m_pushbackDepth(0), 371 | m_contentLength(0), 372 | m_failureCmd(&defaultFailCmd), 373 | m_defaultCmd(&defaultFailCmd), 374 | m_cmdCount(0), 375 | m_urlPathCmd(NULL), 376 | m_bufFill(0) 377 | { 378 | } 379 | 380 | P(webServerHeader) = "Server: Webduino/" WEBDUINO_VERSION_STRING CRLF; 381 | 382 | void WebServer::begin() 383 | { 384 | m_server.begin(); 385 | } 386 | 387 | void WebServer::setDefaultCommand(Command *cmd) 388 | { 389 | m_defaultCmd = cmd; 390 | } 391 | 392 | void WebServer::setFailureCommand(Command *cmd) 393 | { 394 | m_failureCmd = cmd; 395 | } 396 | 397 | void WebServer::addCommand(const char *verb, Command *cmd) 398 | { 399 | if (m_cmdCount < SIZE(m_commands)) 400 | { 401 | m_commands[m_cmdCount].verb = verb; 402 | m_commands[m_cmdCount++].cmd = cmd; 403 | } 404 | } 405 | 406 | void WebServer::setUrlPathCommand(UrlPathCommand *cmd) 407 | { 408 | m_urlPathCmd = cmd; 409 | } 410 | 411 | size_t WebServer::write(uint8_t ch) 412 | { 413 | m_buffer[m_bufFill++] = ch; 414 | 415 | if(m_bufFill == sizeof(m_buffer)) 416 | { 417 | m_client.write(m_buffer, sizeof(m_buffer)); 418 | m_bufFill = 0; 419 | } 420 | 421 | return sizeof(ch); 422 | } 423 | 424 | size_t WebServer::write(const uint8_t *buffer, size_t size) 425 | { 426 | flushBuf(); //Flush any buffered output 427 | return m_client.write(buffer, size); 428 | } 429 | 430 | void WebServer::flushBuf() 431 | { 432 | if(m_bufFill > 0) 433 | { 434 | m_client.write(m_buffer, m_bufFill); 435 | m_bufFill = 0; 436 | } 437 | } 438 | 439 | void WebServer::writeP(const unsigned char *data, size_t length) 440 | { 441 | // copy data out of program memory into local storage 442 | 443 | while (length--) 444 | { 445 | write(pgm_read_byte(data++)); 446 | } 447 | } 448 | 449 | void WebServer::printP(const unsigned char *str) 450 | { 451 | // copy data out of program memory into local storage 452 | 453 | while (uint8_t value = pgm_read_byte(str++)) 454 | { 455 | write(value); 456 | } 457 | } 458 | 459 | void WebServer::printCRLF() 460 | { 461 | print(CRLF); 462 | } 463 | 464 | void WebServer::printf(char *fmt, ... ) 465 | { 466 | char tmp[128]; // resulting string limited to 128 chars 467 | va_list args; 468 | va_start (args, fmt ); 469 | vsnprintf(tmp, 128, fmt, args); 470 | va_end (args); 471 | print(tmp); 472 | } 473 | 474 | #ifdef F 475 | void WebServer::printf(const __FlashStringHelper *format, ... ) 476 | { 477 | char buf[128]; // resulting string limited to 128 chars 478 | va_list ap; 479 | va_start(ap, format); 480 | #ifdef __AVR__ 481 | vsnprintf_P(buf, sizeof(buf), (const char *)format, ap); // progmem for AVR 482 | #else 483 | vsnprintf(buf, sizeof(buf), (const char *)format, ap); // for the rest of the world 484 | #endif 485 | va_end(ap); 486 | print(buf); 487 | } 488 | #endif 489 | 490 | bool WebServer::dispatchCommand(ConnectionType requestType, char *verb, 491 | bool tail_complete) 492 | { 493 | // if there is no URL, i.e. we have a prefix and it's requested without a 494 | // trailing slash or if the URL is just the slash 495 | if ((verb[0] == 0) || ((verb[0] == '/') && (verb[1] == 0))) 496 | { 497 | m_defaultCmd(*this, requestType, (char*)"", tail_complete); 498 | return true; 499 | } 500 | // if the URL is just a slash followed by a question mark 501 | // we're looking at the default command with GET parameters passed 502 | if ((verb[0] == '/') && (verb[1] == '?')) 503 | { 504 | verb+=2; // skip over the "/?" part of the url 505 | m_defaultCmd(*this, requestType, verb, tail_complete); 506 | return true; 507 | } 508 | // We now know that the URL contains at least one character. And, 509 | // if the first character is a slash, there's more after it. 510 | if (verb[0] == '/') 511 | { 512 | uint8_t i; 513 | char *qm_loc; 514 | uint16_t verb_len; 515 | uint8_t qm_offset; 516 | // Skip over the leading "/", because it makes the code more 517 | // efficient and easier to understand. 518 | verb++; 519 | // Look for a "?" separating the filename part of the URL from the 520 | // parameters. If it's not there, compare to the whole URL. 521 | qm_loc = strchr(verb, '?'); 522 | verb_len = (qm_loc == NULL) ? strlen(verb) : (qm_loc - verb); 523 | qm_offset = (qm_loc == NULL) ? 0 : 1; 524 | for (i = 0; i < m_cmdCount; ++i) 525 | { 526 | if ((verb_len == strlen(m_commands[i].verb)) 527 | && (strncmp(verb, m_commands[i].verb, verb_len) == 0)) 528 | { 529 | // Skip over the "verb" part of the URL (and the question 530 | // mark, if present) when passing it to the "action" routine 531 | m_commands[i].cmd(*this, requestType, 532 | verb + verb_len + qm_offset, 533 | tail_complete); 534 | return true; 535 | } 536 | } 537 | // Check if UrlPathCommand is assigned. 538 | if (m_urlPathCmd != NULL) 539 | { 540 | // Initialize with null bytes, so number of parts can be determined. 541 | char *url_path[WEBDUINO_URL_PATH_COMMAND_LENGTH] = {0}; 542 | uint8_t part = 0; 543 | 544 | // URL path should be terminated with null byte. 545 | *(verb + verb_len) = 0; 546 | 547 | // First URL path part is at the start of verb. 548 | url_path[part++] = verb; 549 | // Replace all slashes ('/') with a null byte so every part of the URL 550 | // path is a seperate string. Add every char following a '/' as a new 551 | // part of the URL, even if that char is a '/' (which will be replaced 552 | // with a null byte). 553 | for (char * p = verb; p < verb + verb_len; p++) 554 | { 555 | if (*p == '/') 556 | { 557 | *p = 0; 558 | url_path[part++] = p + 1; 559 | // Don't try to assign out of array bounds. 560 | if (part == WEBDUINO_URL_PATH_COMMAND_LENGTH) break; 561 | } 562 | } 563 | m_urlPathCmd(*this, requestType, url_path, 564 | verb + verb_len + qm_offset, tail_complete); 565 | return true; 566 | } 567 | } 568 | return false; 569 | } 570 | 571 | // processConnection with a default buffer 572 | void WebServer::processConnection() 573 | { 574 | char request[WEBDUINO_DEFAULT_REQUEST_LENGTH]; 575 | int request_len = WEBDUINO_DEFAULT_REQUEST_LENGTH; 576 | processConnection(request, &request_len); 577 | } 578 | 579 | void WebServer::processConnection(char *buff, int *bufflen) 580 | { 581 | int urlPrefixLen = strlen(m_urlPrefix); 582 | 583 | m_client = m_server.available(); 584 | 585 | if (m_client) { 586 | m_readingContent = false; 587 | buff[0] = 0; 588 | ConnectionType requestType = INVALID; 589 | #if WEBDUINO_SERIAL_DEBUGGING > 1 590 | Serial.println("*** checking request ***"); 591 | #endif 592 | getRequest(requestType, buff, bufflen); 593 | #if WEBDUINO_SERIAL_DEBUGGING > 1 594 | Serial.print("*** requestType = "); 595 | Serial.print((int)requestType); 596 | Serial.print(", request = \""); 597 | Serial.print(buff); 598 | Serial.println("\" ***"); 599 | #endif 600 | 601 | // don't even look further at invalid requests. 602 | // this is done to prevent Webduino from hanging 603 | // - when there are illegal requests, 604 | // - when someone contacts it through telnet rather than proper HTTP, 605 | // - etc. 606 | if (requestType != INVALID) 607 | { 608 | processHeaders(); 609 | #if WEBDUINO_SERIAL_DEBUGGING > 1 610 | Serial.println("*** headers complete ***"); 611 | #endif 612 | 613 | if (strcmp(buff, "/robots.txt") == 0) 614 | { 615 | noRobots(requestType); 616 | } 617 | else if (strcmp(buff, "/favicon.ico") == 0) 618 | { 619 | favicon(requestType); 620 | } 621 | } 622 | // Only try to dispatch command if request type and prefix are correct. 623 | // Fix by quarencia. 624 | if (requestType == INVALID || 625 | strncmp(buff, m_urlPrefix, urlPrefixLen) != 0) 626 | { 627 | m_failureCmd(*this, requestType, buff, (*bufflen) >= 0); 628 | } 629 | else if (!dispatchCommand(requestType, buff + urlPrefixLen, 630 | (*bufflen) >= 0)) 631 | { 632 | m_failureCmd(*this, requestType, buff, (*bufflen) >= 0); 633 | } 634 | 635 | flushBuf(); 636 | 637 | #if WEBDUINO_SERIAL_DEBUGGING > 1 638 | Serial.println("*** stopping connection ***"); 639 | #endif 640 | reset(); 641 | } 642 | } 643 | 644 | bool WebServer::checkCredentials(const char authCredentials[45]) 645 | { 646 | char basic[7] = "Basic "; 647 | if((0 == strncmp(m_authCredentials,basic,6)) && 648 | (0 == strcmp(authCredentials, m_authCredentials + 6))) return true; 649 | return false; 650 | } 651 | 652 | void WebServer::httpFail() 653 | { 654 | P(failMsg1) = "HTTP/1.0 400 Bad Request" CRLF; 655 | printP(failMsg1); 656 | 657 | #ifndef WEBDUINO_SUPRESS_SERVER_HEADER 658 | printP(webServerHeader); 659 | #endif 660 | 661 | P(failMsg2) = 662 | "Content-Type: text/html" CRLF 663 | CRLF 664 | WEBDUINO_FAIL_MESSAGE; 665 | 666 | printP(failMsg2); 667 | } 668 | 669 | void WebServer::defaultFailCmd(WebServer &server, 670 | WebServer::ConnectionType type, 671 | char *url_tail, 672 | bool tail_complete) 673 | { 674 | server.httpFail(); 675 | } 676 | 677 | void WebServer::noRobots(ConnectionType type) 678 | { 679 | httpSuccess("text/plain"); 680 | if (type != HEAD) 681 | { 682 | P(allowNoneMsg) = "User-agent: *" CRLF "Disallow: /" CRLF; 683 | printP(allowNoneMsg); 684 | } 685 | } 686 | 687 | void WebServer::favicon(ConnectionType type) 688 | { 689 | httpSuccess("image/x-icon","Cache-Control: max-age=31536000\r\n"); 690 | if (type != HEAD) 691 | { 692 | P(faviconIco) = WEBDUINO_FAVICON_DATA; 693 | writeP(faviconIco, sizeof(faviconIco)); 694 | } 695 | } 696 | 697 | void WebServer::httpUnauthorized() 698 | { 699 | P(unauthMsg1) = "HTTP/1.0 401 Authorization Required" CRLF; 700 | printP(unauthMsg1); 701 | 702 | #ifndef WEBDUINO_SUPRESS_SERVER_HEADER 703 | printP(webServerHeader); 704 | #endif 705 | 706 | P(unauthMsg2) = 707 | "Content-Type: text/html" CRLF 708 | "WWW-Authenticate: Basic realm=\"" WEBDUINO_AUTH_REALM "\"" CRLF 709 | CRLF 710 | WEBDUINO_AUTH_MESSAGE; 711 | 712 | printP(unauthMsg2); 713 | } 714 | 715 | void WebServer::httpServerError() 716 | { 717 | P(servErrMsg1) = "HTTP/1.0 500 Internal Server Error" CRLF; 718 | printP(servErrMsg1); 719 | 720 | #ifndef WEBDUINO_SUPRESS_SERVER_HEADER 721 | printP(webServerHeader); 722 | #endif 723 | 724 | P(servErrMsg2) = 725 | "Content-Type: text/html" CRLF 726 | CRLF 727 | WEBDUINO_SERVER_ERROR_MESSAGE; 728 | 729 | printP(servErrMsg2); 730 | } 731 | 732 | void WebServer::httpNoContent() 733 | { 734 | P(noContentMsg1) = "HTTP/1.0 204 NO CONTENT" CRLF; 735 | printP(noContentMsg1); 736 | 737 | #ifndef WEBDUINO_SUPRESS_SERVER_HEADER 738 | printP(webServerHeader); 739 | #endif 740 | 741 | P(noContentMsg2) = 742 | CRLF 743 | CRLF; 744 | 745 | printP(noContentMsg2); 746 | } 747 | 748 | void WebServer::httpSuccess(const char *contentType, 749 | const char *extraHeaders) 750 | { 751 | P(successMsg1) = "HTTP/1.0 200 OK" CRLF; 752 | printP(successMsg1); 753 | 754 | #ifndef WEBDUINO_SUPRESS_SERVER_HEADER 755 | printP(webServerHeader); 756 | #endif 757 | 758 | P(successMsg2) = 759 | "Access-Control-Allow-Origin: *" CRLF 760 | "Content-Type: "; 761 | 762 | printP(successMsg2); 763 | print(contentType); 764 | printCRLF(); 765 | if (extraHeaders) 766 | print(extraHeaders); 767 | printCRLF(); 768 | } 769 | 770 | void WebServer::httpSeeOther(const char *otherURL) 771 | { 772 | P(seeOtherMsg1) = "HTTP/1.0 303 See Other" CRLF; 773 | printP(seeOtherMsg1); 774 | 775 | #ifndef WEBDUINO_SUPRESS_SERVER_HEADER 776 | printP(webServerHeader); 777 | #endif 778 | 779 | P(seeOtherMsg2) = "Location: "; 780 | printP(seeOtherMsg2); 781 | print(otherURL); 782 | printCRLF(); 783 | printCRLF(); 784 | } 785 | 786 | int WebServer::read() 787 | { 788 | if (!m_client) 789 | return -1; 790 | 791 | if (m_pushbackDepth == 0) 792 | { 793 | unsigned long timeoutTime = millis() + WEBDUINO_READ_TIMEOUT_IN_MS; 794 | 795 | while (m_client.connected()) 796 | { 797 | // stop reading the socket early if we get to content-length 798 | // characters in the POST. This is because some clients leave 799 | // the socket open because they assume HTTP keep-alive. 800 | if (m_readingContent) 801 | { 802 | if (m_contentLength == 0) 803 | { 804 | #if WEBDUINO_SERIAL_DEBUGGING > 1 805 | Serial.println("\n*** End of content, terminating connection"); 806 | #endif 807 | return -1; 808 | } 809 | } 810 | 811 | int ch = m_client.read(); 812 | 813 | // if we get a character, return it, otherwise continue in while 814 | // loop, checking connection status 815 | if (ch != -1) 816 | { 817 | // count character against content-length 818 | if (m_readingContent) 819 | { 820 | --m_contentLength; 821 | } 822 | 823 | #if WEBDUINO_SERIAL_DEBUGGING 824 | if (ch == '\r') 825 | Serial.print(""); 826 | else if (ch == '\n') 827 | Serial.println(""); 828 | else 829 | Serial.print((char)ch); 830 | #endif 831 | return ch; 832 | } 833 | else 834 | { 835 | unsigned long now = millis(); 836 | if (now > timeoutTime) 837 | { 838 | // connection timed out, destroy client, return EOF 839 | #if WEBDUINO_SERIAL_DEBUGGING 840 | Serial.println("*** Connection timed out"); 841 | #endif 842 | reset(); 843 | return -1; 844 | } 845 | } 846 | } 847 | 848 | // connection lost, return EOF 849 | #if WEBDUINO_SERIAL_DEBUGGING 850 | Serial.println("*** Connection lost"); 851 | #endif 852 | return -1; 853 | } 854 | else 855 | return m_pushback[--m_pushbackDepth]; 856 | } 857 | 858 | void WebServer::push(int ch) 859 | { 860 | // don't allow pushing EOF 861 | if (ch == -1) 862 | return; 863 | 864 | m_pushback[m_pushbackDepth++] = ch; 865 | // can't raise error here, so just replace last char over and over 866 | if (m_pushbackDepth == SIZE(m_pushback)) 867 | m_pushbackDepth = SIZE(m_pushback) - 1; 868 | } 869 | 870 | void WebServer::reset() 871 | { 872 | m_pushbackDepth = 0; 873 | m_client.flush(); 874 | m_client.stop(); 875 | } 876 | 877 | bool WebServer::expect(const char *str) 878 | { 879 | const char *curr = str; 880 | while (*curr != 0) 881 | { 882 | int ch = read(); 883 | if (ch != *curr++) 884 | { 885 | // push back ch and the characters we accepted 886 | push(ch); 887 | while (--curr != str) 888 | push(curr[-1]); 889 | return false; 890 | } 891 | } 892 | return true; 893 | } 894 | 895 | bool WebServer::readInt(int &number) 896 | { 897 | bool negate = false; 898 | bool gotNumber = false; 899 | int ch; 900 | number = 0; 901 | 902 | // absorb whitespace 903 | do 904 | { 905 | ch = read(); 906 | } while (ch == ' ' || ch == '\t'); 907 | 908 | // check for leading minus sign 909 | if (ch == '-') 910 | { 911 | negate = true; 912 | ch = read(); 913 | } 914 | 915 | // read digits to update number, exit when we find non-digit 916 | while (ch >= '0' && ch <= '9') 917 | { 918 | gotNumber = true; 919 | number = number * 10 + ch - '0'; 920 | ch = read(); 921 | } 922 | 923 | push(ch); 924 | if (negate) 925 | number = -number; 926 | return gotNumber; 927 | } 928 | 929 | void WebServer::readHeader(char *value, int valueLen) 930 | { 931 | int ch; 932 | memset(value, 0, valueLen); 933 | --valueLen; 934 | 935 | // absorb whitespace 936 | do 937 | { 938 | ch = read(); 939 | } while (ch == ' ' || ch == '\t'); 940 | 941 | // read rest of line 942 | do 943 | { 944 | if (valueLen > 1) 945 | { 946 | *value++=ch; 947 | --valueLen; 948 | } 949 | ch = read(); 950 | } while (ch != '\r'); 951 | push(ch); 952 | } 953 | 954 | bool WebServer::readPOSTparam(char *name, int nameLen, 955 | char *value, int valueLen) 956 | { 957 | // assume name is at current place in stream 958 | int ch; 959 | // to not to miss the last parameter 960 | bool foundSomething = false; 961 | 962 | // clear out name and value so they'll be NUL terminated 963 | memset(name, 0, nameLen); 964 | memset(value, 0, valueLen); 965 | 966 | // decrement length so we don't write into NUL terminator 967 | --nameLen; 968 | --valueLen; 969 | 970 | while ((ch = read()) != -1) 971 | { 972 | foundSomething = true; 973 | if (ch == '+') 974 | { 975 | ch = ' '; 976 | } 977 | else if (ch == '=') 978 | { 979 | /* that's end of name, so switch to storing in value */ 980 | nameLen = 0; 981 | continue; 982 | } 983 | else if (ch == '&') 984 | { 985 | /* that's end of pair, go away */ 986 | return true; 987 | } 988 | else if (ch == '%') 989 | { 990 | /* handle URL encoded characters by converting back to original form */ 991 | int ch1 = read(); 992 | int ch2 = read(); 993 | if (ch1 == -1 || ch2 == -1) 994 | return false; 995 | char hex[3] = { (char)ch1, (char)ch2, '\0' }; 996 | ch = strtoul(hex, NULL, 16); 997 | } 998 | 999 | // output the new character into the appropriate buffer or drop it if 1000 | // there's no room in either one. This code will malfunction in the 1001 | // case where the parameter name is too long to fit into the name buffer, 1002 | // but in that case, it will just overflow into the value buffer so 1003 | // there's no harm. 1004 | if (nameLen > 0) 1005 | { 1006 | *name++ = ch; 1007 | --nameLen; 1008 | } 1009 | else if (valueLen > 0) 1010 | { 1011 | *value++ = ch; 1012 | --valueLen; 1013 | } 1014 | } 1015 | 1016 | if (foundSomething) 1017 | { 1018 | // if we get here, we have one last parameter to serve 1019 | return true; 1020 | } 1021 | else 1022 | { 1023 | // if we get here, we hit the end-of-file, so POST is over and there 1024 | // are no more parameters 1025 | return false; 1026 | } 1027 | } 1028 | 1029 | /* Retrieve a parameter that was encoded as part of the URL, stored in 1030 | * the buffer pointed to by *tail. tail is updated to point just past 1031 | * the last character read from the buffer. */ 1032 | URLPARAM_RESULT WebServer::nextURLparam(char **tail, char *name, int nameLen, 1033 | char *value, int valueLen) 1034 | { 1035 | // assume name is at current place in stream 1036 | char ch, hex[3]; 1037 | URLPARAM_RESULT result = URLPARAM_OK; 1038 | char *s = *tail; 1039 | bool keep_scanning = true; 1040 | bool need_value = true; 1041 | 1042 | // clear out name and value so they'll be NUL terminated 1043 | memset(name, 0, nameLen); 1044 | memset(value, 0, valueLen); 1045 | 1046 | if (*s == 0) 1047 | return URLPARAM_EOS; 1048 | // Read the keyword name 1049 | while (keep_scanning) 1050 | { 1051 | ch = *s++; 1052 | switch (ch) 1053 | { 1054 | case 0: 1055 | s--; // Back up to point to terminating NUL 1056 | // Fall through to "stop the scan" code 1057 | case '&': 1058 | /* that's end of pair, go away */ 1059 | keep_scanning = false; 1060 | need_value = false; 1061 | break; 1062 | case '+': 1063 | ch = ' '; 1064 | break; 1065 | case '%': 1066 | /* handle URL encoded characters by converting back 1067 | * to original form */ 1068 | if ((hex[0] = *s++) == 0) 1069 | { 1070 | s--; // Back up to NUL 1071 | keep_scanning = false; 1072 | need_value = false; 1073 | } 1074 | else 1075 | { 1076 | if ((hex[1] = *s++) == 0) 1077 | { 1078 | s--; // Back up to NUL 1079 | keep_scanning = false; 1080 | need_value = false; 1081 | } 1082 | else 1083 | { 1084 | hex[2] = 0; 1085 | ch = strtoul(hex, NULL, 16); 1086 | } 1087 | } 1088 | break; 1089 | case '=': 1090 | /* that's end of name, so switch to storing in value */ 1091 | keep_scanning = false; 1092 | break; 1093 | } 1094 | 1095 | 1096 | // check against 1 so we don't overwrite the final NUL 1097 | if (keep_scanning && (nameLen > 1)) 1098 | { 1099 | *name++ = ch; 1100 | --nameLen; 1101 | } 1102 | else if(keep_scanning) 1103 | result = URLPARAM_NAME_OFLO; 1104 | } 1105 | 1106 | if (need_value && (*s != 0)) 1107 | { 1108 | keep_scanning = true; 1109 | while (keep_scanning) 1110 | { 1111 | ch = *s++; 1112 | switch (ch) 1113 | { 1114 | case 0: 1115 | s--; // Back up to point to terminating NUL 1116 | // Fall through to "stop the scan" code 1117 | case '&': 1118 | /* that's end of pair, go away */ 1119 | keep_scanning = false; 1120 | need_value = false; 1121 | break; 1122 | case '+': 1123 | ch = ' '; 1124 | break; 1125 | case '%': 1126 | /* handle URL encoded characters by converting back to original form */ 1127 | if ((hex[0] = *s++) == 0) 1128 | { 1129 | s--; // Back up to NUL 1130 | keep_scanning = false; 1131 | need_value = false; 1132 | } 1133 | else 1134 | { 1135 | if ((hex[1] = *s++) == 0) 1136 | { 1137 | s--; // Back up to NUL 1138 | keep_scanning = false; 1139 | need_value = false; 1140 | } 1141 | else 1142 | { 1143 | hex[2] = 0; 1144 | ch = strtoul(hex, NULL, 16); 1145 | } 1146 | 1147 | } 1148 | break; 1149 | } 1150 | 1151 | 1152 | // check against 1 so we don't overwrite the final NUL 1153 | if (keep_scanning && (valueLen > 1)) 1154 | { 1155 | *value++ = ch; 1156 | --valueLen; 1157 | } 1158 | else if(keep_scanning) 1159 | result = (result == URLPARAM_OK) ? 1160 | URLPARAM_VALUE_OFLO : 1161 | URLPARAM_BOTH_OFLO; 1162 | } 1163 | } 1164 | *tail = s; 1165 | return result; 1166 | } 1167 | 1168 | 1169 | 1170 | // Read and parse the first line of the request header. 1171 | // The "command" (GET/HEAD/POST) is translated into a numeric value in type. 1172 | // The URL is stored in request, up to the length passed in length 1173 | // NOTE 1: length must include one byte for the terminating NUL. 1174 | // NOTE 2: request is NOT checked for NULL, nor length for a value < 1. 1175 | // Reading stops when the code encounters a space, CR, or LF. If the HTTP 1176 | // version was supplied by the client, it will still be waiting in the input 1177 | // stream when we exit. 1178 | // 1179 | // On return, length contains the amount of space left in request. If it's 1180 | // less than 0, the URL was longer than the buffer, and part of it had to 1181 | // be discarded. 1182 | 1183 | void WebServer::getRequest(WebServer::ConnectionType &type, 1184 | char *request, int *length) 1185 | { 1186 | --*length; // save room for NUL 1187 | 1188 | type = INVALID; 1189 | 1190 | // store the HTTP method line of the request 1191 | if (expect("GET ")) 1192 | type = GET; 1193 | else if (expect("HEAD ")) 1194 | type = HEAD; 1195 | else if (expect("POST ")) 1196 | type = POST; 1197 | else if (expect("PUT ")) 1198 | type = PUT; 1199 | else if (expect("DELETE ")) 1200 | type = DELETE; 1201 | else if (expect("PATCH ")) 1202 | type = PATCH; 1203 | 1204 | // if it doesn't start with any of those, we have an unknown method 1205 | // so just get out of here 1206 | else 1207 | return; 1208 | 1209 | int ch; 1210 | while ((ch = read()) != -1) 1211 | { 1212 | // stop storing at first space or end of line 1213 | if (ch == ' ' || ch == '\n' || ch == '\r') 1214 | { 1215 | break; 1216 | } 1217 | if (*length > 0) 1218 | { 1219 | *request = ch; 1220 | ++request; 1221 | } 1222 | --*length; 1223 | } 1224 | // NUL terminate 1225 | *request = 0; 1226 | } 1227 | 1228 | void WebServer::processHeaders() 1229 | { 1230 | // look for three things: the Content-Length header, the Authorization 1231 | // header, and the double-CRLF that ends the headers. 1232 | 1233 | // empty the m_authCredentials before every run of this function. 1234 | // otherwise users who don't send an Authorization header would be treated 1235 | // like the last user who tried to authenticate (possibly successful) 1236 | m_authCredentials[0]=0; 1237 | 1238 | while (1) 1239 | { 1240 | if (expect("Content-Length:")) 1241 | { 1242 | readInt(m_contentLength); 1243 | #if WEBDUINO_SERIAL_DEBUGGING > 1 1244 | Serial.print("\n*** got Content-Length of "); 1245 | Serial.print(m_contentLength); 1246 | Serial.print(" ***"); 1247 | #endif 1248 | continue; 1249 | } 1250 | 1251 | if (expect("Authorization:")) 1252 | { 1253 | readHeader(m_authCredentials,51); 1254 | #if WEBDUINO_SERIAL_DEBUGGING > 1 1255 | Serial.print("\n*** got Authorization: of "); 1256 | Serial.print(m_authCredentials); 1257 | Serial.print(" ***"); 1258 | #endif 1259 | continue; 1260 | } 1261 | 1262 | if (expect(CRLF CRLF)) 1263 | { 1264 | m_readingContent = true; 1265 | return; 1266 | } 1267 | 1268 | // no expect checks hit, so just absorb a character and try again 1269 | if (read() == -1) 1270 | { 1271 | return; 1272 | } 1273 | } 1274 | } 1275 | 1276 | void WebServer::outputCheckboxOrRadio(const char *element, const char *name, 1277 | const char *val, const char *label, 1278 | bool selected) 1279 | { 1280 | P(cbPart1a) = "