CMSDK 2.0.1
Cross-platform C++ base library and SDK for the Psyclone AIOS platform
Loading...
Searching...
No Matches
NetworkProtocols.cpp
Go to the documentation of this file.
1
7#include "NetworkProtocols.h"
8#include "PsyTime.h"
9#include "UnitTestFramework.h"
10
11namespace cmlabs{
12
14// Data Types
16
17//HTTPRequest::HTTPRequest(uint64 startRecTime) {
18// if (startRecTime)
19// time = startRecTime;
20// else
21// time = GetTimeNow();
22// endReceiveTime = 0;
23// this->source = 0;
24// type = 0;
25// headerLength = 0;
26// contentLength = 0;
27// ifModifiedSince = 0;
28// keepAlive = true;
29// data = NULL;
30//}
31
32HTTPRequest* HTTPRequest::CreateWebsocketRequest(const char* uri, const char* host, const char* protocolName, const char* origin) {
33 HTTPRequest* req = new HTTPRequest();
34 if (req->createWebsocketRequest(uri, host, protocolName, origin))
35 return req;
36 else {
37 delete req;
38 return NULL;
39 }
40}
41
42HTTPRequest::HTTPRequest(uint64 source, uint64 startRecTime) {
43 if (startRecTime)
44 time = startRecTime;
45 else
46 time = GetTimeNow();
48 this->source = source;
49 type = 0;
50 headerLength = 0;
51 contentLength = 0;
53 keepAlive = true;
54 data = NULL;
55}
56
58 time = req->time;
60 source = req->source;
61 type = req->type;
65 keepAlive = req->keepAlive;
66 data = new char[headerLength+contentLength+1];
67 memcpy(data, req->data, headerLength+contentLength+1);
68 entries = req->entries;
69 params = req->params;
70}
71
72
74 if (data != NULL)
75 delete [] data;
76 data = NULL;
77 std::map<std::string, HTTPPostEntry*>::iterator i = postEntries.begin(), e = postEntries.end();
78 while (i != e) {
79 delete(i->second);
80 i++;
81 }
82 postEntries.clear();
83}
84
85// Reading it in from a socket
86bool HTTPRequest::processHeader(const char* buffer, uint32 size, bool &isInvalid) {
87 isInvalid = false;
88 if (!buffer || (size < 5))
89 return false;
90
91 if (utils::stristr(buffer, "GET ") == buffer)
92 type = HTTP_GET;
93 else if (utils::stristr(buffer, "PUT ") == buffer)
94 type = HTTP_PUT;
95 else if (utils::stristr(buffer, "POST ") == buffer)
97 else if (utils::stristr(buffer, "HEAD ") == buffer)
99 else if (utils::stristr(buffer, "DELETE ") == buffer)
101 else if (utils::stristr(buffer, "OPTIONS ") == buffer)
103 else {
104 isInvalid = true;
105 return false;
106 }
107
108 // Find end of header
109 uint32 n;
110 for (n = 3; n < size; n++) {
111 if (buffer[n] == 10) {
112 if ( (size-n >= 2) && (buffer[n+1] == 10) ) {
113 // Just using LF, no CR
114 headerLength = n+2;
115 break;
116 }
117 else if ( (size-n >= 3) && (buffer[n+1] == 13) && (buffer[n+2] == 10) && (buffer[n-1] == 13) ) {
118 // Using CRLF
119 headerLength = n+3;
120 break;
121 }
122 }
123 }
124
125 if (headerLength == 0)
126 return false;
127
128 uint32 tempContentLength = 1024;
129 data = new char[headerLength+tempContentLength+1];
130 memcpy(data, buffer, headerLength);
131 data[headerLength] = 0; // temp sting end
132
133 // Now process header entries
134 std::string key, val, uri;
135 const char* s = data, *t;
136 n = 0;
137 uint32 crSize = 0;
138 uint32 left = headerLength;
139 size_t i, j;
140 while (n < headerLength) {
141 if (!utils::GetNextLineEnd(s, left, n, crSize))
142 break;
143 // Process line
144 if ((t = strchr(s, ' ')) && (t - s < (int32)n)) {
145 t -= 1;
146 if (*t != ':') {
147 t++;
148 key.assign(s, t - s);
149 }
150 else {
151 key.assign(s, t - s);
152 t++;
153 }
154 while (*t == 32) t++;
155 if (*t > 32)
156 val.assign(t, n + s - t);
157 else
158 val = "";
159 }
160 else {
161 key.assign(s, n);
162 val = "";
163 }
164 // Record entry
165 if (s == data) {
166 entries["http_fullrequest"] = val;
167 if ((i = val.find(' ')) != std::string::npos) {
168 uri = val.substr(0, i);
169 entries["http_uri"] = uri;
170 //printf("*** URI: %s\n", uri.c_str());
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); // without the first /
174 parseURIParameters(uri.substr(j+1).c_str());
175 }
176 else
177 entries["http_request"] = uri.substr(1, i-1); // without the first /
178 }
179 else {
180 uri = val;
181 entries["http_uri"] = uri;
182 entries["http_protocol"] = "";
183 if ((j = uri.find('?')) != std::string::npos) {
184 entries["http_request"] = uri.substr(1, j-1); // without the first /
185 parseURIParameters(uri.substr(j+1).c_str());
186 }
187 else
188 entries["http_request"] = uri.substr(1); // without the first /
189 }
190 }
191 else if (key.length() > 0) {
192 if (entries.find(key) != entries.end())
193 entries[key] = entries[key] + ";" + val;
194 else
195 entries[key] = val;
196 // Check entry
197 if (stricmp(key.c_str(), "content-length") == 0)
198 contentLength = atoi(val.c_str());
199 else if (stricmp(key.c_str(), "if-modified-since") == 0)
200 ifModifiedSince = GetTimeFromString(val.c_str());
201 else if (stricmp(key.c_str(), "connection") == 0)
202 keepAlive = (stricmp(val.c_str(), "close") != 0);
203 else if (stricmp(key.c_str(), "content-type") == 0) {
204 std::multimap<std::string,std::string> conTypes = utils::TextMultiMapSplit(val.c_str(), ";", "=");
205 std::multimap<std::string,std::string>::iterator ci = conTypes.find("boundary");
206 if (ci != conTypes.end())
207 postBoundary = utils::StringFormat("--%s", ci->second.c_str());
208 }
209 }
210 // Next line
211 s += n + crSize;
212 left -= n + crSize;
213 }
214
215 if (contentLength > tempContentLength) {
216 delete [] data;
217 data = new char[headerLength+contentLength+1];
218 memcpy(data, buffer, headerLength);
219 data[headerLength] = 0; // temp string end
221 }
222
223 if (!contentLength)
225
226 return true;
227}
228
229bool HTTPRequest::processContent(const char* buffer, uint32 size) {
230 if (!buffer || (size != contentLength))
231 return false;
232 memcpy(data+headerLength, buffer, size);
236 return true;
237}
238
239const char* HTTPRequest::getContent(uint32& size) {
240 size = contentLength;
241 return data+headerLength;
242}
243
244const char* HTTPRequest::getRawContent(uint32& size) {
246 return data;
247}
248
249const char* HTTPRequest::getHeaderEntry(const char* entry) {
250 std::map<std::string, std::string>::iterator it = entries.find(entry);
251 if (it != entries.end())
252 return it->second.c_str();
253 else
254 return NULL;
255}
256
258 return getHeaderEntry("http_request");
259}
260
261const char* HTTPRequest::getURI() {
262 return getHeaderEntry("http_uri");
263}
264
266 return getHeaderEntry("http_protocol");
267}
268
270 const char* auth = getHeaderEntry("Authorization");
271 if (!auth || !utils::TextStartsWith(auth, "Basic ", false))
272 return NULL;
273 return auth + 6; // after 'Basic '
274}
275
277 const char* auth = getBasicAuthorization();
278 if (!auth) return "";
279 std::string authString = base64_decode(auth);
280 if (!authString.length() || (authString.find(":") == std::string::npos))
281 return "";
282 return authString;
283}
284
286 std::string authString = decodeBasicAuthorization();
287 if (!authString.length())
288 return "";
289 return utils::TextListSplit(authString.c_str(), ":").at(0);
290}
291
293 std::string authString = decodeBasicAuthorization();
294 if (!authString.length())
295 return "";
296 return utils::TextListSplit(authString.c_str(), ":").at(1);
297}
298
299bool HTTPRequest::setBasicAuthorization(const char* authB64) {
300 if (!authB64 || !strlen(authB64)) {
301 if (entries.find("Authorization") != entries.end())
302 entries.erase("Authorization");
303 }
304 else {
305 entries["Authorization"] = authB64;
306 }
307 return true;
308}
309
310bool HTTPRequest::setBasicAuthorization(const char* user, const char* password) {
311 if (!user || !strlen(user) || !password || !strlen(password))
312 return false;
313 std::string rawAuth = utils::StringFormat("%s:%s", user, password);
314 std::string auth = base64_encode(rawAuth);
315 entries["Authorization"] = auth;
316 return true;
317}
318
319bool HTTPRequest::parseContentParameters(const char* content, uint32 size) {
320// "------WebKitFormBoundarynP2yLpjkwmT3R8j5\r\nContent-Disposition: form-data; name="Username"\r\n\r\nundefined\r\n"
321// "------WebKitFormBoundarynP2yLpjkwmT3R8j5\r\nContent-Disposition: form-data; name="Password"\r\n\r\nundefined\r\n"
322// "------WebKitFormBoundarynP2yLpjkwmT3R8j5\r\nContent-Disposition: form-data; name="Authentication"\r\n\r\nundefined\r\n"
323// "------WebKitFormBoundarynP2yLpjkwmT3R8j5--\r\n"
324 if (!content)
325 return false;
326
327 // check main content-type
328 std::string contentType = entries["Content-Type"];
329
330 if (contentType.find("application/x-www-form-urlencoded") != std::string::npos) {
331 // post just contains one content chunk
332 parseContentChunk(content, size);
333 }
334 else if (contentType.find("multipart/form-data") != std::string::npos) {
335 // Check if using boundaries
336 if (!postBoundary.length() || (size < (2 * postBoundary.length())) )
337 return false;
338 //printf("[%u] %s\n", size, content);
339 uint32 boundarySize = (uint32)postBoundary.length();
340
341 // Check content
342 const char* boundary = postBoundary.c_str();
343 if (memcmp(content, boundary, boundarySize) != 0)
344 return false;
345
346 const char* lastStart = content + boundarySize + 2;
347 const char* c = lastStart;
348
349 for (uint32 n=boundarySize; n<size; n++) {
350 //if ((*c == '-') && (memcmp(c, boundary, boundarySize) == 0)) {
351 if (memcmp(c, boundary, boundarySize) == 0) {
352 parseContentChunk(lastStart, (uint32)(c - lastStart));
353 lastStart = c += boundarySize + 2;
354 n += boundarySize + 2;
355 }
356 else
357 c++;
358 }
359 }
360 else {
361 // Post content is something else
362 HTTPPostEntry* postEntry = new HTTPPostEntry();
363 postEntry->name = "POST";
364 postEntry->type = contentType;
365 postEntry->setContent(content, size);
366 postEntries[postEntry->name] = postEntry;
367 }
368
369
370 return true;
371}
372
373bool HTTPRequest::parseContentChunk(const char* chunk, uint32 size) {
374// "Content-Disposition: form-data; name="Username"\r\n\r\nundefined\r\n"
375
376 const char* valContent = strstr(chunk, "\r\n\r\n");
377 if (!valContent)
378 return false;
379 uint32 headerSize = (uint32)(valContent-chunk);
380 valContent += 4;
381 uint32 valContentSize = size - headerSize - 6;
382
383 HTTPPostEntry* postEntry = new HTTPPostEntry();
384 char* headerText = new char[headerSize+1];
385 memcpy(headerText, chunk, headerSize);
386 headerText[headerSize] = 0;
387
388 //printf("*** %s\n", headerText);
389
390 std::multimap<std::string, std::string> entryMap;
391 std::multimap<std::string, std::string>::iterator ei;
392
393 std::vector<std::string> headerEntries = utils::TextListSplit(headerText, "\r\n", true, true);
394 std::vector<std::string>::iterator i = headerEntries.begin(), e = headerEntries.end();
395 while (i != e) {
396 if (utils::TextStartsWith((*i).c_str(), "Content-Disposition:", false)) {
397 entryMap = utils::TextMultiMapSplit((*i).c_str(), ";", "=");
398 if ( (ei = entryMap.find("name")) != entryMap.end())
399 postEntry->name = utils::TextTrimQuotes(ei->second.c_str());
400 if ( (ei = entryMap.find("filename")) != entryMap.end())
401 postEntry->filename = utils::TextTrimQuotes(ei->second.c_str());
402 }
403 else if (utils::TextStartsWith((*i).c_str(), "Content-Type:", false)) {
404 postEntry->type = (*i).substr(14);
405 }
406 i++;
407 }
408 delete [] headerText;
409 if (postEntry->name.length())
410 postEntry->setContent(valContent, valContentSize);
411
412 if (postEntry->isValid()) {
413 postEntries[postEntry->name] = postEntry;
414 return true;
415 }
416 else {
417 delete(postEntry);
418 return false;
419 }
420
421
422 //CGIRequestEntry reqEntry;
423
424 //int pos;
425 //QStringList entries;
426 //for (int n=0; n<headerList.size(); n++) {
427 // if ((entry = headerList.at(n).trimmed()).length()) {
428 // if (entry.startsWith("Content-Disposition:", Qt::CaseInsensitive)) {
429 // entries = entry.split(";");
430 // for (int m=0; m<entries.size(); m++) {
431 // if ( (pos = entries.at(m).indexOf(" name=")) >= 0) {
432 // reqEntry.name = entries.at(m).mid(pos+6).replace("\"","");
433 // }
434 // else if ( (pos = entries.at(m).indexOf(" filename=")) >= 0) {
435 // reqEntry.filename = entries.at(m).mid(pos+10).replace("\"","");
436 // }
437 // }
438 // }
439 // else if (entry.startsWith("Content-Type:", Qt::CaseInsensitive)) {
440 // reqEntry.type = entry.split(":").at(1).trimmed().replace("\"","");
441 // }
442 // }
443 //}
444
445 //if (!reqEntry.name.length())
446 // return false;
447
448 //reqEntry.data = dataChunk.mid(doubleBreakPos+4);
449 //requestEntries[reqEntry.name] = reqEntry;
451 //return true;
452}
453
454bool HTTPRequest::parseURIParameters(const char* text) {
455 if (!text)
456 return false;
457
458 std::multimap<std::string, std::string> paramMap = utils::TextMultiMapSplit(text, "&", "=");
459 std::multimap<std::string, std::string>::iterator i = paramMap.begin(), e = paramMap.end();
460 while (i != e) {
461 params[html::DecodeHTML(i->first)] = html::DecodeHTML(i->second);
462 i++;
463 }
464 return true;
465
466// uint32 len = (uint32)strlen(text);
467// const char* s = text;
468// const char* e = strchr(s, '=');
469// const char* p = strchr(s, '&');
470
471// if (!e && !p) {
472// key.assign(s);
473// params[html::DecodeHTML(key)] = "";
474// return true;
475// }
476
477// std::string key, val;
478
479// while ( e ) {
480// key.assign(s, e-s);
481// if (p) {
482// val.assign(e+1, p-e-1);
483// params[html::DecodeHTML(key)] = html::DecodeHTML(val);
484// s = p+1;
485// e = strchr(s, '=');
486// p = strchr(s, '&');
487// }
488// else {
489// val.assign(e+1);
490// params[html::DecodeHTML(key)] = html::DecodeHTML(val);
491// break;
492// }
493// }
494// return true;
495}
496
498 DataMessage* msg = new DataMessage();
499 std::map<std::string, std::string>::iterator i, e;
500
501 msg->setInt("HTTP_OPERATION", type);
502 msg->setString("REQUEST", this->getRequest());
503 msg->setString("URI", this->getURI());
504 msg->setTime("SOURCE", this->source);
505
506 for (i=entries.begin(), e=entries.end(); i!=e; i++)
507 msg->setString(i->first.c_str(), i->second.c_str());
508
509 for (i=params.begin(), e=params.end(); i!=e; i++)
510 msg->setString(i->first.c_str(), i->second.c_str());
511
512 std::map<std::string, HTTPPostEntry*>::iterator pi = postEntries.begin(), pe = postEntries.end();
513 while (pi != pe) {
514 if (pi->second->type.length()) {
515 msg->setString((pi->first+"_CONTENT_TYPE_").c_str(), pi->second->type.c_str());
516 if (utils::stristr(pi->second->type.c_str(), "json"))
517 msg->setString(pi->first.c_str(), pi->second->content);
518 else if (utils::stristr(pi->second->type.c_str(), "xml"))
519 msg->setString(pi->first.c_str(), pi->second->content);
520 else if (utils::stristr(pi->second->type.c_str(), "text"))
521 msg->setString(pi->first.c_str(), pi->second->content);
522 else
523 msg->setData(pi->first.c_str(), pi->second->content, pi->second->contentSize);
524 }
525 else
526 msg->setString(pi->first.c_str(), pi->second->content);
527 pi++;
528 }
529 msg->setCreatedTime(time);
531
532 return msg;
533}
534
536 const char* connection = this->getHeaderEntry("Connection");
537 if (!connection || stricmp(connection, "Upgrade"))
538 return false;
539
540 const char* upgrade = this->getHeaderEntry("Upgrade");
541 if (!upgrade || stricmp(upgrade, "websocket"))
542 return false;
543
544 return true;
545}
546
547
548
549const char* HTTPRequest::getParameter(const char* entry) {
550 std::map<std::string, std::string>::iterator it = params.find(entry);
551 if (it != params.end())
552 return it->second.c_str();
553 else
554 return NULL;
555}
556
557const char* HTTPRequest::getPostData(const char* entry, uint32& size, const char** type) {
558 std::map<std::string, HTTPPostEntry*>::iterator i = postEntries.find(entry);
559 if (i == postEntries.end())
560 return NULL;
561 size = i->second->contentSize;
562 *type = i->second->type.c_str();
563 return i->second->content;
564}
565
566const char* HTTPRequest::getPostData(const char* entry, uint32& size) {
567 std::map<std::string, HTTPPostEntry*>::iterator i = postEntries.find(entry);
568 if (i == postEntries.end())
569 return NULL;
570 size = i->second->contentSize;
571 return i->second->content;
572}
573
574const char* HTTPRequest::getPostDataType(const char* entry) {
575 std::map<std::string, HTTPPostEntry*>::iterator i = postEntries.find(entry);
576 if (i == postEntries.end())
577 return NULL;
578 return i->second->type.c_str();
579}
580
581
583 uint8 type, const char* host, const char* uri, const char* content,
584 uint32 contentSize, bool keepAlive, uint64 ifModifiedSince) {
585
586 char timeString[1024];
587 if (!ifModifiedSince) {
588 if (!GetHTTPTime(TIME_YEAR_1970, timeString, 1024))
589 timeString[0] = 0;
590 }
591 else {
592 if (!GetHTTPTime(ifModifiedSince, timeString, 1024))
593 timeString[0] = 0;
594 }
595
596 if (data)
597 delete [] data;
598 data = new char[4096 + contentSize];
599 if (!content) {
600 snprintf(data, 4096+contentSize,
601"%s %s HTTP/1.1\r\n\
602Host: %s\r\n\
603Accept: */*\r\n\
604User-Agent: CMLabsHTTP/1.0\r\n\
605if-modified-since: %s\r\n\r\n",
606 HTTP_Type[type], uri, host, timeString);
607 }
608 else {
609 snprintf(data, 4096+contentSize,
610"%s %s HTTP/1.1\r\n\
611Host: %s\r\n\
612Accept: */*\r\n\
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)));
617 }
618
619 headerLength = (uint32)strlen(data);
620 memcpy(data+headerLength, content, contentSize);
621 contentLength = contentSize;
623
624 return true;
625}
626
627bool HTTPRequest::createRequest(uint8 type, const char* host, const char* uri, std::map<std::string, std::string>& headerEntries, bool keepAlive, uint64 ifModifiedSince) {
628
629 // First create the header
630
631 char timeString[1024];
632 if (!ifModifiedSince) {
633 if (!GetHTTPTime(TIME_YEAR_1970, timeString, 1024))
634 timeString[0] = 0;
635 }
636 else {
637 if (!GetHTTPTime(ifModifiedSince, timeString, 1024))
638 timeString[0] = 0;
639 }
640
641 std::string headerContent = utils::StringFormat(
642 "%s %s HTTP/1.1\r\n\
643Host: %s\r\n\
644Accept: */*\r\n\
645User-Agent: CMLabsHTTP/1.0\r\n\
646if-modified-since: %s\r\n",
647HTTP_Type[type], uri, host, timeString);
648
649 std::map<std::string, std::string>::iterator hI = headerEntries.begin(), hE = headerEntries.end();
650 while (hI != hE) {
651 if ((stricmp(hI->first.c_str(), "content-type") != 0) &&
652 (stricmp(hI->first.c_str(), "content-length") != 0)) {
653 headerContent += utils::StringFormat("%s: %s\r\n", hI->first.c_str(), hI->second.c_str());
654 }
655 hI++;
656 }
657
658 // Calculate content size
659 headerLength = (uint32)headerContent.length();
660
661 if (data)
662 delete [] data;
663 data = new char[headerLength + 1];
664
665 memcpy(data, headerContent.c_str(), headerLength);
666 data[headerLength] = 0;
667 return true;
668}
669
670
671bool HTTPRequest::createRequest(uint8 type, const char* host, const char* uri, std::map<std::string, std::string>& headerEntries, HTTPPostEntry* bodyEntry, bool keepAlive, uint64 ifModifiedSince) {
672 if (!bodyEntry)
673 return createRequest(type, host, uri, headerEntries, keepAlive, ifModifiedSince);
674 else
675 return createRequest(type, host, uri, headerEntries, bodyEntry->content, bodyEntry->type.c_str(), bodyEntry->contentSize, keepAlive, ifModifiedSince);
676}
677
678bool HTTPRequest::createRequest(uint8 type, const char* host, const char* uri, std::map<std::string, std::string>& headerEntries, const char* content, const char* contentType, uint32 contentSize, bool keepAlive, uint64 ifModifiedSince) {
679
680 if (!contentSize)
681 return createRequest(type, host, uri, headerEntries, keepAlive, ifModifiedSince);
682
683 if (!content || !contentType)
684 return false;
685
686 // First create the header
687 char timeString[1024];
688 if (!ifModifiedSince) {
689 if (!GetHTTPTime(TIME_YEAR_1970, timeString, 1024))
690 timeString[0] = 0;
691 }
692 else {
693 if (!GetHTTPTime(ifModifiedSince, timeString, 1024))
694 timeString[0] = 0;
695 }
696
697 std::string headerContent = utils::StringFormat(
698 "%s %s HTTP/1.1\r\n\
699Host: %s\r\n\
700Accept: */*\r\n\
701User-Agent: CMLabsHTTP/1.0\r\n\
702Content-Type: %s\r\n\
703if-modified-since: %s\r\n",
704HTTP_Type[type], uri, host, contentType, timeString);
705
706 std::map<std::string, std::string>::iterator hI = headerEntries.begin(), hE = headerEntries.end();
707 while (hI != hE) {
708 if ((stricmp(hI->first.c_str(), "content-type") != 0) &&
709 (stricmp(hI->first.c_str(), "content-length") != 0)) {
710 headerContent += utils::StringFormat("%s: %s\r\n", hI->first.c_str(), hI->second.c_str());
711 }
712 hI++;
713 }
714
715 // Calculate content size
716 contentLength = contentSize;
717 headerContent += utils::StringFormat("Content-Length: %u\r\n\r\n", contentLength);
718 headerLength = (uint32)headerContent.length();
719
720 if (data)
721 delete [] data;
722 data = new char[headerLength + contentLength + 1];
723
724 memcpy(data, headerContent.c_str(), headerLength);
725 char* dst = data + headerLength;
726
727 memcpy(dst, content, contentLength);
729 return true;
730}
731
732
734 uint8 type, const char* host, const char* uri, std::map<std::string, std::string>& headerEntries,
735 std::map<std::string, HTTPPostEntry*>& bodyEntries, bool keepAlive, uint64 ifModifiedSince) {
736
737 // First create the header
738
739 std::string boundaryString = utils::StringFormat("----cmboundary%lld", utils::RandomInt(0, 10000000));
740 uint32 boundaryLength = (uint32)boundaryString.length();
741
742 char timeString[1024];
743 if (!ifModifiedSince) {
744 if (!GetHTTPTime(TIME_YEAR_1970, timeString, 1024))
745 timeString[0] = 0;
746 }
747 else {
748 if (!GetHTTPTime(ifModifiedSince, timeString, 1024))
749 timeString[0] = 0;
750 }
751
752 std::string headerContent = utils::StringFormat(
753 "%s %s HTTP/1.1\r\n\
754Host: %s\r\n\
755Accept: */*\r\n\
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);
760
761 std::map<std::string, std::string>::iterator hI = headerEntries.begin(), hE = headerEntries.end();
762 while (hI != hE) {
763 if ((stricmp(hI->first.c_str(), "content-type") != 0) &&
764 (stricmp(hI->first.c_str(), "content-length") != 0)) {
765 headerContent += utils::StringFormat("%s: %s\r\n", hI->first.c_str(), hI->second.c_str());
766 }
767 hI++;
768 }
769
770 // Remember to add this to the header at the end... Content-Length: %u\r\n\r\n",
771
772 // Calculate content size
773 contentLength = 2 + boundaryLength + 2; // Add the first --boundary\r\n
774
775 uint32 filenameStatementLength = 0;
776
777 std::map<std::string, HTTPPostEntry*>::iterator bI = bodyEntries.begin(), bE = bodyEntries.end();
778 while (bI != bE) {
779 if (html::IsMimeBinary(bI->second->type.c_str()))
780 filenameStatementLength = 19;
781 else
782 filenameStatementLength = 0;
783 // Content-Type: image/jpeg\r\n
784 // Content-Disposition: form-data; name="image"; filename="nofile"\r\n\r\n
785 // <content>\r\n
786 // --boundary
787 contentLength += 14 + (uint32)bI->second->type.length() + 2
788 + 38 + (uint32)bI->second->name.length() + 5 + filenameStatementLength
789 + bI->second->contentSize + 2 + 2 + boundaryLength;
790 if (bI != bE) {
791 contentLength += 2;
792 }
793 bI++;
794 }
795 contentLength += 4; // for the last --\r\n
796
797 headerContent += utils::StringFormat("Content-Length: %u\r\n\r\n", contentLength);
798 headerLength = (uint32)headerContent.length();
799
800 if (data)
801 delete [] data;
802 data = new char[headerLength + contentLength + 1];
803
804 memcpy(data, headerContent.c_str(), headerLength);
805 char* dst = data + headerLength;
806
807 //sprintf(dst, "--%s\r\n", boundaryString.c_str());
808 snprintf(dst, 2 + boundaryLength + 2, "--%s\r\n", boundaryString.c_str());
809 dst += 2 + boundaryLength + 2;
810
811
812 bI = bodyEntries.begin();
813 while (bI != bE) {
814 // Content-Type: image/jpeg\r\n
815 // Content-Disposition: form-data; name="image"; filename="nofile"\r\n\r\n
816 // <content>\r\n
817 // --boundary
818 if (html::IsMimeBinary(bI->second->type.c_str())) {
819 filenameStatementLength = 19;
820 // sprintf(dst, "Content-Type: %s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"nofile\"\r\n\r\n",
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());
824 }
825 else {
826 // sprintf(dst, "Content-Type: %s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n",
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;
831 }
832
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;
837 // sprintf(dst, "\r\n--%s", boundaryString.c_str());
838 snprintf(dst, 2 + 2 + boundaryLength, "\r\n--%s", boundaryString.c_str());
839 dst += 2 + 2 + boundaryLength;
840 bI++;
841 if (bI != bE) {
842 // sprintf(dst, "\r\n");
843 snprintf(dst, 4, "\r\n"); // put 4 in instead of 2 to avoid compiler warning
844 dst += 2;
845 }
846 }
847 // sprintf(dst, "--\r\n");
848 snprintf(dst, 6, "--\r\n"); // put 6 in instead of 4 to avoid compiler warning
849 dst += 4;
851 //uint32 check = (uint32)(dst - data);
852 //uint32 check2 = headerLength + contentLength;
853 //printf("Memory size: %u - data size: %u\n", check2, check);
854 return true;
855}
856
857bool HTTPRequest::createWebsocketRequest(const char* uri, const char* host, const char* protocolName, const char* origin) {
858
859 //GET /chat HTTP/1.1
860 //Host: server.example.com
861 //Upgrade: websocket
862 //Connection: Upgrade
863 //Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
864 //Sec-WebSocket-Protocol: chat, superchat
865 //Sec-WebSocket-Version: 13
866 //Origin: http://example.com
867
868 if (!host)
869 return false;
870
871 std::string uriString;
872 if (uri && strlen(uri))
873 uriString = uri;
874 if (!utils::TextStartsWith(uriString.c_str(), "/", false))
875 uriString = utils::StringFormat("/%s", uriString.c_str());
876
877 std::string originString;
878 if (origin && strlen(origin))
879 originString = origin;
880 else
881 originString = utils::StringFormat("http://%s/", host);
882
883 std::string protocolNameString;
884 if (protocolName && strlen(protocolName))
885 protocolNameString = protocolName;
886 else
887 protocolNameString = "Default";
888
889 char* randomKey = new char[17];
890 for (uint32 n = 0; n < 16; n++)
891 randomKey[n] = (uint8)utils::RandomInt(33, 255);
892 randomKey[16] = 0;
893 std::string key = base64_encode(randomKey);
894 //std::string key = "x3JJHMbDL1EzLkh9GBhXDw==";
895
896 if (data)
897 delete [] data;
898 data = new char[4096];
899 snprintf(data, 4096,
900"GET %s HTTP/1.1\r\n\
901Host: %s\r\n\
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\
907Origin: %s\r\n\r\n",
908 uriString.c_str(), host, key.c_str(), protocolNameString.c_str(), originString.c_str());
909
910 headerLength = (uint32)strlen(data);
911 contentLength = 0;
912 data[headerLength] = 0;
913 return true;
914}
915
916
918 if (data)
920 else
921 return 0;
922}
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942WebsocketData::WebsocketData(uint64 source, uint64 startRecTime) {
943 this->source = source;
944 headerLength = 0;
946 opcode = 0;
947 maskingKey = 0;
948 packages = 0;
949 data = NULL;
950 rawData = NULL;
951 dataType = NONE;
952 status = IDLE;
953 isFinal = false;
954}
955
957 headerLength = wsData->headerLength;
958 payloadSize = wsData->payloadSize;
959 contentSize = wsData->contentSize;
960 opcode = wsData->opcode;
961 maskingKey = wsData->maskingKey;
962 packages = wsData->packages;
963 if (wsData->data) {
964 data = new char[(uint32)payloadSize + 1];
965 memcpy(data, wsData->data, (uint32)payloadSize);
966 data[payloadSize] = 0;
967 }
968 dataType = wsData->dataType;
969 if (wsData->rawData) {
970 rawData = new char[(uint32)payloadSize + headerLength + 1];
971 memcpy(rawData, wsData->rawData, (uint32)payloadSize + headerLength);
973 }
974 status = wsData->status;
975 isFinal = wsData->isFinal;
976}
977
979 if (data != NULL)
980 delete [] data;
981 data = NULL;
982 if (rawData != NULL)
983 // delete [], not delete: rawData is new char[headerLength+size+1] in
984 // setData(). Scalar delete on an array allocation is an alloc-dealloc
985 // mismatch - undefined behaviour, reported by ASan. Note `data` directly
986 // above was already correct, which is how this one stayed unnoticed.
987 delete [] rawData;
988 rawData = NULL;
989 status = IDLE;
990}
991
993 WebsocketData* wsData = new WebsocketData();
994 wsData->setData(wsData->CLOSE, false);
995 return wsData;
996}
997
999 WebsocketData* wsData = new WebsocketData();
1000 wsData->setData(wsData->PING, false);
1001 return wsData;
1002}
1003
1005 WebsocketData* wsData = new WebsocketData();
1006 wsData->setData(wsData->PONG, false);
1007 return wsData;
1008}
1009
1011 return (dataType == CLOSE);
1012}
1013
1017
1019 return (dataType == PING);
1020}
1021
1023 return (dataType == PONG);
1024}
1025
1026bool WebsocketData::setData(DataType dataType, bool maskData, const char* data, uint64 size) {
1027 // Builds the complete RFC 6455 frame into rawData:
1028 // byte 0: FIN | RSV1-3 | opcode (we always send FIN = single frame)
1029 // byte 1: MASK | 7-bit payload length (126 => 16-bit ext, 127 => 64-bit ext)
1030 // [2 or 8 bytes extended length, network byte order]
1031 // [4-byte masking key when maskData] (clients MUST mask per the RFC)
1032 // payload (XOR-masked with the key when maskData)
1033 if ((dataType == NONE))
1034 return false;
1035
1036 this->dataType = dataType;
1037 if (maskData && !maskingKey)
1039
1040 headerLength = basic_header_length; // +sizeof(uint32);
1041 if (maskData)
1042 headerLength += sizeof(uint32);
1043 uint32 mkOffset = basic_header_length;
1044 uint32 basic_value = 0;
1045
1046 if (size <= payload_size_basic) {
1047 basic_value = (uint8)size;
1048 }
1049 else if (size <= payload_size_extended) {
1050 mkOffset += 2;
1051 headerLength += 2;
1052 basic_value = payload_size_code_16bit;
1053 }
1054 else {
1055 headerLength += 8;
1056 mkOffset += 8;
1057 basic_value = payload_size_code_64bit;
1058 }
1059
1060 if (rawData)
1061 delete [] rawData; // new char[] below - see the destructor comment
1062
1063 uint8 opcode = (uint8)dataType;
1064
1065 rawData = new char[headerLength + (uint32)size + 1];
1066 memset(rawData, 0, headerLength);
1067
1068 uint8* b0 = (uint8*)rawData;
1069 uint8* b1 = (uint8*)(rawData + 1);
1070
1071 *b0 |= BHB0_FIN;
1072 *b0 |= (opcode & BHB0_OPCODE);
1073 if (maskData)
1074 *b1 |= BHB1_MASK;
1075
1076 *b1 |= basic_value;
1077
1078 if (maskData)
1079 *(uint32*)(rawData + mkOffset) = maskingKey;
1080
1081 contentSize = payloadSize = size;
1082
1083 if (!size)
1084 return true;
1085
1086 // Write the extended length field. NOTE: the branch ordering here means the
1087 // 64-bit ("jumbo", > 65535 bytes) case is unreachable — any size above 125
1088 // takes the first branch and is truncated to 16 bits by ntohs((uint16)size).
1089 // Payloads up to 64KB (the sizes actually used by the SDK) are unaffected;
1090 // larger single frames would be framed incorrectly. (ntohs is used for
1091 // host->network conversion; it is its own inverse, so this is equivalent
1092 // to htons.)
1093 if (size > payload_size_basic) {
1094 uint16* h2 = (uint16*)(rawData + 2);
1095 *h2 = ntohs((uint16)size);
1096 }
1097 else if (size > payload_size_extended) {
1098 uint64* h3 = (uint64*)(rawData + 2);
1099 *h3 = utils::ntoh64(&size);
1100 }
1101
1102 if (maskData) {
1103 // RFC 6455 masking: payload byte i is XORed with key byte (i mod 4).
1104 // The same loop unmasks on the receive side (XOR is symmetric).
1105 uint8* mask = (uint8*)(&maskingKey);
1106 uint8* src = (uint8*)data;
1107 uint8* dst = (uint8*)(rawData + headerLength);
1108 for (uint64 n = 0; n < size; n++) {
1109 *dst = *src ^ mask[n % 4];
1110 dst++;
1111 src++;
1112 }
1113 }
1114 else {
1115 memcpy(rawData + headerLength, data, (uint32)size);
1116 }
1118 status = COMPLETE;
1119 return true;
1120}
1121
1122const char* WebsocketData::getRawData(uint64& size) {
1123 if (status != COMPLETE) {
1124 size = 0;
1125 return NULL;
1126 }
1127 size = headerLength + payloadSize;
1128 return rawData;
1129}
1130
1131
1133 return contentSize;
1134}
1135
1137 return payloadSize;
1138}
1139
1140
1142 return (status == COMPLETE);
1143}
1144
1145bool WebsocketData::processHeader(const char* buffer, uint64 size, bool &isInvalid) {
1146 // Decodes the RFC 6455 basic header (2 bytes) plus, depending on the 7-bit
1147 // length code, a 16-bit (code 126) or 64-bit (code 127) extended length in
1148 // network byte order, then the 4-byte masking key when the MASK bit is set.
1149 // Requires at least 4 bytes buffered even though a minimal unmasked frame
1150 // is only 2 — callers always have the masked client header available.
1151 // On a continuation frame (status != IDLE) the opcode is 0, so dataType is
1152 // only latched from the first fragment.
1153 isInvalid = false;
1154 if (!buffer || (size < 4))
1155 return false;
1156
1157// utils::WriteAFile("d:/ws.bin", buffer, size, true);
1158
1159 time = GetTimeNow();
1160
1161 uint8 b0 = *(const unsigned char*)buffer;
1162 uint8 b1 = *(const unsigned char*)(buffer + 1);
1163
1164 isFinal = b0 & BHB0_FIN;
1165 uint8 opcode = b0 & BHB0_OPCODE;
1166
1167 // if this is the first package
1168 if (status == IDLE) {
1169 if (opcode == 1)
1170 dataType = TEXT;
1171 else if (opcode == 2)
1172 dataType = BINARY;
1173 else if (opcode == 8)
1174 dataType = CLOSE;
1175 else if (opcode == 9)
1176 dataType = PING;
1177 else if (opcode == 10)
1178 dataType = PONG;
1179 }
1180
1181 bool mask = b1 & BHB1_MASK;
1182 uint8 basic_length = b1 & BHB1_PAYLOAD;
1183 payloadSize = 0;
1184
1185 uint32 mkOffset = basic_header_length;
1186 uint32 payloadOffset = basic_header_length;
1187
1188 if (basic_length <= payload_size_basic) {
1189 payloadSize = basic_length;
1190 }
1191 else if (basic_length == payload_size_code_16bit) {
1192 uint16 h2 = *(const uint16*)(buffer + 2);
1193 payloadSize = ntohs(h2);
1194 mkOffset += 2;
1195 payloadOffset += 2;
1196 // get_extended_size(e);
1197 }
1198 else {
1199 uint64 h3 = *(const uint64*)(buffer + 2);
1201 mkOffset += 8;
1202 payloadOffset += 8;
1203 // get_jumbo_size(e);
1204 }
1205
1206 // if just a control message
1207 if (!payloadSize) {
1208 status = COMPLETE;
1209 return true;
1210 }
1211
1212 maskingKey = 0;
1213 if (mask) {
1214 maskingKey = *(uint32*)(buffer + mkOffset);
1215 //maskingKey = 2112143814;
1216 payloadOffset += 4;
1217 }
1218
1219 // printf("%s\n\n", utils::PrintBitFieldString(buffer, size, 8 * size, "ws").c_str());
1220
1221/* LogPrint(0, LOG_NETWORK, 3, "%s: [%u] (%u) size: %llu offset: %u total size: %llu\n",
1222 isFinal ? "Final" : "Continue",
1223 opcode,
1224 maskingKey,
1225 payloadSize,
1226 payloadOffset,
1227 size
1228 );*/
1229
1230 headerLength = payloadOffset;
1231 //contentSize = payloadSize;
1232
1233 return true;
1234}
1235
1236bool WebsocketData::processContent(const char* buffer, uint64 size) {
1237 // Unmasks (XOR with the 4-byte key; a no-op when maskingKey == 0, i.e.
1238 // unmasked server-to-client frames) and appends this fragment's payload.
1239 // Fragmented messages are reassembled here: each continuation reallocates
1240 // data to contentSize + payloadSize and copies the previous bytes over, so
1241 // the final buffer holds the whole logical message; status only becomes
1242 // COMPLETE on the FIN fragment.
1243 if (!buffer || (size != payloadSize))
1244 return false;
1245
1246// utils::WriteAFile("d:/ws_payload.bin", buffer, size, true);
1247
1248 uint8* mask = (uint8*)(&maskingKey);
1249
1250 uint8* src = (uint8*)buffer;
1251 uint8* dst;
1252
1253 if (status == IDLE) {
1254 if (data)
1255 delete [] data;
1256 data = new char[(uint32)payloadSize + 1];
1257 dst = (uint8*)data;
1258 }
1259 else {
1260 char* newData = new char[(uint32)contentSize + (uint32)payloadSize + 1];
1261 memcpy(newData, data, (uint32)contentSize);
1262 delete [] data;
1263 data = newData;
1264 dst = (uint8*)(data+ (uint32)contentSize);
1265 }
1266
1267 for (uint64 n = 0; n < payloadSize; n++) {
1268 *dst = *src ^ mask[n % 4];
1269 dst++;
1270 src++;
1271 }
1273 data[contentSize] = 0;
1275 if (isFinal)
1276 status = COMPLETE;
1277 else
1279 packages++;
1280
1281 //LogPrint(0, LOG_NETWORK, 0, "Content received: %s\n",
1282 // data
1283 //);
1284
1285 if (isFinal) {
1286 if (packages == 1)
1287 LogPrint(0, LOG_NETWORK, 3, "Single Websocket package content received: %llu -> %llu\n", size, contentSize);
1288 else
1289 LogPrint(0, LOG_NETWORK, 3, "Last Websocket content package received: %llu -> %llu (%u)\n", size, contentSize, packages);
1290 }
1291 else {
1292 if (packages == 1)
1293 LogPrint(0, LOG_NETWORK, 3, "First Websocket content received: %llu -> %llu\n", size, contentSize);
1294 // else
1295 // LogPrint(0, LOG_NETWORK, 0, "Next content received: %llu -> %llu\n", size, contentSize);
1296 }
1297
1298// utils::WriteAFile("d:/ws_content.bin", data, contentSize, true);
1299
1300 return true;
1301}
1302
1303
1304
1305const char* WebsocketData::getContent(uint64& size) {
1306 size = 0;
1307 if (!data || !payloadSize || (status != COMPLETE))
1308 return NULL;
1309 size = contentSize;
1310 return data;
1311}
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1325 this->source = source;
1326 data = NULL;
1327 time = GetTimeNow();
1328 source = 0;
1329 size = 0;
1330 user = 0;
1331 cr[0] = 13;
1332 cr[1] = 10;
1333 cr[2] = 0;
1334}
1335
1337 if (data != NULL)
1338 delete [] data;
1339 data=NULL;
1340}
1341
1342bool TelnetLine::setCR(uint8 type) {
1343 switch(type) {
1344 default:
1345 case TELNET_WINDOWS:
1346 cr[0] = 13;
1347 cr[1] = 10;
1348 cr[2] = 0;
1349 break;
1350 case TELNET_UNIX:
1351 cr[0] = 10;
1352 cr[1] = 0;
1353 break;
1354 }
1355 return true;
1356}
1357
1359 if (!data)
1360 return 0;
1361 if (size)
1362 return size;
1363 else
1364 return (uint32)strlen(data);
1365}
1366
1367bool TelnetLine::setLine(const char* buffer, bool addCR) {
1368 return setLine(buffer, (uint32)strlen(buffer), addCR);
1369}
1370
1371bool TelnetLine::setLine(const char* buffer, uint32 len, bool addCR) {
1372 if (data)
1373 delete [] data;
1374
1375 data = new char[len+3];
1376 if (buffer && len > 0) {
1377 memcpy(data, buffer, len);
1378 if (addCR)
1379 memcpy(data+len, cr, 3);
1380 else
1381 data[len] = 0;
1382 }
1383 else
1384 data[len] = 0;
1385 return true;
1386}
1387
1388bool TelnetLine::giveData(char* buffer, uint32 len) {
1389 if (data)
1390 delete [] data;
1391 data = buffer;
1392 size = len;
1393 return true;
1394}
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1408 HTTPReply* reply = new HTTPReply((uint64)0);
1409 reply->type = type;
1410 return reply;
1411}
1412
1414 HTTPReply* reply = new HTTPReply((uint64)0);
1415 reply->createAuthorizationReply(realm);
1416 return reply;
1417}
1418
1419HTTPReply* HTTPReply::CreateWebsocketHTTPReply(const char* key, const char* version) {
1420 HTTPReply* reply = new HTTPReply((uint64)0);
1421 reply->createWebsocketHTTPReply(key, version);
1422 return reply;
1423}
1424
1425
1427 time = GetTimeNow();
1428 this->source = source;
1429 data = NULL;
1430 type = 0;
1432 keepAlive = true;
1433 chunked = false;
1434}
1435
1437 time = reply->time;
1438 source = reply->source;
1439 type = reply->type;
1440 headerLength = reply->headerLength;
1442 keepAlive = reply->keepAlive;
1443 chunked = reply->chunked;
1444 data = new char[headerLength+contentLength+1];
1445 if (reply->data)
1446 memcpy(data, reply->data, headerLength+contentLength+1); // incl. string end
1447 else
1448 memset(data, 0, headerLength+contentLength+1);
1449 entries = reply->entries;
1450 params = reply->params;
1451}
1452
1454 if (data != NULL)
1455 delete [] data;
1456 data = NULL;
1457}
1458
1459// Generating the data
1460
1461bool HTTPReply::createOptionsResponse(uint8 status, uint64 time, const char* serverName, bool keepAlive, const char* origin, const char* operations) {
1462
1463 //OPTIONS /cors HTTP/1.1
1464 //Origin: http://api.bob.com
1465 //Access-Control-Request-Method: PUT
1466 //Access-Control-Request-Headers: X-Custom-Header
1467 //Host: api.alice.com
1468 //Accept-Language: en-US
1469 //Connection: keep-alive
1470 //User-Agent: Mozilla/5.0...
1471 //
1472 //Preflight Response:
1473 //
1474 //Access-Control-Allow-Origin: http://api.bob.com
1475 //Access-Control-Allow-Methods: GET, POST, PUT
1476 //Access-Control-Allow-Headers: X-Custom-Header
1477 //Content-Type: text/html; charset=utf-8
1478
1479 char timeString[1024];
1480 if (!GetHTTPTime(time, timeString, 1024))
1481 timeString[0] = 0;
1482
1483 if (data)
1484 delete [] data;
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");
1488//"Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept\r\n\r\n");
1489
1490//"%s\r\nDate: %s\r\n
1491//Server: %s\r\n
1492//Connection: %s\r\n
1493//"Access-Control-Allow-Origin: %s\r\n
1494//Access-Control-Allow-Methods: %s\r\n
1495//Access-Control-Allow-Headers: X-Custom-Header\r\n
1496//Content-Type: text/html\r\n\r\n",
1497// //HTTP_Status[status], timeString, serverName,
1498// //keepAlive ? "Keep-Alive" : "close",
1499// origin, operations);
1500
1501 headerLength = (uint32)strlen(data);
1502 contentLength = 0;
1503 return true;
1504}
1505
1506
1507bool HTTPReply::createErrorPage(uint8 status, uint64 time, const char* serverName, bool keepAlive) {
1508
1509 char timeString[1024];
1510 if (!GetHTTPTime(time, timeString, 1024))
1511 timeString[0] = 0;
1512
1513 if (data)
1514 delete [] data;
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",
1520 HTTP_Status[status], timeString, serverName,
1521 keepAlive ? "Keep-Alive" : "close");
1522
1523 headerLength = (uint32)strlen(data);
1524 contentLength = 0;
1525 return true;
1526}
1527
1529
1530 char timeString[1024];
1531 if (!GetHTTPTime(time, timeString, 1024))
1532 timeString[0] = 0;
1533
1534 if (data)
1535 delete [] data;
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",
1540
1541 headerLength = (uint32)strlen(data);
1542 contentLength = 0;
1543 return true;
1544}
1545
1546bool HTTPReply::createWebsocketHTTPReply(const char* key, const char* version) {
1547 // RFC 6455 §4.2.2 handshake: the server proves it understood the upgrade by
1548 // concatenating the client's Sec-WebSocket-Key with the fixed magic GUID
1549 // 258EAFA5-E914-47DA-95CA-C5AB0DC85B11, SHA-1 hashing the result and
1550 // returning it base64-encoded as Sec-WebSocket-Accept in a 101 reply.
1551
1552 if (!key || !strlen(key))
1553 return false;
1554
1555 std::string magicString = utils::StringFormat("%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", key);
1556 hash::SHA1 sha1;
1557 sha1.reset();
1558 sha1.add(magicString.c_str(), magicString.size());
1559 std::string rawMagic = sha1.getHashRaw();
1560
1561 std::string acceptKey = base64_encode(rawMagic);
1562
1563 if (data)
1564 delete [] data;
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",
1568 HTTP_Status[HTTP_SWITCH_PROTOCOL], acceptKey.c_str());
1569
1570 headerLength = (uint32)strlen(data);
1571 contentLength = 0;
1572 return true;
1573}
1574
1575bool HTTPReply::createPage(uint8 status, uint64 time, const char* serverName,
1576 uint64 lastMod, bool keepAlive, bool cache, const char* contentType,
1577 const char* content, uint32 contentSize, const char* additionalHeaderEntries) {
1578
1579 char timeString[1024];
1580 if (!GetHTTPTime(time, timeString, 1024))
1581 timeString[0] = 0;
1582
1583 contentLength = (contentSize > 0) ? contentSize : (uint32)strlen(content);
1584
1585 if (data)
1586 delete [] data;
1587 data = new char[4096 + contentLength];
1588 if (additionalHeaderEntries && strlen(additionalHeaderEntries)) {
1589 snprintf(data, 4096 + contentLength,
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"
1594 "%s\r\n\r\n",
1595 HTTP_Status[status], timeString, serverName, timeString, contentLength,
1596 keepAlive ? "Keep-Alive" : "close", contentType, cache ? "Public" : "No-Cache",
1597 additionalHeaderEntries);
1598 }
1599 else {
1600 snprintf(data, 4096 + contentLength,
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",
1605 HTTP_Status[status], timeString, serverName, timeString, contentLength,
1606 keepAlive ? "Keep-Alive" : "close", contentType, cache ? "Public" : "No-Cache");
1607 }
1608
1609 headerLength = (uint32)strlen(data);
1610 memcpy(data+headerLength, content, contentLength);
1612
1613 return true;
1614}
1615
1616bool HTTPReply::createFromFile(uint64 time, const char* serverName,
1617 uint64 ifLastMod, bool keepAlive, bool cache,
1618 const char* filename) {
1619
1620 std::string actualFilename = filename;
1621 const char* question = strstr(filename, "?");
1622 if (question)
1623 actualFilename = std::string(filename, question - filename);
1624
1625 uint64 lastModifiedTime = 0;
1626 if (ifLastMod) {
1627 utils::FileDetails fileInfo = utils::GetFileDetails(actualFilename.c_str());
1628 if (fileInfo.doesExist && !fileInfo.isDirectory && (fileInfo.lastModifyTime < ifLastMod) )
1629 return createErrorPage(HTTP_USE_LOCAL_COPY, time, serverName, keepAlive);
1630 }
1631
1632 char* filedata = NULL;
1633 uint32 datasize = 0;
1634 std::string html;
1635 bool isBinary = false;
1636 char* exttype = new char[1024];
1637 if (!html::GetMimeType(exttype, actualFilename.c_str(), 1024, isBinary)) {
1638 html = utils::StringFormat("Unsupported mime type for file '%s'...", actualFilename.c_str());
1639 }
1640 else if (!isBinary) {
1641 if (!(html = utils::ReadAFileString(actualFilename.c_str())).size()) {
1642 //html = utils::StringFormat("Could read file '%s'", actualFilename.c_str());
1643 }
1644 }
1645 else {
1646 if ( !(filedata = utils::ReadAFile(actualFilename.c_str(), datasize, true))) {
1647 //html = utils::StringFormat("Could read file '%s'", actualFilename.c_str());
1648 }
1649 }
1650
1651 if (filedata)
1652 createPage(HTTP_OK, GetTimeNow(), serverName, 0, keepAlive, cache, exttype, filedata, datasize);
1653 else if (html.length())
1654 createPage(HTTP_OK, GetTimeNow(), serverName, 0, keepAlive, cache, exttype, html.c_str());
1655 else {
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>";
1657 createPage(HTTP_FILE_NOT_FOUND, GetTimeNow(), serverName, 0, keepAlive, cache, "text/html", html.c_str());
1658 }
1659
1660 delete [] filedata;
1661 delete [] exttype;
1662
1663 return true;
1664}
1665
1668 return false;
1669
1670 const char* connection = this->getHeaderEntry("Connection");
1671 if (!connection || stricmp(connection, "Upgrade"))
1672 return false;
1673
1674 const char* upgrade = this->getHeaderEntry("Upgrade");
1675 if (!upgrade || stricmp(upgrade, "websocket"))
1676 return false;
1677
1678 return true;
1679}
1680
1681
1682// Reading it in from a socket
1683bool HTTPReply::processHeader(const char* buffer, uint32 size, bool &isInvalid) {
1684 isInvalid = false;
1685 if (!buffer || (size < 5))
1686 return false;
1687
1688 // Find end of header
1689 uint32 n;
1690 for (n = 3; n < size; n++) {
1691 if (buffer[n] == 10) {
1692 if ( (size-n >= 2) && (buffer[n+1] == 10) ) {
1693 // Just using LF, no CR
1694 headerLength = n+2;
1695 break;
1696 }
1697 else if ( (size-n >= 3) && (buffer[n+1] == 13) && (buffer[n+2] == 10) && (buffer[n-1] == 13) ) {
1698 // Using CRLF
1699 headerLength = n+3;
1700 break;
1701 }
1702 }
1703 }
1704
1705 if (headerLength == 0) {
1706 isInvalid = true;
1707 return false;
1708 }
1709
1710 uint32 tempContentLength = 4096;
1711 data = new char[headerLength+tempContentLength+1];
1712 memcpy(data, buffer, headerLength);
1713 data[headerLength] = 0; // temp string end
1714
1715 // Now process header entries
1716 std::string key, val, uri;
1717 const char* s = data, *t;
1718 n = 0;
1719 uint32 crSize = 0;
1720 uint32 left = headerLength;
1721 while (n < headerLength) {
1722 if (!utils::GetNextLineEnd(s, left, n, crSize))
1723 break;
1724 // Process line
1725 //if (t = strchr(s, ' ')) {
1726 if ((t = strchr(s, ' ')) && (t - s < (int32)n)) {
1727 t -= 1;
1728 if (*t != ':') {
1729 t++;
1730 key.assign(s, t-s);
1731 }
1732 else {
1733 key.assign(s, t-s);
1734 t++;
1735 }
1736 while (*t == 32) t++;
1737 if (*t > 32)
1738 val.assign(t, n+s-t);
1739 else
1740 val = "";
1741 }
1742 else {
1743 key.assign(s, n);
1744 val = "";
1745 }
1746 // Record entry
1747 if (s == data) {
1748 entries["http_protocol"] = key;
1749 if (val.find("200") != val.npos)
1750 type = HTTP_OK;
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)
1763 // 400/401/501/503/504 used to fall through to atoi("504 Gateway Timeout") = 504,
1764 // which wraps to 248 in the uint8 status id and matches no HTTP_* constant.
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)
1775 else
1776 type = atoi(val.c_str());
1777 }
1778 else {
1779 entries[key] = val;
1780 // Check entry
1781 if (stricmp(key.c_str(), "content-length") == 0)
1782 contentLength = atoi(val.c_str());
1783 else if (stricmp(key.c_str(), "transfer-encoding") == 0) {
1784 // Body arrives as HTTP/1.1 chunked framing (no Content-Length)
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)
1788 chunked = true;
1789 }
1790 else if (stricmp(key.c_str(), "date") == 0)
1791 time = GetTimeFromString(val.c_str());
1792 else if (stricmp(key.c_str(), "connection") == 0)
1793 keepAlive = (stricmp(val.c_str(), "close") != 0);
1794 }
1795 // Next line
1796 s += n + crSize;
1797 left -= n + crSize;
1798 }
1799
1800 if (contentLength > tempContentLength) {
1801 delete [] data;
1802 data = new char[headerLength+contentLength+1];
1803 memcpy(data, buffer, headerLength);
1804 data[headerLength] = 0; // temp string end
1806 }
1807
1808 return true;
1809}
1810
1811bool HTTPReply::processContent(const char* buffer, uint32 size) {
1812 if (!buffer || (size != contentLength))
1813 return false;
1814 memcpy(data+headerLength, buffer, size);
1816 return true;
1817}
1818
1819bool HTTPReply::setDecodedContent(const char* buffer, uint32 size) {
1820 if (!data || !headerLength)
1821 return false;
1822 char* newData = new char[headerLength+size+1];
1823 memcpy(newData, data, headerLength);
1824 if (size && buffer)
1825 memcpy(newData+headerLength, buffer, size);
1826 newData[headerLength+size] = 0;
1827 delete [] data;
1828 data = newData;
1829 contentLength = size;
1830 return true;
1831}
1832
1833const char* HTTPReply::getContent(uint32& size) {
1834 size = contentLength;
1835 return data+headerLength;
1836}
1837
1838const char* HTTPReply::getRawContent(uint32& size) {
1839 size = contentLength + headerLength;
1840 return data;
1841}
1842
1843const char* HTTPReply::getHeaderEntry(const char* entry) {
1844 std::map<std::string, std::string>::iterator it = entries.find(entry);
1845 if (it != entries.end())
1846 return it->second.c_str();
1847 else
1848 return NULL;
1849}
1850
1852 return getHeaderEntry("http_protocol");
1853}
1854
1856 if (data)
1857 return headerLength + contentLength;
1858 else
1859 return 0;
1860}
1861
1862
1864// HTTP Server Protocol
1866
1867bool HTTPProtocol::CheckBufferForCompatibility(const char* buffer, uint32 length) {
1868 if (length < 4)
1869 return false;
1870 return ( utils::stristr(buffer, "GET ") == buffer ||
1871 utils::stristr(buffer, "PUT ") == buffer ||
1872 utils::stristr(buffer, "POST ") == buffer ||
1873 utils::stristr(buffer, "DELETE ") == buffer ||
1874 utils::stristr(buffer, "OPTIONS ") == buffer ||
1875 utils::stristr(buffer, "HEAD ") == buffer );
1876}
1877
1879 return false;
1880}
1881
1883 if (!con || !reply)
1884 return false;
1885 //return con->send((char*)reply->data, reply->getSize());
1886 if (con->send((char*)reply->data, reply->getSize())) {
1887 LogPrint(0, LOG_NETWORK, 4, "Replying to HTTP request with %u bytes", reply->getSize());
1888 // printf("--- HTTP sent reply (%u)...\n", reply->contentLength);
1889 return true;
1890 }
1891 else {
1892 LogPrint(0, LOG_NETWORK, 2, "Failed to send reply to HTTP request with %u bytes", reply->getSize());
1893 // printf("--- HTTP failed to send reply (%u)...\n", reply->contentLength);
1894 return false;
1895 }
1896
1897}
1898
1900
1901 if (!con || !con->waitForDataToRead(timeout))
1902 return NULL;
1903 // return HTTPReply::CreateErrorReply(HTTP_SERVER_UNAVAILABLE);
1904
1905 uint32 maxSize = 4096, size;
1906 char* buffer = new char[maxSize];
1907
1908 uint64 start = GetTimeNow();
1909 uint32 wait = 0;
1910
1911 bool isInvalid = false;
1912 bool sawData = false;
1913 HTTPReply* reply = NULL;
1914 while (true) {
1915 if (!con->receiveAvailable(buffer, size, maxSize, wait, true)) {
1916 delete [] buffer;
1918 }
1919 if (size > 0) {
1920 sawData = true;
1921 reply = new HTTPReply(con->getRemoteAddress());
1922 if (reply->processHeader(buffer, size, isInvalid))
1923 break;
1924 delete(reply);
1925 reply = NULL;
1926 if (isInvalid) {
1927 // ##############
1928 }
1929 }
1930 if (GetTimeAgeMS(start) >= (int32)timeout) {
1931 delete [] buffer;
1932 // No application data at all inside the window. This happens on
1933 // spurious readability wakeups (e.g. TLS 1.3 session tickets that
1934 // carry no application bytes): it is not a protocol error, there is
1935 // simply nothing to read yet, so let the caller poll again instead
1936 // of queueing a NOREPLY error reply for a response still in flight.
1937 if (!sawData)
1938 return NULL;
1940 }
1941 wait = 20;
1942 }
1943
1944 // Now we know how much to actually expect
1945 // First read the header proper and ignore
1946 if (!con->discard(reply->headerLength)) {
1947 delete [] buffer;
1948 delete(reply);
1950 }
1951
1952// printf("Receiving HTTPReply content (%u)...\n\n", reply->contentLength);
1953 // Then read the rest of the content
1954 if (reply->chunked) {
1955 // HTTP/1.1 chunked transfer encoding: repeated <hex-size>[;ext]CRLF <data>CRLF,
1956 // terminated by a zero-size chunk. Trailers (if any) are left unread; the
1957 // client paths using this function tear the connection down afterwards.
1958 std::string body;
1959 char peekBuf[4096];
1960 uint64 chunkStart = GetTimeNow();
1961 bool failed = false;
1962 while (true) {
1963 // Read the chunk-size line: peek until a LF is visible, then consume it
1964 std::string line;
1965 while (true) {
1966 uint32 avail = 0;
1967 if (!con->receiveAvailable(peekBuf, avail, sizeof(peekBuf), 20, true)) {
1968 failed = true;
1969 break;
1970 }
1971 const char* lf = avail ? (const char*)memchr(peekBuf, '\n', avail) : NULL;
1972 if (lf) {
1973 line.assign(peekBuf, (size_t)(lf-peekBuf));
1974 con->discard((uint32)(lf-peekBuf)+1);
1975 break;
1976 }
1977 if (GetTimeAgeMS(chunkStart) >= 30000) {
1978 failed = true;
1979 break;
1980 }
1981 }
1982 if (failed)
1983 break;
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);
1987 if (!chunkSize)
1988 break; // final chunk
1989 char* chunkBuf = new char[chunkSize+2]; // chunk data + trailing CRLF
1990 if (!con->receive(chunkBuf, chunkSize+2, 10000)) {
1991 delete [] chunkBuf;
1992 failed = true;
1993 break;
1994 }
1995 body.append(chunkBuf, chunkSize);
1996 delete [] chunkBuf;
1997 if (GetTimeAgeMS(chunkStart) >= 30000) {
1998 failed = true;
1999 break;
2000 }
2001 }
2002 if (failed || !reply->setDecodedContent(body.data(), (uint32)body.length())) {
2003 printf("Error receiving chunked HTTPReply content (%u so far)...\n\n", (uint32)body.length());
2004 delete [] buffer;
2005 delete(reply);
2007 }
2008 }
2009 else if (reply->contentLength) {
2010 if (reply->contentLength > maxSize - reply->headerLength) {
2011 delete [] buffer;
2012 buffer = new char[reply->contentLength];
2013 // We have to read the rest, otherwise we will mess up the connection
2014 if (!con->receive(buffer, reply->contentLength, 3000)) {
2015 printf("Error receiving HTTPReply content (%u)...\n\n", reply->contentLength);
2016 delete [] buffer;
2017 delete(reply);
2019 }
2020
2021 if (!reply->processContent(buffer, reply->contentLength)) {
2022 delete [] buffer;
2023 delete(reply);
2025 }
2026 }
2027 else {
2028 // We have to read the rest, otherwise we will mess up the connection
2029 if (!con->receive(buffer, reply->contentLength, 3000)) {
2030 printf("Error receiving HTTPReply content (%u)...\n\n", reply->contentLength);
2031 delete [] buffer;
2032 delete(reply);
2034 }
2035
2036 if (!reply->processContent(buffer, reply->contentLength)) {
2037 delete [] buffer;
2038 delete(reply);
2040 }
2041 }
2042 }
2043
2044// printf("Got HTTPReply...\n\n");
2045 delete [] buffer;
2046 return reply;
2047}
2048
2050 if (!con || !req)
2051 return false;
2052 return con->send((char*)req->data, req->getSize());
2053}
2054
2056
2057 // ##########################
2058
2059 if (!con || !con->waitForDataToRead(timeout)) {
2060 return NULL;
2061 }
2062
2063 uint32 maxSize = 4096, size;
2064 char* buffer = new char[maxSize];
2065
2066 uint64 start = GetTimeNow();
2067 uint64 startReceive = 0;
2068 uint32 wait = 0;
2069
2070 bool isInvalid;
2071 HTTPRequest* req = NULL;
2072 while (true) {
2073 if (!con->receiveAvailable(buffer, size, maxSize, wait, true)) {
2074 delete [] buffer;
2075 return NULL;
2076 }
2077 if (size > 0) {
2078 LogPrint(0, LOG_NETWORK, 5, "Started receiving incoming HTTP request, size so far %u", size);
2079 if (!startReceive)
2080 startReceive = GetTimeNow();
2081 req = new HTTPRequest(con->getRemoteAddress(), startReceive);
2082 //utils::PrintBinary(buffer, size, false, "HTTP: ");
2083 if (req->processHeader(buffer, size, isInvalid)) {
2084 //printf("HTTP Done\n");
2085 break;
2086 }
2087 else if (isInvalid) {
2088 LogPrint(0, LOG_NETWORK, 2, "Received invalid incoming HTTP request, size %u", size);
2089 //utils::WriteAFile("d:\\http_invalid.bin", buffer, size, true);
2090 //DataMessage* test = new DataMessage(buffer);
2091 delete [] buffer;
2092 con->discard(size);
2093 return req;
2094 }
2095 else if (size == maxSize) {
2096 LogPrint(0, LOG_NETWORK, 2, "Received incoming HTTP request, buffer needs resizing %u", size);
2097 // maxSize isn't big enough to satisfy processHeader
2098 //printf("HTTP MaxSize, waiting for more...\n");
2099 //utils::PrintBinary(buffer, size, false, "HTTP MaxSize: ");
2100 //utils::WriteAFile("d:\\http_maxsize.bin", buffer, size, true);
2101 utils::Sleep(10);
2102 }
2103 else {
2104 LogPrint(0, LOG_NETWORK, 5, "Received incoming HTTP request, got %u, waiting for more...", size);
2105 //utils::PrintBinary(buffer, size, false, "HTTP WaitMore: ");
2106 //printf("HTTP waiting for more...\n");
2107 //utils::WriteAFile("d:\\http_notenough.bin", buffer, size, true);
2108 utils::Sleep(10);
2109 }
2110 delete(req);
2111 req = NULL;
2112 }
2113 if (GetTimeAgeMS(start) < (int32)timeout) {
2114 delete [] buffer;
2115 return NULL;
2116 }
2117 wait = 20;
2118 }
2119
2120 // Now we know how much to actually expect
2121 // First read the header proper and ignore
2122 if (!con->discard(req->headerLength)) {
2123 delete [] buffer;
2124 delete(req);
2125 return NULL;
2126 }
2127 // Then read the rest of the content
2128 if (req->contentLength) {
2129 if (req->contentLength > maxSize) {
2130 delete [] buffer;
2131 buffer = new char[req->contentLength];
2132 }
2133 // We have to read the rest, otherwise we will mess up the connection
2134 if (!con->receive(buffer, req->contentLength, 30000)) {
2135 LogPrint(0, LOG_NETWORK, 2, "Received HTTP header of %u, couldn't read the content of size %u", req->headerLength, req->contentLength);
2136 delete [] buffer;
2137 delete(req);
2138 return NULL;
2139 }
2140
2141 if (!req->processContent(buffer, req->contentLength)) {
2142 LogPrint(0, LOG_NETWORK, 2, "Received HTTP header of %u, content of size %u, failed processing the content buffer", req->headerLength, req->contentLength);
2143 delete [] buffer;
2144 delete(req);
2145 return NULL;
2146 }
2147 }
2148
2149 delete [] buffer;
2150 return req;
2151}
2152
2153
2154
2156
2157 if (!con || !con->waitForDataToRead(timeout)) {
2158 return NULL;
2159 }
2160
2161 uint32 maxSize = 4096, size;
2162 char* buffer = new char[maxSize];
2163
2164 uint64 start = GetTimeNow();
2165 uint64 startReceive = 0;
2166 uint32 wait = 0;
2167 uint64 remoteAddress;
2168 bool isInvalid;
2169 WebsocketData* wsData = NULL;
2170 while (!wsData || !wsData->isComplete()) {
2171 while (true) {
2172 if (!wsData && !con->receiveAvailable(buffer, size, maxSize, wait, true)) {
2173 delete[] buffer;
2174 return NULL;
2175 }
2176 else
2177 con->receiveAvailable(buffer, size, maxSize, wait, true);
2178 if (size > 0) {
2179 //utils::WriteAFile("d:/c/ws.bin", buffer, size, true);
2180 if (!wsData) {
2181 if (!startReceive)
2182 startReceive = GetTimeNow();
2183 wsData = new WebsocketData(con->getRemoteAddress(), startReceive);
2184 //utils::PrintBinary(buffer, size, false, "HTTP: ");
2185 }
2186
2187 if (wsData->processHeader(buffer, size, isInvalid)) {
2188 //printf("HTTP Done\n");
2189 break;
2190 }
2191 else if (isInvalid) {
2192 remoteAddress = con->getRemoteAddress();
2193 LogPrint(0, LOG_NETWORK, 1, "Invalid data received on Websocket from %u.%u.%u.%u", GETIPADDRESSQUAD(remoteAddress));
2194 //utils::WriteAFile("d:\\http_invalid.bin", buffer, size, true);
2195 //DataMessage* test = new DataMessage(buffer);
2196 delete[] buffer;
2197 con->discard(size);
2198 return wsData;
2199 }
2200 else if (size == maxSize) {
2201 // maxSize isn't big enough to satisfy processHeader
2202 //printf("HTTP MaxSize, waiting for more...\n");
2203 //utils::PrintBinary(buffer, size, false, "HTTP MaxSize: ");
2204 //utils::WriteAFile("d:\\http_maxsize.bin", buffer, size, true);
2205 utils::Sleep(10);
2206 }
2207 else {
2208 //utils::PrintBinary(buffer, size, false, "HTTP WaitMore: ");
2209 //printf("HTTP waiting for more...\n");
2210 //utils::WriteAFile("d:\\http_notenough.bin", buffer, size, true);
2211 utils::Sleep(10);
2212 }
2213 delete(wsData);
2214 wsData = NULL;
2215 }
2216 if (!wsData && (GetTimeAgeMS(start) < (int32)timeout)) {
2217 remoteAddress = con->getRemoteAddress();
2218 LogPrint(0, LOG_NETWORK, 1, "Timeout receiving data on Websocket from %u.%u.%u.%u", GETIPADDRESSQUAD(remoteAddress));
2219 delete[] buffer;
2220 return NULL;
2221 }
2222 wait = 20;
2223 }
2224
2225 // Now we know how much to actually expect
2226 // First read the header proper and ignore
2227 if (!con->discard(wsData->headerLength)) {
2228 delete[] buffer;
2229 delete(wsData);
2230 return NULL;
2231 }
2232 size -= wsData->headerLength;
2233 // Then read the rest of the content
2234 if (wsData->payloadSize) {
2235 if (wsData->payloadSize > maxSize) {
2236 delete[] buffer;
2237 buffer = new char[(uint32)(wsData->payloadSize)];
2238 }
2239 // We have to read the rest, otherwise we will mess up the connection
2240 if (!con->receive(buffer, (uint32)wsData->payloadSize, 30000)) {
2241 remoteAddress = con->getRemoteAddress();
2242 LogPrint(0, LOG_NETWORK, 1, "Unable to receive payload data on Websocket from %u.%u.%u.%u", GETIPADDRESSQUAD(remoteAddress));
2243 delete[] buffer;
2244 delete(wsData);
2245 return NULL;
2246 }
2247
2248 if (!wsData->processContent(buffer, wsData->payloadSize)) {
2249 remoteAddress = con->getRemoteAddress();
2250 LogPrint(0, LOG_NETWORK, 1, "Unable to process payload data on Websocket from %u.%u.%u.%u", GETIPADDRESSQUAD(remoteAddress));
2251 delete[] buffer;
2252 delete(wsData);
2253 return NULL;
2254 }
2255 size -= (uint32)(wsData->payloadSize);
2256 }
2257 if (wsData->isComplete()) {
2258 break;
2259 }
2260 }
2261
2262 delete[] buffer;
2263 return wsData;
2264}
2265
2267 if (!con || !wsData)
2268 return false;
2269 uint64 size = 0;
2270 const char* data = wsData->getRawData(size);
2271 if (!data || !size)
2272 return false;
2273 //return con->send((char*)reply->data, reply->getSize());
2274 if (con->send(data, (uint32)size)) {
2275 // printf("--- HTTP sent reply (%u)...\n", reply->contentLength);
2276 return true;
2277 }
2278 else {
2279 // printf("--- HTTP failed to send reply (%u)...\n", reply->contentLength);
2280 return false;
2281 }
2282
2283}
2284
2285
2286
2287
2288
2290// HTTP Client Protocol
2292
2293bool HTTPClientProtocol::CheckBufferForCompatibility(const char* buffer, uint32 length) {
2294 return false;
2295}
2296
2300
2302 return false;
2303}
2304
2306 return NULL;
2307}
2308
2310 return false;
2311}
2312
2314 return NULL;
2315}
2316
2317
2318
2320// Message Protocol
2322
2323bool MessageProtocol::CheckBufferForCompatibility(const char* buffer, uint32 length) {
2324 if (length < 2*sizeof(uint32))
2325 return false;
2326 return (GetObjID(buffer) == DATAMESSAGEID);
2327}
2328
2332
2334 if (!con || !msg)
2335 return false;
2336// uint64 addr = con->getRemoteAddress();
2337// LogPrint(0,0,0,"Protocol sending msg %llu to %u.%u.%u.%u:%u", msg->getType(), GETIPADDRESSQUAD(addr), GETIPPORT(addr));
2338// LogPrint(0,0,0,"Protocol sending msg: %u", msg->getType()[15]);
2339 return con->send((char*)msg->data, msg->getSize(), receiver);
2340}
2341
2343// uint64 t = GetTimeNow();
2344 if (!con) {
2345// if (!con || !con->waitForDataToRead(timeout)) {
2346// printf("Failed ReceiveMessage: %ld\n", GetTimeAgeMS(t));
2347 return NULL;
2348 }
2349
2350 // Peek just the header (size + object id) into a small stack buffer so the
2351 // body allocation can be sized exactly once. The previous code malloc'd a 1KB
2352 // scratch buffer for every message and then freed and re-malloc'd it for any
2353 // message bigger than 1KB - two allocations plus a free per large message.
2354 char header[2*sizeof(uint32)];
2355 if (!con->receive(header, sizeof(header), timeout, true))
2356 return NULL;
2357 if (GetObjID(header) != DATAMESSAGEID)
2358 return NULL;
2359 uint32 size = *(uint32*)header;
2360 if (size < sizeof(header))
2361 return NULL; // implausible size for a DataMessage header
2362
2363 char* buffer = (char*) malloc(size);
2364 if (!buffer)
2365 return NULL;
2366 if (!con->receive(buffer, size, timeout)) {
2367 free(buffer);
2368 return NULL;
2369 }
2370 // DataMessage adopts the buffer (copy=false) and frees it on destruction.
2371 DataMessage* msg = new DataMessage(buffer);
2372// uint64 addr = con->getRemoteAddress();
2373// LogPrint(0,0,0,"Protocol received msg %llu from %u.%u.%u.%u:%u", msg->getType(), GETIPADDRESSQUAD(addr), GETIPPORT(addr));
2374 //printf("Protocol received msg: %u.%u.%u (%p)...\n", msg->getType()[0], msg->getType()[1], msg->getType()[2], msg);
2375 return msg;
2376}
2377
2378
2380// Telnet Protocol
2382
2383bool TelnetProtocol::CheckBufferForCompatibility(const char* buffer, uint32 length) {
2384 if (length < 1)
2385 return false;
2386 return ((buffer[0] == 13) || (buffer[0] == 10));
2387}
2388
2392
2394 if (!con || !line)
2395 return false;
2396 return con->send((char*)line->data, line->getSize());
2397}
2398
2400 if (!con || !con->waitForDataToRead(timeout))
2401 return NULL;
2402
2403 uint32 maxSize = 1024, size;
2404 char* buffer = new char[maxSize];
2405 TelnetLine* line = NULL;
2406
2407 uint64 start = GetTimeNow();
2408 uint32 wait = 0, n;
2409
2410 while (GetTimeAgeMS(start) < (int32)timeout) {
2411 if (!con->receiveAvailable(buffer, size, maxSize, wait, true)) {
2412 delete [] buffer;
2413 return NULL;
2414 }
2415 for ( n=0; n<size; n++) {
2416 if (buffer[n] == 13) {
2417 line = new TelnetLine(con->getRemoteAddress());
2418 line->setCR(TELNET_WINDOWS);
2419 line->setLine(buffer, n);
2420 if ( (n < size-1) && (buffer[n+1] == 10) )
2421 n++;
2422 con->discard(n+1);
2423 delete [] buffer;
2424 // printf("\n");
2425 return line;
2426 }
2427 else if (buffer[n] == 10) {
2428 line = new TelnetLine(con->getRemoteAddress());
2429 line->setCR(TELNET_UNIX);
2430 line->setLine(buffer, n);
2431 if ( (n < size-1) && (buffer[n+1] == 13) )
2432 n++;
2433 con->discard(n+1);
2434 delete [] buffer;
2435 // printf("\n");
2436 return line;
2437 }
2438 //else
2439 // printf("[%c]", buffer[n]);
2440 }
2441 if (size == maxSize) {
2442 delete [] buffer;
2443 maxSize *= 4;
2444 buffer = new char[maxSize];
2445 wait = 0;
2446 }
2447 else
2448 wait = 20;
2449 }
2450 delete [] buffer;
2451 return NULL;
2452}
2453
2455 if (!con || !con->waitForDataToRead(timeout))
2456 return NULL;
2457
2458 char* buffer = new char[size];
2459
2460 if (!con->receive(buffer, size, timeout)) {
2461 delete [] buffer;
2462 return NULL;
2463 }
2464
2465 TelnetLine* reply = new TelnetLine(con->getRemoteAddress());
2466 reply->data = buffer;
2467 reply->size = size;
2468 return reply;
2469}
2470
2471
2472
2473
2474
2475
2477 jmData = NULL;
2478 jmSize = mOffset = 0;
2479}
2480
2481JSONM::JSONM(const char* data, uint64 size) {
2482 jmSize = size;
2483 if (jmSize) {
2484 jmData = new char[(uint32)jmSize];
2485 memcpy(jmData, data, (uint32)jmSize);
2486 mOffset = strlen(data) + 1;
2487 extractMultipartInfo();
2488 }
2489 else
2490 jmData = NULL;
2491}
2492
2493JSONM::JSONM(const char* json) {
2494 jmData = NULL;
2495 jmSize = 0;
2496 if (json)
2497 setJSON(json);
2498 else
2499 setJSON("{}");
2500}
2501
2503 mutex.enter(1000);
2504 if (jmData)
2505 delete[] jmData;
2506 jmData = NULL;
2507 jmSize = 0;
2508 mOffset = 0;
2509}
2510
2512 if (!mutex.enter(1000)) return 0;
2513 uint64 s = jmSize;
2514 mutex.leave();
2515 return s;
2516}
2518 if (!mutex.enter(1000)) return 0;
2519 uint32 s = (uint32)entries.size();
2520 mutex.leave();
2521 return s;
2522}
2523
2525 if (!jmData || !jmSize)
2526 return false;
2527 if (!strchr(jmData, '{') || !utils::laststrstr(jmData, "}"))
2528 return false;
2529 // Check that the sizes add up to the correct full size
2530 uint64 jsonLength = strlen(jmData);
2531 uint64 multipartLength = 0;
2532 std::map<uint32, JSONMEntry>::iterator i = entries.begin(), e = entries.end();
2533 while (i != e) {
2534 multipartLength += i->second.size;
2535 i++;
2536 }
2537 if (jsonLength + 1 + multipartLength != jmSize) {
2538 LogPrint(0, LOG_NETWORK, 0, "Invalid JSONM structure: JSON(%llu) + 1 + multiparts(%llu) != %llu", jsonLength, multipartLength, jmSize);
2539 return false;
2540 }
2541 return true;
2542}
2543
2544std::string JSONM::getJSON() {
2545 if (!jmData || !jmSize)
2546 return "";
2547 if (!strchr(jmData, '{') || !utils::laststrstr(jmData, "}"))
2548 return "";
2549 if (strlen(jmData) > jmSize)
2550 return std::string(jmData, jmSize);
2551 return jmData;
2552}
2553
2554const char* JSONM::getData(const char* name, uint64& size) {
2555 if (!mutex.enter(1000)) return NULL;
2556 size = 0;
2557 if (!name || !jmData || !jmSize) {
2558 mutex.leave();
2559 return NULL;
2560 }
2561 std::map<uint32, JSONMEntry>::iterator i = entries.begin(), e = entries.end();
2562 while (i != e) {
2563 if (stricmp(i->second.name.c_str(), name) == 0) {
2564 if (mOffset + i->second.offset + i->second.size > jmSize) {
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);
2567 mutex.leave();
2568 return NULL;
2569 }
2570 size = i->second.size;
2571 mutex.leave();
2572 return jmData + mOffset + i->second.offset;
2573 }
2574 i++;
2575 }
2576 mutex.leave();
2577 return NULL;
2578}
2579
2580std::string JSONM::getDataType(const char* name) {
2581 if (!mutex.enter(1000)) return "";
2582 if (!name || !jmData || !jmSize) {
2583 mutex.leave();
2584 return NULL;
2585 }
2586 std::map<uint32, JSONMEntry>::iterator i = entries.begin(), e = entries.end();
2587 while (i != e) {
2588 if (stricmp(i->second.name.c_str(), name) == 0) {
2589 mutex.leave();
2590 return i->second.type;
2591 }
2592 i++;
2593 }
2594 mutex.leave();
2595 return "";
2596}
2597
2598std::string JSONM::getDataType(uint32 chunk) {
2599 if (!mutex.enter(1000)) return NULL;
2600 if (!jmData || !jmSize || !chunk)
2601 return "";
2602
2603 std::map<uint32, JSONMEntry>::iterator i = entries.find(chunk);
2604 if (i == entries.end()) {
2605 mutex.leave();
2606 return "";
2607 }
2608
2609 mutex.leave();
2610 return i->second.type;
2611}
2612
2613std::string JSONM::getDataName(uint32 chunk) {
2614 if (!mutex.enter(1000)) return NULL;
2615 if (!jmData || !jmSize || !chunk) {
2616 mutex.leave();
2617 return "";
2618 }
2619
2620 std::map<uint32, JSONMEntry>::iterator i = entries.find(chunk);
2621 if (i == entries.end()) {
2622 mutex.leave();
2623 return "";
2624 }
2625
2626 mutex.leave();
2627 return i->second.name;
2628}
2629
2630
2631
2632const char* JSONM::getData(uint32 chunk, uint64& size) {
2633 if (!mutex.enter(1000)) return NULL;
2634 size = 0;
2635 if (!jmData || !jmSize) {
2636 mutex.leave();
2637 return NULL;
2638 }
2639
2640 std::map<uint32, JSONMEntry>::iterator i = entries.find(chunk);
2641 if (i == entries.end()) {
2642 mutex.leave();
2643 return NULL;
2644 }
2645
2646 if (mOffset + i->second.offset + i->second.size > jmSize) {
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);
2649 mutex.leave();
2650 return NULL;
2651 }
2652
2653 size = i->second.size;
2654 mutex.leave();
2655 return jmData + mOffset + i->second.offset;
2656}
2657
2658bool JSONM::setRawJSON(const char* json) {
2659 if (!json)
2660 return false;
2661 if (!jmData) {
2662 jmSize = mOffset = strlen(json) + 1;
2663 jmData = new char[(uint32)jmSize];
2664 utils::strcpyavail(jmData, json, (uint32)jmSize, true);
2665 return true;
2666 }
2667 // else we have JSON and possibly data already
2668 uint64 newMOffset = strlen(json) + 1;
2669 uint64 newSize = newMOffset + (jmSize - mOffset);
2670 char* newData = new char[(uint32)newSize];
2671 utils::strcpyavail(newData, json, (uint32)newMOffset, true);
2672 // copy in old data
2673 memcpy(newData + (uint32)newMOffset, jmData + (uint32)mOffset, (uint32)(jmSize - mOffset));
2674 delete[] jmData;
2675 jmData = newData;
2676 mOffset = newMOffset;
2677 jmSize = newSize;
2678 return true;
2679}
2680
2681bool JSONM::setJSON(const char* json) {
2682 if (!json)
2683 return false;
2684 if (!mutex.enter(1000)) return false;
2685 if (setRawJSON(json))
2686 updateMultipartJSON();
2687 mutex.leave();
2688 return true;
2689}
2690
2691uint32 JSONM::addData(const char* name, const char* data, uint64 size, const char* type) {
2692 if (!mutex.enter(1000)) return 0;
2693 // check for name in use
2694 uint64 s;
2695 if (getData(name, s)) {
2696 mutex.leave();
2697 return 0;
2698 }
2699 if (!jmData)
2700 setJSON("{}");
2701 JSONMEntry entry;
2702 entry.chunk = getCount() + 1;
2703 entry.name = name;
2704 entry.size = size;
2705 entry.type = type;
2706 entry.offset = jmSize - this->mOffset;
2707 uint64 newSize = jmSize + size;
2708 char* newData = new char[(uint32)newSize];
2709 memcpy(newData, jmData, (uint32)jmSize);
2710 memcpy(newData + (uint32)jmSize, data, (uint32)size);
2711 entries[entry.chunk] = entry;
2712 delete[] jmData;
2713 jmData = newData;
2714 jmSize = newSize;
2715 updateMultipartJSON();
2716 mutex.leave();
2717 return entry.chunk;
2718}
2719
2721 if (!mutex.enter(1000)) return "";
2722 if (!jmData || !jmSize) {
2723 mutex.leave();
2724 return "";
2725 }
2726 std::string info = utils::StringFormat("JSON length: %u\nTotal size: %s\nChunks: %u total: %s\n%s\n",
2727 mOffset - 1,
2728 utils::BytifySize((uint32)jmSize).c_str(),
2729 getCount(),
2730 utils::BytifySize((uint32)(jmSize - mOffset)).c_str(),
2731 jmData);
2732 std::map<uint32, JSONMEntry>::iterator i = entries.begin(), e = entries.end();
2733 while (i != e) {
2734 info += i->second.getInfoString();
2735 i++;
2736 }
2737 mutex.leave();
2738 return info;
2739}
2740
2741
2742
2743
2744bool JSONM::extractMultipartInfo() {
2745 // Assume multipart info is in JSON
2746 // read and create entries from this
2747 // also assume mutex is already locked
2748
2749 if (!jmData || !jmSize || (jmSize - mOffset < 10))
2750 return false;
2751
2752 jsmn_parser parser;
2753 jsmntok_t* tokens = new jsmntok_t[1024];
2754 int tokenCount = 0;
2755 //const char* val;
2756 //int valSize;
2757 jsmn_init(&parser);
2758 if ((tokenCount = jsmn_parse(&parser, jmData, strlen(jmData), tokens, 1024)) <= 0) {
2759 // No JSON found
2760 delete[] tokens;
2761 return false;
2762 }
2763
2764 JSONMEntry entry;
2765 //int token;
2766 std::vector<int> jsonArrayIdx;
2767 std::vector<int>::iterator i, e;
2768 jsonArrayIdx = GetJSONChildArrayIndexes(tokens, tokenCount, jmData, "_multipart_", 0, JSMN_UNDEFINED);
2769 if (!jsonArrayIdx.size()) {
2770 // No info found
2771 delete[] tokens;
2772 return false;
2773 }
2774 i = jsonArrayIdx.begin();
2775 e = jsonArrayIdx.end();
2776
2777 uint64 offset = 0;
2778 while (i != e) {
2779 if ((entry.name = GetJSONChildValueString(tokens, tokenCount, jmData, "name", *i)).length()) {
2780 entry.type = GetJSONChildValueString(tokens, tokenCount, jmData, "mimetype", *i);
2781 entry.size = GetJSONChildValueUint64(tokens, tokenCount, jmData, "bytesize", *i);
2782 entry.chunk = (uint32)entries.size() + 1;
2783 entry.offset = offset;
2784 entries[entry.chunk] = entry;
2785 offset += entry.size;
2786 }
2787 i++;
2788 }
2789 return true;
2790}
2791
2792bool JSONM::updateMultipartJSON() {
2793 // Assume entries and binary chunks updated
2794 // create new JSON entry and replace old one, if present
2795 // also assume mutex is already locked
2796
2797 std::string newJSON;
2798
2799 std::map<uint32, JSONMEntry>::iterator i = entries.begin(), e = entries.end();
2800 while (i != e) {
2801 if (newJSON.length())
2802 newJSON += ",";
2803 newJSON += i->second.toJSON();
2804 i++;
2805 }
2806
2807 jsmn_parser parser;
2808 jsmntok_t* tokens = new jsmntok_t[1024];
2809 int tokenCount = 0;
2810 //const char* val;
2811 //int valSize;
2812 jsmn_init(&parser);
2813 if ((tokenCount = jsmn_parse(&parser, jmData, strlen(jmData), tokens, 1024)) <= 0) {
2814 // No JSON found
2815 delete[] tokens;
2816 return false;
2817 }
2818
2819 std::string fullJSON = jmData;
2820 JSONMEntry entry;
2821 int token = GetJSONToken(tokens, tokenCount, jmData, "_multipart_", 0);
2822 if (token > 0) {
2823 uint32 begin = tokens[token].start;
2824 uint32 end = tokens[token].end;
2825 //uint32 end = tokens[tokens[tokens[token].parent].parent].end;
2826 fullJSON = utils::StringFormat("%s[%s]%s",
2827 fullJSON.substr(0, begin).c_str(),
2828 newJSON.c_str(),
2829 fullJSON.substr(end).c_str()
2830 );
2831 }
2832 else {
2833 // just add at the end
2834 const char* lastBracket = utils::laststrstr(jmData, "}");
2835 if (!lastBracket) {
2836 // Broken JSON found
2837 delete[] tokens;
2838 return false;
2839 }
2840 fullJSON = utils::StringFormat("%s,\"_multipart_\": [%s]}", fullJSON.substr(0, lastBracket-jmData).c_str(), newJSON.c_str());
2841 }
2842 this->setRawJSON(fullJSON.c_str());
2843 return true;
2844}
2845
2847 DataMessage* msg = new DataMessage();
2848 uint64 now = GetTimeNow();
2849
2850 msg->setString("POST", this->getJSON().c_str());
2851 msg->setString("POST_CONTENT_TYPE_", "application/json");
2852
2853 std::map<uint32, JSONMEntry>::iterator i = entries.begin(), e = entries.end();
2854 while (i != e) {
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());
2857 i++;
2858 }
2859 msg->setCreatedTime(now);
2860 msg->setRecvTime(now);
2861 return msg;
2862}
2863
2864
2865// ################# Unit Test #################
2866
2867// Encode a WebsocketData frame, then decode it back through the
2868// processHeader/processContent path and verify the recovered payload matches.
2869// All in-memory, no sockets. Returns true on success.
2870static bool TestWebsocketRoundTrip(WebsocketData::DataType type, bool maskData,
2871 const char* payload, uint64 payloadSize) {
2872
2873 WebsocketData enc;
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);
2877 return false;
2878 }
2879 if (!enc.isComplete()) {
2880 unittest::fail("encoded WebsocketData is not COMPLETE");
2881 return false;
2882 }
2883
2884 uint64 rawSize = 0;
2885 const char* raw = enc.getRawData(rawSize);
2886 if (!raw || !rawSize) {
2887 unittest::fail("getRawData returned no bytes");
2888 return false;
2889 }
2890 if (rawSize != enc.headerLength + payloadSize) {
2891 unittest::fail("raw size %llu != header %u + payload %llu",
2892 rawSize, enc.headerLength, payloadSize);
2893 return false;
2894 }
2895
2896 // First byte must carry FIN + opcode; opcode must equal the data type.
2897 uint8 b0 = *(const unsigned char*)raw;
2898 if (!(b0 & WebsocketData::BHB0_FIN)) {
2899 unittest::fail("encoded frame missing FIN bit");
2900 return false;
2901 }
2902 if ((b0 & WebsocketData::BHB0_OPCODE) != (uint8)type) {
2903 unittest::fail("encoded opcode %u != type %u", (uint8)(b0 & WebsocketData::BHB0_OPCODE), (uint8)type);
2904 return false;
2905 }
2906
2907 // Decode: split the raw bytes into header + payload, exactly as the socket
2908 // reader would, but feeding from the in-memory buffer.
2909 WebsocketData dec;
2910 bool isInvalid = false;
2911 if (!dec.processHeader(raw, rawSize, isInvalid) || isInvalid) {
2912 unittest::fail("processHeader failed (invalid=%d)", (int)isInvalid);
2913 return false;
2914 }
2915 if (dec.dataType != type) {
2916 unittest::fail("decoded dataType %d != %d", (int)dec.dataType, (int)type);
2917 return false;
2918 }
2919 if (dec.getPayloadSize() != payloadSize) {
2920 unittest::fail("decoded payloadSize %llu != %llu", dec.getPayloadSize(), payloadSize);
2921 return false;
2922 }
2923
2924 if (payloadSize) {
2925 if (!dec.processContent(raw + dec.headerLength, payloadSize)) {
2926 unittest::fail("processContent failed");
2927 return false;
2928 }
2929 uint64 outSize = 0;
2930 const char* out = dec.getContent(outSize);
2931 if (!out || (outSize != payloadSize)) {
2932 unittest::fail("getContent size %llu != %llu", outSize, payloadSize);
2933 return false;
2934 }
2935 if (memcmp(out, payload, (size_t)payloadSize) != 0) {
2936 unittest::fail("decoded payload bytes differ from original");
2937 return false;
2938 }
2939 }
2940 return true;
2941}
2942
2944
2945 // ---- Phase 1: WebsocketData encode/decode round-trips ----
2946 unittest::progress(5, "websocket frames");
2947
2948 const char* shortText = "hello websocket";
2949 if (!TestWebsocketRoundTrip(WebsocketData::TEXT, false, shortText, strlen(shortText)))
2950 return false;
2951 unittest::detail("websocket short unmasked TEXT round-trip ok (%zu bytes)", strlen(shortText));
2952
2953 // Masked frame: exercises the client->server masking path.
2954 if (!TestWebsocketRoundTrip(WebsocketData::TEXT, true, shortText, strlen(shortText)))
2955 return false;
2956 unittest::detail("websocket short masked TEXT round-trip ok");
2957
2958 if (!TestWebsocketRoundTrip(WebsocketData::BINARY, false, shortText, strlen(shortText)))
2959 return false;
2960
2961 unittest::progress(25, "websocket extended frames");
2962
2963 // Extended (16-bit length) payload: > 125 bytes forces the 2-byte length field.
2964 std::string big;
2965 big.reserve(4096);
2966 for (uint32 i = 0; i < 4000; i++)
2967 big.push_back((char)('A' + (i % 26)));
2968 if (!TestWebsocketRoundTrip(WebsocketData::BINARY, false, big.data(), big.size()))
2969 return false;
2970 if (!TestWebsocketRoundTrip(WebsocketData::BINARY, true, big.data(), big.size()))
2971 return false;
2972 unittest::detail("websocket extended (16-bit length) masked+unmasked round-trip ok (%zu bytes)", big.size());
2973
2974 // ---- Phase 2: HTTP request build/parse round-trip ----
2975 unittest::progress(50, "http request");
2976
2977 const char* body = "field1=value1&field2=value2";
2978 uint32 bodyLen = (uint32)strlen(body);
2979
2980 // Put query parameters in the URI so processHeader -> parseURIParameters
2981 // populates params; the urlencoded body is carried verbatim and checked
2982 // byte-for-byte below.
2983 const char* fullURI = "/path/script.cgi?home=Cosby&favorite=flies";
2984
2985 HTTPRequest reqEnc;
2986 if (!reqEnc.createRequest(HTTP_POST, "example.com", fullURI,
2987 body, bodyLen, true, 0)) {
2988 unittest::fail("HTTPRequest::createRequest returned false");
2989 return false;
2990 }
2991
2992 uint32 rawSize = 0;
2993 const char* raw = reqEnc.getRawContent(rawSize);
2994 if (!raw || !rawSize) {
2995 unittest::fail("HTTPRequest::getRawContent returned no bytes");
2996 return false;
2997 }
2998
2999 // Parse it back through a fresh request, feeding header then content
3000 // from the in-memory buffer (header ends at reqEnc.headerLength).
3001 HTTPRequest reqDec;
3002 bool isInvalid = false;
3003 if (!reqDec.processHeader(raw, rawSize, isInvalid) || isInvalid) {
3004 unittest::fail("HTTPRequest::processHeader failed (invalid=%d)", (int)isInvalid);
3005 return false;
3006 }
3007 if (reqDec.type != HTTP_POST) {
3008 unittest::fail("parsed method %u != HTTP_POST", reqDec.type);
3009 return false;
3010 }
3011 const char* uri = reqDec.getURI();
3012 if (!uri || strcmp(uri, fullURI) != 0) {
3013 unittest::fail("parsed URI '%s' != '%s'", uri ? uri : "(null)", fullURI);
3014 return false;
3015 }
3016 const char* request = reqDec.getRequest();
3017 if (!request || strcmp(request, "path/script.cgi") != 0) {
3018 unittest::fail("parsed request '%s' != 'path/script.cgi'", request ? request : "(null)");
3019 return false;
3020 }
3021 if (reqDec.contentLength != bodyLen) {
3022 unittest::fail("parsed Content-Length %u != %u", reqDec.contentLength, bodyLen);
3023 return false;
3024 }
3025
3026 unittest::progress(70, "http content");
3027
3028 // Feed the body (located right after the header in the same buffer).
3029 if (!reqDec.processContent(raw + reqDec.headerLength, bodyLen)) {
3030 unittest::fail("HTTPRequest::processContent failed");
3031 return false;
3032 }
3033 uint32 outSize = 0;
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);
3037 return false;
3038 }
3039 // query-string params (from the URI) should have been parsed.
3040 const char* p = reqDec.getParameter("home");
3041 if (!p || strcmp(p, "Cosby") != 0) {
3042 unittest::fail("parsed URI param home='%s' != 'Cosby'", p ? p : "(null)");
3043 return false;
3044 }
3045 unittest::detail("http POST build/parse round-trip ok (body %u bytes)", bodyLen);
3046
3047 // ---- Phase 3: parse throughput metric ----
3048 unittest::progress(85, "throughput");
3049
3050 const uint32 iters = 2000;
3051 uint64 t0 = GetTimeNow();
3052 for (uint32 i = 0; i < iters; i++) {
3053 HTTPRequest r;
3054 bool inv = false;
3055 if (!r.processHeader(raw, reqEnc.headerLength, inv) || inv) {
3056 unittest::fail("throughput loop processHeader failed at iter %u", i);
3057 return false;
3058 }
3059 }
3060 double us = (double)(GetTimeNow() - t0);
3061 if (us > 0.0)
3062 unittest::metric("http_header_parse_throughput", (double)iters / us * 1e6, "ops/s", true);
3063
3064 unittest::progress(100, "done");
3065 return true;
3066}
3067
3070 "networkprotocols", NetworkProtocols_UnitTest,
3071 "HTTP request and WebSocket frame build/parse round-trips", "network");
3072}
3073
3074}
std::string base64_decode(const char *encoded_string)
Decode Base64 text.
Definition Base64.cpp:49
std::string base64_encode(std::string string_to_encode)
Base64-encode a string's bytes.
Definition Base64.cpp:44
Wire-protocol layer: HTTP request/reply, WebSocket frames, Telnet lines, binary DataMessages,...
#define HTTP_OK
200 OK
#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_HEAD
HEAD.
#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_GET
GET.
#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_PUT
PUT.
#define HTTP_SERVER_NOREPLY
500 (no reply from backend server)
#define HTTP_POST
POST.
#define HTTP_NOT_IMPLEMENTED
501 Not Implemented
#define DATAMESSAGEID
Definition ObjectIDs.h:75
#define GetObjID(data)
Extract the cid field from a binary object block: the uint32 at byte offset 4 (after the leading size...
Definition ObjectIDs.h:26
CMSDK time: µs-resolution 64-bit timestamps and the Time Mapping Constant (TMC).
#define MAXVALUINT32
Definition Types.h:87
Small, dependency-free unit test harness used by all CMSDK object tests.
#define GETIPADDRESSQUAD(a)
Definition Utils.h:1649
#define stricmp
Definition Utils.h:132
#define LogPrint
Definition Utils.h:313
#define LOG_NETWORK
Definition Utils.h:198
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.
bool isValid()
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.
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.
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 * 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)
std::string getJSON()
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 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).
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.
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.
compute SHA1 hash
Definition sha1.h:45
std::string getHashRaw()
return latest hash as raw characters
void reset()
restart
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.
Definition PsyTime.cpp:69
#define TIME_YEAR_1970
Same value as USEC_YEAR_0_TO_1970: the PsyTime timestamp of 1970-01-01 00:00:00 UTC.
Definition PsyTime.h:94
uint32 GetHTTPTime(uint64 time, char *buffer, uint32 size)
Format a timestamp as an HTTP-date (RFC 7231) string, e.g.
Definition PsyTime.cpp:518
uint64 GetTimeFromString(const char *str)
Parse a textual date/time into a PsyTime timestamp.
Definition PsyTime.cpp:438
int32 GetTimeAgeMS(uint64 t)
Age of a timestamp relative to now, in milliseconds.
Definition PsyTime.cpp:35
const char * laststrstr(const char *str1, const char *str2)
Find the last occurrence of str2 in str1.
Definition Utils.cpp:8624
bool TextStartsWith(const char *str, const char *start, bool caseSensitive=true)
Test whether str starts with start.
Definition Utils.cpp:8681
bool Sleep(uint32 ms)
Suspend the calling thread.
Definition Utils.cpp:3121
const char * stristr(const char *str, const char *substr, uint32 len=0)
Case-insensitive strstr.
Definition Utils.cpp:7476
bool GetNextLineEnd(const char *str, uint32 size, uint32 &len, uint32 &crSize)
Find the end of the current line.
Definition Utils.cpp:7515
uint64 ntoh64(const uint64 *input)
Convert a 64-bit value from network to host byte order.
Definition Utils.cpp:2702
uint32 strcpyavail(char *dst, const char *src, uint32 maxlen, bool copyAvailable)
Bounded strcpy that always NUL-terminates.
Definition Utils.cpp:7497
@ JSMN_UNDEFINED
Definition jsmn.h:29
std::string GetJSONChildValueString(jsmntok_t *tokens, int tokenCount, const char *json, const char *key, int parent, jsmntype_t type=JSMN_UNDEFINED)
Definition jsmn.cpp:441
std::vector< int > GetJSONChildArrayIndexes(jsmntok_t *tokens, int tokenCount, const char *json, const char *key, int parent, jsmntype_t type=JSMN_UNDEFINED)
Definition jsmn.cpp:492
int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, unsigned int num_tokens)
Run JSON parser.
Definition jsmn.cpp:156
uint64 GetJSONChildValueUint64(jsmntok_t *tokens, int tokenCount, const char *json, const char *key, int parent, jsmntype_t type=JSMN_UNDEFINED)
Definition jsmn.cpp:453
void jsmn_init(jsmn_parser *parser)
Create JSON parser over an array of tokens.
Definition jsmn.cpp:311
int GetJSONToken(jsmntok_t *tokens, int tokenCount, const char *json, const char *key, int parent, jsmntype_t type=JSMN_UNDEFINED)
Definition jsmn.cpp:328
bool GetMimeType(char *dest, const char *filename, uint32 maxsize)
Look up the MIME type for a filename based on its extension.
Definition HTML.cpp:137
bool IsMimeBinary(const char *type)
Determine whether a MIME type denotes binary content.
Definition HTML.cpp:130
std::string DecodeHTML(std::string str)
Decode HTML entities in a string (e.g.
Definition HTML.cpp:12
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.
Definition Utils.cpp:8238
std::vector< std::string > TextListSplit(const char *text, const char *split, bool keepEmpty=true, bool autoTrim=false)
Split text on a separator.
Definition Utils.cpp:7952
FileDetails GetFileDetails(const char *filename)
Stat a file.
Definition Utils.cpp:8805
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).
Definition Utils.cpp:7625
std::string TextTrimQuotes(const char *text)
Strip a single pair of surrounding quotes if present.
Definition Utils.cpp:7592
std::string BytifySize(double val)
Format a byte count with binary units, e.g.
Definition Utils.cpp:9165
std::string StringFormat(const char *format,...)
printf into a std::string.
Definition Utils.cpp:8067
char * ReadAFile(const char *filename, uint32 &length, bool binary=false)
Read an entire file into a new buffer.
Definition Utils.cpp:8259
int64 RandomInt(int64 from=0, int64 to=100)
Uniform random integer in [from,to].
Definition Utils.cpp:9156
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().
Definition Utils.h:1869
JSON parser.
Definition jsmn.h:65
JSON token description.
Definition jsmn.h:51
int start
Definition jsmn.h:53
int end
Definition jsmn.h:54