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(rawData);
984 rawData = NULL;
985 status = IDLE;
986}
987
989 WebsocketData* wsData = new WebsocketData();
990 wsData->setData(wsData->CLOSE, false);
991 return wsData;
992}
993
995 WebsocketData* wsData = new WebsocketData();
996 wsData->setData(wsData->PING, false);
997 return wsData;
998}
999
1001 WebsocketData* wsData = new WebsocketData();
1002 wsData->setData(wsData->PONG, false);
1003 return wsData;
1004}
1005
1007 return (dataType == CLOSE);
1008}
1009
1013
1015 return (dataType == PING);
1016}
1017
1019 return (dataType == PONG);
1020}
1021
1022bool WebsocketData::setData(DataType dataType, bool maskData, const char* data, uint64 size) {
1023 // Builds the complete RFC 6455 frame into rawData:
1024 // byte 0: FIN | RSV1-3 | opcode (we always send FIN = single frame)
1025 // byte 1: MASK | 7-bit payload length (126 => 16-bit ext, 127 => 64-bit ext)
1026 // [2 or 8 bytes extended length, network byte order]
1027 // [4-byte masking key when maskData] (clients MUST mask per the RFC)
1028 // payload (XOR-masked with the key when maskData)
1029 if ((dataType == NONE))
1030 return false;
1031
1032 this->dataType = dataType;
1033 if (maskData && !maskingKey)
1035
1036 headerLength = basic_header_length; // +sizeof(uint32);
1037 if (maskData)
1038 headerLength += sizeof(uint32);
1039 uint32 mkOffset = basic_header_length;
1040 uint32 basic_value = 0;
1041
1042 if (size <= payload_size_basic) {
1043 basic_value = (uint8)size;
1044 }
1045 else if (size <= payload_size_extended) {
1046 mkOffset += 2;
1047 headerLength += 2;
1048 basic_value = payload_size_code_16bit;
1049 }
1050 else {
1051 headerLength += 8;
1052 mkOffset += 8;
1053 basic_value = payload_size_code_64bit;
1054 }
1055
1056 if (rawData)
1057 delete rawData;
1058
1059 uint8 opcode = (uint8)dataType;
1060
1061 rawData = new char[headerLength + (uint32)size + 1];
1062 memset(rawData, 0, headerLength);
1063
1064 uint8* b0 = (uint8*)rawData;
1065 uint8* b1 = (uint8*)(rawData + 1);
1066
1067 *b0 |= BHB0_FIN;
1068 *b0 |= (opcode & BHB0_OPCODE);
1069 if (maskData)
1070 *b1 |= BHB1_MASK;
1071
1072 *b1 |= basic_value;
1073
1074 if (maskData)
1075 *(uint32*)(rawData + mkOffset) = maskingKey;
1076
1077 contentSize = payloadSize = size;
1078
1079 if (!size)
1080 return true;
1081
1082 // Write the extended length field. NOTE: the branch ordering here means the
1083 // 64-bit ("jumbo", > 65535 bytes) case is unreachable — any size above 125
1084 // takes the first branch and is truncated to 16 bits by ntohs((uint16)size).
1085 // Payloads up to 64KB (the sizes actually used by the SDK) are unaffected;
1086 // larger single frames would be framed incorrectly. (ntohs is used for
1087 // host->network conversion; it is its own inverse, so this is equivalent
1088 // to htons.)
1089 if (size > payload_size_basic) {
1090 uint16* h2 = (uint16*)(rawData + 2);
1091 *h2 = ntohs((uint16)size);
1092 }
1093 else if (size > payload_size_extended) {
1094 uint64* h3 = (uint64*)(rawData + 2);
1095 *h3 = utils::ntoh64(&size);
1096 }
1097
1098 if (maskData) {
1099 // RFC 6455 masking: payload byte i is XORed with key byte (i mod 4).
1100 // The same loop unmasks on the receive side (XOR is symmetric).
1101 uint8* mask = (uint8*)(&maskingKey);
1102 uint8* src = (uint8*)data;
1103 uint8* dst = (uint8*)(rawData + headerLength);
1104 for (uint64 n = 0; n < size; n++) {
1105 *dst = *src ^ mask[n % 4];
1106 dst++;
1107 src++;
1108 }
1109 }
1110 else {
1111 memcpy(rawData + headerLength, data, (uint32)size);
1112 }
1114 status = COMPLETE;
1115 return true;
1116}
1117
1118const char* WebsocketData::getRawData(uint64& size) {
1119 if (status != COMPLETE) {
1120 size = 0;
1121 return NULL;
1122 }
1123 size = headerLength + payloadSize;
1124 return rawData;
1125}
1126
1127
1129 return contentSize;
1130}
1131
1133 return payloadSize;
1134}
1135
1136
1138 return (status == COMPLETE);
1139}
1140
1141bool WebsocketData::processHeader(const char* buffer, uint64 size, bool &isInvalid) {
1142 // Decodes the RFC 6455 basic header (2 bytes) plus, depending on the 7-bit
1143 // length code, a 16-bit (code 126) or 64-bit (code 127) extended length in
1144 // network byte order, then the 4-byte masking key when the MASK bit is set.
1145 // Requires at least 4 bytes buffered even though a minimal unmasked frame
1146 // is only 2 — callers always have the masked client header available.
1147 // On a continuation frame (status != IDLE) the opcode is 0, so dataType is
1148 // only latched from the first fragment.
1149 isInvalid = false;
1150 if (!buffer || (size < 4))
1151 return false;
1152
1153// utils::WriteAFile("d:/ws.bin", buffer, size, true);
1154
1155 time = GetTimeNow();
1156
1157 uint8 b0 = *(const unsigned char*)buffer;
1158 uint8 b1 = *(const unsigned char*)(buffer + 1);
1159
1160 isFinal = b0 & BHB0_FIN;
1161 uint8 opcode = b0 & BHB0_OPCODE;
1162
1163 // if this is the first package
1164 if (status == IDLE) {
1165 if (opcode == 1)
1166 dataType = TEXT;
1167 else if (opcode == 2)
1168 dataType = BINARY;
1169 else if (opcode == 8)
1170 dataType = CLOSE;
1171 else if (opcode == 9)
1172 dataType = PING;
1173 else if (opcode == 10)
1174 dataType = PONG;
1175 }
1176
1177 bool mask = b1 & BHB1_MASK;
1178 uint8 basic_length = b1 & BHB1_PAYLOAD;
1179 payloadSize = 0;
1180
1181 uint32 mkOffset = basic_header_length;
1182 uint32 payloadOffset = basic_header_length;
1183
1184 if (basic_length <= payload_size_basic) {
1185 payloadSize = basic_length;
1186 }
1187 else if (basic_length == payload_size_code_16bit) {
1188 uint16 h2 = *(const uint16*)(buffer + 2);
1189 payloadSize = ntohs(h2);
1190 mkOffset += 2;
1191 payloadOffset += 2;
1192 // get_extended_size(e);
1193 }
1194 else {
1195 uint64 h3 = *(const uint64*)(buffer + 2);
1197 mkOffset += 8;
1198 payloadOffset += 8;
1199 // get_jumbo_size(e);
1200 }
1201
1202 // if just a control message
1203 if (!payloadSize) {
1204 status = COMPLETE;
1205 return true;
1206 }
1207
1208 maskingKey = 0;
1209 if (mask) {
1210 maskingKey = *(uint32*)(buffer + mkOffset);
1211 //maskingKey = 2112143814;
1212 payloadOffset += 4;
1213 }
1214
1215 // printf("%s\n\n", utils::PrintBitFieldString(buffer, size, 8 * size, "ws").c_str());
1216
1217/* LogPrint(0, LOG_NETWORK, 3, "%s: [%u] (%u) size: %llu offset: %u total size: %llu\n",
1218 isFinal ? "Final" : "Continue",
1219 opcode,
1220 maskingKey,
1221 payloadSize,
1222 payloadOffset,
1223 size
1224 );*/
1225
1226 headerLength = payloadOffset;
1227 //contentSize = payloadSize;
1228
1229 return true;
1230}
1231
1232bool WebsocketData::processContent(const char* buffer, uint64 size) {
1233 // Unmasks (XOR with the 4-byte key; a no-op when maskingKey == 0, i.e.
1234 // unmasked server-to-client frames) and appends this fragment's payload.
1235 // Fragmented messages are reassembled here: each continuation reallocates
1236 // data to contentSize + payloadSize and copies the previous bytes over, so
1237 // the final buffer holds the whole logical message; status only becomes
1238 // COMPLETE on the FIN fragment.
1239 if (!buffer || (size != payloadSize))
1240 return false;
1241
1242// utils::WriteAFile("d:/ws_payload.bin", buffer, size, true);
1243
1244 uint8* mask = (uint8*)(&maskingKey);
1245
1246 uint8* src = (uint8*)buffer;
1247 uint8* dst;
1248
1249 if (status == IDLE) {
1250 if (data)
1251 delete data;
1252 data = new char[(uint32)payloadSize + 1];
1253 dst = (uint8*)data;
1254 }
1255 else {
1256 char* newData = new char[(uint32)contentSize + (uint32)payloadSize + 1];
1257 memcpy(newData, data, (uint32)contentSize);
1258 delete data;
1259 data = newData;
1260 dst = (uint8*)(data+ (uint32)contentSize);
1261 }
1262
1263 for (uint64 n = 0; n < payloadSize; n++) {
1264 *dst = *src ^ mask[n % 4];
1265 dst++;
1266 src++;
1267 }
1269 data[contentSize] = 0;
1271 if (isFinal)
1272 status = COMPLETE;
1273 else
1275 packages++;
1276
1277 //LogPrint(0, LOG_NETWORK, 0, "Content received: %s\n",
1278 // data
1279 //);
1280
1281 if (isFinal) {
1282 if (packages == 1)
1283 LogPrint(0, LOG_NETWORK, 3, "Single Websocket package content received: %llu -> %llu\n", size, contentSize);
1284 else
1285 LogPrint(0, LOG_NETWORK, 3, "Last Websocket content package received: %llu -> %llu (%u)\n", size, contentSize, packages);
1286 }
1287 else {
1288 if (packages == 1)
1289 LogPrint(0, LOG_NETWORK, 3, "First Websocket content received: %llu -> %llu\n", size, contentSize);
1290 // else
1291 // LogPrint(0, LOG_NETWORK, 0, "Next content received: %llu -> %llu\n", size, contentSize);
1292 }
1293
1294// utils::WriteAFile("d:/ws_content.bin", data, contentSize, true);
1295
1296 return true;
1297}
1298
1299
1300
1301const char* WebsocketData::getContent(uint64& size) {
1302 size = 0;
1303 if (!data || !payloadSize || (status != COMPLETE))
1304 return NULL;
1305 size = contentSize;
1306 return data;
1307}
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1321 this->source = source;
1322 data = NULL;
1323 time = GetTimeNow();
1324 source = 0;
1325 size = 0;
1326 user = 0;
1327 cr[0] = 13;
1328 cr[1] = 10;
1329 cr[2] = 0;
1330}
1331
1333 if (data != NULL)
1334 delete(data);
1335 data=NULL;
1336}
1337
1338bool TelnetLine::setCR(uint8 type) {
1339 switch(type) {
1340 default:
1341 case TELNET_WINDOWS:
1342 cr[0] = 13;
1343 cr[1] = 10;
1344 cr[2] = 0;
1345 break;
1346 case TELNET_UNIX:
1347 cr[0] = 10;
1348 cr[1] = 0;
1349 break;
1350 }
1351 return true;
1352}
1353
1355 if (!data)
1356 return 0;
1357 if (size)
1358 return size;
1359 else
1360 return (uint32)strlen(data);
1361}
1362
1363bool TelnetLine::setLine(const char* buffer, bool addCR) {
1364 return setLine(buffer, (uint32)strlen(buffer), addCR);
1365}
1366
1367bool TelnetLine::setLine(const char* buffer, uint32 len, bool addCR) {
1368 if (data)
1369 delete(data);
1370
1371 data = new char[len+3];
1372 if (buffer && len > 0) {
1373 memcpy(data, buffer, len);
1374 if (addCR)
1375 memcpy(data+len, cr, 3);
1376 else
1377 data[len] = 0;
1378 }
1379 else
1380 data[len] = 0;
1381 return true;
1382}
1383
1384bool TelnetLine::giveData(char* buffer, uint32 len) {
1385 if (data)
1386 delete(data);
1387 data = buffer;
1388 size = len;
1389 return true;
1390}
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1404 HTTPReply* reply = new HTTPReply((uint64)0);
1405 reply->type = type;
1406 return reply;
1407}
1408
1410 HTTPReply* reply = new HTTPReply((uint64)0);
1411 reply->createAuthorizationReply(realm);
1412 return reply;
1413}
1414
1415HTTPReply* HTTPReply::CreateWebsocketHTTPReply(const char* key, const char* version) {
1416 HTTPReply* reply = new HTTPReply((uint64)0);
1417 reply->createWebsocketHTTPReply(key, version);
1418 return reply;
1419}
1420
1421
1423 time = GetTimeNow();
1424 this->source = source;
1425 data = NULL;
1426 type = 0;
1428 keepAlive = true;
1429 chunked = false;
1430}
1431
1433 time = reply->time;
1434 source = reply->source;
1435 type = reply->type;
1436 headerLength = reply->headerLength;
1438 keepAlive = reply->keepAlive;
1439 chunked = reply->chunked;
1440 data = new char[headerLength+contentLength+1];
1441 if (reply->data)
1442 memcpy(data, reply->data, headerLength+contentLength+1); // incl. string end
1443 else
1444 memset(data, 0, headerLength+contentLength+1);
1445 entries = reply->entries;
1446 params = reply->params;
1447}
1448
1450 if (data != NULL)
1451 delete(data);
1452 data = NULL;
1453}
1454
1455// Generating the data
1456
1457bool HTTPReply::createOptionsResponse(uint8 status, uint64 time, const char* serverName, bool keepAlive, const char* origin, const char* operations) {
1458
1459 //OPTIONS /cors HTTP/1.1
1460 //Origin: http://api.bob.com
1461 //Access-Control-Request-Method: PUT
1462 //Access-Control-Request-Headers: X-Custom-Header
1463 //Host: api.alice.com
1464 //Accept-Language: en-US
1465 //Connection: keep-alive
1466 //User-Agent: Mozilla/5.0...
1467 //
1468 //Preflight Response:
1469 //
1470 //Access-Control-Allow-Origin: http://api.bob.com
1471 //Access-Control-Allow-Methods: GET, POST, PUT
1472 //Access-Control-Allow-Headers: X-Custom-Header
1473 //Content-Type: text/html; charset=utf-8
1474
1475 char timeString[1024];
1476 if (!GetHTTPTime(time, timeString, 1024))
1477 timeString[0] = 0;
1478
1479 if (data)
1480 delete(data);
1481 data = new char[4096];
1482 snprintf(data, 4096,
1483"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");
1484//"Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept\r\n\r\n");
1485
1486//"%s\r\nDate: %s\r\n
1487//Server: %s\r\n
1488//Connection: %s\r\n
1489//"Access-Control-Allow-Origin: %s\r\n
1490//Access-Control-Allow-Methods: %s\r\n
1491//Access-Control-Allow-Headers: X-Custom-Header\r\n
1492//Content-Type: text/html\r\n\r\n",
1493// //HTTP_Status[status], timeString, serverName,
1494// //keepAlive ? "Keep-Alive" : "close",
1495// origin, operations);
1496
1497 headerLength = (uint32)strlen(data);
1498 contentLength = 0;
1499 return true;
1500}
1501
1502
1503bool HTTPReply::createErrorPage(uint8 status, uint64 time, const char* serverName, bool keepAlive) {
1504
1505 char timeString[1024];
1506 if (!GetHTTPTime(time, timeString, 1024))
1507 timeString[0] = 0;
1508
1509 if (data)
1510 delete(data);
1511 data = new char[4096];
1512 snprintf(data, 4096,
1513 "%s\r\nDate: %s\r\nServer: %s\r\n"
1514 "X-Frame-Options: SAMEORIGIN\r\n"
1515 "Connection: %s\r\n\r\n",
1516 HTTP_Status[status], timeString, serverName,
1517 keepAlive ? "Keep-Alive" : "close");
1518
1519 headerLength = (uint32)strlen(data);
1520 contentLength = 0;
1521 return true;
1522}
1523
1525
1526 char timeString[1024];
1527 if (!GetHTTPTime(time, timeString, 1024))
1528 timeString[0] = 0;
1529
1530 if (data)
1531 delete(data);
1532 data = new char[4096];
1533 snprintf(data, 4096,
1534 "%s\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n",
1536
1537 headerLength = (uint32)strlen(data);
1538 contentLength = 0;
1539 return true;
1540}
1541
1542bool HTTPReply::createWebsocketHTTPReply(const char* key, const char* version) {
1543 // RFC 6455 §4.2.2 handshake: the server proves it understood the upgrade by
1544 // concatenating the client's Sec-WebSocket-Key with the fixed magic GUID
1545 // 258EAFA5-E914-47DA-95CA-C5AB0DC85B11, SHA-1 hashing the result and
1546 // returning it base64-encoded as Sec-WebSocket-Accept in a 101 reply.
1547
1548 if (!key || !strlen(key))
1549 return false;
1550
1551 std::string magicString = utils::StringFormat("%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", key);
1552 hash::SHA1 sha1;
1553 sha1.reset();
1554 sha1.add(magicString.c_str(), magicString.size());
1555 std::string rawMagic = sha1.getHashRaw();
1556
1557 std::string acceptKey = base64_encode(rawMagic);
1558
1559 if (data)
1560 delete(data);
1561 data = new char[4096];
1562 snprintf(data, 4096,
1563 "%s\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %s\r\n\r\n",
1564 HTTP_Status[HTTP_SWITCH_PROTOCOL], acceptKey.c_str());
1565
1566 headerLength = (uint32)strlen(data);
1567 contentLength = 0;
1568 return true;
1569}
1570
1571bool HTTPReply::createPage(uint8 status, uint64 time, const char* serverName,
1572 uint64 lastMod, bool keepAlive, bool cache, const char* contentType,
1573 const char* content, uint32 contentSize, const char* additionalHeaderEntries) {
1574
1575 char timeString[1024];
1576 if (!GetHTTPTime(time, timeString, 1024))
1577 timeString[0] = 0;
1578
1579 contentLength = (contentSize > 0) ? contentSize : (uint32)strlen(content);
1580
1581 if (data)
1582 delete(data);
1583 data = new char[4096 + contentLength];
1584 if (additionalHeaderEntries && strlen(additionalHeaderEntries)) {
1585 snprintf(data, 4096 + contentLength,
1586 "%s\r\nDate: %s\r\nServer: %s\r\n"
1587 "X-Frame-Options: SAMEORIGIN\r\n"
1588 "Last-Modified: %s\r\nContent-Length: %u\r\n"
1589 "Connection: %s\r\nContent-Type: %s\r\nCache-Control: %s\r\n"
1590 "%s\r\n\r\n",
1591 HTTP_Status[status], timeString, serverName, timeString, contentLength,
1592 keepAlive ? "Keep-Alive" : "close", contentType, cache ? "Public" : "No-Cache",
1593 additionalHeaderEntries);
1594 }
1595 else {
1596 snprintf(data, 4096 + contentLength,
1597 "%s\r\nDate: %s\r\nServer: %s\r\n"
1598 "X-Frame-Options: SAMEORIGIN\r\n"
1599 "Last-Modified: %s\r\nContent-Length: %u\r\n"
1600 "Connection: %s\r\nContent-Type: %s\r\nCache-Control: %s\r\n\r\n",
1601 HTTP_Status[status], timeString, serverName, timeString, contentLength,
1602 keepAlive ? "Keep-Alive" : "close", contentType, cache ? "Public" : "No-Cache");
1603 }
1604
1605 headerLength = (uint32)strlen(data);
1606 memcpy(data+headerLength, content, contentLength);
1608
1609 return true;
1610}
1611
1612bool HTTPReply::createFromFile(uint64 time, const char* serverName,
1613 uint64 ifLastMod, bool keepAlive, bool cache,
1614 const char* filename) {
1615
1616 std::string actualFilename = filename;
1617 const char* question = strstr(filename, "?");
1618 if (question)
1619 actualFilename = std::string(filename, question - filename);
1620
1621 uint64 lastModifiedTime = 0;
1622 if (ifLastMod) {
1623 utils::FileDetails fileInfo = utils::GetFileDetails(actualFilename.c_str());
1624 if (fileInfo.doesExist && !fileInfo.isDirectory && (fileInfo.lastModifyTime < ifLastMod) )
1625 return createErrorPage(HTTP_USE_LOCAL_COPY, time, serverName, keepAlive);
1626 }
1627
1628 char* filedata = NULL;
1629 uint32 datasize = 0;
1630 std::string html;
1631 bool isBinary = false;
1632 char* exttype = new char[1024];
1633 if (!html::GetMimeType(exttype, actualFilename.c_str(), 1024, isBinary)) {
1634 html = utils::StringFormat("Unsupported mime type for file '%s'...", actualFilename.c_str());
1635 }
1636 else if (!isBinary) {
1637 if (!(html = utils::ReadAFileString(actualFilename.c_str())).size()) {
1638 //html = utils::StringFormat("Could read file '%s'", actualFilename.c_str());
1639 }
1640 }
1641 else {
1642 if ( !(filedata = utils::ReadAFile(actualFilename.c_str(), datasize, true))) {
1643 //html = utils::StringFormat("Could read file '%s'", actualFilename.c_str());
1644 }
1645 }
1646
1647 if (filedata)
1648 createPage(HTTP_OK, GetTimeNow(), serverName, 0, keepAlive, cache, exttype, filedata, datasize);
1649 else if (html.length())
1650 createPage(HTTP_OK, GetTimeNow(), serverName, 0, keepAlive, cache, exttype, html.c_str());
1651 else {
1652 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>";
1653 createPage(HTTP_FILE_NOT_FOUND, GetTimeNow(), serverName, 0, keepAlive, cache, "text/html", html.c_str());
1654 }
1655
1656 delete [] filedata;
1657 delete [] exttype;
1658
1659 return true;
1660}
1661
1664 return false;
1665
1666 const char* connection = this->getHeaderEntry("Connection");
1667 if (!connection || stricmp(connection, "Upgrade"))
1668 return false;
1669
1670 const char* upgrade = this->getHeaderEntry("Upgrade");
1671 if (!upgrade || stricmp(upgrade, "websocket"))
1672 return false;
1673
1674 return true;
1675}
1676
1677
1678// Reading it in from a socket
1679bool HTTPReply::processHeader(const char* buffer, uint32 size, bool &isInvalid) {
1680 isInvalid = false;
1681 if (!buffer || (size < 5))
1682 return false;
1683
1684 // Find end of header
1685 uint32 n;
1686 for (n = 3; n < size; n++) {
1687 if (buffer[n] == 10) {
1688 if ( (size-n >= 2) && (buffer[n+1] == 10) ) {
1689 // Just using LF, no CR
1690 headerLength = n+2;
1691 break;
1692 }
1693 else if ( (size-n >= 3) && (buffer[n+1] == 13) && (buffer[n+2] == 10) && (buffer[n-1] == 13) ) {
1694 // Using CRLF
1695 headerLength = n+3;
1696 break;
1697 }
1698 }
1699 }
1700
1701 if (headerLength == 0) {
1702 isInvalid = true;
1703 return false;
1704 }
1705
1706 uint32 tempContentLength = 4096;
1707 data = new char[headerLength+tempContentLength+1];
1708 memcpy(data, buffer, headerLength);
1709 data[headerLength] = 0; // temp string end
1710
1711 // Now process header entries
1712 std::string key, val, uri;
1713 const char* s = data, *t;
1714 n = 0;
1715 uint32 crSize = 0;
1716 uint32 left = headerLength;
1717 while (n < headerLength) {
1718 if (!utils::GetNextLineEnd(s, left, n, crSize))
1719 break;
1720 // Process line
1721 //if (t = strchr(s, ' ')) {
1722 if ((t = strchr(s, ' ')) && (t - s < (int32)n)) {
1723 t -= 1;
1724 if (*t != ':') {
1725 t++;
1726 key.assign(s, t-s);
1727 }
1728 else {
1729 key.assign(s, t-s);
1730 t++;
1731 }
1732 while (*t == 32) t++;
1733 if (*t > 32)
1734 val.assign(t, n+s-t);
1735 else
1736 val = "";
1737 }
1738 else {
1739 key.assign(s, n);
1740 val = "";
1741 }
1742 // Record entry
1743 if (s == data) {
1744 entries["http_protocol"] = key;
1745 if (val.find("200") != val.npos)
1746 type = HTTP_OK;
1747 else if (val.find("101") != val.npos)
1749 else if (val.find("301") != val.npos)
1751 else if (val.find("304") != val.npos)
1753 else if (val.find("403") != val.npos)
1755 else if (val.find("404") != val.npos)
1757 else if (val.find("500") != val.npos)
1759 else
1760 type = atoi(val.c_str());
1761 }
1762 else {
1763 entries[key] = val;
1764 // Check entry
1765 if (stricmp(key.c_str(), "content-length") == 0)
1766 contentLength = atoi(val.c_str());
1767 else if (stricmp(key.c_str(), "transfer-encoding") == 0) {
1768 // Body arrives as HTTP/1.1 chunked framing (no Content-Length)
1769 std::string te = val;
1770 for (size_t c = 0; c < te.length(); c++) te[c] = (char)tolower((unsigned char)te[c]);
1771 if (te.find("chunked") != std::string::npos)
1772 chunked = true;
1773 }
1774 else if (stricmp(key.c_str(), "date") == 0)
1775 time = GetTimeFromString(val.c_str());
1776 else if (stricmp(key.c_str(), "connection") == 0)
1777 keepAlive = (stricmp(val.c_str(), "close") != 0);
1778 }
1779 // Next line
1780 s += n + crSize;
1781 left -= n + crSize;
1782 }
1783
1784 if (contentLength > tempContentLength) {
1785 delete(data);
1786 data = new char[headerLength+contentLength+1];
1787 memcpy(data, buffer, headerLength);
1788 data[headerLength] = 0; // temp string end
1790 }
1791
1792 return true;
1793}
1794
1795bool HTTPReply::processContent(const char* buffer, uint32 size) {
1796 if (!buffer || (size != contentLength))
1797 return false;
1798 memcpy(data+headerLength, buffer, size);
1800 return true;
1801}
1802
1803bool HTTPReply::setDecodedContent(const char* buffer, uint32 size) {
1804 if (!data || !headerLength)
1805 return false;
1806 char* newData = new char[headerLength+size+1];
1807 memcpy(newData, data, headerLength);
1808 if (size && buffer)
1809 memcpy(newData+headerLength, buffer, size);
1810 newData[headerLength+size] = 0;
1811 delete [] data;
1812 data = newData;
1813 contentLength = size;
1814 return true;
1815}
1816
1817const char* HTTPReply::getContent(uint32& size) {
1818 size = contentLength;
1819 return data+headerLength;
1820}
1821
1822const char* HTTPReply::getRawContent(uint32& size) {
1823 size = contentLength + headerLength;
1824 return data;
1825}
1826
1827const char* HTTPReply::getHeaderEntry(const char* entry) {
1828 std::map<std::string, std::string>::iterator it = entries.find(entry);
1829 if (it != entries.end())
1830 return it->second.c_str();
1831 else
1832 return NULL;
1833}
1834
1836 return getHeaderEntry("http_protocol");
1837}
1838
1840 if (data)
1841 return headerLength + contentLength;
1842 else
1843 return 0;
1844}
1845
1846
1848// HTTP Server Protocol
1850
1851bool HTTPProtocol::CheckBufferForCompatibility(const char* buffer, uint32 length) {
1852 if (length < 4)
1853 return false;
1854 return ( utils::stristr(buffer, "GET ") == buffer ||
1855 utils::stristr(buffer, "PUT ") == buffer ||
1856 utils::stristr(buffer, "POST ") == buffer ||
1857 utils::stristr(buffer, "DELETE ") == buffer ||
1858 utils::stristr(buffer, "OPTIONS ") == buffer ||
1859 utils::stristr(buffer, "HEAD ") == buffer );
1860}
1861
1863 return false;
1864}
1865
1867 if (!con || !reply)
1868 return false;
1869 //return con->send((char*)reply->data, reply->getSize());
1870 if (con->send((char*)reply->data, reply->getSize())) {
1871 LogPrint(0, LOG_NETWORK, 4, "Replying to HTTP request with %u bytes", reply->getSize());
1872 // printf("--- HTTP sent reply (%u)...\n", reply->contentLength);
1873 return true;
1874 }
1875 else {
1876 LogPrint(0, LOG_NETWORK, 2, "Failed to send reply to HTTP request with %u bytes", reply->getSize());
1877 // printf("--- HTTP failed to send reply (%u)...\n", reply->contentLength);
1878 return false;
1879 }
1880
1881}
1882
1884
1885 if (!con || !con->waitForDataToRead(timeout))
1886 return NULL;
1887 // return HTTPReply::CreateErrorReply(HTTP_SERVER_UNAVAILABLE);
1888
1889 uint32 maxSize = 4096, size;
1890 char* buffer = new char[maxSize];
1891
1892 uint64 start = GetTimeNow();
1893 uint32 wait = 0;
1894
1895 bool isInvalid = false;
1896 bool sawData = false;
1897 HTTPReply* reply = NULL;
1898 while (true) {
1899 if (!con->receiveAvailable(buffer, size, maxSize, wait, true)) {
1900 delete [] buffer;
1902 }
1903 if (size > 0) {
1904 sawData = true;
1905 reply = new HTTPReply(con->getRemoteAddress());
1906 if (reply->processHeader(buffer, size, isInvalid))
1907 break;
1908 delete(reply);
1909 reply = NULL;
1910 if (isInvalid) {
1911 // ##############
1912 }
1913 }
1914 if (GetTimeAgeMS(start) >= (int32)timeout) {
1915 delete [] buffer;
1916 // No application data at all inside the window. This happens on
1917 // spurious readability wakeups (e.g. TLS 1.3 session tickets that
1918 // carry no application bytes): it is not a protocol error, there is
1919 // simply nothing to read yet, so let the caller poll again instead
1920 // of queueing a NOREPLY error reply for a response still in flight.
1921 if (!sawData)
1922 return NULL;
1924 }
1925 wait = 20;
1926 }
1927
1928 // Now we know how much to actually expect
1929 // First read the header proper and ignore
1930 if (!con->discard(reply->headerLength)) {
1931 delete [] buffer;
1932 delete(reply);
1934 }
1935
1936// printf("Receiving HTTPReply content (%u)...\n\n", reply->contentLength);
1937 // Then read the rest of the content
1938 if (reply->chunked) {
1939 // HTTP/1.1 chunked transfer encoding: repeated <hex-size>[;ext]CRLF <data>CRLF,
1940 // terminated by a zero-size chunk. Trailers (if any) are left unread; the
1941 // client paths using this function tear the connection down afterwards.
1942 std::string body;
1943 char peekBuf[4096];
1944 uint64 chunkStart = GetTimeNow();
1945 bool failed = false;
1946 while (true) {
1947 // Read the chunk-size line: peek until a LF is visible, then consume it
1948 std::string line;
1949 while (true) {
1950 uint32 avail = 0;
1951 if (!con->receiveAvailable(peekBuf, avail, sizeof(peekBuf), 20, true)) {
1952 failed = true;
1953 break;
1954 }
1955 const char* lf = avail ? (const char*)memchr(peekBuf, '\n', avail) : NULL;
1956 if (lf) {
1957 line.assign(peekBuf, (size_t)(lf-peekBuf));
1958 con->discard((uint32)(lf-peekBuf)+1);
1959 break;
1960 }
1961 if (GetTimeAgeMS(chunkStart) >= 30000) {
1962 failed = true;
1963 break;
1964 }
1965 }
1966 if (failed)
1967 break;
1968 while (line.length() && (line[line.length()-1] == '\r'))
1969 line.erase(line.length()-1);
1970 uint32 chunkSize = (uint32)strtoul(line.c_str(), NULL, 16);
1971 if (!chunkSize)
1972 break; // final chunk
1973 char* chunkBuf = new char[chunkSize+2]; // chunk data + trailing CRLF
1974 if (!con->receive(chunkBuf, chunkSize+2, 10000)) {
1975 delete [] chunkBuf;
1976 failed = true;
1977 break;
1978 }
1979 body.append(chunkBuf, chunkSize);
1980 delete [] chunkBuf;
1981 if (GetTimeAgeMS(chunkStart) >= 30000) {
1982 failed = true;
1983 break;
1984 }
1985 }
1986 if (failed || !reply->setDecodedContent(body.data(), (uint32)body.length())) {
1987 printf("Error receiving chunked HTTPReply content (%u so far)...\n\n", (uint32)body.length());
1988 delete [] buffer;
1989 delete(reply);
1991 }
1992 }
1993 else if (reply->contentLength) {
1994 if (reply->contentLength > maxSize - reply->headerLength) {
1995 delete [] buffer;
1996 buffer = new char[reply->contentLength];
1997 // We have to read the rest, otherwise we will mess up the connection
1998 if (!con->receive(buffer, reply->contentLength, 3000)) {
1999 printf("Error receiving HTTPReply content (%u)...\n\n", reply->contentLength);
2000 delete [] buffer;
2001 delete(reply);
2003 }
2004
2005 if (!reply->processContent(buffer, reply->contentLength)) {
2006 delete [] buffer;
2007 delete(reply);
2009 }
2010 }
2011 else {
2012 // We have to read the rest, otherwise we will mess up the connection
2013 if (!con->receive(buffer, reply->contentLength, 3000)) {
2014 printf("Error receiving HTTPReply content (%u)...\n\n", reply->contentLength);
2015 delete [] buffer;
2016 delete(reply);
2018 }
2019
2020 if (!reply->processContent(buffer, reply->contentLength)) {
2021 delete [] buffer;
2022 delete(reply);
2024 }
2025 }
2026 }
2027
2028// printf("Got HTTPReply...\n\n");
2029 delete [] buffer;
2030 return reply;
2031}
2032
2034 if (!con || !req)
2035 return false;
2036 return con->send((char*)req->data, req->getSize());
2037}
2038
2040
2041 // ##########################
2042
2043 if (!con || !con->waitForDataToRead(timeout)) {
2044 return NULL;
2045 }
2046
2047 uint32 maxSize = 4096, size;
2048 char* buffer = new char[maxSize];
2049
2050 uint64 start = GetTimeNow();
2051 uint64 startReceive = 0;
2052 uint32 wait = 0;
2053
2054 bool isInvalid;
2055 HTTPRequest* req = NULL;
2056 while (true) {
2057 if (!con->receiveAvailable(buffer, size, maxSize, wait, true)) {
2058 delete [] buffer;
2059 return NULL;
2060 }
2061 if (size > 0) {
2062 LogPrint(0, LOG_NETWORK, 5, "Started receiving incoming HTTP request, size so far %u", size);
2063 if (!startReceive)
2064 startReceive = GetTimeNow();
2065 req = new HTTPRequest(con->getRemoteAddress(), startReceive);
2066 //utils::PrintBinary(buffer, size, false, "HTTP: ");
2067 if (req->processHeader(buffer, size, isInvalid)) {
2068 //printf("HTTP Done\n");
2069 break;
2070 }
2071 else if (isInvalid) {
2072 LogPrint(0, LOG_NETWORK, 2, "Received invalid incoming HTTP request, size %u", size);
2073 //utils::WriteAFile("d:\\http_invalid.bin", buffer, size, true);
2074 //DataMessage* test = new DataMessage(buffer);
2075 delete [] buffer;
2076 con->discard(size);
2077 return req;
2078 }
2079 else if (size == maxSize) {
2080 LogPrint(0, LOG_NETWORK, 2, "Received incoming HTTP request, buffer needs resizing %u", size);
2081 // maxSize isn't big enough to satisfy processHeader
2082 //printf("HTTP MaxSize, waiting for more...\n");
2083 //utils::PrintBinary(buffer, size, false, "HTTP MaxSize: ");
2084 //utils::WriteAFile("d:\\http_maxsize.bin", buffer, size, true);
2085 utils::Sleep(10);
2086 }
2087 else {
2088 LogPrint(0, LOG_NETWORK, 5, "Received incoming HTTP request, got %u, waiting for more...", size);
2089 //utils::PrintBinary(buffer, size, false, "HTTP WaitMore: ");
2090 //printf("HTTP waiting for more...\n");
2091 //utils::WriteAFile("d:\\http_notenough.bin", buffer, size, true);
2092 utils::Sleep(10);
2093 }
2094 delete(req);
2095 req = NULL;
2096 }
2097 if (GetTimeAgeMS(start) < (int32)timeout) {
2098 delete [] buffer;
2099 return NULL;
2100 }
2101 wait = 20;
2102 }
2103
2104 // Now we know how much to actually expect
2105 // First read the header proper and ignore
2106 if (!con->discard(req->headerLength)) {
2107 delete [] buffer;
2108 delete(req);
2109 return NULL;
2110 }
2111 // Then read the rest of the content
2112 if (req->contentLength) {
2113 if (req->contentLength > maxSize) {
2114 delete [] buffer;
2115 buffer = new char[req->contentLength];
2116 }
2117 // We have to read the rest, otherwise we will mess up the connection
2118 if (!con->receive(buffer, req->contentLength, 30000)) {
2119 LogPrint(0, LOG_NETWORK, 2, "Received HTTP header of %u, couldn't read the content of size %u", req->headerLength, req->contentLength);
2120 delete [] buffer;
2121 delete(req);
2122 return NULL;
2123 }
2124
2125 if (!req->processContent(buffer, req->contentLength)) {
2126 LogPrint(0, LOG_NETWORK, 2, "Received HTTP header of %u, content of size %u, failed processing the content buffer", req->headerLength, req->contentLength);
2127 delete [] buffer;
2128 delete(req);
2129 return NULL;
2130 }
2131 }
2132
2133 delete [] buffer;
2134 return req;
2135}
2136
2137
2138
2140
2141 if (!con || !con->waitForDataToRead(timeout)) {
2142 return NULL;
2143 }
2144
2145 uint32 maxSize = 4096, size;
2146 char* buffer = new char[maxSize];
2147
2148 uint64 start = GetTimeNow();
2149 uint64 startReceive = 0;
2150 uint32 wait = 0;
2151 uint64 remoteAddress;
2152 bool isInvalid;
2153 WebsocketData* wsData = NULL;
2154 while (!wsData || !wsData->isComplete()) {
2155 while (true) {
2156 if (!wsData && !con->receiveAvailable(buffer, size, maxSize, wait, true)) {
2157 delete[] buffer;
2158 return NULL;
2159 }
2160 else
2161 con->receiveAvailable(buffer, size, maxSize, wait, true);
2162 if (size > 0) {
2163 //utils::WriteAFile("d:/c/ws.bin", buffer, size, true);
2164 if (!wsData) {
2165 if (!startReceive)
2166 startReceive = GetTimeNow();
2167 wsData = new WebsocketData(con->getRemoteAddress(), startReceive);
2168 //utils::PrintBinary(buffer, size, false, "HTTP: ");
2169 }
2170
2171 if (wsData->processHeader(buffer, size, isInvalid)) {
2172 //printf("HTTP Done\n");
2173 break;
2174 }
2175 else if (isInvalid) {
2176 remoteAddress = con->getRemoteAddress();
2177 LogPrint(0, LOG_NETWORK, 1, "Invalid data received on Websocket from %u.%u.%u.%u", GETIPADDRESSQUAD(remoteAddress));
2178 //utils::WriteAFile("d:\\http_invalid.bin", buffer, size, true);
2179 //DataMessage* test = new DataMessage(buffer);
2180 delete[] buffer;
2181 con->discard(size);
2182 return wsData;
2183 }
2184 else if (size == maxSize) {
2185 // maxSize isn't big enough to satisfy processHeader
2186 //printf("HTTP MaxSize, waiting for more...\n");
2187 //utils::PrintBinary(buffer, size, false, "HTTP MaxSize: ");
2188 //utils::WriteAFile("d:\\http_maxsize.bin", buffer, size, true);
2189 utils::Sleep(10);
2190 }
2191 else {
2192 //utils::PrintBinary(buffer, size, false, "HTTP WaitMore: ");
2193 //printf("HTTP waiting for more...\n");
2194 //utils::WriteAFile("d:\\http_notenough.bin", buffer, size, true);
2195 utils::Sleep(10);
2196 }
2197 delete(wsData);
2198 wsData = NULL;
2199 }
2200 if (!wsData && (GetTimeAgeMS(start) < (int32)timeout)) {
2201 remoteAddress = con->getRemoteAddress();
2202 LogPrint(0, LOG_NETWORK, 1, "Timeout receiving data on Websocket from %u.%u.%u.%u", GETIPADDRESSQUAD(remoteAddress));
2203 delete[] buffer;
2204 return NULL;
2205 }
2206 wait = 20;
2207 }
2208
2209 // Now we know how much to actually expect
2210 // First read the header proper and ignore
2211 if (!con->discard(wsData->headerLength)) {
2212 delete[] buffer;
2213 delete(wsData);
2214 return NULL;
2215 }
2216 size -= wsData->headerLength;
2217 // Then read the rest of the content
2218 if (wsData->payloadSize) {
2219 if (wsData->payloadSize > maxSize) {
2220 delete[] buffer;
2221 buffer = new char[(uint32)(wsData->payloadSize)];
2222 }
2223 // We have to read the rest, otherwise we will mess up the connection
2224 if (!con->receive(buffer, (uint32)wsData->payloadSize, 30000)) {
2225 remoteAddress = con->getRemoteAddress();
2226 LogPrint(0, LOG_NETWORK, 1, "Unable to receive payload data on Websocket from %u.%u.%u.%u", GETIPADDRESSQUAD(remoteAddress));
2227 delete[] buffer;
2228 delete(wsData);
2229 return NULL;
2230 }
2231
2232 if (!wsData->processContent(buffer, wsData->payloadSize)) {
2233 remoteAddress = con->getRemoteAddress();
2234 LogPrint(0, LOG_NETWORK, 1, "Unable to process payload data on Websocket from %u.%u.%u.%u", GETIPADDRESSQUAD(remoteAddress));
2235 delete[] buffer;
2236 delete(wsData);
2237 return NULL;
2238 }
2239 size -= (uint32)(wsData->payloadSize);
2240 }
2241 if (wsData->isComplete()) {
2242 break;
2243 }
2244 }
2245
2246 delete[] buffer;
2247 return wsData;
2248}
2249
2251 if (!con || !wsData)
2252 return false;
2253 uint64 size = 0;
2254 const char* data = wsData->getRawData(size);
2255 if (!data || !size)
2256 return false;
2257 //return con->send((char*)reply->data, reply->getSize());
2258 if (con->send(data, (uint32)size)) {
2259 // printf("--- HTTP sent reply (%u)...\n", reply->contentLength);
2260 return true;
2261 }
2262 else {
2263 // printf("--- HTTP failed to send reply (%u)...\n", reply->contentLength);
2264 return false;
2265 }
2266
2267}
2268
2269
2270
2271
2272
2274// HTTP Client Protocol
2276
2277bool HTTPClientProtocol::CheckBufferForCompatibility(const char* buffer, uint32 length) {
2278 return false;
2279}
2280
2284
2286 return false;
2287}
2288
2290 return NULL;
2291}
2292
2294 return false;
2295}
2296
2298 return NULL;
2299}
2300
2301
2302
2304// Message Protocol
2306
2307bool MessageProtocol::CheckBufferForCompatibility(const char* buffer, uint32 length) {
2308 if (length < 2*sizeof(uint32))
2309 return false;
2310 return (GetObjID(buffer) == DATAMESSAGEID);
2311}
2312
2316
2318 if (!con || !msg)
2319 return false;
2320// uint64 addr = con->getRemoteAddress();
2321// LogPrint(0,0,0,"Protocol sending msg %llu to %u.%u.%u.%u:%u", msg->getType(), GETIPADDRESSQUAD(addr), GETIPPORT(addr));
2322// LogPrint(0,0,0,"Protocol sending msg: %u", msg->getType()[15]);
2323 return con->send((char*)msg->data, msg->getSize(), receiver);
2324}
2325
2327// uint64 t = GetTimeNow();
2328 if (!con) {
2329// if (!con || !con->waitForDataToRead(timeout)) {
2330// printf("Failed ReceiveMessage: %ld\n", GetTimeAgeMS(t));
2331 return NULL;
2332 }
2333
2334 // Peek just the header (size + object id) into a small stack buffer so the
2335 // body allocation can be sized exactly once. The previous code malloc'd a 1KB
2336 // scratch buffer for every message and then freed and re-malloc'd it for any
2337 // message bigger than 1KB - two allocations plus a free per large message.
2338 char header[2*sizeof(uint32)];
2339 if (!con->receive(header, sizeof(header), timeout, true))
2340 return NULL;
2341 if (GetObjID(header) != DATAMESSAGEID)
2342 return NULL;
2343 uint32 size = *(uint32*)header;
2344 if (size < sizeof(header))
2345 return NULL; // implausible size for a DataMessage header
2346
2347 char* buffer = (char*) malloc(size);
2348 if (!buffer)
2349 return NULL;
2350 if (!con->receive(buffer, size, timeout)) {
2351 free(buffer);
2352 return NULL;
2353 }
2354 // DataMessage adopts the buffer (copy=false) and frees it on destruction.
2355 DataMessage* msg = new DataMessage(buffer);
2356// uint64 addr = con->getRemoteAddress();
2357// LogPrint(0,0,0,"Protocol received msg %llu from %u.%u.%u.%u:%u", msg->getType(), GETIPADDRESSQUAD(addr), GETIPPORT(addr));
2358 //printf("Protocol received msg: %u.%u.%u (%p)...\n", msg->getType()[0], msg->getType()[1], msg->getType()[2], msg);
2359 return msg;
2360}
2361
2362
2364// Telnet Protocol
2366
2367bool TelnetProtocol::CheckBufferForCompatibility(const char* buffer, uint32 length) {
2368 if (length < 1)
2369 return false;
2370 return ((buffer[0] == 13) || (buffer[0] == 10));
2371}
2372
2376
2378 if (!con || !line)
2379 return false;
2380 return con->send((char*)line->data, line->getSize());
2381}
2382
2384 if (!con || !con->waitForDataToRead(timeout))
2385 return NULL;
2386
2387 uint32 maxSize = 1024, size;
2388 char* buffer = new char[maxSize];
2389 TelnetLine* line = NULL;
2390
2391 uint64 start = GetTimeNow();
2392 uint32 wait = 0, n;
2393
2394 while (GetTimeAgeMS(start) < (int32)timeout) {
2395 if (!con->receiveAvailable(buffer, size, maxSize, wait, true)) {
2396 delete [] buffer;
2397 return NULL;
2398 }
2399 for ( n=0; n<size; n++) {
2400 if (buffer[n] == 13) {
2401 line = new TelnetLine(con->getRemoteAddress());
2402 line->setCR(TELNET_WINDOWS);
2403 line->setLine(buffer, n);
2404 if ( (n < size-1) && (buffer[n+1] == 10) )
2405 n++;
2406 con->discard(n+1);
2407 delete [] buffer;
2408 // printf("\n");
2409 return line;
2410 }
2411 else if (buffer[n] == 10) {
2412 line = new TelnetLine(con->getRemoteAddress());
2413 line->setCR(TELNET_UNIX);
2414 line->setLine(buffer, n);
2415 if ( (n < size-1) && (buffer[n+1] == 13) )
2416 n++;
2417 con->discard(n+1);
2418 delete [] buffer;
2419 // printf("\n");
2420 return line;
2421 }
2422 //else
2423 // printf("[%c]", buffer[n]);
2424 }
2425 if (size == maxSize) {
2426 delete [] buffer;
2427 maxSize *= 4;
2428 buffer = new char[maxSize];
2429 wait = 0;
2430 }
2431 else
2432 wait = 20;
2433 }
2434 delete [] buffer;
2435 return NULL;
2436}
2437
2439 if (!con || !con->waitForDataToRead(timeout))
2440 return NULL;
2441
2442 char* buffer = new char[size];
2443
2444 if (!con->receive(buffer, size, timeout)) {
2445 delete [] buffer;
2446 return NULL;
2447 }
2448
2449 TelnetLine* reply = new TelnetLine(con->getRemoteAddress());
2450 reply->data = buffer;
2451 reply->size = size;
2452 return reply;
2453}
2454
2455
2456
2457
2458
2459
2461 jmData = NULL;
2462 jmSize = mOffset = 0;
2463}
2464
2465JSONM::JSONM(const char* data, uint64 size) {
2466 jmSize = size;
2467 if (jmSize) {
2468 jmData = new char[(uint32)jmSize];
2469 memcpy(jmData, data, (uint32)jmSize);
2470 mOffset = strlen(data) + 1;
2471 extractMultipartInfo();
2472 }
2473 else
2474 jmData = NULL;
2475}
2476
2477JSONM::JSONM(const char* json) {
2478 jmData = NULL;
2479 jmSize = 0;
2480 if (json)
2481 setJSON(json);
2482 else
2483 setJSON("{}");
2484}
2485
2487 mutex.enter(1000);
2488 if (jmData)
2489 delete[] jmData;
2490 jmData = NULL;
2491 jmSize = 0;
2492 mOffset = 0;
2493}
2494
2496 if (!mutex.enter(1000)) return 0;
2497 uint64 s = jmSize;
2498 mutex.leave();
2499 return s;
2500}
2502 if (!mutex.enter(1000)) return 0;
2503 uint32 s = (uint32)entries.size();
2504 mutex.leave();
2505 return s;
2506}
2507
2509 if (!jmData || !jmSize)
2510 return false;
2511 if (!strchr(jmData, '{') || !utils::laststrstr(jmData, "}"))
2512 return false;
2513 // Check that the sizes add up to the correct full size
2514 uint64 jsonLength = strlen(jmData);
2515 uint64 multipartLength = 0;
2516 std::map<uint32, JSONMEntry>::iterator i = entries.begin(), e = entries.end();
2517 while (i != e) {
2518 multipartLength += i->second.size;
2519 i++;
2520 }
2521 if (jsonLength + 1 + multipartLength != jmSize) {
2522 LogPrint(0, LOG_NETWORK, 0, "Invalid JSONM structure: JSON(%llu) + 1 + multiparts(%llu) != %llu", jsonLength, multipartLength, jmSize);
2523 return false;
2524 }
2525 return true;
2526}
2527
2528std::string JSONM::getJSON() {
2529 if (!jmData || !jmSize)
2530 return "";
2531 if (!strchr(jmData, '{') || !utils::laststrstr(jmData, "}"))
2532 return "";
2533 if (strlen(jmData) > jmSize)
2534 return std::string(jmData, jmSize);
2535 return jmData;
2536}
2537
2538const char* JSONM::getData(const char* name, uint64& size) {
2539 if (!mutex.enter(1000)) return NULL;
2540 size = 0;
2541 if (!name || !jmData || !jmSize) {
2542 mutex.leave();
2543 return NULL;
2544 }
2545 std::map<uint32, JSONMEntry>::iterator i = entries.begin(), e = entries.end();
2546 while (i != e) {
2547 if (stricmp(i->second.name.c_str(), name) == 0) {
2548 if (mOffset + i->second.offset + i->second.size > jmSize) {
2549 LogPrint(0, LOG_NETWORK, 0, "Invalid JSONM structure: Entry '%s' offset %llu size %llu exceeds the JSONM size %llu",
2550 name, i->second.offset, i->second.size, jmSize);
2551 mutex.leave();
2552 return NULL;
2553 }
2554 size = i->second.size;
2555 mutex.leave();
2556 return jmData + mOffset + i->second.offset;
2557 }
2558 i++;
2559 }
2560 mutex.leave();
2561 return NULL;
2562}
2563
2564std::string JSONM::getDataType(const char* name) {
2565 if (!mutex.enter(1000)) return "";
2566 if (!name || !jmData || !jmSize) {
2567 mutex.leave();
2568 return NULL;
2569 }
2570 std::map<uint32, JSONMEntry>::iterator i = entries.begin(), e = entries.end();
2571 while (i != e) {
2572 if (stricmp(i->second.name.c_str(), name) == 0) {
2573 mutex.leave();
2574 return i->second.type;
2575 }
2576 i++;
2577 }
2578 mutex.leave();
2579 return "";
2580}
2581
2582std::string JSONM::getDataType(uint32 chunk) {
2583 if (!mutex.enter(1000)) return NULL;
2584 if (!jmData || !jmSize || !chunk)
2585 return "";
2586
2587 std::map<uint32, JSONMEntry>::iterator i = entries.find(chunk);
2588 if (i == entries.end()) {
2589 mutex.leave();
2590 return "";
2591 }
2592
2593 mutex.leave();
2594 return i->second.type;
2595}
2596
2597std::string JSONM::getDataName(uint32 chunk) {
2598 if (!mutex.enter(1000)) return NULL;
2599 if (!jmData || !jmSize || !chunk) {
2600 mutex.leave();
2601 return "";
2602 }
2603
2604 std::map<uint32, JSONMEntry>::iterator i = entries.find(chunk);
2605 if (i == entries.end()) {
2606 mutex.leave();
2607 return "";
2608 }
2609
2610 mutex.leave();
2611 return i->second.name;
2612}
2613
2614
2615
2616const char* JSONM::getData(uint32 chunk, uint64& size) {
2617 if (!mutex.enter(1000)) return NULL;
2618 size = 0;
2619 if (!jmData || !jmSize) {
2620 mutex.leave();
2621 return NULL;
2622 }
2623
2624 std::map<uint32, JSONMEntry>::iterator i = entries.find(chunk);
2625 if (i == entries.end()) {
2626 mutex.leave();
2627 return NULL;
2628 }
2629
2630 if (mOffset + i->second.offset + i->second.size > jmSize) {
2631 LogPrint(0, LOG_NETWORK, 0, "Invalid JSONM structure: Entry %u '%s' offset %llu size %llu exceeds the JSONM size %llu",
2632 chunk, i->second.name.c_str(), i->second.offset, i->second.size, jmSize);
2633 mutex.leave();
2634 return NULL;
2635 }
2636
2637 size = i->second.size;
2638 mutex.leave();
2639 return jmData + mOffset + i->second.offset;
2640}
2641
2642bool JSONM::setRawJSON(const char* json) {
2643 if (!json)
2644 return false;
2645 if (!jmData) {
2646 jmSize = mOffset = strlen(json) + 1;
2647 jmData = new char[(uint32)jmSize];
2648 utils::strcpyavail(jmData, json, (uint32)jmSize, true);
2649 return true;
2650 }
2651 // else we have JSON and possibly data already
2652 uint64 newMOffset = strlen(json) + 1;
2653 uint64 newSize = newMOffset + (jmSize - mOffset);
2654 char* newData = new char[(uint32)newSize];
2655 utils::strcpyavail(newData, json, (uint32)newMOffset, true);
2656 // copy in old data
2657 memcpy(newData + (uint32)newMOffset, jmData + (uint32)mOffset, (uint32)(jmSize - mOffset));
2658 delete[] jmData;
2659 jmData = newData;
2660 mOffset = newMOffset;
2661 jmSize = newSize;
2662 return true;
2663}
2664
2665bool JSONM::setJSON(const char* json) {
2666 if (!json)
2667 return false;
2668 if (!mutex.enter(1000)) return false;
2669 if (setRawJSON(json))
2670 updateMultipartJSON();
2671 mutex.leave();
2672 return true;
2673}
2674
2675uint32 JSONM::addData(const char* name, const char* data, uint64 size, const char* type) {
2676 if (!mutex.enter(1000)) return 0;
2677 // check for name in use
2678 uint64 s;
2679 if (getData(name, s)) {
2680 mutex.leave();
2681 return 0;
2682 }
2683 if (!jmData)
2684 setJSON("{}");
2685 JSONMEntry entry;
2686 entry.chunk = getCount() + 1;
2687 entry.name = name;
2688 entry.size = size;
2689 entry.type = type;
2690 entry.offset = jmSize - this->mOffset;
2691 uint64 newSize = jmSize + size;
2692 char* newData = new char[(uint32)newSize];
2693 memcpy(newData, jmData, (uint32)jmSize);
2694 memcpy(newData + (uint32)jmSize, data, (uint32)size);
2695 entries[entry.chunk] = entry;
2696 delete[] jmData;
2697 jmData = newData;
2698 jmSize = newSize;
2699 updateMultipartJSON();
2700 mutex.leave();
2701 return entry.chunk;
2702}
2703
2705 if (!mutex.enter(1000)) return "";
2706 if (!jmData || !jmSize) {
2707 mutex.leave();
2708 return "";
2709 }
2710 std::string info = utils::StringFormat("JSON length: %u\nTotal size: %s\nChunks: %u total: %s\n%s\n",
2711 mOffset - 1,
2712 utils::BytifySize((uint32)jmSize).c_str(),
2713 getCount(),
2714 utils::BytifySize((uint32)(jmSize - mOffset)).c_str(),
2715 jmData);
2716 std::map<uint32, JSONMEntry>::iterator i = entries.begin(), e = entries.end();
2717 while (i != e) {
2718 info += i->second.getInfoString();
2719 i++;
2720 }
2721 mutex.leave();
2722 return info;
2723}
2724
2725
2726
2727
2728bool JSONM::extractMultipartInfo() {
2729 // Assume multipart info is in JSON
2730 // read and create entries from this
2731 // also assume mutex is already locked
2732
2733 if (!jmData || !jmSize || (jmSize - mOffset < 10))
2734 return false;
2735
2736 jsmn_parser parser;
2737 jsmntok_t* tokens = new jsmntok_t[1024];
2738 int tokenCount = 0;
2739 //const char* val;
2740 //int valSize;
2741 jsmn_init(&parser);
2742 if ((tokenCount = jsmn_parse(&parser, jmData, strlen(jmData), tokens, 1024)) <= 0) {
2743 // No JSON found
2744 delete[] tokens;
2745 return false;
2746 }
2747
2748 JSONMEntry entry;
2749 //int token;
2750 std::vector<int> jsonArrayIdx;
2751 std::vector<int>::iterator i, e;
2752 jsonArrayIdx = GetJSONChildArrayIndexes(tokens, tokenCount, jmData, "_multipart_", 0, JSMN_UNDEFINED);
2753 if (!jsonArrayIdx.size()) {
2754 // No info found
2755 delete[] tokens;
2756 return false;
2757 }
2758 i = jsonArrayIdx.begin();
2759 e = jsonArrayIdx.end();
2760
2761 uint64 offset = 0;
2762 while (i != e) {
2763 if ((entry.name = GetJSONChildValueString(tokens, tokenCount, jmData, "name", *i)).length()) {
2764 entry.type = GetJSONChildValueString(tokens, tokenCount, jmData, "mimetype", *i);
2765 entry.size = GetJSONChildValueUint64(tokens, tokenCount, jmData, "bytesize", *i);
2766 entry.chunk = (uint32)entries.size() + 1;
2767 entry.offset = offset;
2768 entries[entry.chunk] = entry;
2769 offset += entry.size;
2770 }
2771 i++;
2772 }
2773 return true;
2774}
2775
2776bool JSONM::updateMultipartJSON() {
2777 // Assume entries and binary chunks updated
2778 // create new JSON entry and replace old one, if present
2779 // also assume mutex is already locked
2780
2781 std::string newJSON;
2782
2783 std::map<uint32, JSONMEntry>::iterator i = entries.begin(), e = entries.end();
2784 while (i != e) {
2785 if (newJSON.length())
2786 newJSON += ",";
2787 newJSON += i->second.toJSON();
2788 i++;
2789 }
2790
2791 jsmn_parser parser;
2792 jsmntok_t* tokens = new jsmntok_t[1024];
2793 int tokenCount = 0;
2794 //const char* val;
2795 //int valSize;
2796 jsmn_init(&parser);
2797 if ((tokenCount = jsmn_parse(&parser, jmData, strlen(jmData), tokens, 1024)) <= 0) {
2798 // No JSON found
2799 delete[] tokens;
2800 return false;
2801 }
2802
2803 std::string fullJSON = jmData;
2804 JSONMEntry entry;
2805 int token = GetJSONToken(tokens, tokenCount, jmData, "_multipart_", 0);
2806 if (token > 0) {
2807 uint32 begin = tokens[token].start;
2808 uint32 end = tokens[token].end;
2809 //uint32 end = tokens[tokens[tokens[token].parent].parent].end;
2810 fullJSON = utils::StringFormat("%s[%s]%s",
2811 fullJSON.substr(0, begin).c_str(),
2812 newJSON.c_str(),
2813 fullJSON.substr(end).c_str()
2814 );
2815 }
2816 else {
2817 // just add at the end
2818 const char* lastBracket = utils::laststrstr(jmData, "}");
2819 if (!lastBracket) {
2820 // Broken JSON found
2821 delete[] tokens;
2822 return false;
2823 }
2824 fullJSON = utils::StringFormat("%s,\"_multipart_\": [%s]}", fullJSON.substr(0, lastBracket-jmData).c_str(), newJSON.c_str());
2825 }
2826 this->setRawJSON(fullJSON.c_str());
2827 return true;
2828}
2829
2831 DataMessage* msg = new DataMessage();
2832 uint64 now = GetTimeNow();
2833
2834 msg->setString("POST", this->getJSON().c_str());
2835 msg->setString("POST_CONTENT_TYPE_", "application/json");
2836
2837 std::map<uint32, JSONMEntry>::iterator i = entries.begin(), e = entries.end();
2838 while (i != e) {
2839 msg->setData(i->second.name.c_str(), jmData + mOffset + i->second.offset, (uint32)i->second.size);
2840 msg->setString((i->second.name + "_CONTENT_TYPE_").c_str(), i->second.type.c_str());
2841 i++;
2842 }
2843 msg->setCreatedTime(now);
2844 msg->setRecvTime(now);
2845 return msg;
2846}
2847
2848
2849// ################# Unit Test #################
2850
2851// Encode a WebsocketData frame, then decode it back through the
2852// processHeader/processContent path and verify the recovered payload matches.
2853// All in-memory, no sockets. Returns true on success.
2854static bool TestWebsocketRoundTrip(WebsocketData::DataType type, bool maskData,
2855 const char* payload, uint64 payloadSize) {
2856
2857 WebsocketData enc;
2858 if (!enc.setData(type, maskData, payload, payloadSize)) {
2859 unittest::fail("WebsocketData::setData returned false (type %d, mask %d, size %llu)",
2860 (int)type, (int)maskData, payloadSize);
2861 return false;
2862 }
2863 if (!enc.isComplete()) {
2864 unittest::fail("encoded WebsocketData is not COMPLETE");
2865 return false;
2866 }
2867
2868 uint64 rawSize = 0;
2869 const char* raw = enc.getRawData(rawSize);
2870 if (!raw || !rawSize) {
2871 unittest::fail("getRawData returned no bytes");
2872 return false;
2873 }
2874 if (rawSize != enc.headerLength + payloadSize) {
2875 unittest::fail("raw size %llu != header %u + payload %llu",
2876 rawSize, enc.headerLength, payloadSize);
2877 return false;
2878 }
2879
2880 // First byte must carry FIN + opcode; opcode must equal the data type.
2881 uint8 b0 = *(const unsigned char*)raw;
2882 if (!(b0 & WebsocketData::BHB0_FIN)) {
2883 unittest::fail("encoded frame missing FIN bit");
2884 return false;
2885 }
2886 if ((b0 & WebsocketData::BHB0_OPCODE) != (uint8)type) {
2887 unittest::fail("encoded opcode %u != type %u", (uint8)(b0 & WebsocketData::BHB0_OPCODE), (uint8)type);
2888 return false;
2889 }
2890
2891 // Decode: split the raw bytes into header + payload, exactly as the socket
2892 // reader would, but feeding from the in-memory buffer.
2893 WebsocketData dec;
2894 bool isInvalid = false;
2895 if (!dec.processHeader(raw, rawSize, isInvalid) || isInvalid) {
2896 unittest::fail("processHeader failed (invalid=%d)", (int)isInvalid);
2897 return false;
2898 }
2899 if (dec.dataType != type) {
2900 unittest::fail("decoded dataType %d != %d", (int)dec.dataType, (int)type);
2901 return false;
2902 }
2903 if (dec.getPayloadSize() != payloadSize) {
2904 unittest::fail("decoded payloadSize %llu != %llu", dec.getPayloadSize(), payloadSize);
2905 return false;
2906 }
2907
2908 if (payloadSize) {
2909 if (!dec.processContent(raw + dec.headerLength, payloadSize)) {
2910 unittest::fail("processContent failed");
2911 return false;
2912 }
2913 uint64 outSize = 0;
2914 const char* out = dec.getContent(outSize);
2915 if (!out || (outSize != payloadSize)) {
2916 unittest::fail("getContent size %llu != %llu", outSize, payloadSize);
2917 return false;
2918 }
2919 if (memcmp(out, payload, (size_t)payloadSize) != 0) {
2920 unittest::fail("decoded payload bytes differ from original");
2921 return false;
2922 }
2923 }
2924 return true;
2925}
2926
2928
2929 // ---- Phase 1: WebsocketData encode/decode round-trips ----
2930 unittest::progress(5, "websocket frames");
2931
2932 const char* shortText = "hello websocket";
2933 if (!TestWebsocketRoundTrip(WebsocketData::TEXT, false, shortText, strlen(shortText)))
2934 return false;
2935 unittest::detail("websocket short unmasked TEXT round-trip ok (%zu bytes)", strlen(shortText));
2936
2937 // Masked frame: exercises the client->server masking path.
2938 if (!TestWebsocketRoundTrip(WebsocketData::TEXT, true, shortText, strlen(shortText)))
2939 return false;
2940 unittest::detail("websocket short masked TEXT round-trip ok");
2941
2942 if (!TestWebsocketRoundTrip(WebsocketData::BINARY, false, shortText, strlen(shortText)))
2943 return false;
2944
2945 unittest::progress(25, "websocket extended frames");
2946
2947 // Extended (16-bit length) payload: > 125 bytes forces the 2-byte length field.
2948 std::string big;
2949 big.reserve(4096);
2950 for (uint32 i = 0; i < 4000; i++)
2951 big.push_back((char)('A' + (i % 26)));
2952 if (!TestWebsocketRoundTrip(WebsocketData::BINARY, false, big.data(), big.size()))
2953 return false;
2954 if (!TestWebsocketRoundTrip(WebsocketData::BINARY, true, big.data(), big.size()))
2955 return false;
2956 unittest::detail("websocket extended (16-bit length) masked+unmasked round-trip ok (%zu bytes)", big.size());
2957
2958 // ---- Phase 2: HTTP request build/parse round-trip ----
2959 unittest::progress(50, "http request");
2960
2961 const char* body = "field1=value1&field2=value2";
2962 uint32 bodyLen = (uint32)strlen(body);
2963
2964 // Put query parameters in the URI so processHeader -> parseURIParameters
2965 // populates params; the urlencoded body is carried verbatim and checked
2966 // byte-for-byte below.
2967 const char* fullURI = "/path/script.cgi?home=Cosby&favorite=flies";
2968
2969 HTTPRequest reqEnc;
2970 if (!reqEnc.createRequest(HTTP_POST, "example.com", fullURI,
2971 body, bodyLen, true, 0)) {
2972 unittest::fail("HTTPRequest::createRequest returned false");
2973 return false;
2974 }
2975
2976 uint32 rawSize = 0;
2977 const char* raw = reqEnc.getRawContent(rawSize);
2978 if (!raw || !rawSize) {
2979 unittest::fail("HTTPRequest::getRawContent returned no bytes");
2980 return false;
2981 }
2982
2983 // Parse it back through a fresh request, feeding header then content
2984 // from the in-memory buffer (header ends at reqEnc.headerLength).
2985 HTTPRequest reqDec;
2986 bool isInvalid = false;
2987 if (!reqDec.processHeader(raw, rawSize, isInvalid) || isInvalid) {
2988 unittest::fail("HTTPRequest::processHeader failed (invalid=%d)", (int)isInvalid);
2989 return false;
2990 }
2991 if (reqDec.type != HTTP_POST) {
2992 unittest::fail("parsed method %u != HTTP_POST", reqDec.type);
2993 return false;
2994 }
2995 const char* uri = reqDec.getURI();
2996 if (!uri || strcmp(uri, fullURI) != 0) {
2997 unittest::fail("parsed URI '%s' != '%s'", uri ? uri : "(null)", fullURI);
2998 return false;
2999 }
3000 const char* request = reqDec.getRequest();
3001 if (!request || strcmp(request, "path/script.cgi") != 0) {
3002 unittest::fail("parsed request '%s' != 'path/script.cgi'", request ? request : "(null)");
3003 return false;
3004 }
3005 if (reqDec.contentLength != bodyLen) {
3006 unittest::fail("parsed Content-Length %u != %u", reqDec.contentLength, bodyLen);
3007 return false;
3008 }
3009
3010 unittest::progress(70, "http content");
3011
3012 // Feed the body (located right after the header in the same buffer).
3013 if (!reqDec.processContent(raw + reqDec.headerLength, bodyLen)) {
3014 unittest::fail("HTTPRequest::processContent failed");
3015 return false;
3016 }
3017 uint32 outSize = 0;
3018 const char* outBody = reqDec.getContent(outSize);
3019 if (!outBody || (outSize != bodyLen) || (memcmp(outBody, body, bodyLen) != 0)) {
3020 unittest::fail("parsed body differs from original (size %u != %u)", outSize, bodyLen);
3021 return false;
3022 }
3023 // query-string params (from the URI) should have been parsed.
3024 const char* p = reqDec.getParameter("home");
3025 if (!p || strcmp(p, "Cosby") != 0) {
3026 unittest::fail("parsed URI param home='%s' != 'Cosby'", p ? p : "(null)");
3027 return false;
3028 }
3029 unittest::detail("http POST build/parse round-trip ok (body %u bytes)", bodyLen);
3030
3031 // ---- Phase 3: parse throughput metric ----
3032 unittest::progress(85, "throughput");
3033
3034 const uint32 iters = 2000;
3035 uint64 t0 = GetTimeNow();
3036 for (uint32 i = 0; i < iters; i++) {
3037 HTTPRequest r;
3038 bool inv = false;
3039 if (!r.processHeader(raw, reqEnc.headerLength, inv) || inv) {
3040 unittest::fail("throughput loop processHeader failed at iter %u", i);
3041 return false;
3042 }
3043 }
3044 double us = (double)(GetTimeNow() - t0);
3045 if (us > 0.0)
3046 unittest::metric("http_header_parse_throughput", (double)iters / us * 1e6, "ops/s", true);
3047
3048 unittest::progress(100, "done");
3049 return true;
3050}
3051
3054 "networkprotocols", NetworkProtocols_UnitTest,
3055 "HTTP request and WebSocket frame build/parse round-trips", "network");
3056}
3057
3058}
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_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 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:1505
#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:7590
bool TextStartsWith(const char *str, const char *start, bool caseSensitive=true)
Test whether str starts with start.
Definition Utils.cpp:7647
bool Sleep(uint32 ms)
Suspend the calling thread.
Definition Utils.cpp:2830
const char * stristr(const char *str, const char *substr, uint32 len=0)
Case-insensitive strstr.
Definition Utils.cpp:6442
bool GetNextLineEnd(const char *str, uint32 size, uint32 &len, uint32 &crSize)
Find the end of the current line.
Definition Utils.cpp:6481
uint64 ntoh64(const uint64 *input)
Convert a 64-bit value from network to host byte order.
Definition Utils.cpp:2442
uint32 strcpyavail(char *dst, const char *src, uint32 maxlen, bool copyAvailable)
Bounded strcpy that always NUL-terminates.
Definition Utils.cpp:6463
@ 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:7204
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:6918
FileDetails GetFileDetails(const char *filename)
Stat a file.
Definition Utils.cpp:7771
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:6591
std::string TextTrimQuotes(const char *text)
Strip a single pair of surrounding quotes if present.
Definition Utils.cpp:6558
std::string BytifySize(double val)
Format a byte count with binary units, e.g.
Definition Utils.cpp:8131
std::string StringFormat(const char *format,...)
printf into a std::string.
Definition Utils.cpp:7033
char * ReadAFile(const char *filename, uint32 &length, bool binary=false)
Read an entire file into a new buffer.
Definition Utils.cpp:7225
int64 RandomInt(int64 from=0, int64 to=100)
Uniform random integer in [from,to].
Definition Utils.cpp:8122
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:1725
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