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