88 if (!buffer || (size < 5))
110 for (n = 3; n < size; n++) {
111 if (buffer[n] == 10) {
112 if ( (size-n >= 2) && (buffer[n+1] == 10) ) {
117 else if ( (size-n >= 3) && (buffer[n+1] == 13) && (buffer[n+2] == 10) && (buffer[n-1] == 13) ) {
128 uint32 tempContentLength = 1024;
134 std::string key, val, uri;
135 const char* s =
data, *t;
144 if ((t = strchr(s,
' ')) && (t - s < (int32)n)) {
148 key.assign(s, t - s);
151 key.assign(s, t - s);
154 while (*t == 32) t++;
156 val.assign(t, n + s - t);
166 entries[
"http_fullrequest"] = val;
167 if ((i = val.find(
' ')) != std::string::npos) {
168 uri = val.substr(0, i);
171 entries[
"http_protocol"] = val.substr(i+1);
172 if ((j = uri.find(
'?')) != std::string::npos) {
173 entries[
"http_request"] = uri.substr(1, j-1);
177 entries[
"http_request"] = uri.substr(1, i-1);
183 if ((j = uri.find(
'?')) != std::string::npos) {
184 entries[
"http_request"] = uri.substr(1, j-1);
188 entries[
"http_request"] = uri.substr(1);
191 else if (key.length() > 0) {
197 if (
stricmp(key.c_str(),
"content-length") == 0)
199 else if (
stricmp(key.c_str(),
"if-modified-since") == 0)
201 else if (
stricmp(key.c_str(),
"connection") == 0)
203 else if (
stricmp(key.c_str(),
"content-type") == 0) {
205 std::multimap<std::string,std::string>::iterator ci = conTypes.find(
"boundary");
206 if (ci != conTypes.end())
250 std::map<std::string, std::string>::iterator it =
entries.find(entry);
252 return it->second.c_str();
278 if (!auth)
return "";
280 if (!authString.length() || (authString.find(
":") == std::string::npos))
287 if (!authString.length())
294 if (!authString.length())
300 if (!authB64 || !strlen(authB64)) {
302 entries.erase(
"Authorization");
305 entries[
"Authorization"] = authB64;
311 if (!user || !strlen(user) || !password || !strlen(password))
315 entries[
"Authorization"] = auth;
328 std::string contentType =
entries[
"Content-Type"];
330 if (contentType.find(
"application/x-www-form-urlencoded") != std::string::npos) {
334 else if (contentType.find(
"multipart/form-data") != std::string::npos) {
343 if (memcmp(content, boundary, boundarySize) != 0)
346 const char* lastStart = content + boundarySize + 2;
347 const char* c = lastStart;
349 for (uint32 n=boundarySize; n<size; n++) {
351 if (memcmp(c, boundary, boundarySize) == 0) {
353 lastStart = c += boundarySize + 2;
354 n += boundarySize + 2;
363 postEntry->
name =
"POST";
364 postEntry->
type = contentType;
376 const char* valContent = strstr(chunk,
"\r\n\r\n");
379 uint32 headerSize = (uint32)(valContent-chunk);
381 uint32 valContentSize = size - headerSize - 6;
384 char* headerText =
new char[headerSize+1];
385 memcpy(headerText, chunk, headerSize);
386 headerText[headerSize] = 0;
390 std::multimap<std::string, std::string> entryMap;
391 std::multimap<std::string, std::string>::iterator ei;
394 std::vector<std::string>::iterator i = headerEntries.begin(), e = headerEntries.end();
398 if ( (ei = entryMap.find(
"name")) != entryMap.end())
400 if ( (ei = entryMap.find(
"filename")) != entryMap.end())
404 postEntry->
type = (*i).substr(14);
408 delete [] headerText;
409 if (postEntry->
name.length())
410 postEntry->
setContent(valContent, valContentSize);
459 std::multimap<std::string, std::string>::iterator i = paramMap.begin(), e = paramMap.end();
499 std::map<std::string, std::string>::iterator i, e;
507 msg->
setString(i->first.c_str(), i->second.c_str());
510 msg->
setString(i->first.c_str(), i->second.c_str());
514 if (pi->second->type.length()) {
515 msg->
setString((pi->first+
"_CONTENT_TYPE_").c_str(), pi->second->type.c_str());
517 msg->
setString(pi->first.c_str(), pi->second->content);
519 msg->
setString(pi->first.c_str(), pi->second->content);
521 msg->
setString(pi->first.c_str(), pi->second->content);
523 msg->
setData(pi->first.c_str(), pi->second->content, pi->second->contentSize);
526 msg->
setString(pi->first.c_str(), pi->second->content);
537 if (!connection ||
stricmp(connection,
"Upgrade"))
541 if (!upgrade ||
stricmp(upgrade,
"websocket"))
550 std::map<std::string, std::string>::iterator it =
params.find(entry);
552 return it->second.c_str();
558 std::map<std::string, HTTPPostEntry*>::iterator i =
postEntries.find(entry);
561 size = i->second->contentSize;
562 *
type = i->second->type.c_str();
563 return i->second->content;
567 std::map<std::string, HTTPPostEntry*>::iterator i =
postEntries.find(entry);
570 size = i->second->contentSize;
571 return i->second->content;
575 std::map<std::string, HTTPPostEntry*>::iterator i =
postEntries.find(entry);
578 return i->second->type.c_str();
583 uint8
type,
const char* host,
const char* uri,
const char* content,
586 char timeString[1024];
598 data =
new char[4096 + contentSize];
600 snprintf(
data, 4096+contentSize,
604User-Agent: CMLabsHTTP/1.0\r\n\
605if-modified-since: %s\r\n\r\n",
609 snprintf(
data, 4096+contentSize,
613User-Agent: CMLabsHTTP/1.0\r\n\
614Content-Type: application/x-www-form-urlencoded\r\n\
615Content-Length: %u\r\n\r\n",
616 HTTP_Type[
type], uri, host, (uint32) (contentSize ? contentSize : strlen(content)));
631 char timeString[1024];
645User-Agent: CMLabsHTTP/1.0\r\n\
646if-modified-since: %s\r\n",
649 std::map<std::string, std::string>::iterator hI = headerEntries.begin(), hE = headerEntries.end();
651 if ((
stricmp(hI->first.c_str(),
"content-type") != 0) &&
652 (
stricmp(hI->first.c_str(),
"content-length") != 0)) {
683 if (!content || !contentType)
687 char timeString[1024];
701User-Agent: CMLabsHTTP/1.0\r\n\
703if-modified-since: %s\r\n",
706 std::map<std::string, std::string>::iterator hI = headerEntries.begin(), hE = headerEntries.end();
708 if ((
stricmp(hI->first.c_str(),
"content-type") != 0) &&
709 (
stricmp(hI->first.c_str(),
"content-length") != 0)) {
734 uint8
type,
const char* host,
const char* uri, std::map<std::string, std::string>& headerEntries,
740 uint32 boundaryLength = (uint32)boundaryString.length();
742 char timeString[1024];
756User-Agent: CMLabsHTTP/1.0\r\n\
757Content-Type: multipart/form-data; boundary=%s\r\n\
758if-modified-since: %s\r\n",
759 HTTP_Type[
type], uri, host, boundaryString.c_str(), timeString);
761 std::map<std::string, std::string>::iterator hI = headerEntries.begin(), hE = headerEntries.end();
763 if ((
stricmp(hI->first.c_str(),
"content-type") != 0) &&
764 (
stricmp(hI->first.c_str(),
"content-length") != 0)) {
775 uint32 filenameStatementLength = 0;
777 std::map<std::string, HTTPPostEntry*>::iterator bI = bodyEntries.begin(), bE = bodyEntries.end();
780 filenameStatementLength = 19;
782 filenameStatementLength = 0;
788 + 38 + (uint32)bI->second->name.length() + 5 + filenameStatementLength
789 + bI->second->contentSize + 2 + 2 + boundaryLength;
808 snprintf(dst, 2 + boundaryLength + 2,
"--%s\r\n", boundaryString.c_str());
809 dst += 2 + boundaryLength + 2;
812 bI = bodyEntries.begin();
819 filenameStatementLength = 19;
821 snprintf(dst, 14 + bI->second->type.length() + 2 + 38 + bI->second->name.length() + 5 + 19,
"Content-Type: %s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"nofile\"\r\n\r\n",
822 bI->second->type.c_str(),
823 bI->second->name.c_str());
827 snprintf(dst, 14 + bI->second->type.length() + 2 + 38 + bI->second->name.length() + 5,
"Content-Type: %s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n",
828 bI->second->type.c_str(),
829 bI->second->name.c_str());
830 filenameStatementLength = 0;
833 dst += 14 + bI->second->type.length() + 2
834 + 38 + bI->second->name.length() + 5 + filenameStatementLength;
835 memcpy(dst, bI->second->content, bI->second->contentSize);
836 dst += bI->second->contentSize;
838 snprintf(dst, 2 + 2 + boundaryLength,
"\r\n--%s", boundaryString.c_str());
839 dst += 2 + 2 + boundaryLength;
843 snprintf(dst, 4,
"\r\n");
848 snprintf(dst, 6,
"--\r\n");
871 std::string uriString;
872 if (uri && strlen(uri))
877 std::string originString;
878 if (origin && strlen(origin))
879 originString = origin;
883 std::string protocolNameString;
884 if (protocolName && strlen(protocolName))
885 protocolNameString = protocolName;
887 protocolNameString =
"Default";
889 char* randomKey =
new char[17];
890 for (uint32 n = 0; n < 16; n++)
898 data =
new char[4096];
902Upgrade: websocket\r\n\
903Connection: Upgrade\r\n\
904Sec-WebSocket-Key: %s\r\n\
905Sec-WebSocket-Protocol: %s\r\n\
906Sec-WebSocket-Version: 13\r\n\
908 uriString.c_str(), host, key.c_str(), protocolNameString.c_str(), originString.c_str());
1044 uint32 basic_value = 0;
1047 basic_value = (uint8)size;
1069 uint8* b1 = (uint8*)(
rawData + 1);
1094 uint16* h2 = (uint16*)(
rawData + 2);
1095 *h2 = ntohs((uint16)size);
1098 uint64* h3 = (uint64*)(
rawData + 2);
1106 uint8* src = (uint8*)
data;
1108 for (uint64 n = 0; n < size; n++) {
1109 *dst = *src ^ mask[n % 4];
1154 if (!buffer || (size < 4))
1161 uint8 b0 = *(
const unsigned char*)buffer;
1162 uint8 b1 = *(
const unsigned char*)(buffer + 1);
1192 uint16 h2 = *(
const uint16*)(buffer + 2);
1199 uint64 h3 = *(
const uint64*)(buffer + 2);
1250 uint8* src = (uint8*)buffer;
1268 *dst = *src ^ mask[n % 4];
1364 return (uint32)strlen(
data);
1368 return setLine(buffer, (uint32)strlen(buffer), addCR);
1375 data =
new char[len+3];
1376 if (buffer && len > 0) {
1377 memcpy(
data, buffer, len);
1479 char timeString[1024];
1485 data =
new char[4096];
1486 snprintf(
data, 4096,
1487"Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: GET, PUT, POST, DELETE\r\nAccess-Control-Allow-Headers: accept, authorization, origin\r\n\r\n");
1509 char timeString[1024];
1515 data =
new char[4096];
1516 snprintf(
data, 4096,
1517 "%s\r\nDate: %s\r\nServer: %s\r\n"
1518 "X-Frame-Options: SAMEORIGIN\r\n"
1519 "Connection: %s\r\n\r\n",
1530 char timeString[1024];
1536 data =
new char[4096];
1537 snprintf(
data, 4096,
1538 "%s\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n",
1552 if (!key || !strlen(key))
1555 std::string magicString =
utils::StringFormat(
"%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", key);
1558 sha1.
add(magicString.c_str(), magicString.size());
1565 data =
new char[4096];
1566 snprintf(
data, 4096,
1567 "%s\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %s\r\n\r\n",
1576 uint64 lastMod,
bool keepAlive,
bool cache,
const char* contentType,
1577 const char* content, uint32 contentSize,
const char* additionalHeaderEntries) {
1579 char timeString[1024];
1583 contentLength = (contentSize > 0) ? contentSize : (uint32)strlen(content);
1588 if (additionalHeaderEntries && strlen(additionalHeaderEntries)) {
1590 "%s\r\nDate: %s\r\nServer: %s\r\n"
1591 "X-Frame-Options: SAMEORIGIN\r\n"
1592 "Last-Modified: %s\r\nContent-Length: %u\r\n"
1593 "Connection: %s\r\nContent-Type: %s\r\nCache-Control: %s\r\n"
1596 keepAlive ?
"Keep-Alive" :
"close", contentType, cache ?
"Public" :
"No-Cache",
1597 additionalHeaderEntries);
1601 "%s\r\nDate: %s\r\nServer: %s\r\n"
1602 "X-Frame-Options: SAMEORIGIN\r\n"
1603 "Last-Modified: %s\r\nContent-Length: %u\r\n"
1604 "Connection: %s\r\nContent-Type: %s\r\nCache-Control: %s\r\n\r\n",
1606 keepAlive ?
"Keep-Alive" :
"close", contentType, cache ?
"Public" :
"No-Cache");
1617 uint64 ifLastMod,
bool keepAlive,
bool cache,
1618 const char* filename) {
1620 std::string actualFilename = filename;
1621 const char* question = strstr(filename,
"?");
1623 actualFilename = std::string(filename, question - filename);
1625 uint64 lastModifiedTime = 0;
1632 char* filedata = NULL;
1633 uint32 datasize = 0;
1635 bool isBinary =
false;
1636 char* exttype =
new char[1024];
1640 else if (!isBinary) {
1646 if ( !(filedata =
utils::ReadAFile(actualFilename.c_str(), datasize,
true))) {
1653 else if (
html.length())
1656 html =
"<html><head><title>404 Not Found</title></head><body><h1>Not Found</h1><p>The requested URL was not found on this server.</p></body></html>";
1671 if (!connection ||
stricmp(connection,
"Upgrade"))
1675 if (!upgrade ||
stricmp(upgrade,
"websocket"))
1685 if (!buffer || (size < 5))
1690 for (n = 3; n < size; n++) {
1691 if (buffer[n] == 10) {
1692 if ( (size-n >= 2) && (buffer[n+1] == 10) ) {
1697 else if ( (size-n >= 3) && (buffer[n+1] == 13) && (buffer[n+2] == 10) && (buffer[n-1] == 13) ) {
1710 uint32 tempContentLength = 4096;
1716 std::string key, val, uri;
1717 const char* s =
data, *t;
1726 if ((t = strchr(s,
' ')) && (t - s < (int32)n)) {
1736 while (*t == 32) t++;
1738 val.assign(t, n+s-t);
1748 entries[
"http_protocol"] = key;
1749 if (val.find(
"200") != val.npos)
1751 else if (val.find(
"101") != val.npos)
1753 else if (val.find(
"301") != val.npos)
1755 else if (val.find(
"304") != val.npos)
1757 else if (val.find(
"403") != val.npos)
1759 else if (val.find(
"404") != val.npos)
1761 else if (val.find(
"500") != val.npos)
1765 else if (val.find(
"400") != val.npos)
1767 else if (val.find(
"401") != val.npos)
1769 else if (val.find(
"501") != val.npos)
1771 else if (val.find(
"503") != val.npos)
1773 else if (val.find(
"504") != val.npos)
1776 type = atoi(val.c_str());
1781 if (
stricmp(key.c_str(),
"content-length") == 0)
1783 else if (
stricmp(key.c_str(),
"transfer-encoding") == 0) {
1785 std::string te = val;
1786 for (
size_t c = 0; c < te.length(); c++) te[c] = (
char)tolower((
unsigned char)te[c]);
1787 if (te.find(
"chunked") != std::string::npos)
1790 else if (
stricmp(key.c_str(),
"date") == 0)
1792 else if (
stricmp(key.c_str(),
"connection") == 0)
1844 std::map<std::string, std::string>::iterator it =
entries.find(entry);
1846 return it->second.c_str();
1905 uint32 maxSize = 4096, size;
1906 char* buffer =
new char[maxSize];
1911 bool isInvalid =
false;
1912 bool sawData =
false;
1961 bool failed =
false;
1971 const char* lf = avail ? (
const char*)memchr(peekBuf,
'\n', avail) : NULL;
1973 line.assign(peekBuf, (
size_t)(lf-peekBuf));
1974 con->
discard((uint32)(lf-peekBuf)+1);
1984 while (line.length() && (line[line.length()-1] ==
'\r'))
1985 line.erase(line.length()-1);
1986 uint32 chunkSize = (uint32)strtoul(line.c_str(), NULL, 16);
1989 char* chunkBuf =
new char[chunkSize+2];
1990 if (!con->
receive(chunkBuf, chunkSize+2, 10000)) {
1995 body.append(chunkBuf, chunkSize);
2003 printf(
"Error receiving chunked HTTPReply content (%u so far)...\n\n", (uint32)body.length());
2015 printf(
"Error receiving HTTPReply content (%u)...\n\n", reply->
contentLength);
2030 printf(
"Error receiving HTTPReply content (%u)...\n\n", reply->
contentLength);
2063 uint32 maxSize = 4096, size;
2064 char* buffer =
new char[maxSize];
2067 uint64 startReceive = 0;
2078 LogPrint(0,
LOG_NETWORK, 5,
"Started receiving incoming HTTP request, size so far %u", size);
2087 else if (isInvalid) {
2095 else if (size == maxSize) {
2096 LogPrint(0,
LOG_NETWORK, 2,
"Received incoming HTTP request, buffer needs resizing %u", size);
2104 LogPrint(0,
LOG_NETWORK, 5,
"Received incoming HTTP request, got %u, waiting for more...", size);
2161 uint32 maxSize = 4096, size;
2162 char* buffer =
new char[maxSize];
2165 uint64 startReceive = 0;
2167 uint64 remoteAddress;
2172 if (!wsData && !con->
receiveAvailable(buffer, size, maxSize, wait,
true)) {
2191 else if (isInvalid) {
2200 else if (size == maxSize) {
2216 if (!wsData && (
GetTimeAgeMS(start) < (int32)timeout)) {
2267 if (!con || !wsData)
2274 if (con->
send(data, (uint32)size)) {
2324 if (length < 2*
sizeof(uint32))
2354 char header[2*
sizeof(uint32)];
2355 if (!con->
receive(header,
sizeof(header), timeout,
true))
2359 uint32 size = *(uint32*)header;
2360 if (size <
sizeof(header))
2363 char* buffer = (
char*) malloc(size);
2366 if (!con->
receive(buffer, size, timeout)) {
2386 return ((buffer[0] == 13) || (buffer[0] == 10));
2403 uint32 maxSize = 1024, size;
2404 char* buffer =
new char[maxSize];
2415 for ( n=0; n<size; n++) {
2416 if (buffer[n] == 13) {
2420 if ( (n < size-1) && (buffer[n+1] == 10) )
2427 else if (buffer[n] == 10) {
2431 if ( (n < size-1) && (buffer[n+1] == 13) )
2441 if (size == maxSize) {
2444 buffer =
new char[maxSize];
2458 char* buffer =
new char[size];
2460 if (!con->
receive(buffer, size, timeout)) {
2466 reply->
data = buffer;
2487 extractMultipartInfo();
2512 if (!mutex.enter(1000))
return 0;
2518 if (!mutex.enter(1000))
return 0;
2519 uint32 s = (uint32)
entries.size();
2530 uint64 jsonLength = strlen(
jmData);
2531 uint64 multipartLength = 0;
2532 std::map<uint32, JSONMEntry>::iterator i =
entries.begin(), e =
entries.end();
2534 multipartLength += i->second.size;
2537 if (jsonLength + 1 + multipartLength !=
jmSize) {
2538 LogPrint(0,
LOG_NETWORK, 0,
"Invalid JSONM structure: JSON(%llu) + 1 + multiparts(%llu) != %llu", jsonLength, multipartLength,
jmSize);
2555 if (!mutex.enter(1000))
return NULL;
2561 std::map<uint32, JSONMEntry>::iterator i =
entries.begin(), e =
entries.end();
2563 if (
stricmp(i->second.name.c_str(), name) == 0) {
2565 LogPrint(0,
LOG_NETWORK, 0,
"Invalid JSONM structure: Entry '%s' offset %llu size %llu exceeds the JSONM size %llu",
2566 name, i->second.offset, i->second.size,
jmSize);
2570 size = i->second.size;
2581 if (!mutex.enter(1000))
return "";
2586 std::map<uint32, JSONMEntry>::iterator i =
entries.begin(), e =
entries.end();
2588 if (
stricmp(i->second.name.c_str(), name) == 0) {
2590 return i->second.type;
2599 if (!mutex.enter(1000))
return NULL;
2603 std::map<uint32, JSONMEntry>::iterator i =
entries.find(chunk);
2610 return i->second.type;
2614 if (!mutex.enter(1000))
return NULL;
2620 std::map<uint32, JSONMEntry>::iterator i =
entries.find(chunk);
2627 return i->second.name;
2633 if (!mutex.enter(1000))
return NULL;
2640 std::map<uint32, JSONMEntry>::iterator i =
entries.find(chunk);
2647 LogPrint(0,
LOG_NETWORK, 0,
"Invalid JSONM structure: Entry %u '%s' offset %llu size %llu exceeds the JSONM size %llu",
2648 chunk, i->second.name.c_str(), i->second.offset, i->second.size,
jmSize);
2653 size = i->second.size;
2658bool JSONM::setRawJSON(
const char* json) {
2668 uint64 newMOffset = strlen(json) + 1;
2670 char* newData =
new char[(uint32)newSize];
2684 if (!mutex.enter(1000))
return false;
2685 if (setRawJSON(json))
2686 updateMultipartJSON();
2691uint32
JSONM::addData(
const char* name,
const char* data, uint64 size,
const char* type) {
2692 if (!mutex.enter(1000))
return 0;
2707 uint64 newSize =
jmSize + size;
2708 char* newData =
new char[(uint32)newSize];
2710 memcpy(newData + (uint32)
jmSize, data, (uint32)size);
2715 updateMultipartJSON();
2721 if (!mutex.enter(1000))
return "";
2726 std::string info =
utils::StringFormat(
"JSON length: %u\nTotal size: %s\nChunks: %u total: %s\n%s\n",
2732 std::map<uint32, JSONMEntry>::iterator i =
entries.begin(), e =
entries.end();
2734 info += i->second.getInfoString();
2744bool JSONM::extractMultipartInfo() {
2766 std::vector<int> jsonArrayIdx;
2767 std::vector<int>::iterator i, e;
2769 if (!jsonArrayIdx.size()) {
2774 i = jsonArrayIdx.begin();
2775 e = jsonArrayIdx.end();
2782 entry.chunk = (uint32)
entries.size() + 1;
2783 entry.offset = offset;
2785 offset += entry.size;
2792bool JSONM::updateMultipartJSON() {
2797 std::string newJSON;
2799 std::map<uint32, JSONMEntry>::iterator i =
entries.begin(), e =
entries.end();
2801 if (newJSON.length())
2803 newJSON += i->second.toJSON();
2808 jsmntok_t* tokens =
new jsmntok_t[1024];
2819 std::string fullJSON =
jmData;
2823 uint32 begin = tokens[token].
start;
2824 uint32 end = tokens[token].
end;
2827 fullJSON.substr(0, begin).c_str(),
2829 fullJSON.substr(end).c_str()
2840 fullJSON =
utils::StringFormat(
"%s,\"_multipart_\": [%s]}", fullJSON.substr(0, lastBracket-
jmData).c_str(), newJSON.c_str());
2842 this->setRawJSON(fullJSON.c_str());
2851 msg->
setString(
"POST_CONTENT_TYPE_",
"application/json");
2853 std::map<uint32, JSONMEntry>::iterator i =
entries.begin(), e =
entries.end();
2855 msg->
setData(i->second.name.c_str(),
jmData +
mOffset + i->second.offset, (uint32)i->second.size);
2856 msg->
setString((i->second.name +
"_CONTENT_TYPE_").c_str(), i->second.type.c_str());
2871 const char* payload, uint64 payloadSize) {
2874 if (!enc.
setData(type, maskData, payload, payloadSize)) {
2875 unittest::fail(
"WebsocketData::setData returned false (type %d, mask %d, size %llu)",
2876 (
int)type, (
int)maskData, payloadSize);
2886 if (!raw || !rawSize) {
2897 uint8 b0 = *(
const unsigned char*)raw;
2910 bool isInvalid =
false;
2911 if (!dec.
processHeader(raw, rawSize, isInvalid) || isInvalid) {
2912 unittest::fail(
"processHeader failed (invalid=%d)", (
int)isInvalid);
2931 if (!out || (outSize != payloadSize)) {
2932 unittest::fail(
"getContent size %llu != %llu", outSize, payloadSize);
2935 if (memcmp(out, payload, (
size_t)payloadSize) != 0) {
2948 const char* shortText =
"hello websocket";
2951 unittest::detail(
"websocket short unmasked TEXT round-trip ok (%zu bytes)", strlen(shortText));
2966 for (uint32 i = 0; i < 4000; i++)
2967 big.push_back((
char)(
'A' + (i % 26)));
2972 unittest::detail(
"websocket extended (16-bit length) masked+unmasked round-trip ok (%zu bytes)", big.size());
2977 const char* body =
"field1=value1&field2=value2";
2978 uint32 bodyLen = (uint32)strlen(body);
2983 const char* fullURI =
"/path/script.cgi?home=Cosby&favorite=flies";
2987 body, bodyLen,
true, 0)) {
2994 if (!raw || !rawSize) {
3002 bool isInvalid =
false;
3003 if (!reqDec.
processHeader(raw, rawSize, isInvalid) || isInvalid) {
3004 unittest::fail(
"HTTPRequest::processHeader failed (invalid=%d)", (
int)isInvalid);
3011 const char* uri = reqDec.
getURI();
3012 if (!uri || strcmp(uri, fullURI) != 0) {
3013 unittest::fail(
"parsed URI '%s' != '%s'", uri ? uri :
"(null)", fullURI);
3017 if (!request || strcmp(request,
"path/script.cgi") != 0) {
3018 unittest::fail(
"parsed request '%s' != 'path/script.cgi'", request ? request :
"(null)");
3034 const char* outBody = reqDec.
getContent(outSize);
3035 if (!outBody || (outSize != bodyLen) || (memcmp(outBody, body, bodyLen) != 0)) {
3036 unittest::fail(
"parsed body differs from original (size %u != %u)", outSize, bodyLen);
3041 if (!p || strcmp(p,
"Cosby") != 0) {
3042 unittest::fail(
"parsed URI param home='%s' != 'Cosby'", p ? p :
"(null)");
3045 unittest::detail(
"http POST build/parse round-trip ok (body %u bytes)", bodyLen);
3050 const uint32 iters = 2000;
3052 for (uint32 i = 0; i < iters; i++) {
3056 unittest::fail(
"throughput loop processHeader failed at iter %u", i);
3062 unittest::metric(
"http_header_parse_throughput", (
double)iters / us * 1e6,
"ops/s",
true);
3071 "HTTP request and WebSocket frame build/parse round-trips",
"network");
std::string base64_decode(const char *encoded_string)
Decode Base64 text.
std::string base64_encode(std::string string_to_encode)
Base64-encode a string's bytes.
Wire-protocol layer: HTTP request/reply, WebSocket frames, Telnet lines, binary DataMessages,...
#define HTTP_SERVICE_NOT_AVAILABLE
503 Service Unavailable
#define HTTP_MALFORMED_URL
400 Bad Request
#define HTTP_GATEWAY_TIMEOUT
504 Gateway Timeout
#define HTTP_SERVER_MALFORMED_REPLY
500 (malformed backend reply)
#define HTTP_SWITCH_PROTOCOL
101 Switching Protocols (WebSocket upgrade)
#define HTTP_INTERNAL_ERROR
500 Internal Server Error (page generation)
#define HTTP_OPTIONS
OPTIONS.
#define TELNET_UNIX
LF (\n) line endings.
#define HTTP_DELETE
DELETE.
#define TELNET_WINDOWS
CRLF (\r\n) line endings.
#define HTTP_MOVED_PERMANENTLY
301 Moved Permanently
#define HTTP_USE_LOCAL_COPY
304 Not Modified (use cached copy)
#define HTTP_ACCESS_DENIED
403 Forbidden
#define HTTP_FILE_NOT_FOUND
404 Not Found
#define HTTP_UNAUTHORIZED
401 Unauthorized (triggers Basic auth)
#define HTTP_SERVER_NOREPLY
500 (no reply from backend server)
#define HTTP_NOT_IMPLEMENTED
501 Not Implemented
#define GetObjID(data)
Extract the cid field from a binary object block: the uint32 at byte offset 4 (after the leading size...
CMSDK time: µs-resolution 64-bit timestamps and the Time Mapping Constant (TMC).
Small, dependency-free unit test harness used by all CMSDK object tests.
#define GETIPADDRESSQUAD(a)
The central Psyclone data container: a self-contained binary message with typed, named user entries.
bool setTime(const char *key, uint64 value)
setTime(const char* key, uint64 value)
bool setString(const char *key, const char *value)
setString(const char* key, const char* value)
bool setInt(const char *key, int64 value)
setInt(const char* key, int64 value)
DataMessageHeader * data
Pointer to the message's flat memory block (header + user entries).
uint32 getSize()
getSize() Get message size Many types of data of any size can be put into a message as user entries; ...
bool setRecvTime(uint64 time)
setRecvTime(uint64 time)
bool setCreatedTime(uint64 t)
setCreatedTime(uint64 t)
bool setData(const char *key, const char *value, uint32 size)
setData(const char* key, const char* value, uint32 size)
static HTTPRequest * ReceiveHTTPRequest(NetworkConnection *con, uint32 timeout)
Read a full request from the connection.
static bool SendHTTPRequest(NetworkConnection *con, HTTPRequest *req)
Serialise and send a request.
static HTTPReply * ReceiveHTTPReply(NetworkConnection *con, uint32 timeout)
Read a full reply from the connection.
static bool SendHTTPReply(NetworkConnection *con, HTTPReply *reply)
Serialise and send a reply.
static bool CheckBufferForCompatibility(const char *buffer, uint32 length)
static bool InitialiseConversation(NetworkConnection *con)
No initial exchange needed.
One named entry of an HTTP POST body (form field or uploaded file part).
std::string type
MIME Content-Type of this part.
bool setContent(const char *content, uint32 size)
Copy content bytes into this entry (NUL-terminated internally).
std::string filename
Original filename for file uploads (empty for plain fields).
uint32 contentSize
Size of content in bytes.
std::string name
Form field name (Content-Disposition name attribute).
char * content
Owned content bytes (NUL-terminated for convenience).
static bool InitialiseConversation(NetworkConnection *con)
No initial exchange needed for HTTP.
static bool SendHTTPReply(NetworkConnection *con, HTTPReply *reply)
Serialise and send a reply.
static HTTPReply * ReceiveHTTPReply(NetworkConnection *con, uint32 timeout)
Read a full reply from the connection.
static bool SendHTTPRequest(NetworkConnection *con, HTTPRequest *req)
Serialise and send a request.
static HTTPRequest * ReceiveHTTPRequest(NetworkConnection *con, uint32 timeout)
Read a full request from the connection.
static bool CheckBufferForCompatibility(const char *buffer, uint32 length)
static WebsocketData * ReceiveWebsocketData(NetworkConnection *con, uint32 timeout)
Read one complete WebSocket message (reassembling fragments).
static bool SendWebsocketData(NetworkConnection *con, WebsocketData *wsData)
Send a WebSocket frame.
A parsed or generated HTTP response.
bool isWebsocketUpgrade()
uint32 contentLength
Body length in bytes.
const char * getRawContent(uint32 &size)
Get the raw, untransformed body bytes.
bool createAuthorizationReply(const char *realm)
Build a 401 Basic-auth challenge.
const char * getProtocol()
const char * getContent(uint32 &size)
Get the (decoded) response body.
uint8 type
HTTP_* status id of this reply.
uint64 time
Timestamp of creation/receipt (ms epoch).
bool processHeader(const char *buffer, uint32 size, bool &isInvalid)
Parse the response header block from raw bytes.
uint32 headerLength
Header block length within data, in bytes.
bool setDecodedContent(const char *buffer, uint32 size)
Replace the body with already-decoded bytes (e.g.
static HTTPReply * CreateErrorReply(uint8 type)
Build a canned error reply.
static HTTPReply * CreateWebsocketHTTPReply(const char *key, const char *version)
Build the "101 Switching Protocols" reply for a WebSocket handshake.
std::map< std::string, std::string > params
Auxiliary parsed parameters.
bool chunked
Body uses HTTP/1.1 chunked transfer encoding.
bool processContent(const char *buffer, uint32 size)
Append body bytes after the header has been parsed.
bool createFromFile(uint64 time, const char *serverName, uint64 ifLastMod, bool keepAlive, bool cache, const char *filename)
Build a response by reading a file from disk (MIME type from extension).
static HTTPReply * CreateAuthorizationReply(const char *realm)
Build a 401 reply requesting Basic authentication.
bool createOptionsResponse(uint8 status, uint64 time, const char *serverName, bool keepAlive, const char *origin, const char *operations)
Build a CORS-preflight (OPTIONS) response.
bool createWebsocketHTTPReply(const char *key, const char *version)
Build the 101 WebSocket handshake completion in-place.
char * data
Owned raw reply bytes (header followed by body).
bool keepAlive
Whether the connection stays open after this reply.
std::map< std::string, std::string > entries
Parsed header fields (name to value).
HTTPReply(uint64 source=0)
Construct an empty reply.
bool createPage(uint8 status, uint64 time, const char *serverName, uint64 lastMod, bool keepAlive, bool cache, const char *contentType, const char *content, uint32 contentSize=0, const char *additionalHeaderEntries=NULL)
Build a complete response with headers and content.
const char * getHeaderEntry(const char *entry)
Look up a header field (case-insensitive).
bool createErrorPage(uint8 status, uint64 time, const char *serverName, bool keepAlive)
Build a canned error page for the given status.
uint64 source
Packed uint64 endpoint of the sender (0 if local).
A parsed or generated HTTP request (also used for WebSocket upgrade handshakes).
bool parseURIParameters(const char *text)
Parse "a=1&b=2" style parameters from a URI query string into params.
const char * getBasicAuthorization()
const char * getContent(uint32 &size)
Get the (decoded) request body.
const char * getRawContent(uint32 &size)
Get the raw, untransformed body bytes as received.
bool parseContentParameters(const char *content, uint32 size)
Parse a URL-encoded form body into params.
uint64 time
Timestamp when reception/creation started (ms epoch).
std::string getBasicAuthorizationPassword()
const char * getHeaderEntry(const char *entry)
Look up a header field (case-insensitive).
DataMessage * convertToMessage()
Convert this HTTP request into a binary DataMessage (URI, params and content mapped to message fields...
std::map< std::string, std::string > entries
Parsed header fields (name to value).
HTTPRequest(uint64 source=0, uint64 startRecTime=0)
Construct an empty request.
uint32 headerLength
Length of the header block in data, in bytes.
uint32 contentLength
Body length (from Content-Length / parsing), in bytes.
bool parseContentChunk(const char *chunk, uint32 size)
Parse one chunk of a multipart body (between boundaries) into postEntries.
const char * getPostData(const char *entry, uint32 &size, const char **type)
Get a multipart POST part's content, size and MIME type.
static HTTPRequest * CreateWebsocketRequest(const char *uri, const char *host, const char *protocolName, const char *origin)
Build a client-side WebSocket upgrade request (RFC 6455 handshake).
uint8 type
HTTP_* method id.
bool createWebsocketRequest(const char *uri, const char *host, const char *protocolName, const char *origin)
Fill this object with a WebSocket upgrade handshake request.
char * data
Owned raw request bytes (header followed by body).
uint64 ifModifiedSince
Parsed If-Modified-Since timestamp (ms epoch; 0 = absent).
std::map< std::string, HTTPPostEntry * > postEntries
Multipart POST parts by name (owned).
std::string decodeBasicAuthorization()
Decode the Basic Authorization header.
bool processContent(const char *buffer, uint32 size)
Append body bytes after the header has been parsed.
bool processHeader(const char *buffer, uint32 size, bool &isInvalid)
Parse the HTTP header block from raw bytes.
const char * getProtocol()
std::string getBasicAuthorizationUser()
bool createRequest(uint8 type, const char *host, const char *uri, const char *content, uint32 contentSize, bool keepAlive, uint64 ifModifiedSince)
Build a simple request with optional raw body.
std::map< std::string, std::string > params
URI query and form parameters (name to value).
uint64 source
Packed uint64 endpoint of the sender (0 if local).
uint64 endReceiveTime
Timestamp when the request was fully received (ms epoch).
bool createMultipartRequest(uint8 type, const char *host, const char *uri, std::map< std::string, std::string > &headerEntries, std::map< std::string, HTTPPostEntry * > &bodyEntries, bool keepAlive, uint64 ifModifiedSince)
Build a multipart/form-data request from several named parts.
std::string postBoundary
Multipart boundary string, when applicable.
const char * getPostDataType(const char *entry)
bool setBasicAuthorization(const char *authB64)
Set the Authorization header from a pre-encoded base64 credential.
bool keepAlive
Whether the connection should be kept open after replying.
const char * getParameter(const char *entry)
Look up a URI query or form parameter by name.
const char * getRequest()
bool isWebsocketUpgrade()
const char * getData(const char *name, uint64 &size)
Get an attachment's bytes by name.
uint64 mOffset
Byte offset where the binary multipart section begins.
uint32 addData(const char *name, const char *data, uint64 size, const char *type)
Append a binary attachment.
uint64 jmSize
Total size of jmData in bytes.
DataMessage * convertToMessage()
Convert this container into a binary DataMessage.
std::string getDataType(const char *name)
std::string getInfoString()
std::map< uint32, JSONMEntry > entries
Attachment metadata by chunk index.
std::string getDataName(uint32 chunk)
char * jmData
Owned serialised container buffer (JSON + attachments).
bool setJSON(const char *json)
Replace the JSON text portion (multipart metadata is regenerated).
static bool SendMessage(NetworkConnection *con, DataMessage *msg, uint64 receiver=0)
Serialise and send one message.
static bool CheckBufferForCompatibility(const char *buffer, uint32 length)
static bool InitialiseConversation(NetworkConnection *con)
Perform the initial message-protocol greeting.
static DataMessage * ReceiveMessage(NetworkConnection *con, uint32 timeout)
Read one full message frame.
Abstract base class for all point-to-point network connections.
virtual bool send(const char *data, uint32 size, uint64 receiver=0)=0
Send raw bytes on the connection.
virtual uint64 getRemoteAddress()
virtual bool receiveAvailable(char *data, uint32 &size, uint32 maxSize, uint32 timeout, bool peek=false)
Receive whatever bytes are available (up to maxSize).
virtual bool receive(char *data, uint32 size, uint32 timeout, bool peek=false)
Receive exactly size bytes into data, waiting up to timeout ms.
virtual bool waitForDataToRead(uint32 timeout)
Block until data is readable (buffered or on the socket).
virtual bool discard(uint32 size)
Drop size bytes from the front of the receive buffer (after a peek).
One line of Telnet-style text traffic.
bool giveData(char *buffer, uint32 len)
Take ownership of an existing buffer as the line text (no copy).
uint32 size
Line length in bytes.
TelnetLine(uint64 source)
Construct an empty line.
char cr[3]
Active line-ending characters (\r\n or \n, NUL-terminated).
uint32 user
Optional user/session tag for multi-user telnet servers.
bool setCR(uint8 type)
Select the line-ending convention.
char * data
Owned line text.
uint64 time
Timestamp of creation/receipt (ms epoch).
uint64 source
Packed uint64 endpoint of the sender.
bool setLine(const char *buffer, bool addCR)
Set the line text from a NUL-terminated string.
static TelnetLine * ReceiveTelnetLine(NetworkConnection *con, uint32 timeout)
Read one line terminated by CR/LF.
static bool CheckBufferForCompatibility(const char *buffer, uint32 length)
static bool SendTelnetLine(NetworkConnection *con, TelnetLine *line)
Send one line (with its line ending).
static bool InitialiseConversation(NetworkConnection *con)
Send any initial prompt/negotiation.
static UnitTestRunner & instance()
Access the singleton (created on first use).
void registerTest(const char *name, UnitTestFunc func, const char *description="", const char *category="", bool inDefaultRun=true)
Register a test with the runner.
One WebSocket frame/message (RFC 6455): parsing, generation and control frames.
uint64 endReceiveTime
Timestamp when the frame was fully received (ms epoch).
const char * getRawData(uint64 &size)
Get the serialised on-the-wire frame bytes (header + masked payload).
uint64 payloadSize
Declared payload length from the header.
static uint8 const payload_size_code_16bit
Basic length 126: a 16-bit big-endian length follows.
static uint8 const BHB1_PAYLOAD
Byte 1: 7-bit basic payload length / extension code.
static uint8 const payload_size_code_64bit
Basic length 127: a 64-bit big-endian length follows.
static uint8 const BHB0_OPCODE
Byte 0: 4-bit opcode (see DataType).
bool isTerminationConfirmation()
uint64 contentSize
Payload bytes accumulated so far.
static uint8 const payload_size_basic
Maximum size of a basic WebSocket payload.
const char * getContent(uint64 &size)
Get the decoded (unmasked) payload.
uint32 headerLength
Parsed frame header length in bytes (2..14).
static WebsocketData * CreatePing()
bool isFinal
FIN bit: true when this is the last fragment of a message.
static uint8 const basic_header_length
char * data
Owned decoded payload bytes.
bool isTerminationRequest()
uint8 opcode
RFC 6455 opcode (see DataType).
DataType
Frame content type, mapping to the RFC 6455 opcode.
static WebsocketData * CreatePong()
enum cmlabs::WebsocketData::DataType dataType
uint32 maskingKey
32-bit masking key (0 when unmasked, i.e.
bool processContent(const char *buffer, uint64 size)
Append (and unmask) payload bytes after the header has been parsed.
static WebsocketData * CreateTerminationConfirmation()
char * rawData
Owned serialised frame bytes (for sending).
static uint8 const BHB0_FIN
Byte 0: FIN — set on the final fragment of a message.
WebsocketData(uint64 source=0, uint64 startRecTime=0)
Construct an empty frame.
static uint16 const payload_size_extended
Maximum size of an extended WebSocket payload (basic payload = 126).
bool setData(DataType dataType, bool maskData, const char *data=NULL, uint64 size=0)
Set the payload and build the serialised frame for sending.
static uint8 const BHB1_MASK
Byte 1: MASK — payload is XOR-masked (required client-to-server).
uint64 time
Timestamp when reception/creation started (ms epoch).
enum cmlabs::WebsocketData::WSSTATUS status
uint64 source
Packed uint64 endpoint of the sender (0 if local).
bool processHeader(const char *buffer, uint64 size, bool &isInvalid)
Parse the frame header from raw bytes (handles 16/64-bit extended lengths).
uint32 packages
Number of fragments accumulated into this message.
std::string getHashRaw()
return latest hash as raw characters
void add(const void *data, size_t numBytes)
add arbitrary number of bytes
uint64 GetTimeNow()
Return the current absolute time (µs since year 0) according to the TMC.
#define TIME_YEAR_1970
Same value as USEC_YEAR_0_TO_1970: the PsyTime timestamp of 1970-01-01 00:00:00 UTC.
uint32 GetHTTPTime(uint64 time, char *buffer, uint32 size)
Format a timestamp as an HTTP-date (RFC 7231) string, e.g.
uint64 GetTimeFromString(const char *str)
Parse a textual date/time into a PsyTime timestamp.
int32 GetTimeAgeMS(uint64 t)
Age of a timestamp relative to now, in milliseconds.
const char * laststrstr(const char *str1, const char *str2)
Find the last occurrence of str2 in str1.
bool TextStartsWith(const char *str, const char *start, bool caseSensitive=true)
Test whether str starts with start.
bool Sleep(uint32 ms)
Suspend the calling thread.
const char * stristr(const char *str, const char *substr, uint32 len=0)
Case-insensitive strstr.
bool GetNextLineEnd(const char *str, uint32 size, uint32 &len, uint32 &crSize)
Find the end of the current line.
uint64 ntoh64(const uint64 *input)
Convert a 64-bit value from network to host byte order.
uint32 strcpyavail(char *dst, const char *src, uint32 maxlen, bool copyAvailable)
Bounded strcpy that always NUL-terminates.
std::string GetJSONChildValueString(jsmntok_t *tokens, int tokenCount, const char *json, const char *key, int parent, jsmntype_t type=JSMN_UNDEFINED)
std::vector< int > GetJSONChildArrayIndexes(jsmntok_t *tokens, int tokenCount, const char *json, const char *key, int parent, jsmntype_t type=JSMN_UNDEFINED)
int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, unsigned int num_tokens)
Run JSON parser.
uint64 GetJSONChildValueUint64(jsmntok_t *tokens, int tokenCount, const char *json, const char *key, int parent, jsmntype_t type=JSMN_UNDEFINED)
void jsmn_init(jsmn_parser *parser)
Create JSON parser over an array of tokens.
int GetJSONToken(jsmntok_t *tokens, int tokenCount, const char *json, const char *key, int parent, jsmntype_t type=JSMN_UNDEFINED)
bool GetMimeType(char *dest, const char *filename, uint32 maxsize)
Look up the MIME type for a filename based on its extension.
bool IsMimeBinary(const char *type)
Determine whether a MIME type denotes binary content.
std::string DecodeHTML(std::string str)
Decode HTML entities in a string (e.g.
void fail(const char *fmt,...)
Set an explanatory reason shown on the FAIL line.
void metric(const char *name, double value, const char *unit="", bool higherIsBetter=true)
Record a performance metric.
void detail(const char *fmt,...)
Verbose-only indented diagnostic line (shown only when verbose=1).
void progress(int percent, const char *action)
Report progress with a short description of the current action.
std::string ReadAFileString(std::string filename)
Read an entire file into a std::string.
std::vector< std::string > TextListSplit(const char *text, const char *split, bool keepEmpty=true, bool autoTrim=false)
Split text on a separator.
FileDetails GetFileDetails(const char *filename)
Stat a file.
std::multimap< std::string, std::string > TextMultiMapSplit(const char *text, const char *outersplit, const char *innersplit)
Split "k=v<sep>k=v..." text into a multimap (duplicate keys preserved).
std::string TextTrimQuotes(const char *text)
Strip a single pair of surrounding quotes if present.
std::string BytifySize(double val)
Format a byte count with binary units, e.g.
std::string StringFormat(const char *format,...)
printf into a std::string.
char * ReadAFile(const char *filename, uint32 &length, bool binary=false)
Read an entire file into a new buffer.
int64 RandomInt(int64 from=0, int64 to=100)
Uniform random integer in [from,to].
bool NetworkProtocols_UnitTest()
static char HTTP_Status[][128]
Full HTTP status lines (with inline error bodies for error cases), indexed by the HTTP_* status ids a...
static char HTTP_Type[][8]
HTTP method name strings, indexed by the HTTP_* method ids (index 0 unused).
void Register_NetworkProtocols_Tests()
static bool TestWebsocketRoundTrip(WebsocketData::DataType type, bool maskData, const char *payload, uint64 payloadSize)
Metadata for one binary attachment chunk inside a JSONM container.
uint64 size
Attachment size in bytes.
std::string type
MIME type of the attachment.
uint64 offset
Byte offset of the attachment within the container data.
uint32 chunk
Chunk index within the container (1-based).
std::string name
Attachment name.
Existence, type, permission and timestamp information for one file, as returned by GetFileDetails().