first commit
This commit is contained in:
12
libraries/ArduinoHttpClient/src/ArduinoHttpClient.h
Normal file
12
libraries/ArduinoHttpClient/src/ArduinoHttpClient.h
Normal file
@@ -0,0 +1,12 @@
|
||||
// Library to simplify HTTP fetching on Arduino
|
||||
// (c) Copyright Arduino. 2016
|
||||
// Released under Apache License, version 2.0
|
||||
|
||||
#ifndef ArduinoHttpClient_h
|
||||
#define ArduinoHttpClient_h
|
||||
|
||||
#include "HttpClient.h"
|
||||
#include "WebSocketClient.h"
|
||||
#include "URLEncoder.h"
|
||||
|
||||
#endif
|
||||
863
libraries/ArduinoHttpClient/src/HttpClient.cpp
Normal file
863
libraries/ArduinoHttpClient/src/HttpClient.cpp
Normal file
@@ -0,0 +1,863 @@
|
||||
// Class to simplify HTTP fetching on Arduino
|
||||
// (c) Copyright 2010-2011 MCQN Ltd
|
||||
// Released under Apache License, version 2.0
|
||||
|
||||
#include "HttpClient.h"
|
||||
#include "b64.h"
|
||||
|
||||
// Initialize constants
|
||||
const char* HttpClient::kUserAgent = "Arduino/2.2.0";
|
||||
const char* HttpClient::kContentLengthPrefix = HTTP_HEADER_CONTENT_LENGTH ": ";
|
||||
const char* HttpClient::kTransferEncodingChunked = HTTP_HEADER_TRANSFER_ENCODING ": " HTTP_HEADER_VALUE_CHUNKED;
|
||||
|
||||
HttpClient::HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort)
|
||||
: iClient(&aClient), iServerName(aServerName), iServerAddress(), iServerPort(aServerPort),
|
||||
iConnectionClose(true), iSendDefaultRequestHeaders(true)
|
||||
{
|
||||
resetState();
|
||||
}
|
||||
|
||||
HttpClient::HttpClient(Client& aClient, const String& aServerName, uint16_t aServerPort)
|
||||
: HttpClient(aClient, aServerName.c_str(), aServerPort)
|
||||
{
|
||||
}
|
||||
|
||||
HttpClient::HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort)
|
||||
: iClient(&aClient), iServerName(NULL), iServerAddress(aServerAddress), iServerPort(aServerPort),
|
||||
iConnectionClose(true), iSendDefaultRequestHeaders(true)
|
||||
{
|
||||
resetState();
|
||||
}
|
||||
|
||||
void HttpClient::resetState()
|
||||
{
|
||||
iState = eIdle;
|
||||
iStatusCode = 0;
|
||||
iContentLength = kNoContentLengthHeader;
|
||||
iBodyLengthConsumed = 0;
|
||||
iContentLengthPtr = kContentLengthPrefix;
|
||||
iTransferEncodingChunkedPtr = kTransferEncodingChunked;
|
||||
iIsChunked = false;
|
||||
iChunkLength = 0;
|
||||
iHttpResponseTimeout = kHttpResponseTimeout;
|
||||
iHttpWaitForDataDelay = kHttpWaitForDataDelay;
|
||||
}
|
||||
|
||||
void HttpClient::stop()
|
||||
{
|
||||
iClient->stop();
|
||||
resetState();
|
||||
}
|
||||
|
||||
void HttpClient::connectionKeepAlive()
|
||||
{
|
||||
iConnectionClose = false;
|
||||
}
|
||||
|
||||
void HttpClient::noDefaultRequestHeaders()
|
||||
{
|
||||
iSendDefaultRequestHeaders = false;
|
||||
}
|
||||
|
||||
void HttpClient::beginRequest()
|
||||
{
|
||||
iState = eRequestStarted;
|
||||
}
|
||||
|
||||
int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod,
|
||||
const char* aContentType, int aContentLength, const byte aBody[])
|
||||
{
|
||||
if (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk)
|
||||
{
|
||||
flushClientRx();
|
||||
|
||||
resetState();
|
||||
}
|
||||
|
||||
tHttpState initialState = iState;
|
||||
|
||||
if ((eIdle != iState) && (eRequestStarted != iState))
|
||||
{
|
||||
return HTTP_ERROR_API;
|
||||
}
|
||||
|
||||
if (iConnectionClose || !iClient->connected())
|
||||
{
|
||||
if (iServerName)
|
||||
{
|
||||
if (!(iClient->connect(iServerName, iServerPort) > 0))
|
||||
{
|
||||
#ifdef LOGGING
|
||||
Serial.println("Connection failed");
|
||||
#endif
|
||||
return HTTP_ERROR_CONNECTION_FAILED;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(iClient->connect(iServerAddress, iServerPort) > 0))
|
||||
{
|
||||
#ifdef LOGGING
|
||||
Serial.println("Connection failed");
|
||||
#endif
|
||||
return HTTP_ERROR_CONNECTION_FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef LOGGING
|
||||
Serial.println("Connection already open");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Now we're connected, send the first part of the request
|
||||
int ret = sendInitialHeaders(aURLPath, aHttpMethod);
|
||||
|
||||
if (HTTP_SUCCESS == ret)
|
||||
{
|
||||
if (aContentType)
|
||||
{
|
||||
sendHeader(HTTP_HEADER_CONTENT_TYPE, aContentType);
|
||||
}
|
||||
|
||||
if (aContentLength > 0)
|
||||
{
|
||||
sendHeader(HTTP_HEADER_CONTENT_LENGTH, aContentLength);
|
||||
}
|
||||
|
||||
bool hasBody = (aBody && aContentLength > 0);
|
||||
|
||||
if (initialState == eIdle || hasBody)
|
||||
{
|
||||
// This was a simple version of the API, so terminate the headers now
|
||||
finishHeaders();
|
||||
}
|
||||
// else we'll call it in endRequest or in the first call to print, etc.
|
||||
|
||||
if (hasBody)
|
||||
{
|
||||
write(aBody, aContentLength);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int HttpClient::sendInitialHeaders(const char* aURLPath, const char* aHttpMethod)
|
||||
{
|
||||
#ifdef LOGGING
|
||||
Serial.println("Connected");
|
||||
#endif
|
||||
// Send the HTTP command, i.e. "GET /somepath/ HTTP/1.0"
|
||||
iClient->print(aHttpMethod);
|
||||
iClient->print(" ");
|
||||
|
||||
iClient->print(aURLPath);
|
||||
iClient->println(" HTTP/1.1");
|
||||
if (iSendDefaultRequestHeaders)
|
||||
{
|
||||
// The host header, if required
|
||||
if (iServerName)
|
||||
{
|
||||
iClient->print("Host: ");
|
||||
iClient->print(iServerName);
|
||||
if (iServerPort != kHttpPort && iServerPort != kHttpsPort)
|
||||
{
|
||||
iClient->print(":");
|
||||
iClient->print(iServerPort);
|
||||
}
|
||||
iClient->println();
|
||||
}
|
||||
// And user-agent string
|
||||
sendHeader(HTTP_HEADER_USER_AGENT, kUserAgent);
|
||||
}
|
||||
|
||||
if (iConnectionClose)
|
||||
{
|
||||
// Tell the server to
|
||||
// close this connection after we're done
|
||||
sendHeader(HTTP_HEADER_CONNECTION, "close");
|
||||
}
|
||||
|
||||
// Everything has gone well
|
||||
iState = eRequestStarted;
|
||||
return HTTP_SUCCESS;
|
||||
}
|
||||
|
||||
void HttpClient::sendHeader(const char* aHeader)
|
||||
{
|
||||
iClient->println(aHeader);
|
||||
}
|
||||
|
||||
void HttpClient::sendHeader(const char* aHeaderName, const char* aHeaderValue)
|
||||
{
|
||||
iClient->print(aHeaderName);
|
||||
iClient->print(": ");
|
||||
iClient->println(aHeaderValue);
|
||||
}
|
||||
|
||||
void HttpClient::sendHeader(const char* aHeaderName, const int aHeaderValue)
|
||||
{
|
||||
iClient->print(aHeaderName);
|
||||
iClient->print(": ");
|
||||
iClient->println(aHeaderValue);
|
||||
}
|
||||
|
||||
void HttpClient::sendBasicAuth(const char* aUser, const char* aPassword)
|
||||
{
|
||||
// Send the initial part of this header line
|
||||
iClient->print("Authorization: Basic ");
|
||||
// Now Base64 encode "aUser:aPassword" and send that
|
||||
// This seems trickier than it should be but it's mostly to avoid either
|
||||
// (a) some arbitrarily sized buffer which hopes to be big enough, or
|
||||
// (b) allocating and freeing memory
|
||||
// ...so we'll loop through 3 bytes at a time, outputting the results as we
|
||||
// go.
|
||||
// In Base64, each 3 bytes of unencoded data become 4 bytes of encoded data
|
||||
unsigned char input[3];
|
||||
unsigned char output[5]; // Leave space for a '\0' terminator so we can easily print
|
||||
int userLen = strlen(aUser);
|
||||
int passwordLen = strlen(aPassword);
|
||||
int inputOffset = 0;
|
||||
for (int i = 0; i < (userLen+1+passwordLen); i++)
|
||||
{
|
||||
// Copy the relevant input byte into the input
|
||||
if (i < userLen)
|
||||
{
|
||||
input[inputOffset++] = aUser[i];
|
||||
}
|
||||
else if (i == userLen)
|
||||
{
|
||||
input[inputOffset++] = ':';
|
||||
}
|
||||
else
|
||||
{
|
||||
input[inputOffset++] = aPassword[i-(userLen+1)];
|
||||
}
|
||||
// See if we've got a chunk to encode
|
||||
if ( (inputOffset == 3) || (i == userLen+passwordLen) )
|
||||
{
|
||||
// We've either got to a 3-byte boundary, or we've reached then end
|
||||
b64_encode(input, inputOffset, output, 4);
|
||||
// NUL-terminate the output string
|
||||
output[4] = '\0';
|
||||
// And write it out
|
||||
iClient->print((char*)output);
|
||||
// FIXME We might want to fill output with '=' characters if b64_encode doesn't
|
||||
// FIXME do it for us when we're encoding the final chunk
|
||||
inputOffset = 0;
|
||||
}
|
||||
}
|
||||
// And end the header we've sent
|
||||
iClient->println();
|
||||
}
|
||||
|
||||
void HttpClient::finishHeaders()
|
||||
{
|
||||
iClient->println();
|
||||
iState = eRequestSent;
|
||||
}
|
||||
|
||||
void HttpClient::flushClientRx()
|
||||
{
|
||||
while (iClient->available())
|
||||
{
|
||||
iClient->read();
|
||||
}
|
||||
}
|
||||
|
||||
void HttpClient::endRequest()
|
||||
{
|
||||
beginBody();
|
||||
}
|
||||
|
||||
void HttpClient::beginBody()
|
||||
{
|
||||
if (iState < eRequestSent)
|
||||
{
|
||||
// We still need to finish off the headers
|
||||
finishHeaders();
|
||||
}
|
||||
// else the end of headers has already been sent, so nothing to do here
|
||||
}
|
||||
|
||||
int HttpClient::get(const char* aURLPath)
|
||||
{
|
||||
return startRequest(aURLPath, HTTP_METHOD_GET);
|
||||
}
|
||||
|
||||
int HttpClient::get(const String& aURLPath)
|
||||
{
|
||||
return get(aURLPath.c_str());
|
||||
}
|
||||
|
||||
int HttpClient::post(const char* aURLPath)
|
||||
{
|
||||
return startRequest(aURLPath, HTTP_METHOD_POST);
|
||||
}
|
||||
|
||||
int HttpClient::post(const String& aURLPath)
|
||||
{
|
||||
return post(aURLPath.c_str());
|
||||
}
|
||||
|
||||
int HttpClient::post(const char* aURLPath, const char* aContentType, const char* aBody)
|
||||
{
|
||||
return post(aURLPath, aContentType, strlen(aBody), (const byte*)aBody);
|
||||
}
|
||||
|
||||
int HttpClient::post(const String& aURLPath, const String& aContentType, const String& aBody)
|
||||
{
|
||||
return post(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str());
|
||||
}
|
||||
|
||||
int HttpClient::post(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[])
|
||||
{
|
||||
return startRequest(aURLPath, HTTP_METHOD_POST, aContentType, aContentLength, aBody);
|
||||
}
|
||||
|
||||
int HttpClient::put(const char* aURLPath)
|
||||
{
|
||||
return startRequest(aURLPath, HTTP_METHOD_PUT);
|
||||
}
|
||||
|
||||
int HttpClient::put(const String& aURLPath)
|
||||
{
|
||||
return put(aURLPath.c_str());
|
||||
}
|
||||
|
||||
int HttpClient::put(const char* aURLPath, const char* aContentType, const char* aBody)
|
||||
{
|
||||
return put(aURLPath, aContentType, strlen(aBody), (const byte*)aBody);
|
||||
}
|
||||
|
||||
int HttpClient::put(const String& aURLPath, const String& aContentType, const String& aBody)
|
||||
{
|
||||
return put(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str());
|
||||
}
|
||||
|
||||
int HttpClient::put(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[])
|
||||
{
|
||||
return startRequest(aURLPath, HTTP_METHOD_PUT, aContentType, aContentLength, aBody);
|
||||
}
|
||||
|
||||
int HttpClient::patch(const char* aURLPath)
|
||||
{
|
||||
return startRequest(aURLPath, HTTP_METHOD_PATCH);
|
||||
}
|
||||
|
||||
int HttpClient::patch(const String& aURLPath)
|
||||
{
|
||||
return patch(aURLPath.c_str());
|
||||
}
|
||||
|
||||
int HttpClient::patch(const char* aURLPath, const char* aContentType, const char* aBody)
|
||||
{
|
||||
return patch(aURLPath, aContentType, strlen(aBody), (const byte*)aBody);
|
||||
}
|
||||
|
||||
int HttpClient::patch(const String& aURLPath, const String& aContentType, const String& aBody)
|
||||
{
|
||||
return patch(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str());
|
||||
}
|
||||
|
||||
int HttpClient::patch(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[])
|
||||
{
|
||||
return startRequest(aURLPath, HTTP_METHOD_PATCH, aContentType, aContentLength, aBody);
|
||||
}
|
||||
|
||||
int HttpClient::del(const char* aURLPath)
|
||||
{
|
||||
return startRequest(aURLPath, HTTP_METHOD_DELETE);
|
||||
}
|
||||
|
||||
int HttpClient::del(const String& aURLPath)
|
||||
{
|
||||
return del(aURLPath.c_str());
|
||||
}
|
||||
|
||||
int HttpClient::del(const char* aURLPath, const char* aContentType, const char* aBody)
|
||||
{
|
||||
return del(aURLPath, aContentType, strlen(aBody), (const byte*)aBody);
|
||||
}
|
||||
|
||||
int HttpClient::del(const String& aURLPath, const String& aContentType, const String& aBody)
|
||||
{
|
||||
return del(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str());
|
||||
}
|
||||
|
||||
int HttpClient::del(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[])
|
||||
{
|
||||
return startRequest(aURLPath, HTTP_METHOD_DELETE, aContentType, aContentLength, aBody);
|
||||
}
|
||||
|
||||
int HttpClient::responseStatusCode()
|
||||
{
|
||||
if (iState < eRequestSent)
|
||||
{
|
||||
return HTTP_ERROR_API;
|
||||
}
|
||||
// The first line will be of the form Status-Line:
|
||||
// HTTP-Version SP Status-Code SP Reason-Phrase CRLF
|
||||
// Where HTTP-Version is of the form:
|
||||
// HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT
|
||||
|
||||
int c = '\0';
|
||||
do
|
||||
{
|
||||
// Make sure the status code is reset, and likewise the state. This
|
||||
// lets us easily cope with 1xx informational responses by just
|
||||
// ignoring them really, and reading the next line for a proper response
|
||||
iStatusCode = 0;
|
||||
iState = eRequestSent;
|
||||
|
||||
unsigned long timeoutStart = millis();
|
||||
// Psuedo-regexp we're expecting before the status-code
|
||||
const char* statusPrefix = "HTTP/*.* ";
|
||||
const char* statusPtr = statusPrefix;
|
||||
// Whilst we haven't timed out & haven't reached the end of the headers
|
||||
while ((c != '\n') &&
|
||||
( (millis() - timeoutStart) < iHttpResponseTimeout ))
|
||||
{
|
||||
if (available())
|
||||
{
|
||||
c = HttpClient::read();
|
||||
if (c != -1)
|
||||
{
|
||||
switch(iState)
|
||||
{
|
||||
case eRequestSent:
|
||||
// We haven't reached the status code yet
|
||||
if ( (*statusPtr == '*') || (*statusPtr == c) )
|
||||
{
|
||||
// This character matches, just move along
|
||||
statusPtr++;
|
||||
if (*statusPtr == '\0')
|
||||
{
|
||||
// We've reached the end of the prefix
|
||||
iState = eReadingStatusCode;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return HTTP_ERROR_INVALID_RESPONSE;
|
||||
}
|
||||
break;
|
||||
case eReadingStatusCode:
|
||||
if (isdigit(c))
|
||||
{
|
||||
// This assumes we won't get more than the 3 digits we
|
||||
// want
|
||||
iStatusCode = iStatusCode*10 + (c - '0');
|
||||
}
|
||||
else
|
||||
{
|
||||
// We've reached the end of the status code
|
||||
// We could sanity check it here or double-check for ' '
|
||||
// rather than anything else, but let's be lenient
|
||||
iState = eStatusCodeRead;
|
||||
}
|
||||
break;
|
||||
case eStatusCodeRead:
|
||||
// We're just waiting for the end of the line now
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
};
|
||||
// We read something, reset the timeout counter
|
||||
timeoutStart = millis();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We haven't got any data, so let's pause to allow some to
|
||||
// arrive
|
||||
delay(iHttpWaitForDataDelay);
|
||||
}
|
||||
}
|
||||
if ( (c == '\n') && (iStatusCode < 200 && iStatusCode != 101) )
|
||||
{
|
||||
// We've reached the end of an informational status line
|
||||
c = '\0'; // Clear c so we'll go back into the data reading loop
|
||||
}
|
||||
}
|
||||
// If we've read a status code successfully but it's informational (1xx)
|
||||
// loop back to the start
|
||||
while ( (iState == eStatusCodeRead) && (iStatusCode < 200 && iStatusCode != 101) );
|
||||
|
||||
if ( (c == '\n') && (iState == eStatusCodeRead) )
|
||||
{
|
||||
// We've read the status-line successfully
|
||||
return iStatusCode;
|
||||
}
|
||||
else if (c != '\n')
|
||||
{
|
||||
// We must've timed out before we reached the end of the line
|
||||
return HTTP_ERROR_TIMED_OUT;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This wasn't a properly formed status line, or at least not one we
|
||||
// could understand
|
||||
return HTTP_ERROR_INVALID_RESPONSE;
|
||||
}
|
||||
}
|
||||
|
||||
int HttpClient::skipResponseHeaders()
|
||||
{
|
||||
// Just keep reading until we finish reading the headers or time out
|
||||
unsigned long timeoutStart = millis();
|
||||
// Whilst we haven't timed out & haven't reached the end of the headers
|
||||
while ((!endOfHeadersReached()) &&
|
||||
( (millis() - timeoutStart) < iHttpResponseTimeout ))
|
||||
{
|
||||
if (available())
|
||||
{
|
||||
(void)readHeader();
|
||||
// We read something, reset the timeout counter
|
||||
timeoutStart = millis();
|
||||
}
|
||||
else
|
||||
{
|
||||
// We haven't got any data, so let's pause to allow some to
|
||||
// arrive
|
||||
delay(iHttpWaitForDataDelay);
|
||||
}
|
||||
}
|
||||
if (endOfHeadersReached())
|
||||
{
|
||||
// Success
|
||||
return HTTP_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We must've timed out
|
||||
return HTTP_ERROR_TIMED_OUT;
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpClient::endOfHeadersReached()
|
||||
{
|
||||
return (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk);
|
||||
};
|
||||
|
||||
long HttpClient::contentLength()
|
||||
{
|
||||
// skip the response headers, if they haven't been read already
|
||||
if (!endOfHeadersReached())
|
||||
{
|
||||
skipResponseHeaders();
|
||||
}
|
||||
|
||||
return iContentLength;
|
||||
}
|
||||
|
||||
String HttpClient::responseBody()
|
||||
{
|
||||
int bodyLength = contentLength();
|
||||
String response;
|
||||
|
||||
if (bodyLength > 0)
|
||||
{
|
||||
// try to reserve bodyLength bytes
|
||||
if (response.reserve(bodyLength) == 0) {
|
||||
// String reserve failed
|
||||
return String((const char*)NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// keep on timedRead'ing, until:
|
||||
// - we have a content length: body length equals consumed or no bytes
|
||||
// available
|
||||
// - no content length: no bytes are available
|
||||
while (iBodyLengthConsumed != bodyLength)
|
||||
{
|
||||
int c = timedRead();
|
||||
|
||||
if (c == -1) {
|
||||
// read timed out, done
|
||||
break;
|
||||
}
|
||||
|
||||
if (!response.concat((char)c)) {
|
||||
// adding char failed
|
||||
return String((const char*)NULL);
|
||||
}
|
||||
}
|
||||
|
||||
if (bodyLength > 0 && (unsigned int)bodyLength != response.length()) {
|
||||
// failure, we did not read in response content length bytes
|
||||
return String((const char*)NULL);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
bool HttpClient::endOfBodyReached()
|
||||
{
|
||||
if (endOfHeadersReached() && (contentLength() != kNoContentLengthHeader))
|
||||
{
|
||||
// We've got to the body and we know how long it will be
|
||||
return (iBodyLengthConsumed >= contentLength());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int HttpClient::available()
|
||||
{
|
||||
if (iState == eReadingChunkLength)
|
||||
{
|
||||
while (iClient->available())
|
||||
{
|
||||
char c = iClient->read();
|
||||
|
||||
if (c == '\n')
|
||||
{
|
||||
iState = eReadingBodyChunk;
|
||||
break;
|
||||
}
|
||||
else if (c == '\r')
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
else if (isHexadecimalDigit(c))
|
||||
{
|
||||
char digit[2] = {c, '\0'};
|
||||
|
||||
iChunkLength = (iChunkLength * 16) + strtol(digit, NULL, 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (iState == eReadingBodyChunk && iChunkLength == 0)
|
||||
{
|
||||
iState = eReadingChunkLength;
|
||||
}
|
||||
|
||||
if (iState == eReadingChunkLength)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int clientAvailable = iClient->available();
|
||||
|
||||
if (iState == eReadingBodyChunk)
|
||||
{
|
||||
return min(clientAvailable, iChunkLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
return clientAvailable;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int HttpClient::read()
|
||||
{
|
||||
if (iIsChunked && !available())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret = iClient->read();
|
||||
if (ret >= 0)
|
||||
{
|
||||
if (endOfHeadersReached() && iContentLength > 0)
|
||||
{
|
||||
// We're outputting the body now and we've seen a Content-Length header
|
||||
// So keep track of how many bytes are left
|
||||
iBodyLengthConsumed++;
|
||||
}
|
||||
|
||||
if (iState == eReadingBodyChunk)
|
||||
{
|
||||
iChunkLength--;
|
||||
|
||||
if (iChunkLength == 0)
|
||||
{
|
||||
iState = eReadingChunkLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool HttpClient::headerAvailable()
|
||||
{
|
||||
// clear the currently stored header line
|
||||
iHeaderLine = "";
|
||||
|
||||
while (!endOfHeadersReached())
|
||||
{
|
||||
// read a byte from the header
|
||||
int c = readHeader();
|
||||
|
||||
if (c == '\r' || c == '\n')
|
||||
{
|
||||
if (iHeaderLine.length())
|
||||
{
|
||||
// end of the line, all done
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ignore any CR or LF characters
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// append byte to header line
|
||||
iHeaderLine += (char)c;
|
||||
}
|
||||
|
||||
return (iHeaderLine.length() > 0);
|
||||
}
|
||||
|
||||
String HttpClient::readHeaderName()
|
||||
{
|
||||
int colonIndex = iHeaderLine.indexOf(':');
|
||||
|
||||
if (colonIndex == -1)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return iHeaderLine.substring(0, colonIndex);
|
||||
}
|
||||
|
||||
String HttpClient::readHeaderValue()
|
||||
{
|
||||
int colonIndex = iHeaderLine.indexOf(':');
|
||||
int startIndex = colonIndex + 1;
|
||||
|
||||
if (colonIndex == -1)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
// trim any leading whitespace
|
||||
while (startIndex < (int)iHeaderLine.length() && isSpace(iHeaderLine[startIndex]))
|
||||
{
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
return iHeaderLine.substring(startIndex);
|
||||
}
|
||||
|
||||
int HttpClient::read(uint8_t *buf, size_t size)
|
||||
{
|
||||
int ret =iClient->read(buf, size);
|
||||
if (endOfHeadersReached() && iContentLength > 0)
|
||||
{
|
||||
// We're outputting the body now and we've seen a Content-Length header
|
||||
// So keep track of how many bytes are left
|
||||
if (ret >= 0)
|
||||
{
|
||||
iBodyLengthConsumed += ret;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int HttpClient::readHeader()
|
||||
{
|
||||
char c = HttpClient::read();
|
||||
|
||||
if (endOfHeadersReached())
|
||||
{
|
||||
// We've passed the headers, but rather than return an error, we'll just
|
||||
// act as a slightly less efficient version of read()
|
||||
return c;
|
||||
}
|
||||
|
||||
// Whilst reading out the headers to whoever wants them, we'll keep an
|
||||
// eye out for the "Content-Length" header
|
||||
switch(iState)
|
||||
{
|
||||
case eStatusCodeRead:
|
||||
// We're at the start of a line, or somewhere in the middle of reading
|
||||
// the Content-Length prefix
|
||||
if (*iContentLengthPtr == c)
|
||||
{
|
||||
// This character matches, just move along
|
||||
iContentLengthPtr++;
|
||||
if (*iContentLengthPtr == '\0')
|
||||
{
|
||||
// We've reached the end of the prefix
|
||||
iState = eReadingContentLength;
|
||||
// Just in case we get multiple Content-Length headers, this
|
||||
// will ensure we just get the value of the last one
|
||||
iContentLength = 0;
|
||||
iBodyLengthConsumed = 0;
|
||||
}
|
||||
}
|
||||
else if (*iTransferEncodingChunkedPtr == c)
|
||||
{
|
||||
// This character matches, just move along
|
||||
iTransferEncodingChunkedPtr++;
|
||||
if (*iTransferEncodingChunkedPtr == '\0')
|
||||
{
|
||||
// We've reached the end of the Transfer Encoding: chunked header
|
||||
iIsChunked = true;
|
||||
iState = eSkipToEndOfHeader;
|
||||
}
|
||||
}
|
||||
else if (((iContentLengthPtr == kContentLengthPrefix) && (iTransferEncodingChunkedPtr == kTransferEncodingChunked)) && (c == '\r'))
|
||||
{
|
||||
// We've found a '\r' at the start of a line, so this is probably
|
||||
// the end of the headers
|
||||
iState = eLineStartingCRFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This isn't the Content-Length or Transfer Encoding chunked header, skip to the end of the line
|
||||
iState = eSkipToEndOfHeader;
|
||||
}
|
||||
break;
|
||||
case eReadingContentLength:
|
||||
if (isdigit(c))
|
||||
{
|
||||
iContentLength = iContentLength*10 + (c - '0');
|
||||
}
|
||||
else
|
||||
{
|
||||
// We've reached the end of the content length
|
||||
// We could sanity check it here or double-check for "\r\n"
|
||||
// rather than anything else, but let's be lenient
|
||||
iState = eSkipToEndOfHeader;
|
||||
}
|
||||
break;
|
||||
case eLineStartingCRFound:
|
||||
if (c == '\n')
|
||||
{
|
||||
if (iIsChunked)
|
||||
{
|
||||
iState = eReadingChunkLength;
|
||||
iChunkLength = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
iState = eReadingBody;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// We're just waiting for the end of the line now
|
||||
break;
|
||||
};
|
||||
|
||||
if ( (c == '\n') && !endOfHeadersReached() )
|
||||
{
|
||||
// We've got to the end of this line, start processing again
|
||||
iState = eStatusCodeRead;
|
||||
iContentLengthPtr = kContentLengthPrefix;
|
||||
iTransferEncodingChunkedPtr = kTransferEncodingChunked;
|
||||
}
|
||||
// And return the character read to whoever wants it
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
|
||||
396
libraries/ArduinoHttpClient/src/HttpClient.h
Normal file
396
libraries/ArduinoHttpClient/src/HttpClient.h
Normal file
@@ -0,0 +1,396 @@
|
||||
// Class to simplify HTTP fetching on Arduino
|
||||
// (c) Copyright MCQN Ltd. 2010-2012
|
||||
// Released under Apache License, version 2.0
|
||||
|
||||
#ifndef HttpClient_h
|
||||
#define HttpClient_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <IPAddress.h>
|
||||
#include "Client.h"
|
||||
|
||||
static const int HTTP_SUCCESS =0;
|
||||
// The end of the headers has been reached. This consumes the '\n'
|
||||
// Could not connect to the server
|
||||
static const int HTTP_ERROR_CONNECTION_FAILED =-1;
|
||||
// This call was made when the HttpClient class wasn't expecting it
|
||||
// to be called. Usually indicates your code is using the class
|
||||
// incorrectly
|
||||
static const int HTTP_ERROR_API =-2;
|
||||
// Spent too long waiting for a reply
|
||||
static const int HTTP_ERROR_TIMED_OUT =-3;
|
||||
// The response from the server is invalid, is it definitely an HTTP
|
||||
// server?
|
||||
static const int HTTP_ERROR_INVALID_RESPONSE =-4;
|
||||
|
||||
// Define some of the common methods and headers here
|
||||
// That lets other code reuse them without having to declare another copy
|
||||
// of them, so saves code space and RAM
|
||||
#define HTTP_METHOD_GET "GET"
|
||||
#define HTTP_METHOD_POST "POST"
|
||||
#define HTTP_METHOD_PUT "PUT"
|
||||
#define HTTP_METHOD_PATCH "PATCH"
|
||||
#define HTTP_METHOD_DELETE "DELETE"
|
||||
#define HTTP_HEADER_CONTENT_LENGTH "Content-Length"
|
||||
#define HTTP_HEADER_CONTENT_TYPE "Content-Type"
|
||||
#define HTTP_HEADER_CONNECTION "Connection"
|
||||
#define HTTP_HEADER_TRANSFER_ENCODING "Transfer-Encoding"
|
||||
#define HTTP_HEADER_USER_AGENT "User-Agent"
|
||||
#define HTTP_HEADER_VALUE_CHUNKED "chunked"
|
||||
|
||||
class HttpClient : public Client
|
||||
{
|
||||
public:
|
||||
static const int kNoContentLengthHeader =-1;
|
||||
static const int kHttpPort =80;
|
||||
static const int kHttpsPort =443;
|
||||
static const char* kUserAgent;
|
||||
|
||||
// FIXME Write longer API request, using port and user-agent, example
|
||||
// FIXME Update tempToPachube example to calculate Content-Length correctly
|
||||
|
||||
HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort = kHttpPort);
|
||||
HttpClient(Client& aClient, const String& aServerName, uint16_t aServerPort = kHttpPort);
|
||||
HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort = kHttpPort);
|
||||
|
||||
/** Start a more complex request.
|
||||
Use this when you need to send additional headers in the request,
|
||||
but you will also need to call endRequest() when you are finished.
|
||||
*/
|
||||
void beginRequest();
|
||||
|
||||
/** End a more complex request.
|
||||
Use this when you need to have sent additional headers in the request,
|
||||
but you will also need to call beginRequest() at the start.
|
||||
*/
|
||||
void endRequest();
|
||||
|
||||
/** Start the body of a more complex request.
|
||||
Use this when you need to send the body after additional headers
|
||||
in the request, but can optionally call endRequest() when
|
||||
you are finished.
|
||||
*/
|
||||
void beginBody();
|
||||
|
||||
/** Connect to the server and start to send a GET request.
|
||||
@param aURLPath Url to request
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int get(const char* aURLPath);
|
||||
int get(const String& aURLPath);
|
||||
|
||||
/** Connect to the server and start to send a POST request.
|
||||
@param aURLPath Url to request
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int post(const char* aURLPath);
|
||||
int post(const String& aURLPath);
|
||||
|
||||
/** Connect to the server and send a POST request
|
||||
with body and content type
|
||||
@param aURLPath Url to request
|
||||
@param aContentType Content type of request body
|
||||
@param aBody Body of the request
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int post(const char* aURLPath, const char* aContentType, const char* aBody);
|
||||
int post(const String& aURLPath, const String& aContentType, const String& aBody);
|
||||
int post(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]);
|
||||
|
||||
/** Connect to the server and start to send a PUT request.
|
||||
@param aURLPath Url to request
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int put(const char* aURLPath);
|
||||
int put(const String& aURLPath);
|
||||
|
||||
/** Connect to the server and send a PUT request
|
||||
with body and content type
|
||||
@param aURLPath Url to request
|
||||
@param aContentType Content type of request body
|
||||
@param aBody Body of the request
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int put(const char* aURLPath, const char* aContentType, const char* aBody);
|
||||
int put(const String& aURLPath, const String& aContentType, const String& aBody);
|
||||
int put(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]);
|
||||
|
||||
/** Connect to the server and start to send a PATCH request.
|
||||
@param aURLPath Url to request
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int patch(const char* aURLPath);
|
||||
int patch(const String& aURLPath);
|
||||
|
||||
/** Connect to the server and send a PATCH request
|
||||
with body and content type
|
||||
@param aURLPath Url to request
|
||||
@param aContentType Content type of request body
|
||||
@param aBody Body of the request
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int patch(const char* aURLPath, const char* aContentType, const char* aBody);
|
||||
int patch(const String& aURLPath, const String& aContentType, const String& aBody);
|
||||
int patch(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]);
|
||||
|
||||
/** Connect to the server and start to send a DELETE request.
|
||||
@param aURLPath Url to request
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int del(const char* aURLPath);
|
||||
int del(const String& aURLPath);
|
||||
|
||||
/** Connect to the server and send a DELETE request
|
||||
with body and content type
|
||||
@param aURLPath Url to request
|
||||
@param aContentType Content type of request body
|
||||
@param aBody Body of the request
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int del(const char* aURLPath, const char* aContentType, const char* aBody);
|
||||
int del(const String& aURLPath, const String& aContentType, const String& aBody);
|
||||
int del(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]);
|
||||
|
||||
/** Connect to the server and start to send the request.
|
||||
If a body is provided, the entire request (including headers and body) will be sent
|
||||
@param aURLPath Url to request
|
||||
@param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc.
|
||||
@param aContentType Content type of request body (optional)
|
||||
@param aContentLength Length of request body (optional)
|
||||
@param aBody Body of request (optional)
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int startRequest(const char* aURLPath,
|
||||
const char* aHttpMethod,
|
||||
const char* aContentType = NULL,
|
||||
int aContentLength = -1,
|
||||
const byte aBody[] = NULL);
|
||||
|
||||
/** Send an additional header line. This can only be called in between the
|
||||
calls to beginRequest and endRequest.
|
||||
@param aHeader Header line to send, in its entirety (but without the
|
||||
trailing CRLF. E.g. "Authorization: Basic YQDDCAIGES"
|
||||
*/
|
||||
void sendHeader(const char* aHeader);
|
||||
|
||||
void sendHeader(const String& aHeader)
|
||||
{ sendHeader(aHeader.c_str()); }
|
||||
|
||||
/** Send an additional header line. This is an alternate form of
|
||||
sendHeader() which takes the header name and content as separate strings.
|
||||
The call will add the ": " to separate the header, so for example, to
|
||||
send a XXXXXX header call sendHeader("XXXXX", "Something")
|
||||
@param aHeaderName Type of header being sent
|
||||
@param aHeaderValue Value for that header
|
||||
*/
|
||||
void sendHeader(const char* aHeaderName, const char* aHeaderValue);
|
||||
|
||||
void sendHeader(const String& aHeaderName, const String& aHeaderValue)
|
||||
{ sendHeader(aHeaderName.c_str(), aHeaderValue.c_str()); }
|
||||
|
||||
/** Send an additional header line. This is an alternate form of
|
||||
sendHeader() which takes the header name and content separately but where
|
||||
the value is provided as an integer.
|
||||
The call will add the ": " to separate the header, so for example, to
|
||||
send a XXXXXX header call sendHeader("XXXXX", 123)
|
||||
@param aHeaderName Type of header being sent
|
||||
@param aHeaderValue Value for that header
|
||||
*/
|
||||
void sendHeader(const char* aHeaderName, const int aHeaderValue);
|
||||
|
||||
void sendHeader(const String& aHeaderName, const int aHeaderValue)
|
||||
{ sendHeader(aHeaderName.c_str(), aHeaderValue); }
|
||||
|
||||
/** Send a basic authentication header. This will encode the given username
|
||||
and password, and send them in suitable header line for doing Basic
|
||||
Authentication.
|
||||
@param aUser Username for the authorization
|
||||
@param aPassword Password for the user aUser
|
||||
*/
|
||||
void sendBasicAuth(const char* aUser, const char* aPassword);
|
||||
|
||||
void sendBasicAuth(const String& aUser, const String& aPassword)
|
||||
{ sendBasicAuth(aUser.c_str(), aPassword.c_str()); }
|
||||
|
||||
/** Get the HTTP status code contained in the response.
|
||||
For example, 200 for successful request, 404 for file not found, etc.
|
||||
*/
|
||||
int responseStatusCode();
|
||||
|
||||
/** Check if a header is available to be read.
|
||||
Use readHeaderName() to read header name, and readHeaderValue() to
|
||||
read the header value
|
||||
MUST be called after responseStatusCode() and before contentLength()
|
||||
*/
|
||||
bool headerAvailable();
|
||||
|
||||
/** Read the name of the current response header.
|
||||
Returns empty string if a header is not available.
|
||||
*/
|
||||
String readHeaderName();
|
||||
|
||||
/** Read the value of the current response header.
|
||||
Returns empty string if a header is not available.
|
||||
*/
|
||||
String readHeaderValue();
|
||||
|
||||
/** Read the next character of the response headers.
|
||||
This functions in the same way as read() but to be used when reading
|
||||
through the headers. Check whether or not the end of the headers has
|
||||
been reached by calling endOfHeadersReached(), although after that point
|
||||
this will still return data as read() would, but slightly less efficiently
|
||||
MUST be called after responseStatusCode() and before contentLength()
|
||||
@return The next character of the response headers
|
||||
*/
|
||||
int readHeader();
|
||||
|
||||
/** Skip any response headers to get to the body.
|
||||
Use this if you don't want to do any special processing of the headers
|
||||
returned in the response. You can also use it after you've found all of
|
||||
the headers you're interested in, and just want to get on with processing
|
||||
the body.
|
||||
MUST be called after responseStatusCode()
|
||||
@return HTTP_SUCCESS if successful, else an error code
|
||||
*/
|
||||
int skipResponseHeaders();
|
||||
|
||||
/** Test whether all of the response headers have been consumed.
|
||||
@return true if we are now processing the response body, else false
|
||||
*/
|
||||
bool endOfHeadersReached();
|
||||
|
||||
/** Test whether the end of the body has been reached.
|
||||
Only works if the Content-Length header was returned by the server
|
||||
@return true if we are now at the end of the body, else false
|
||||
*/
|
||||
bool endOfBodyReached();
|
||||
virtual bool endOfStream() { return endOfBodyReached(); };
|
||||
virtual bool completed() { return endOfBodyReached(); };
|
||||
|
||||
/** Return the length of the body.
|
||||
Also skips response headers if they have not been read already
|
||||
MUST be called after responseStatusCode()
|
||||
@return Length of the body, in bytes, or kNoContentLengthHeader if no
|
||||
Content-Length header was returned by the server
|
||||
*/
|
||||
long contentLength();
|
||||
|
||||
/** Returns if the response body is chunked
|
||||
@return true if response body is chunked, false otherwise
|
||||
*/
|
||||
int isResponseChunked() { return iIsChunked; }
|
||||
|
||||
/** Return the response body as a String
|
||||
Also skips response headers if they have not been read already
|
||||
MUST be called after responseStatusCode()
|
||||
@return response body of request as a String
|
||||
*/
|
||||
String responseBody();
|
||||
|
||||
/** Enables connection keep-alive mode
|
||||
*/
|
||||
void connectionKeepAlive();
|
||||
|
||||
/** Disables sending the default request headers (Host and User Agent)
|
||||
*/
|
||||
void noDefaultRequestHeaders();
|
||||
|
||||
// Inherited from Print
|
||||
// Note: 1st call to these indicates the user is sending the body, so if need
|
||||
// Note: be we should finish the header first
|
||||
virtual size_t write(uint8_t aByte) { if (iState < eRequestSent) { finishHeaders(); }; return iClient-> write(aByte); };
|
||||
virtual size_t write(const uint8_t *aBuffer, size_t aSize) { if (iState < eRequestSent) { finishHeaders(); }; return iClient->write(aBuffer, aSize); };
|
||||
// Inherited from Stream
|
||||
virtual int available();
|
||||
/** Read the next byte from the server.
|
||||
@return Byte read or -1 if there are no bytes available.
|
||||
*/
|
||||
virtual int read();
|
||||
virtual int read(uint8_t *buf, size_t size);
|
||||
virtual int peek() { return iClient->peek(); };
|
||||
virtual void flush() { iClient->flush(); };
|
||||
|
||||
// Inherited from Client
|
||||
virtual int connect(IPAddress ip, uint16_t port) { return iClient->connect(ip, port); };
|
||||
virtual int connect(const char *host, uint16_t port) { return iClient->connect(host, port); };
|
||||
virtual void stop();
|
||||
virtual uint8_t connected() { return iClient->connected(); };
|
||||
virtual operator bool() { return bool(iClient); };
|
||||
virtual uint32_t httpResponseTimeout() { return iHttpResponseTimeout; };
|
||||
virtual void setHttpResponseTimeout(uint32_t timeout) { iHttpResponseTimeout = timeout; };
|
||||
virtual uint32_t httpWaitForDataDelay() { return iHttpWaitForDataDelay; };
|
||||
virtual void setHttpWaitForDataDelay(uint32_t delay) { iHttpWaitForDataDelay = delay; };
|
||||
protected:
|
||||
/** Reset internal state data back to the "just initialised" state
|
||||
*/
|
||||
void resetState();
|
||||
|
||||
/** Send the first part of the request and the initial headers.
|
||||
@param aURLPath Url to request
|
||||
@param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc.
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int sendInitialHeaders(const char* aURLPath,
|
||||
const char* aHttpMethod);
|
||||
|
||||
/* Let the server know that we've reached the end of the headers
|
||||
*/
|
||||
void finishHeaders();
|
||||
|
||||
/** Reading any pending data from the client (used in connection keep alive mode)
|
||||
*/
|
||||
void flushClientRx();
|
||||
|
||||
// Number of milliseconds that we wait each time there isn't any data
|
||||
// available to be read (during status code and header processing)
|
||||
static const int kHttpWaitForDataDelay = 100;
|
||||
// Number of milliseconds that we'll wait in total without receiving any
|
||||
// data before returning HTTP_ERROR_TIMED_OUT (during status code and header
|
||||
// processing)
|
||||
static const int kHttpResponseTimeout = 30*1000;
|
||||
static const char* kContentLengthPrefix;
|
||||
static const char* kTransferEncodingChunked;
|
||||
typedef enum {
|
||||
eIdle,
|
||||
eRequestStarted,
|
||||
eRequestSent,
|
||||
eReadingStatusCode,
|
||||
eStatusCodeRead,
|
||||
eReadingContentLength,
|
||||
eSkipToEndOfHeader,
|
||||
eLineStartingCRFound,
|
||||
eReadingBody,
|
||||
eReadingChunkLength,
|
||||
eReadingBodyChunk
|
||||
} tHttpState;
|
||||
// Client we're using
|
||||
Client* iClient;
|
||||
// Server we are connecting to
|
||||
const char* iServerName;
|
||||
IPAddress iServerAddress;
|
||||
// Port of server we are connecting to
|
||||
uint16_t iServerPort;
|
||||
// Current state of the finite-state-machine
|
||||
tHttpState iState;
|
||||
// Stores the status code for the response, once known
|
||||
int iStatusCode;
|
||||
// Stores the value of the Content-Length header, if present
|
||||
long iContentLength;
|
||||
// How many bytes of the response body have been read by the user
|
||||
int iBodyLengthConsumed;
|
||||
// How far through a Content-Length header prefix we are
|
||||
const char* iContentLengthPtr;
|
||||
// How far through a Transfer-Encoding chunked header we are
|
||||
const char* iTransferEncodingChunkedPtr;
|
||||
// Stores if the response body is chunked
|
||||
bool iIsChunked;
|
||||
// Stores the value of the current chunk length, if present
|
||||
int iChunkLength;
|
||||
uint32_t iHttpResponseTimeout;
|
||||
uint32_t iHttpWaitForDataDelay;
|
||||
bool iConnectionClose;
|
||||
bool iSendDefaultRequestHeaders;
|
||||
String iHeaderLine;
|
||||
};
|
||||
|
||||
#endif
|
||||
53
libraries/ArduinoHttpClient/src/URLEncoder.cpp
Normal file
53
libraries/ArduinoHttpClient/src/URLEncoder.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
// Library to simplify HTTP fetching on Arduino
|
||||
// (c) Copyright Arduino. 2019
|
||||
// Released under Apache License, version 2.0
|
||||
|
||||
#include "URLEncoder.h"
|
||||
|
||||
URLEncoderClass::URLEncoderClass()
|
||||
{
|
||||
}
|
||||
|
||||
URLEncoderClass::~URLEncoderClass()
|
||||
{
|
||||
}
|
||||
|
||||
String URLEncoderClass::encode(const char* str)
|
||||
{
|
||||
return encode(str, strlen(str));
|
||||
}
|
||||
|
||||
String URLEncoderClass::encode(const String& str)
|
||||
{
|
||||
return encode(str.c_str(), str.length());
|
||||
}
|
||||
|
||||
String URLEncoderClass::encode(const char* str, int length)
|
||||
{
|
||||
String encoded;
|
||||
|
||||
encoded.reserve(length);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = str[i];
|
||||
|
||||
const char HEX_DIGIT_MAPPER[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
|
||||
|
||||
if (isAlphaNumeric(c) || (c == '-') || (c == '.') || (c == '_') || (c == '~')) {
|
||||
encoded += c;
|
||||
} else {
|
||||
char s[4];
|
||||
|
||||
s[0] = '%';
|
||||
s[1] = HEX_DIGIT_MAPPER[(c >> 4) & 0xf];
|
||||
s[2] = HEX_DIGIT_MAPPER[(c & 0x0f)];
|
||||
s[3] = 0;
|
||||
|
||||
encoded += s;
|
||||
}
|
||||
}
|
||||
|
||||
return encoded;
|
||||
}
|
||||
|
||||
URLEncoderClass URLEncoder;
|
||||
25
libraries/ArduinoHttpClient/src/URLEncoder.h
Normal file
25
libraries/ArduinoHttpClient/src/URLEncoder.h
Normal file
@@ -0,0 +1,25 @@
|
||||
// Library to simplify HTTP fetching on Arduino
|
||||
// (c) Copyright Arduino. 2019
|
||||
// Released under Apache License, version 2.0
|
||||
|
||||
#ifndef URL_ENCODER_H
|
||||
#define URL_ENCODER_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
class URLEncoderClass
|
||||
{
|
||||
public:
|
||||
URLEncoderClass();
|
||||
virtual ~URLEncoderClass();
|
||||
|
||||
static String encode(const char* str);
|
||||
static String encode(const String& str);
|
||||
|
||||
private:
|
||||
static String encode(const char* str, int length);
|
||||
};
|
||||
|
||||
extern URLEncoderClass URLEncoder;
|
||||
|
||||
#endif
|
||||
108
libraries/ArduinoHttpClient/src/URLParser.h
Normal file
108
libraries/ArduinoHttpClient/src/URLParser.h
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* PackageLicenseDeclared: Apache-2.0
|
||||
* Copyright (c) 2017 ARM Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The following class is defined in mbed libraries, in case of STM32H7 include the original library
|
||||
*/
|
||||
#if defined __has_include
|
||||
# if __has_include(<utility/http_parsed_url.h>)
|
||||
# include <utility/http_parsed_url.h>
|
||||
# else
|
||||
# define NO_HTTP_PARSED
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef NO_HTTP_PARSED
|
||||
#ifndef _MBED_HTTP_PARSED_URL_H_
|
||||
#define _MBED_HTTP_PARSED_URL_H_
|
||||
|
||||
#include "utility/URLParser/http_parser.h"
|
||||
|
||||
class ParsedUrl {
|
||||
public:
|
||||
ParsedUrl(const char* url) {
|
||||
struct http_parser_url parsed_url;
|
||||
http_parser_parse_url(url, strlen(url), false, &parsed_url);
|
||||
|
||||
for (size_t ix = 0; ix < UF_MAX; ix++) {
|
||||
char* value;
|
||||
if (parsed_url.field_set & (1 << ix)) {
|
||||
value = (char*)calloc(parsed_url.field_data[ix].len + 1, 1);
|
||||
memcpy(value, url + parsed_url.field_data[ix].off,
|
||||
parsed_url.field_data[ix].len);
|
||||
}
|
||||
else {
|
||||
value = (char*)calloc(1, 1);
|
||||
}
|
||||
|
||||
switch ((http_parser_url_fields)ix) {
|
||||
case UF_SCHEMA: _schema = value; break;
|
||||
case UF_HOST: _host = value; break;
|
||||
case UF_PATH: _path = value; break;
|
||||
case UF_QUERY: _query = value; break;
|
||||
case UF_USERINFO: _userinfo = value; break;
|
||||
default:
|
||||
// PORT is already parsed, FRAGMENT is not relevant for HTTP requests
|
||||
free(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_port = parsed_url.port;
|
||||
if (!_port) {
|
||||
if (strcmp(_schema, "https") == 0 || strcmp(_schema, "wss") == 0) {
|
||||
_port = 443;
|
||||
}
|
||||
else {
|
||||
_port = 80;
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(_path, "") == 0) {
|
||||
free(_path);
|
||||
_path = (char*)calloc(2, 1);
|
||||
_path[0] = '/';
|
||||
}
|
||||
}
|
||||
|
||||
~ParsedUrl() {
|
||||
if (_schema) free(_schema);
|
||||
if (_host) free(_host);
|
||||
if (_path) free(_path);
|
||||
if (_query) free(_query);
|
||||
if (_userinfo) free(_userinfo);
|
||||
}
|
||||
|
||||
uint16_t port() const { return _port; }
|
||||
char* schema() const { return _schema; }
|
||||
char* host() const { return _host; }
|
||||
char* path() const { return _path; }
|
||||
char* query() const { return _query; }
|
||||
char* userinfo() const { return _userinfo; }
|
||||
|
||||
private:
|
||||
uint16_t _port;
|
||||
char* _schema;
|
||||
char* _host;
|
||||
char* _path;
|
||||
char* _query;
|
||||
char* _userinfo;
|
||||
};
|
||||
|
||||
#endif // _MBED_HTTP_PARSED_URL_H_
|
||||
#endif // NO_HTTP_PARSED
|
||||
#undef NO_HTTP_PARSED
|
||||
372
libraries/ArduinoHttpClient/src/WebSocketClient.cpp
Normal file
372
libraries/ArduinoHttpClient/src/WebSocketClient.cpp
Normal file
@@ -0,0 +1,372 @@
|
||||
// (c) Copyright Arduino. 2016
|
||||
// Released under Apache License, version 2.0
|
||||
|
||||
#include "b64.h"
|
||||
|
||||
#include "WebSocketClient.h"
|
||||
|
||||
WebSocketClient::WebSocketClient(Client& aClient, const char* aServerName, uint16_t aServerPort)
|
||||
: HttpClient(aClient, aServerName, aServerPort),
|
||||
iTxStarted(false),
|
||||
iRxSize(0)
|
||||
{
|
||||
}
|
||||
|
||||
WebSocketClient::WebSocketClient(Client& aClient, const String& aServerName, uint16_t aServerPort)
|
||||
: HttpClient(aClient, aServerName, aServerPort),
|
||||
iTxStarted(false),
|
||||
iRxSize(0)
|
||||
{
|
||||
}
|
||||
|
||||
WebSocketClient::WebSocketClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort)
|
||||
: HttpClient(aClient, aServerAddress, aServerPort),
|
||||
iTxStarted(false),
|
||||
iRxSize(0)
|
||||
{
|
||||
}
|
||||
|
||||
int WebSocketClient::begin(const char* aPath)
|
||||
{
|
||||
// start the GET request
|
||||
beginRequest();
|
||||
connectionKeepAlive();
|
||||
int status = get(aPath);
|
||||
|
||||
if (status == 0)
|
||||
{
|
||||
uint8_t randomKey[16];
|
||||
char base64RandomKey[25];
|
||||
|
||||
// create a random key for the connection upgrade
|
||||
for (int i = 0; i < (int)sizeof(randomKey); i++)
|
||||
{
|
||||
randomKey[i] = random(0x01, 0xff);
|
||||
}
|
||||
memset(base64RandomKey, 0x00, sizeof(base64RandomKey));
|
||||
b64_encode(randomKey, sizeof(randomKey), (unsigned char*)base64RandomKey, sizeof(base64RandomKey));
|
||||
|
||||
// start the connection upgrade sequence
|
||||
sendHeader("Upgrade", "websocket");
|
||||
sendHeader("Connection", "Upgrade");
|
||||
sendHeader("Sec-WebSocket-Key", base64RandomKey);
|
||||
sendHeader("Sec-WebSocket-Version", "13");
|
||||
endRequest();
|
||||
|
||||
status = responseStatusCode();
|
||||
|
||||
if (status > 0)
|
||||
{
|
||||
skipResponseHeaders();
|
||||
}
|
||||
}
|
||||
|
||||
iRxSize = 0;
|
||||
|
||||
// status code of 101 means success
|
||||
return (status == 101) ? 0 : status;
|
||||
}
|
||||
|
||||
int WebSocketClient::begin(const String& aPath)
|
||||
{
|
||||
return begin(aPath.c_str());
|
||||
}
|
||||
|
||||
int WebSocketClient::beginMessage(int aType)
|
||||
{
|
||||
if (iTxStarted)
|
||||
{
|
||||
// fail TX already started
|
||||
return 1;
|
||||
}
|
||||
|
||||
iTxStarted = true;
|
||||
iTxMessageType = (aType & 0xf);
|
||||
iTxSize = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WebSocketClient::endMessage()
|
||||
{
|
||||
if (!iTxStarted)
|
||||
{
|
||||
// fail TX not started
|
||||
return 1;
|
||||
}
|
||||
|
||||
// send FIN + the message type (opcode)
|
||||
HttpClient::write(0x80 | iTxMessageType);
|
||||
|
||||
// the message is masked (0x80)
|
||||
// send the length
|
||||
if (iTxSize < 126)
|
||||
{
|
||||
HttpClient::write(0x80 | (uint8_t)iTxSize);
|
||||
}
|
||||
else if (iTxSize < 0xffff)
|
||||
{
|
||||
HttpClient::write(0x80 | 126);
|
||||
HttpClient::write((iTxSize >> 8) & 0xff);
|
||||
HttpClient::write((iTxSize >> 0) & 0xff);
|
||||
}
|
||||
else
|
||||
{
|
||||
HttpClient::write(0x80 | 127);
|
||||
HttpClient::write((iTxSize >> 56) & 0xff);
|
||||
HttpClient::write((iTxSize >> 48) & 0xff);
|
||||
HttpClient::write((iTxSize >> 40) & 0xff);
|
||||
HttpClient::write((iTxSize >> 32) & 0xff);
|
||||
HttpClient::write((iTxSize >> 24) & 0xff);
|
||||
HttpClient::write((iTxSize >> 16) & 0xff);
|
||||
HttpClient::write((iTxSize >> 8) & 0xff);
|
||||
HttpClient::write((iTxSize >> 0) & 0xff);
|
||||
}
|
||||
|
||||
uint8_t maskKey[4];
|
||||
|
||||
// create a random mask for the data and send
|
||||
for (int i = 0; i < (int)sizeof(maskKey); i++)
|
||||
{
|
||||
maskKey[i] = random(0xff);
|
||||
}
|
||||
HttpClient::write(maskKey, sizeof(maskKey));
|
||||
|
||||
// mask the data and send
|
||||
for (int i = 0; i < (int)iTxSize; i++) {
|
||||
iTxBuffer[i] ^= maskKey[i % sizeof(maskKey)];
|
||||
}
|
||||
|
||||
size_t txSize = iTxSize;
|
||||
|
||||
iTxStarted = false;
|
||||
iTxSize = 0;
|
||||
|
||||
return (HttpClient::write(iTxBuffer, txSize) == txSize) ? 0 : 1;
|
||||
}
|
||||
|
||||
size_t WebSocketClient::write(uint8_t aByte)
|
||||
{
|
||||
return write(&aByte, sizeof(aByte));
|
||||
}
|
||||
|
||||
size_t WebSocketClient::write(const uint8_t *aBuffer, size_t aSize)
|
||||
{
|
||||
if (iState < eReadingBody)
|
||||
{
|
||||
// have not upgraded the connection yet
|
||||
return HttpClient::write(aBuffer, aSize);
|
||||
}
|
||||
|
||||
if (!iTxStarted)
|
||||
{
|
||||
// fail TX not started
|
||||
return 0;
|
||||
}
|
||||
|
||||
// check if the write size, fits in the buffer
|
||||
if ((iTxSize + aSize) > sizeof(iTxBuffer))
|
||||
{
|
||||
aSize = sizeof(iTxSize) - iTxSize;
|
||||
}
|
||||
|
||||
// copy data into the buffer
|
||||
memcpy(iTxBuffer + iTxSize, aBuffer, aSize);
|
||||
|
||||
iTxSize += aSize;
|
||||
|
||||
return aSize;
|
||||
}
|
||||
|
||||
int WebSocketClient::parseMessage()
|
||||
{
|
||||
flushRx();
|
||||
|
||||
// make sure 2 bytes (opcode + length)
|
||||
// are available
|
||||
if (HttpClient::available() < 2)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// read open code and length
|
||||
uint8_t opcode = HttpClient::read();
|
||||
int length = HttpClient::read();
|
||||
|
||||
if ((opcode & 0x0f) == 0)
|
||||
{
|
||||
// continuation, use previous opcode and update flags
|
||||
iRxOpCode |= opcode;
|
||||
}
|
||||
else
|
||||
{
|
||||
iRxOpCode = opcode;
|
||||
}
|
||||
|
||||
iRxMasked = (length & 0x80);
|
||||
length &= 0x7f;
|
||||
|
||||
// read the RX size
|
||||
if (length < 126)
|
||||
{
|
||||
iRxSize = length;
|
||||
}
|
||||
else if (length == 126)
|
||||
{
|
||||
iRxSize = (HttpClient::read() << 8) | HttpClient::read();
|
||||
}
|
||||
else
|
||||
{
|
||||
iRxSize = ((uint64_t)HttpClient::read() << 56) |
|
||||
((uint64_t)HttpClient::read() << 48) |
|
||||
((uint64_t)HttpClient::read() << 40) |
|
||||
((uint64_t)HttpClient::read() << 32) |
|
||||
((uint64_t)HttpClient::read() << 24) |
|
||||
((uint64_t)HttpClient::read() << 16) |
|
||||
((uint64_t)HttpClient::read() << 8) |
|
||||
(uint64_t)HttpClient::read();
|
||||
}
|
||||
|
||||
// read in the mask, if present
|
||||
if (iRxMasked)
|
||||
{
|
||||
for (int i = 0; i < (int)sizeof(iRxMaskKey); i++)
|
||||
{
|
||||
iRxMaskKey[i] = HttpClient::read();
|
||||
}
|
||||
}
|
||||
|
||||
iRxMaskIndex = 0;
|
||||
|
||||
if (TYPE_CONNECTION_CLOSE == messageType())
|
||||
{
|
||||
flushRx();
|
||||
stop();
|
||||
iRxSize = 0;
|
||||
}
|
||||
else if (TYPE_PING == messageType())
|
||||
{
|
||||
beginMessage(TYPE_PONG);
|
||||
while(available())
|
||||
{
|
||||
write(read());
|
||||
}
|
||||
endMessage();
|
||||
|
||||
iRxSize = 0;
|
||||
}
|
||||
else if (TYPE_PONG == messageType())
|
||||
{
|
||||
flushRx();
|
||||
iRxSize = 0;
|
||||
}
|
||||
|
||||
return iRxSize;
|
||||
}
|
||||
|
||||
int WebSocketClient::messageType()
|
||||
{
|
||||
return (iRxOpCode & 0x0f);
|
||||
}
|
||||
|
||||
bool WebSocketClient::isFinal()
|
||||
{
|
||||
return ((iRxOpCode & 0x80) != 0);
|
||||
}
|
||||
|
||||
String WebSocketClient::readString()
|
||||
{
|
||||
int avail = available();
|
||||
String s;
|
||||
|
||||
if (avail > 0)
|
||||
{
|
||||
s.reserve(avail);
|
||||
|
||||
for (int i = 0; i < avail; i++)
|
||||
{
|
||||
s += (char)read();
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int WebSocketClient::ping()
|
||||
{
|
||||
uint8_t pingData[16];
|
||||
|
||||
// create random data for the ping
|
||||
for (int i = 0; i < (int)sizeof(pingData); i++)
|
||||
{
|
||||
pingData[i] = random(0xff);
|
||||
}
|
||||
|
||||
beginMessage(TYPE_PING);
|
||||
write(pingData, sizeof(pingData));
|
||||
return endMessage();
|
||||
}
|
||||
|
||||
int WebSocketClient::available()
|
||||
{
|
||||
if (iState < eReadingBody)
|
||||
{
|
||||
return HttpClient::available();
|
||||
}
|
||||
|
||||
return iRxSize;
|
||||
}
|
||||
|
||||
int WebSocketClient::read()
|
||||
{
|
||||
byte b;
|
||||
|
||||
if (read(&b, sizeof(b)))
|
||||
{
|
||||
return b;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int WebSocketClient::read(uint8_t *aBuffer, size_t aSize)
|
||||
{
|
||||
int readCount = HttpClient::read(aBuffer, aSize);
|
||||
|
||||
if (readCount > 0)
|
||||
{
|
||||
iRxSize -= readCount;
|
||||
|
||||
// unmask the RX data if needed
|
||||
if (iRxMasked)
|
||||
{
|
||||
for (int i = 0; i < (int)aSize; i++, iRxMaskIndex++)
|
||||
{
|
||||
aBuffer[i] ^= iRxMaskKey[iRxMaskIndex % sizeof(iRxMaskKey)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return readCount;
|
||||
}
|
||||
|
||||
int WebSocketClient::peek()
|
||||
{
|
||||
int p = HttpClient::peek();
|
||||
|
||||
if (p != -1 && iRxMasked)
|
||||
{
|
||||
// unmask the RX data if needed
|
||||
p = (uint8_t)p ^ iRxMaskKey[iRxMaskIndex % sizeof(iRxMaskKey)];
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void WebSocketClient::flushRx()
|
||||
{
|
||||
while(available())
|
||||
{
|
||||
read();
|
||||
}
|
||||
}
|
||||
103
libraries/ArduinoHttpClient/src/WebSocketClient.h
Normal file
103
libraries/ArduinoHttpClient/src/WebSocketClient.h
Normal file
@@ -0,0 +1,103 @@
|
||||
// (c) Copyright Arduino. 2016
|
||||
// Released under Apache License, version 2.0
|
||||
|
||||
#ifndef WebSocketClient_h
|
||||
#define WebSocketClient_h
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "HttpClient.h"
|
||||
|
||||
#ifndef WS_TX_BUFFER_SIZE
|
||||
#define WS_TX_BUFFER_SIZE 128
|
||||
#endif
|
||||
|
||||
static const int TYPE_CONTINUATION = 0x0;
|
||||
static const int TYPE_TEXT = 0x1;
|
||||
static const int TYPE_BINARY = 0x2;
|
||||
static const int TYPE_CONNECTION_CLOSE = 0x8;
|
||||
static const int TYPE_PING = 0x9;
|
||||
static const int TYPE_PONG = 0xa;
|
||||
|
||||
class WebSocketClient : public HttpClient
|
||||
{
|
||||
public:
|
||||
WebSocketClient(Client& aClient, const char* aServerName, uint16_t aServerPort = HttpClient::kHttpPort);
|
||||
WebSocketClient(Client& aClient, const String& aServerName, uint16_t aServerPort = HttpClient::kHttpPort);
|
||||
WebSocketClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort = HttpClient::kHttpPort);
|
||||
|
||||
/** Start the Web Socket connection to the specified path
|
||||
@param aURLPath Path to use in request (optional, "/" is used by default)
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int begin(const char* aPath = "/");
|
||||
int begin(const String& aPath);
|
||||
|
||||
/** Begin to send a message of type (TYPE_TEXT or TYPE_BINARY)
|
||||
Use the write or Stream API's to set message content, followed by endMessage
|
||||
to complete the message.
|
||||
@param aURLPath Path to use in request
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int beginMessage(int aType);
|
||||
|
||||
/** Completes sending of a message started by beginMessage
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int endMessage();
|
||||
|
||||
/** Try to parse an incoming messages
|
||||
@return 0 if no message available, else size of parsed message
|
||||
*/
|
||||
int parseMessage();
|
||||
|
||||
/** Returns type of current parsed message
|
||||
@return type of current parsedMessage (TYPE_TEXT or TYPE_BINARY)
|
||||
*/
|
||||
int messageType();
|
||||
|
||||
/** Returns if the current message is the final chunk of a split
|
||||
message
|
||||
@return true for final message, false otherwise
|
||||
*/
|
||||
bool isFinal();
|
||||
|
||||
/** Read the current messages as a string
|
||||
@return current message as a string
|
||||
*/
|
||||
String readString();
|
||||
|
||||
/** Send a ping
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int ping();
|
||||
|
||||
// Inherited from Print
|
||||
virtual size_t write(uint8_t aByte);
|
||||
virtual size_t write(const uint8_t *aBuffer, size_t aSize);
|
||||
// Inherited from Stream
|
||||
virtual int available();
|
||||
/** Read the next byte from the server.
|
||||
@return Byte read or -1 if there are no bytes available.
|
||||
*/
|
||||
virtual int read();
|
||||
virtual int read(uint8_t *buf, size_t size);
|
||||
virtual int peek();
|
||||
|
||||
private:
|
||||
void flushRx();
|
||||
|
||||
private:
|
||||
bool iTxStarted;
|
||||
uint8_t iTxMessageType;
|
||||
uint8_t iTxBuffer[WS_TX_BUFFER_SIZE];
|
||||
uint64_t iTxSize;
|
||||
|
||||
uint8_t iRxOpCode;
|
||||
uint64_t iRxSize;
|
||||
bool iRxMasked;
|
||||
int iRxMaskIndex;
|
||||
uint8_t iRxMaskKey[4];
|
||||
};
|
||||
|
||||
#endif
|
||||
72
libraries/ArduinoHttpClient/src/b64.cpp
Normal file
72
libraries/ArduinoHttpClient/src/b64.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
// Simple Base64 code
|
||||
// (c) Copyright 2010 MCQN Ltd.
|
||||
// Released under Apache License, version 2.0
|
||||
|
||||
#include "b64.h"
|
||||
|
||||
/* Simple test program
|
||||
#include <stdio.h>
|
||||
void main()
|
||||
{
|
||||
char* in = "amcewen";
|
||||
char out[22];
|
||||
|
||||
b64_encode(in, 15, out, 22);
|
||||
out[21] = '\0';
|
||||
|
||||
printf(out);
|
||||
}
|
||||
*/
|
||||
|
||||
int b64_encode(const unsigned char* aInput, int aInputLen, unsigned char* aOutput, int aOutputLen)
|
||||
{
|
||||
// Work out if we've got enough space to encode the input
|
||||
// Every 6 bits of input becomes a byte of output
|
||||
if (aOutputLen < (aInputLen*8)/6)
|
||||
{
|
||||
// FIXME Should we return an error here, or just the length
|
||||
return (aInputLen*8)/6;
|
||||
}
|
||||
|
||||
// If we get here we've got enough space to do the encoding
|
||||
|
||||
const char* b64_dictionary = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
if (aInputLen == 3)
|
||||
{
|
||||
aOutput[0] = b64_dictionary[aInput[0] >> 2];
|
||||
aOutput[1] = b64_dictionary[(aInput[0] & 0x3)<<4|(aInput[1]>>4)];
|
||||
aOutput[2] = b64_dictionary[(aInput[1]&0x0F)<<2|(aInput[2]>>6)];
|
||||
aOutput[3] = b64_dictionary[aInput[2]&0x3F];
|
||||
}
|
||||
else if (aInputLen == 2)
|
||||
{
|
||||
aOutput[0] = b64_dictionary[aInput[0] >> 2];
|
||||
aOutput[1] = b64_dictionary[(aInput[0] & 0x3)<<4|(aInput[1]>>4)];
|
||||
aOutput[2] = b64_dictionary[(aInput[1]&0x0F)<<2];
|
||||
aOutput[3] = '=';
|
||||
}
|
||||
else if (aInputLen == 1)
|
||||
{
|
||||
aOutput[0] = b64_dictionary[aInput[0] >> 2];
|
||||
aOutput[1] = b64_dictionary[(aInput[0] & 0x3)<<4];
|
||||
aOutput[2] = '=';
|
||||
aOutput[3] = '=';
|
||||
}
|
||||
else
|
||||
{
|
||||
// Break the input into 3-byte chunks and process each of them
|
||||
int i;
|
||||
for (i = 0; i < aInputLen/3; i++)
|
||||
{
|
||||
b64_encode(&aInput[i*3], 3, &aOutput[i*4], 4);
|
||||
}
|
||||
if (aInputLen % 3 > 0)
|
||||
{
|
||||
// It doesn't fit neatly into a 3-byte chunk, so process what's left
|
||||
b64_encode(&aInput[i*3], aInputLen % 3, &aOutput[i*4], aOutputLen - (i*4));
|
||||
}
|
||||
}
|
||||
|
||||
return ((aInputLen+2)/3)*4;
|
||||
}
|
||||
|
||||
6
libraries/ArduinoHttpClient/src/b64.h
Normal file
6
libraries/ArduinoHttpClient/src/b64.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifndef b64_h
|
||||
#define b64_h
|
||||
|
||||
int b64_encode(const unsigned char* aInput, int aInputLen, unsigned char* aOutput, int aOutputLen);
|
||||
|
||||
#endif
|
||||
23
libraries/ArduinoHttpClient/src/utility/URLParser/LICENSE
Normal file
23
libraries/ArduinoHttpClient/src/utility/URLParser/LICENSE
Normal file
@@ -0,0 +1,23 @@
|
||||
http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright
|
||||
Igor Sysoev.
|
||||
|
||||
Additional changes are licensed under the same terms as NGINX and
|
||||
copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
@@ -0,0 +1,5 @@
|
||||
# http_parser library
|
||||
|
||||
This code is imported from: https://github.com/arduino/ArduinoCore-mbed/tree/4.1.1/libraries/SocketWrapper/src/utility/http_parser
|
||||
|
||||
The code is shrinked in size by deleting all the unrelated code to url parse.
|
||||
591
libraries/ArduinoHttpClient/src/utility/URLParser/http_parser.c
Normal file
591
libraries/ArduinoHttpClient/src/utility/URLParser/http_parser.c
Normal file
@@ -0,0 +1,591 @@
|
||||
#if defined __has_include
|
||||
# if ! __has_include(<utility/http_parser/http_parser.h>) && ! __has_include(<http_parser.h>)
|
||||
# define NO_HTTP_PARSER
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef NO_HTTP_PARSER
|
||||
/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev
|
||||
*
|
||||
* Additional changes are licensed under the same terms as NGINX and
|
||||
* copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
#include "http_parser.h"
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
|
||||
#ifndef BIT_AT
|
||||
# define BIT_AT(a, i) \
|
||||
(!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \
|
||||
(1 << ((unsigned int) (i) & 7))))
|
||||
#endif
|
||||
|
||||
#define SET_ERRNO(e) \
|
||||
do { \
|
||||
parser->http_errno = (e); \
|
||||
} while(0)
|
||||
|
||||
#if HTTP_PARSER_STRICT
|
||||
# define T(v) 0
|
||||
#else
|
||||
# define T(v) v
|
||||
#endif
|
||||
|
||||
|
||||
static const uint8_t normal_url_char[32] = {
|
||||
/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
|
||||
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
|
||||
/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
|
||||
0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0,
|
||||
/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
|
||||
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
|
||||
/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
|
||||
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
|
||||
/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
|
||||
0 | 2 | 4 | 0 | 16 | 32 | 64 | 128,
|
||||
/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
|
||||
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
|
||||
/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
|
||||
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
|
||||
/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
|
||||
1 | 2 | 4 | 8 | 16 | 32 | 64 | 0,
|
||||
/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
|
||||
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
|
||||
/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
|
||||
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
|
||||
/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
|
||||
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
|
||||
/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
|
||||
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
|
||||
/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
|
||||
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
|
||||
/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
|
||||
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
|
||||
/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
|
||||
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
|
||||
/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
|
||||
1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, };
|
||||
|
||||
#undef T
|
||||
|
||||
enum state
|
||||
{ s_dead = 1 /* important that this is > 0 */
|
||||
|
||||
, s_start_req
|
||||
|
||||
, s_req_spaces_before_url
|
||||
, s_req_schema
|
||||
, s_req_schema_slash
|
||||
, s_req_schema_slash_slash
|
||||
, s_req_server_start
|
||||
, s_req_server
|
||||
, s_req_server_with_at
|
||||
, s_req_path
|
||||
, s_req_query_string_start
|
||||
, s_req_query_string
|
||||
, s_req_fragment_start
|
||||
, s_req_fragment
|
||||
, s_headers_done
|
||||
};
|
||||
|
||||
enum http_host_state
|
||||
{
|
||||
s_http_host_dead = 1
|
||||
, s_http_userinfo_start
|
||||
, s_http_userinfo
|
||||
, s_http_host_start
|
||||
, s_http_host_v6_start
|
||||
, s_http_host
|
||||
, s_http_host_v6
|
||||
, s_http_host_v6_end
|
||||
, s_http_host_v6_zone_start
|
||||
, s_http_host_v6_zone
|
||||
, s_http_host_port_start
|
||||
, s_http_host_port
|
||||
};
|
||||
|
||||
/* Macros for character classes; depends on strict-mode */
|
||||
#define LOWER(c) (unsigned char)(c | 0x20)
|
||||
#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z')
|
||||
#define IS_NUM(c) ((c) >= '0' && (c) <= '9')
|
||||
#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c))
|
||||
#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f'))
|
||||
#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \
|
||||
(c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \
|
||||
(c) == ')')
|
||||
#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \
|
||||
(c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \
|
||||
(c) == '$' || (c) == ',')
|
||||
|
||||
#if HTTP_PARSER_STRICT
|
||||
#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c))
|
||||
#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-')
|
||||
#else
|
||||
#define IS_URL_CHAR(c) \
|
||||
(BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80))
|
||||
#define IS_HOST_CHAR(c) \
|
||||
(IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')
|
||||
#endif
|
||||
|
||||
/* Our URL parser.
|
||||
*
|
||||
* This is designed to be shared by http_parser_execute() for URL validation,
|
||||
* hence it has a state transition + byte-for-byte interface. In addition, it
|
||||
* is meant to be embedded in http_parser_parse_url(), which does the dirty
|
||||
* work of turning state transitions URL components for its API.
|
||||
*
|
||||
* This function should only be invoked with non-space characters. It is
|
||||
* assumed that the caller cares about (and can detect) the transition between
|
||||
* URL and non-URL states by looking for these.
|
||||
*/
|
||||
static enum state
|
||||
parse_url_char(enum state s, const char ch)
|
||||
{
|
||||
if (ch == ' ' || ch == '\r' || ch == '\n') {
|
||||
return s_dead;
|
||||
}
|
||||
|
||||
#if HTTP_PARSER_STRICT
|
||||
if (ch == '\t' || ch == '\f') {
|
||||
return s_dead;
|
||||
}
|
||||
#endif
|
||||
|
||||
switch (s) {
|
||||
case s_req_spaces_before_url:
|
||||
/* Proxied requests are followed by scheme of an absolute URI (alpha).
|
||||
* All methods except CONNECT are followed by '/' or '*'.
|
||||
*/
|
||||
|
||||
if (ch == '/' || ch == '*') {
|
||||
return s_req_path;
|
||||
}
|
||||
|
||||
if (IS_ALPHA(ch)) {
|
||||
return s_req_schema;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case s_req_schema:
|
||||
if (IS_ALPHA(ch)) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (ch == ':') {
|
||||
return s_req_schema_slash;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case s_req_schema_slash:
|
||||
if (ch == '/') {
|
||||
return s_req_schema_slash_slash;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case s_req_schema_slash_slash:
|
||||
if (ch == '/') {
|
||||
return s_req_server_start;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case s_req_server_with_at:
|
||||
if (ch == '@') {
|
||||
return s_dead;
|
||||
}
|
||||
|
||||
/* FALLTHROUGH */
|
||||
case s_req_server_start:
|
||||
case s_req_server:
|
||||
if (ch == '/') {
|
||||
return s_req_path;
|
||||
}
|
||||
|
||||
if (ch == '?') {
|
||||
return s_req_query_string_start;
|
||||
}
|
||||
|
||||
if (ch == '@') {
|
||||
return s_req_server_with_at;
|
||||
}
|
||||
|
||||
if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') {
|
||||
return s_req_server;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case s_req_path:
|
||||
if (IS_URL_CHAR(ch)) {
|
||||
return s;
|
||||
}
|
||||
|
||||
switch (ch) {
|
||||
case '?':
|
||||
return s_req_query_string_start;
|
||||
|
||||
case '#':
|
||||
return s_req_fragment_start;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case s_req_query_string_start:
|
||||
case s_req_query_string:
|
||||
if (IS_URL_CHAR(ch)) {
|
||||
return s_req_query_string;
|
||||
}
|
||||
|
||||
switch (ch) {
|
||||
case '?':
|
||||
/* allow extra '?' in query string */
|
||||
return s_req_query_string;
|
||||
|
||||
case '#':
|
||||
return s_req_fragment_start;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case s_req_fragment_start:
|
||||
if (IS_URL_CHAR(ch)) {
|
||||
return s_req_fragment;
|
||||
}
|
||||
|
||||
switch (ch) {
|
||||
case '?':
|
||||
return s_req_fragment;
|
||||
|
||||
case '#':
|
||||
return s;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case s_req_fragment:
|
||||
if (IS_URL_CHAR(ch)) {
|
||||
return s;
|
||||
}
|
||||
|
||||
switch (ch) {
|
||||
case '?':
|
||||
case '#':
|
||||
return s;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* We should never fall out of the switch above unless there's an error */
|
||||
return s_dead;
|
||||
}
|
||||
|
||||
static enum http_host_state
|
||||
http_parse_host_char(enum http_host_state s, const char ch) {
|
||||
switch(s) {
|
||||
case s_http_userinfo:
|
||||
case s_http_userinfo_start:
|
||||
if (ch == '@') {
|
||||
return s_http_host_start;
|
||||
}
|
||||
|
||||
if (IS_USERINFO_CHAR(ch)) {
|
||||
return s_http_userinfo;
|
||||
}
|
||||
break;
|
||||
|
||||
case s_http_host_start:
|
||||
if (ch == '[') {
|
||||
return s_http_host_v6_start;
|
||||
}
|
||||
|
||||
if (IS_HOST_CHAR(ch)) {
|
||||
return s_http_host;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case s_http_host:
|
||||
if (IS_HOST_CHAR(ch)) {
|
||||
return s_http_host;
|
||||
}
|
||||
|
||||
/* FALLTHROUGH */
|
||||
case s_http_host_v6_end:
|
||||
if (ch == ':') {
|
||||
return s_http_host_port_start;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case s_http_host_v6:
|
||||
if (ch == ']') {
|
||||
return s_http_host_v6_end;
|
||||
}
|
||||
|
||||
/* FALLTHROUGH */
|
||||
case s_http_host_v6_start:
|
||||
if (IS_HEX(ch) || ch == ':' || ch == '.') {
|
||||
return s_http_host_v6;
|
||||
}
|
||||
|
||||
if (s == s_http_host_v6 && ch == '%') {
|
||||
return s_http_host_v6_zone_start;
|
||||
}
|
||||
break;
|
||||
|
||||
case s_http_host_v6_zone:
|
||||
if (ch == ']') {
|
||||
return s_http_host_v6_end;
|
||||
}
|
||||
|
||||
/* FALLTHROUGH */
|
||||
case s_http_host_v6_zone_start:
|
||||
/* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */
|
||||
if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' ||
|
||||
ch == '~') {
|
||||
return s_http_host_v6_zone;
|
||||
}
|
||||
break;
|
||||
|
||||
case s_http_host_port:
|
||||
case s_http_host_port_start:
|
||||
if (IS_NUM(ch)) {
|
||||
return s_http_host_port;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return s_http_host_dead;
|
||||
}
|
||||
|
||||
static int
|
||||
http_parse_host(const char * buf, struct http_parser_url *u, int found_at) {
|
||||
enum http_host_state s;
|
||||
|
||||
const char *p;
|
||||
uint32_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len;
|
||||
|
||||
assert(u->field_set & (1 << UF_HOST));
|
||||
|
||||
u->field_data[UF_HOST].len = 0;
|
||||
|
||||
s = found_at ? s_http_userinfo_start : s_http_host_start;
|
||||
|
||||
for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) {
|
||||
enum http_host_state new_s = http_parse_host_char(s, *p);
|
||||
|
||||
if (new_s == s_http_host_dead) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
switch(new_s) {
|
||||
case s_http_host:
|
||||
if (s != s_http_host) {
|
||||
u->field_data[UF_HOST].off = p - buf;
|
||||
}
|
||||
u->field_data[UF_HOST].len++;
|
||||
break;
|
||||
|
||||
case s_http_host_v6:
|
||||
if (s != s_http_host_v6) {
|
||||
u->field_data[UF_HOST].off = p - buf;
|
||||
}
|
||||
u->field_data[UF_HOST].len++;
|
||||
break;
|
||||
|
||||
case s_http_host_v6_zone_start:
|
||||
case s_http_host_v6_zone:
|
||||
u->field_data[UF_HOST].len++;
|
||||
break;
|
||||
|
||||
case s_http_host_port:
|
||||
if (s != s_http_host_port) {
|
||||
u->field_data[UF_PORT].off = p - buf;
|
||||
u->field_data[UF_PORT].len = 0;
|
||||
u->field_set |= (1 << UF_PORT);
|
||||
}
|
||||
u->field_data[UF_PORT].len++;
|
||||
break;
|
||||
|
||||
case s_http_userinfo:
|
||||
if (s != s_http_userinfo) {
|
||||
u->field_data[UF_USERINFO].off = p - buf ;
|
||||
u->field_data[UF_USERINFO].len = 0;
|
||||
u->field_set |= (1 << UF_USERINFO);
|
||||
}
|
||||
u->field_data[UF_USERINFO].len++;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
s = new_s;
|
||||
}
|
||||
|
||||
/* Make sure we don't end somewhere unexpected */
|
||||
switch (s) {
|
||||
case s_http_host_start:
|
||||
case s_http_host_v6_start:
|
||||
case s_http_host_v6:
|
||||
case s_http_host_v6_zone_start:
|
||||
case s_http_host_v6_zone:
|
||||
case s_http_host_port_start:
|
||||
case s_http_userinfo:
|
||||
case s_http_userinfo_start:
|
||||
return 1;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
http_parser_url_init(struct http_parser_url *u) {
|
||||
memset(u, 0, sizeof(*u));
|
||||
}
|
||||
|
||||
int
|
||||
http_parser_parse_url(const char *buf, uint32_t buflen, int is_connect,
|
||||
struct http_parser_url *u)
|
||||
{
|
||||
enum state s;
|
||||
const char *p;
|
||||
enum http_parser_url_fields uf, old_uf;
|
||||
int found_at = 0;
|
||||
|
||||
u->port = u->field_set = 0;
|
||||
s = is_connect ? s_req_server_start : s_req_spaces_before_url;
|
||||
old_uf = UF_MAX;
|
||||
|
||||
for (p = buf; p < buf + buflen; p++) {
|
||||
s = parse_url_char(s, *p);
|
||||
|
||||
/* Figure out the next field that we're operating on */
|
||||
switch (s) {
|
||||
case s_dead:
|
||||
return 1;
|
||||
|
||||
/* Skip delimeters */
|
||||
case s_req_schema_slash:
|
||||
case s_req_schema_slash_slash:
|
||||
case s_req_server_start:
|
||||
case s_req_query_string_start:
|
||||
case s_req_fragment_start:
|
||||
continue;
|
||||
|
||||
case s_req_schema:
|
||||
uf = UF_SCHEMA;
|
||||
break;
|
||||
|
||||
case s_req_server_with_at:
|
||||
found_at = 1;
|
||||
|
||||
/* FALLTROUGH */
|
||||
case s_req_server:
|
||||
uf = UF_HOST;
|
||||
break;
|
||||
|
||||
case s_req_path:
|
||||
uf = UF_PATH;
|
||||
break;
|
||||
|
||||
case s_req_query_string:
|
||||
uf = UF_QUERY;
|
||||
break;
|
||||
|
||||
case s_req_fragment:
|
||||
uf = UF_FRAGMENT;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(!"Unexpected state");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Nothing's changed; soldier on */
|
||||
if (uf == old_uf) {
|
||||
u->field_data[uf].len++;
|
||||
continue;
|
||||
}
|
||||
|
||||
u->field_data[uf].off = p - buf;
|
||||
u->field_data[uf].len = 1;
|
||||
|
||||
u->field_set |= (1 << uf);
|
||||
old_uf = uf;
|
||||
}
|
||||
|
||||
/* host must be present if there is a schema */
|
||||
/* parsing http:///toto will fail */
|
||||
if ((u->field_set & (1 << UF_SCHEMA)) &&
|
||||
(u->field_set & (1 << UF_HOST)) == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (u->field_set & (1 << UF_HOST)) {
|
||||
if (http_parse_host(buf, u, found_at) != 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* CONNECT requests can only contain "hostname:port" */
|
||||
if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (u->field_set & (1 << UF_PORT)) {
|
||||
/* Don't bother with endp; we've already validated the string */
|
||||
unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10);
|
||||
|
||||
/* Ports have a max value of 2^16 */
|
||||
if (v > 0xffff) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
u->port = (uint16_t) v;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned long
|
||||
http_parser_version(void) {
|
||||
return HTTP_PARSER_VERSION_MAJOR * 0x10000 |
|
||||
HTTP_PARSER_VERSION_MINOR * 0x00100 |
|
||||
HTTP_PARSER_VERSION_PATCH * 0x00001;
|
||||
}
|
||||
|
||||
#endif // NO_HTTP_PARSER
|
||||
@@ -0,0 +1,96 @@
|
||||
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef http_parser_h
|
||||
#define http_parser_h
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Also update SONAME in the Makefile whenever you change these. */
|
||||
#define HTTP_PARSER_VERSION_MAJOR 2
|
||||
#define HTTP_PARSER_VERSION_MINOR 7
|
||||
#define HTTP_PARSER_VERSION_PATCH 1
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
|
||||
* faster
|
||||
*/
|
||||
#ifndef HTTP_PARSER_STRICT
|
||||
# define HTTP_PARSER_STRICT 1
|
||||
#endif
|
||||
|
||||
|
||||
enum http_parser_url_fields
|
||||
{ UF_SCHEMA = 0
|
||||
, UF_HOST = 1
|
||||
, UF_PORT = 2
|
||||
, UF_PATH = 3
|
||||
, UF_QUERY = 4
|
||||
, UF_FRAGMENT = 5
|
||||
, UF_USERINFO = 6
|
||||
, UF_MAX = 7
|
||||
};
|
||||
|
||||
|
||||
/* Result structure for http_parser_parse_url().
|
||||
*
|
||||
* Callers should index into field_data[] with UF_* values iff field_set
|
||||
* has the relevant (1 << UF_*) bit set. As a courtesy to clients (and
|
||||
* because we probably have padding left over), we convert any port to
|
||||
* a uint16_t.
|
||||
*/
|
||||
struct http_parser_url {
|
||||
uint16_t field_set; /* Bitmask of (1 << UF_*) values */
|
||||
uint16_t port; /* Converted UF_PORT string */
|
||||
|
||||
struct {
|
||||
uint16_t off; /* Offset into buffer in which field starts */
|
||||
uint16_t len; /* Length of run in buffer */
|
||||
} field_data[UF_MAX];
|
||||
};
|
||||
|
||||
|
||||
/* Returns the library version. Bits 16-23 contain the major version number,
|
||||
* bits 8-15 the minor version number and bits 0-7 the patch level.
|
||||
* Usage example:
|
||||
*
|
||||
* unsigned long version = http_parser_version();
|
||||
* unsigned major = (version >> 16) & 255;
|
||||
* unsigned minor = (version >> 8) & 255;
|
||||
* unsigned patch = version & 255;
|
||||
* printf("http_parser v%u.%u.%u\n", major, minor, patch);
|
||||
*/
|
||||
unsigned long http_parser_version(void);
|
||||
|
||||
/* Initialize all http_parser_url members to 0 */
|
||||
void http_parser_url_init(struct http_parser_url *u);
|
||||
|
||||
/* Parse a URL; return nonzero on failure */
|
||||
int http_parser_parse_url(const char *buf, uint32_t buflen,
|
||||
int is_connect,
|
||||
struct http_parser_url *u);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
Reference in New Issue
Block a user