ESP_IOT v2.5
IOT ESP Coding
Parsing.cpp
Go to the documentation of this file.
1/*
2 Parsing.cpp - HTTP request parsing.
3
4 Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
20*/
21
22#include "../../Defines.h"
23#include <Arduino.h>
24#include "WiFiServer.h"
25#include "WiFiClient.h"
26#include "WebServer.h"
27
28//#define DEBUG_ESP_HTTP_SERVER
29#ifdef DEBUG_ESP_PORT
30#define DEBUG_OUTPUT DEBUG_ESP_PORT
31#else
32#define DEBUG_OUTPUT Serial
33#endif
34
35static char* readBytesWithTimeout(WiFiClient& client, size_t maxLength, size_t& dataLength, int timeout_ms)
36{
37 char *buf = nullptr;
38 dataLength = 0;
39 while (dataLength < maxLength) {
40 int tries = timeout_ms;
41 size_t newLength;
42 while (!(newLength = client.available()) && tries--) delay(1);
43 if (!newLength) {
44 break;
45 }
46 if (!buf) {
47 buf = (char *) malloc(newLength + 1);
48 if (!buf) {
49 return nullptr;
50 }
51 }
52 else {
53 char* newBuf = (char *) realloc(buf, dataLength + newLength + 1);
54 if (!newBuf) {
55 free(buf);
56 return nullptr;
57 }
58 buf = newBuf;
59 }
60 client.readBytes(buf + dataLength, newLength);
61 dataLength += newLength;
62 buf[dataLength] = '\0';
63 }
64 return buf;
65}
66
67bool WebServer::_parseRequest(WiFiClient& client) {
68 // Read the first line of HTTP request
69 String req = client.readStringUntil('\r');
70 client.readStringUntil('\n');
71 //reset header value
72 for (int i = 0; i < _headerKeysCount; ++i) {
73 _currentHeaders[i].value =String();
74 }
75
76 // First line of HTTP request looks like "GET /path HTTP/1.1"
77 // Retrieve the "/path" part by finding the spaces
78 int addr_start = req.indexOf(' ');
79 int addr_end = req.indexOf(' ', addr_start + 1);
80 if (addr_start == -1 || addr_end == -1) {
81#ifdef DEBUG_ESP_HTTP_SERVER
82 DEBUG_OUTPUT.print("Invalid request: ");
83 DEBUG_OUTPUT.println(req);
84#endif
85 return false;
86 }
87
88 String methodStr = req.substring(0, addr_start);
89 String url = req.substring(addr_start + 1, addr_end);
90 String versionEnd = req.substring(addr_end + 8);
91 _currentVersion = atoi(versionEnd.c_str());
92 String searchStr = "";
93 int hasSearch = url.indexOf('?');
94 if (hasSearch != -1){
95 searchStr = urlDecode(url.substring(hasSearch + 1));
96 url = url.substring(0, hasSearch);
97 }
98 _currentUri = url;
99 _chunked = false;
100
102 if (methodStr == "POST") {
104 } else if (methodStr == "DELETE") {
106 } else if (methodStr == "OPTIONS") {
108 } else if (methodStr == "PUT") {
110 } else if (methodStr == "PATCH") {
112 }
114
115#ifdef DEBUG_ESP_HTTP_SERVER
116 DEBUG_OUTPUT.print("method: ");
117 DEBUG_OUTPUT.print(methodStr);
118 DEBUG_OUTPUT.print(" url: ");
119 DEBUG_OUTPUT.print(url);
120 DEBUG_OUTPUT.print(" search: ");
121 DEBUG_OUTPUT.println(searchStr);
122#endif
123
124 //attach handler
125 RequestHandler* handler;
126 for (handler = _firstHandler; handler; handler = handler->next()) {
127 if (handler->canHandle(_currentMethod, _currentUri))
128 break;
129 }
130 _currentHandler = handler;
131
132 String formData;
133 // below is needed only when POST type request
135 String boundaryStr;
136 String headerName;
137 String headerValue;
138 bool isForm = false;
139 bool isEncoded = false;
140 uint32_t contentLength = 0;
141 //parse headers
142 while(1){
143 req = client.readStringUntil('\r');
144 client.readStringUntil('\n');
145 if (req == "") break;//no moar headers
146 int headerDiv = req.indexOf(':');
147 if (headerDiv == -1){
148 break;
149 }
150 headerName = req.substring(0, headerDiv);
151 headerValue = req.substring(headerDiv + 1);
152 headerValue.trim();
153 _collectHeader(headerName.c_str(),headerValue.c_str());
154
155 #ifdef DEBUG_ESP_HTTP_SERVER
156 DEBUG_OUTPUT.print("headerName: ");
157 DEBUG_OUTPUT.println(headerName);
158 DEBUG_OUTPUT.print("headerValue: ");
159 DEBUG_OUTPUT.println(headerValue);
160 #endif
161
162 if (headerName.equalsIgnoreCase("Content-Type")){
163 if (headerValue.startsWith("text/plain")){
164 isForm = false;
165 } else if (headerValue.startsWith("application/x-www-form-urlencoded")){
166 isForm = false;
167 isEncoded = true;
168 } else if (headerValue.startsWith("multipart/")){
169 boundaryStr = headerValue.substring(headerValue.indexOf('=')+1);
170 isForm = true;
171 }
172 } else if (headerName.equalsIgnoreCase("Content-Length")){
173 contentLength = headerValue.toInt();
174 } else if (headerName.equalsIgnoreCase("Host")){
175 _hostHeader = headerValue;
176 }
177 }
178
179 if (!isForm){
180 size_t plainLength;
181 char* plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT);
182 if (plainLength < contentLength) {
183 free(plainBuf);
184 return false;
185 }
186 if (contentLength > 0) {
187 if (searchStr != "") searchStr += '&';
188 if(isEncoded){
189 //url encoded form
190 String decoded = urlDecode(plainBuf);
191 size_t decodedLen = decoded.length();
192 memcpy(plainBuf, decoded.c_str(), decodedLen);
193 plainBuf[decodedLen] = 0;
194 searchStr += plainBuf;
195 }
196 _parseArguments(searchStr);
197 if(!isEncoded){
198 //plain post json or other data
200 arg.key = "plain";
201 arg.value = String(plainBuf);
202 }
203
204 #ifdef DEBUG_ESP_HTTP_SERVER
205 DEBUG_OUTPUT.print("Plain: ");
206 DEBUG_OUTPUT.println(plainBuf);
207 #endif
208 free(plainBuf);
209 }
210 }
211
212 if (isForm){
213 _parseArguments(searchStr);
214 if (!_parseForm(client, boundaryStr, contentLength)) {
215 return false;
216 }
217 }
218 } else {
219 String headerName;
220 String headerValue;
221 //parse headers
222 while(1){
223 req = client.readStringUntil('\r');
224 client.readStringUntil('\n');
225 if (req == "") break;//no moar headers
226 int headerDiv = req.indexOf(':');
227 if (headerDiv == -1){
228 break;
229 }
230 headerName = req.substring(0, headerDiv);
231 headerValue = req.substring(headerDiv + 2);
232 _collectHeader(headerName.c_str(),headerValue.c_str());
233
234 #ifdef DEBUG_ESP_HTTP_SERVER
235 DEBUG_OUTPUT.print("headerName: ");
236 DEBUG_OUTPUT.println(headerName);
237 DEBUG_OUTPUT.print("headerValue: ");
238 DEBUG_OUTPUT.println(headerValue);
239 #endif
240
241 if (headerName.equalsIgnoreCase("Host")){
242 _hostHeader = headerValue;
243 }
244 }
245 _parseArguments(searchStr);
246 }
247 client.flush();
248
249#ifdef DEBUG_ESP_HTTP_SERVER
250 DEBUG_OUTPUT.print("Request: ");
251 DEBUG_OUTPUT.println(url);
252 DEBUG_OUTPUT.print(" Arguments: ");
253 DEBUG_OUTPUT.println(searchStr);
254#endif
255
256 return true;
257}
258
259bool WebServer::_collectHeader(const char* headerName, const char* headerValue) {
260 for (int i = 0; i < _headerKeysCount; i++) {
261 if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
262 _currentHeaders[i].value=headerValue;
263 return true;
264 }
265 }
266 return false;
267}
268
269void WebServer::_parseArguments(String data) {
270#ifdef DEBUG_ESP_HTTP_SERVER
271 DEBUG_OUTPUT.print("args: ");
272 DEBUG_OUTPUT.println(data);
273#endif
274 if (_currentArgs)
275 delete[] _currentArgs;
276 _currentArgs = 0;
277 if (data.length() == 0) {
280 return;
281 }
283
284 for (int i = 0; i < (int)data.length(); ) {
285 i = data.indexOf('&', i);
286 if (i == -1)
287 break;
288 ++i;
290 }
291#ifdef DEBUG_ESP_HTTP_SERVER
292 DEBUG_OUTPUT.print("args count: ");
294#endif
295
297 int pos = 0;
298 int iarg;
299 for (iarg = 0; iarg < _currentArgCount;) {
300 int equal_sign_index = data.indexOf('=', pos);
301 int next_arg_index = data.indexOf('&', pos);
302#ifdef DEBUG_ESP_HTTP_SERVER
303 DEBUG_OUTPUT.print("pos ");
304 DEBUG_OUTPUT.print(pos);
305 DEBUG_OUTPUT.print("=@ ");
306 DEBUG_OUTPUT.print(equal_sign_index);
307 DEBUG_OUTPUT.print(" &@ ");
308 DEBUG_OUTPUT.println(next_arg_index);
309#endif
310 if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) {
311#ifdef DEBUG_ESP_HTTP_SERVER
312 DEBUG_OUTPUT.print("arg missing value: ");
313 DEBUG_OUTPUT.println(iarg);
314#endif
315 if (next_arg_index == -1)
316 break;
317 pos = next_arg_index + 1;
318 continue;
319 }
321 arg.key = data.substring(pos, equal_sign_index);
322 arg.value = data.substring(equal_sign_index + 1, next_arg_index);
323#ifdef DEBUG_ESP_HTTP_SERVER
324 DEBUG_OUTPUT.print("arg ");
325 DEBUG_OUTPUT.print(iarg);
326 DEBUG_OUTPUT.print(" key: ");
327 DEBUG_OUTPUT.print(arg.key);
328 DEBUG_OUTPUT.print(" value: ");
329 DEBUG_OUTPUT.println(arg.value);
330#endif
331 ++iarg;
332 if (next_arg_index == -1)
333 break;
334 pos = next_arg_index + 1;
335 }
336 _currentArgCount = iarg;
337#ifdef DEBUG_ESP_HTTP_SERVER
338 DEBUG_OUTPUT.print("args count: ");
340#endif
341
342}
343
350 }
352}
353
354uint8_t WebServer::_uploadReadByte(WiFiClient& client){
355 int res = client.read();
356 if(res == -1){
357 while(!client.available() && client.connected())
358 yield();
359 res = client.read();
360 }
361 return (uint8_t)res;
362}
363
364bool WebServer::_parseForm(WiFiClient& client, String boundary, uint32_t len){
365 (void) len;
366#ifdef DEBUG_ESP_HTTP_SERVER
367 DEBUG_OUTPUT.print("Parse Form: Boundary: ");
368 DEBUG_OUTPUT.print(boundary);
369 DEBUG_OUTPUT.print(" Length: ");
370 DEBUG_OUTPUT.println(len);
371#endif
372 String line;
373 int retry = 0;
374 do {
375 line = client.readStringUntil('\r');
376 ++retry;
377 } while (line.length() == 0 && retry < 3);
378
379 client.readStringUntil('\n');
380 //start reading the form
381 if (line == ("--"+boundary)){
382 RequestArgument* postArgs = new RequestArgument[32];
383 int postArgsLen = 0;
384 while(1){
385 String argName;
386 String argValue;
387 String argType;
388 String argFilename;
389 bool argIsFile = false;
390
391 line = client.readStringUntil('\r');
392 client.readStringUntil('\n');
393 if (line.length() > 19 && line.substring(0, 19).equalsIgnoreCase("Content-Disposition")){
394 int nameStart = line.indexOf('=');
395 if (nameStart != -1){
396 argName = line.substring(nameStart+2);
397 nameStart = argName.indexOf('=');
398 if (nameStart == -1){
399 argName = argName.substring(0, argName.length() - 1);
400 } else {
401 argFilename = argName.substring(nameStart+2, argName.length() - 1);
402 argName = argName.substring(0, argName.indexOf('"'));
403 argIsFile = true;
404#ifdef DEBUG_ESP_HTTP_SERVER
405 DEBUG_OUTPUT.print("PostArg FileName: ");
406 DEBUG_OUTPUT.println(argFilename);
407#endif
408 //use GET to set the filename if uploading using blob
409 if (argFilename == "blob" && hasArg("filename")) argFilename = arg("filename");
410 }
411#ifdef DEBUG_ESP_HTTP_SERVER
412 DEBUG_OUTPUT.print("PostArg Name: ");
413 DEBUG_OUTPUT.println(argName);
414#endif
415 argType = "text/plain";
416 line = client.readStringUntil('\r');
417 client.readStringUntil('\n');
418 if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase("Content-Type")){
419 argType = line.substring(line.indexOf(':')+2);
420 //skip next line
421 client.readStringUntil('\r');
422 client.readStringUntil('\n');
423 }
424#ifdef DEBUG_ESP_HTTP_SERVER
425 DEBUG_OUTPUT.print("PostArg Type: ");
426 DEBUG_OUTPUT.println(argType);
427#endif
428 if (!argIsFile){
429 while(1){
430 line = client.readStringUntil('\r');
431 client.readStringUntil('\n');
432 if (line.startsWith("--"+boundary)) break;
433 if (argValue.length() > 0) argValue += "\n";
434 argValue += line;
435 }
436#ifdef DEBUG_ESP_HTTP_SERVER
437 DEBUG_OUTPUT.print("PostArg Value: ");
438 DEBUG_OUTPUT.println(argValue);
439 DEBUG_OUTPUT.println();
440#endif
441
442 RequestArgument& arg = postArgs[postArgsLen++];
443 arg.key = argName;
444 arg.value = argValue;
445
446 if (line == ("--"+boundary+"--")){
447#ifdef DEBUG_ESP_HTTP_SERVER
448 DEBUG_OUTPUT.println("Done Parsing POST");
449#endif
450 break;
451 }
452 } else {
455 _currentUpload.filename = argFilename;
456 _currentUpload.type = argType;
459#ifdef DEBUG_ESP_HTTP_SERVER
460 DEBUG_OUTPUT.print("Start File: ");
462 DEBUG_OUTPUT.print(" Type: ");
464#endif
468 uint8_t argByte = _uploadReadByte(client);
469readfile:
470 while(argByte != 0x0D){
471 if (!client.connected()) return _parseFormUploadAborted();
472 _uploadWriteByte(argByte);
473 argByte = _uploadReadByte(client);
474 }
475
476 argByte = _uploadReadByte(client);
477 if (!client.connected()) return _parseFormUploadAborted();
478 if (argByte == 0x0A){
479 argByte = _uploadReadByte(client);
480 if (!client.connected()) return _parseFormUploadAborted();
481 if ((char)argByte != '-'){
482 //continue reading the file
483 _uploadWriteByte(0x0D);
484 _uploadWriteByte(0x0A);
485 goto readfile;
486 } else {
487 argByte = _uploadReadByte(client);
488 if (!client.connected()) return _parseFormUploadAborted();
489 if ((char)argByte != '-'){
490 //continue reading the file
491 _uploadWriteByte(0x0D);
492 _uploadWriteByte(0x0A);
493 _uploadWriteByte((uint8_t)('-'));
494 goto readfile;
495 }
496 }
497
498 uint8_t endBuf[boundary.length()];
499 client.readBytes(endBuf, boundary.length());
500
501 if (strstr((const char*)endBuf, boundary.c_str()) != NULL){
508#ifdef DEBUG_ESP_HTTP_SERVER
509 DEBUG_OUTPUT.print("End File: ");
511 DEBUG_OUTPUT.print(" Type: ");
513 DEBUG_OUTPUT.print(" Size: ");
515#endif
516 line = client.readStringUntil(0x0D);
517 client.readStringUntil(0x0A);
518 if (line == "--"){
519#ifdef DEBUG_ESP_HTTP_SERVER
520 DEBUG_OUTPUT.println("Done Parsing POST");
521#endif
522 break;
523 }
524 continue;
525 } else {
526 _uploadWriteByte(0x0D);
527 _uploadWriteByte(0x0A);
528 _uploadWriteByte((uint8_t)('-'));
529 _uploadWriteByte((uint8_t)('-'));
530 uint32_t i = 0;
531 while(i < boundary.length()){
532 _uploadWriteByte(endBuf[i++]);
533 }
534 argByte = _uploadReadByte(client);
535 goto readfile;
536 }
537 } else {
538 _uploadWriteByte(0x0D);
539 goto readfile;
540 }
541 break;
542 }
543 }
544 }
545 }
546
547 int iarg;
548 int totalArgs = ((32 - postArgsLen) < _currentArgCount)?(32 - postArgsLen):_currentArgCount;
549 for (iarg = 0; iarg < totalArgs; iarg++){
550 RequestArgument& arg = postArgs[postArgsLen++];
551 arg.key = _currentArgs[iarg].key;
552 arg.value = _currentArgs[iarg].value;
553 }
554 if (_currentArgs) delete[] _currentArgs;
555 _currentArgs = new RequestArgument[postArgsLen];
556 for (iarg = 0; iarg < postArgsLen; iarg++){
558 arg.key = postArgs[iarg].key;
559 arg.value = postArgs[iarg].value;
560 }
561 _currentArgCount = iarg;
562 if (postArgs) delete[] postArgs;
563 return true;
564 }
565#ifdef DEBUG_ESP_HTTP_SERVER
566 DEBUG_OUTPUT.print("Error: line: ");
567 DEBUG_OUTPUT.println(line);
568#endif
569 return false;
570}
571
572String WebServer::urlDecode(const String& text)
573{
574 String decoded = "";
575 char temp[] = "0x00";
576 unsigned int len = text.length();
577 unsigned int i = 0;
578 while (i < len)
579 {
580 char decodedChar;
581 char encodedChar = text.charAt(i++);
582 if ((encodedChar == '%') && (i + 1 < len))
583 {
584 temp[2] = text.charAt(i++);
585 temp[3] = text.charAt(i++);
586
587 decodedChar = strtol(temp, NULL, 16);
588 }
589 else {
590 if (encodedChar == '+')
591 {
592 decodedChar = ' ';
593 }
594 else {
595 decodedChar = encodedChar; // normal ascii char
596 }
597 }
598 decoded += decodedChar;
599 }
600 return decoded;
601}
602
607 return false;
608}
#define DEBUG_OUTPUT
Definition: Parsing.cpp:32
#define HTTP_MAX_POST_WAIT
Definition: WebServer.h:41
HTTPMethod
Definition: WebServer.h:33
@ HTTP_PUT
Definition: WebServer.h:33
@ HTTP_DELETE
Definition: WebServer.h:33
@ HTTP_GET
Definition: WebServer.h:33
@ HTTP_OPTIONS
Definition: WebServer.h:33
@ HTTP_POST
Definition: WebServer.h:33
@ HTTP_PATCH
Definition: WebServer.h:33
@ UPLOAD_FILE_START
Definition: WebServer.h:34
@ UPLOAD_FILE_END
Definition: WebServer.h:34
@ UPLOAD_FILE_ABORTED
Definition: WebServer.h:35
@ UPLOAD_FILE_WRITE
Definition: WebServer.h:34
#define HTTP_UPLOAD_BUFLEN
Definition: WebServer.h:39
RequestHandler * next()
virtual void upload(WebServer &server, String requestUri, HTTPUpload &upload)
virtual bool canUpload(String uri)
Definition: RequestHandler.h:9
virtual bool canHandle(HTTPMethod method, String uri)
Definition: RequestHandler.h:8
String headerName(int i)
Definition: WebServer.cpp:426
WiFiClient client()
Definition: WebServer.h:93
static String urlDecode(const String &text)
Definition: Parsing.cpp:572
RequestHandler * _currentHandler
Definition: WebServer.h:166
RequestArgument * _currentArgs
Definition: WebServer.h:173
void _parseArguments(String data)
Definition: Parsing.cpp:269
HTTPMethod _currentMethod
Definition: WebServer.h:160
String _currentUri
Definition: WebServer.h:161
String argName(int i)
Definition: WebServer.cpp:382
RequestHandler * _firstHandler
Definition: WebServer.h:167
bool _chunked
Definition: WebServer.h:182
String arg(String name)
Definition: WebServer.cpp:368
bool hasArg(String name)
Definition: WebServer.cpp:392
uint8_t _currentVersion
Definition: WebServer.h:162
int _headerKeysCount
Definition: WebServer.h:176
bool _parseRequest(WiFiClient &client)
Definition: Parsing.cpp:67
RequestArgument * _currentHeaders
Definition: WebServer.h:177
bool _collectHeader(const char *headerName, const char *headerValue)
Definition: Parsing.cpp:259
HTTPUpload _currentUpload
Definition: WebServer.h:174
int _currentArgCount
Definition: WebServer.h:172
bool _parseFormUploadAborted()
Definition: Parsing.cpp:603
String _hostHeader
Definition: WebServer.h:181
bool _parseForm(WiFiClient &client, String boundary, uint32_t len)
Definition: Parsing.cpp:364
uint8_t _uploadReadByte(WiFiClient &client)
Definition: Parsing.cpp:354
HTTPMethod method()
Definition: WebServer.h:92
void _uploadWriteByte(uint8_t b)
Definition: Parsing.cpp:344
size_t currentSize
Definition: WebServer.h:56
uint8_t buf[HTTP_UPLOAD_BUFLEN]
Definition: WebServer.h:57
String type
Definition: WebServer.h:54
String name
Definition: WebServer.h:53
HTTPUploadStatus status
Definition: WebServer.h:51
size_t totalSize
Definition: WebServer.h:55
String filename
Definition: WebServer.h:52