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}
1430
1432 time = reply->time;
1433 source = reply->source;
1434 type = reply->type;
1435 headerLength = reply->headerLength;
1437 keepAlive = reply->keepAlive;
1438 data = new char[headerLength+contentLength+1];
1439 memcpy(reply->data, data, headerLength+contentLength+1); // incl. string end
1440 entries = reply->entries;
1441 params = reply->params;
1442}
1443
1445 if (data != NULL)
1446 delete(data);
1447 data = NULL;
1448}
1449
1450// Generating the data
1451
1452bool HTTPReply::createOptionsResponse(uint8 status, uint64 time, const char* serverName, bool keepAlive, const char* origin, const char* operations) {
1453
1454 //OPTIONS /cors HTTP/1.1
1455 //Origin: http://api.bob.com
1456 //Access-Control-Request-Method: PUT
1457 //Access-Control-Request-Headers: X-Custom-Header
1458 //Host: api.alice.com
1459 //Accept-Language: en-US
1460 //Connection: keep-alive
1461 //User-Agent: Mozilla/5.0...
1462 //
1463 //Preflight Response:
1464 //
1465 //Access-Control-Allow-Origin: http://api.bob.com
1466 //Access-Control-Allow-Methods: GET, POST, PUT
1467 //Access-Control-Allow-Headers: X-Custom-Header
1468 //Content-Type: text/html; charset=utf-8
1469
1470 char timeString[1024];
1471 if (!GetHTTPTime(time, timeString, 1024))
1472 timeString[0] = 0;
1473
1474 if (data)
1475 delete(data);
1476 data = new char[4096];
1477 snprintf(data, 4096,
1478"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");
1479//"Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept\r\n\r\n");
1480
1481//"%s\r\nDate: %s\r\n
1482//Server: %s\r\n
1483//Connection: %s\r\n
1484//"Access-Control-Allow-Origin: %s\r\n
1485//Access-Control-Allow-Methods: %s\r\n
1486//Access-Control-Allow-Headers: X-Custom-Header\r\n
1487//Content-Type: text/html\r\n\r\n",
1488// //HTTP_Status[status], timeString, serverName,
1489// //keepAlive ? "Keep-Alive" : "close",
1490// origin, operations);
1491
1492 headerLength = (uint32)strlen(data);
1493 contentLength = 0;
1494 return true;
1495}
1496
1497
1498bool HTTPReply::createErrorPage(uint8 status, uint64 time, const char* serverName, bool keepAlive) {
1499
1500 char timeString[1024];
1501 if (!GetHTTPTime(time, timeString, 1024))
1502 timeString[0] = 0;
1503
1504 if (data)
1505 delete(data);
1506 data = new char[4096];
1507 snprintf(data, 4096,
1508 "%s\r\nDate: %s\r\nServer: %s\r\n"
1509 "X-Frame-Options: SAMEORIGIN\r\n"
1510 "Connection: %s\r\n\r\n",
1511 HTTP_Status[status], timeString, serverName,
1512 keepAlive ? "Keep-Alive" : "close");
1513
1514 headerLength = (uint32)strlen(data);
1515 contentLength = 0;
1516 return true;
1517}
1518
1520
1521 char timeString[1024];
1522 if (!GetHTTPTime(time, timeString, 1024))
1523 timeString[0] = 0;
1524
1525 if (data)
1526 delete(data);
1527 data = new char[4096];
1528 snprintf(data, 4096,
1529 "%s\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n",
1531
1532 headerLength = (uint32)strlen(data);
1533 contentLength = 0;
1534 return true;
1535}
1536
1537bool HTTPReply::createWebsocketHTTPReply(const char* key, const char* version) {
1538 // RFC 6455 §4.2.2 handshake: the server proves it understood the upgrade by
1539 // concatenating the client's Sec-WebSocket-Key with the fixed magic GUID
1540 // 258EAFA5-E914-47DA-95CA-C5AB0DC85B11, SHA-1 hashing the result and
1541 // returning it base64-encoded as Sec-WebSocket-Accept in a 101 reply.
1542
1543 if (!key || !strlen(key))
1544 return false;
1545
1546 std::string magicString = utils::StringFormat("%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", key);
1547 hash::SHA1 sha1;
1548 sha1.reset();
1549 sha1.add(magicString.c_str(), magicString.size());
1550 std::string rawMagic = sha1.getHashRaw();
1551
1552 std::string acceptKey = base64_encode(rawMagic);
1553
1554 if (data)
1555 delete(data);
1556 data = new char[4096];
1557 snprintf(data, 4096,
1558 "%s\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %s\r\n\r\n",
1559 HTTP_Status[HTTP_SWITCH_PROTOCOL], acceptKey.c_str());
1560
1561 headerLength = (uint32)strlen(data);
1562 contentLength = 0;
1563 return true;
1564}
1565
1566bool HTTPReply::createPage(uint8 status, uint64 time, const char* serverName,
1567 uint64 lastMod, bool keepAlive, bool cache, const char* contentType,
1568 const char* content, uint32 contentSize, const char* additionalHeaderEntries) {
1569
1570 char timeString[1024];
1571 if (!GetHTTPTime(time, timeString, 1024))
1572 timeString[0] = 0;
1573
1574 contentLength = (contentSize > 0) ? contentSize : (uint32)strlen(content);
1575
1576 if (data)
1577 delete(data);
1578 data = new char[4096 + contentLength];
1579 if (additionalHeaderEntries && strlen(additionalHeaderEntries)) {
1580 snprintf(data, 4096 + contentLength,
1581 "%s\r\nDate: %s\r\nServer: %s\r\n"
1582 "X-Frame-Options: SAMEORIGIN\r\n"
1583 "Last-Modified: %s\r\nContent-Length: %u\r\n"
1584 "Connection: %s\r\nContent-Type: %s\r\nCache-Control: %s\r\n"
1585 "%s\r\n\r\n",
1586 HTTP_Status[status], timeString, serverName, timeString, contentLength,
1587 keepAlive ? "Keep-Alive" : "close", contentType, cache ? "Public" : "No-Cache",
1588 additionalHeaderEntries);
1589 }
1590 else {
1591 snprintf(data, 4096 + contentLength,
1592 "%s\r\nDate: %s\r\nServer: %s\r\n"
1593 "X-Frame-Options: SAMEORIGIN\r\n"
1594 "Last-Modified: %s\r\nContent-Length: %u\r\n"
1595 "Connection: %s\r\nContent-Type: %s\r\nCache-Control: %s\r\n\r\n",
1596 HTTP_Status[status], timeString, serverName, timeString, contentLength,
1597 keepAlive ? "Keep-Alive" : "close", contentType, cache ? "Public" : "No-Cache");
1598 }
1599
1600 headerLength = (uint32)strlen(data);
1601 memcpy(data+headerLength, content, contentLength);
1603
1604 return true;
1605}
1606
1607bool HTTPReply::createFromFile(uint64 time, const char* serverName,
1608 uint64 ifLastMod, bool keepAlive, bool cache,
1609 const char* filename) {
1610
1611 std::string actualFilename = filename;
1612 const char* question = strstr(filename, "?");
1613 if (question)
1614 actualFilename = std::string(filename, question - filename);
1615
1616 uint64 lastModifiedTime = 0;
1617 if (ifLastMod) {
1618 utils::FileDetails fileInfo = utils::GetFileDetails(actualFilename.c_str());
1619 if (fileInfo.doesExist && !fileInfo.isDirectory && (fileInfo.lastModifyTime < ifLastMod) )
1620 return createErrorPage(HTTP_USE_LOCAL_COPY, time, serverName, keepAlive);
1621 }
1622
1623 char* filedata = NULL;
1624 uint32 datasize = 0;
1625 std::string html;
1626 bool isBinary = false;
1627 char* exttype = new char[1024];
1628 if (!html::GetMimeType(exttype, actualFilename.c_str(), 1024, isBinary)) {
1629 html = utils::StringFormat("Unsupported mime type for file '%s'...", actualFilename.c_str());
1630 }
1631 else if (!isBinary) {
1632 if (!(html = utils::ReadAFileString(actualFilename.c_str())).size()) {
1633 //html = utils::StringFormat("Could read file '%s'", actualFilename.c_str());
1634 }
1635 }
1636 else {
1637 if ( !(filedata = utils::ReadAFile(actualFilename.c_str(), datasize, true))) {
1638 //html = utils::StringFormat("Could read file '%s'", actualFilename.c_str());
1639 }
1640 }
1641
1642 if (filedata)
1643 createPage(HTTP_OK, GetTimeNow(), serverName, 0, keepAlive, cache, exttype, filedata, datasize);
1644 else if (html.length())
1645 createPage(HTTP_OK, GetTimeNow(), serverName, 0, keepAlive, cache, exttype, html.c_str());
1646 else {
1647 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>";
1648 createPage(HTTP_FILE_NOT_FOUND, GetTimeNow(), serverName, 0, keepAlive, cache, "text/html", html.c_str());
1649 }
1650
1651 delete [] filedata;
1652 delete [] exttype;
1653
1654 return true;
1655}
1656
1659 return false;
1660
1661 const char* connection = this->getHeaderEntry("Connection");
1662 if (!connection || stricmp(connection, "Upgrade"))
1663 return false;
1664
1665 const char* upgrade = this->getHeaderEntry("Upgrade");
1666 if (!upgrade || stricmp(upgrade, "websocket"))
1667 return false;
1668
1669 return true;
1670}
1671
1672
1673// Reading it in from a socket
1674bool HTTPReply::processHeader(const char* buffer, uint32 size, bool &isInvalid) {
1675 isInvalid = false;
1676 if (!buffer || (size < 5))
1677 return false;
1678
1679 // Find end of header
1680 uint32 n;
1681 for (n = 3; n < size; n++) {
1682 if (buffer[n] == 10) {
1683 if ( (size-n >= 2) && (buffer[n+1] == 10) ) {
1684 // Just using LF, no CR
1685 headerLength = n+2;
1686 break;
1687 }
1688 else if ( (size-n >= 3) && (buffer[n+1] == 13) && (buffer[n+2] == 10) && (buffer[n-1] == 13) ) {
1689 // Using CRLF
1690 headerLength = n+3;
1691 break;
1692 }
1693 }
1694 }
1695
1696 if (headerLength == 0) {
1697 isInvalid = true;
1698 return false;
1699 }
1700
1701 uint32 tempContentLength = 4096;
1702 data = new char[headerLength+tempContentLength+1];
1703 memcpy(data, buffer, headerLength);
1704 data[headerLength] = 0; // temp string end
1705
1706 // Now process header entries
1707 std::string key, val, uri;
1708 const char* s = data, *t;
1709 n = 0;
1710 uint32 crSize = 0;
1711 uint32 left = headerLength;
1712 while (n < headerLength) {
1713 if (!utils::GetNextLineEnd(s, left, n, crSize))
1714 break;
1715 // Process line
1716 //if (t = strchr(s, ' ')) {
1717 if ((t = strchr(s, ' ')) && (t - s < (int32)n)) {
1718 t -= 1;
1719 if (*t != ':') {
1720 t++;
1721 key.assign(s, t-s);
1722 }
1723 else {
1724 key.assign(s, t-s);
1725 t++;
1726 }
1727 while (*t == 32) t++;
1728 if (*t > 32)
1729 val.assign(t, n+s-t);
1730 else
1731 val = "";
1732 }
1733 else {
1734 key.assign(s, n);
1735 val = "";
1736 }
1737 // Record entry
1738 if (s == data) {
1739 entries["http_protocol"] = key;
1740 if (val.find("200") != val.npos)
1741 type = HTTP_OK;
1742 else if (val.find("101") != val.npos)
1744 else if (val.find("301") != val.npos)
1746 else if (val.find("304") != val.npos)
1748 else if (val.find("403") != val.npos)
1750 else if (val.find("404") != val.npos)
1752 else if (val.find("500") != val.npos)
1754 else
1755 type = atoi(val.c_str());
1756 }
1757 else {
1758 entries[key] = val;
1759 // Check entry
1760 if (stricmp(key.c_str(), "content-length") == 0)
1761 contentLength = atoi(val.c_str());
1762 else if (stricmp(key.c_str(), "date") == 0)
1763 time = GetTimeFromString(val.c_str());
1764 else if (stricmp(key.c_str(), "connection") == 0)
1765 keepAlive = (stricmp(val.c_str(), "close") != 0);
1766 }
1767 // Next line
1768 s += n + crSize;
1769 left -= n + crSize;
1770 }
1771
1772 if (contentLength > tempContentLength) {
1773 delete(data);
1774 data = new char[headerLength+contentLength+1];
1775 memcpy(data, buffer, headerLength);
1776 data[headerLength] = 0; // temp string end
1778 }
1779
1780 return true;
1781}
1782
1783bool HTTPReply::processContent(const char* buffer, uint32 size) {
1784 if (!buffer || (size != contentLength))
1785 return false;
1786 memcpy(data+headerLength, buffer, size);
1788 return true;
1789}
1790
1791const char* HTTPReply::getContent(uint32& size) {
1792 size = contentLength;
1793 return data+headerLength;
1794}
1795
1796const char* HTTPReply::getRawContent(uint32& size) {
1797 size = contentLength + headerLength;
1798 return data;
1799}
1800
1801const char* HTTPReply::getHeaderEntry(const char* entry) {
1802 std::map<std::string, std::string>::iterator it = entries.find(entry);
1803 if (it != entries.end())
1804 return it->second.c_str();
1805 else
1806 return NULL;
1807}
1808
1810 return getHeaderEntry("http_protocol");
1811}
1812
1814 if (data)
1815 return headerLength + contentLength;
1816 else
1817 return 0;
1818}
1819
1820
1822// HTTP Server Protocol
1824
1825bool HTTPProtocol::CheckBufferForCompatibility(const char* buffer, uint32 length) {
1826 if (length < 4)
1827 return false;
1828 return ( utils::stristr(buffer, "GET ") == buffer ||
1829 utils::stristr(buffer, "PUT ") == buffer ||
1830 utils::stristr(buffer, "POST ") == buffer ||
1831 utils::stristr(buffer, "DELETE ") == buffer ||
1832 utils::stristr(buffer, "OPTIONS ") == buffer ||
1833 utils::stristr(buffer, "HEAD ") == buffer );
1834}
1835
1837 return false;
1838}
1839
1841 if (!con || !reply)
1842 return false;
1843 //return con->send((char*)reply->data, reply->getSize());
1844 if (con->send((char*)reply->data, reply->getSize())) {
1845 LogPrint(0, LOG_NETWORK, 4, "Replying to HTTP request with %u bytes", reply->getSize());
1846 // printf("--- HTTP sent reply (%u)...\n", reply->contentLength);
1847 return true;
1848 }
1849 else {
1850 LogPrint(0, LOG_NETWORK, 2, "Failed to send reply to HTTP request with %u bytes", reply->getSize());
1851 // printf("--- HTTP failed to send reply (%u)...\n", reply->contentLength);
1852 return false;
1853 }
1854
1855}
1856
1858
1859 if (!con || !con->waitForDataToRead(timeout))
1860 return NULL;
1861 // return HTTPReply::CreateErrorReply(HTTP_SERVER_UNAVAILABLE);
1862
1863 uint32 maxSize = 4096, size;
1864 char* buffer = new char[maxSize];
1865
1866 uint64 start = GetTimeNow();
1867 uint32 wait = 0;
1868
1869 bool isInvalid = false;
1870 HTTPReply* reply = NULL;
1871 while (true) {
1872 if (!con->receiveAvailable(buffer, size, maxSize, wait, true)) {
1873 delete [] buffer;
1875 }
1876 if (size > 0) {
1877 reply = new HTTPReply(con->getRemoteAddress());
1878 if (reply->processHeader(buffer, size, isInvalid))
1879 break;
1880 delete(reply);
1881 reply = NULL;
1882 if (isInvalid) {
1883 // ##############
1884 }
1885 }
1886 if (GetTimeAgeMS(start) < (int32)timeout) {
1887 delete [] buffer;
1889 }
1890 wait = 20;
1891 }
1892
1893 // Now we know how much to actually expect
1894 // First read the header proper and ignore
1895 if (!con->discard(reply->headerLength)) {
1896 delete [] buffer;
1897 delete(reply);
1899 }
1900
1901// printf("Receiving HTTPReply content (%u)...\n\n", reply->contentLength);
1902 // Then read the rest of the content
1903 if (reply->contentLength) {
1904 if (reply->contentLength > maxSize - reply->headerLength) {
1905 delete [] buffer;
1906 buffer = new char[reply->contentLength];
1907 // We have to read the rest, otherwise we will mess up the connection
1908 if (!con->receive(buffer, reply->contentLength, 3000)) {
1909 printf("Error receiving HTTPReply content (%u)...\n\n", reply->contentLength);
1910 delete [] buffer;
1911 delete(reply);
1913 }
1914
1915 if (!reply->processContent(buffer, reply->contentLength)) {
1916 delete [] buffer;
1917 delete(reply);
1919 }
1920 }
1921 else {
1922 // We have to read the rest, otherwise we will mess up the connection
1923 if (!con->receive(buffer, reply->contentLength, 3000)) {
1924 printf("Error receiving HTTPReply content (%u)...\n\n", reply->contentLength);
1925 delete [] buffer;
1926 delete(reply);
1928 }
1929
1930 if (!reply->processContent(buffer, reply->contentLength)) {
1931 delete [] buffer;
1932 delete(reply);
1934 }
1935 }
1936 }
1937
1938// printf("Got HTTPReply...\n\n");
1939 delete [] buffer;
1940 return reply;
1941}
1942
1944 if (!con || !req)
1945 return false;
1946 return con->send((char*)req->data, req->getSize());
1947}
1948
1950
1951 // ##########################
1952
1953 if (!con || !con->waitForDataToRead(timeout)) {
1954 return NULL;
1955 }
1956
1957 uint32 maxSize = 4096, size;
1958 char* buffer = new char[maxSize];
1959
1960 uint64 start = GetTimeNow();
1961 uint64 startReceive = 0;
1962 uint32 wait = 0;
1963
1964 bool isInvalid;
1965 HTTPRequest* req = NULL;
1966 while (true) {
1967 if (!con->receiveAvailable(buffer, size, maxSize, wait, true)) {
1968 delete [] buffer;
1969 return NULL;
1970 }
1971 if (size > 0) {
1972 LogPrint(0, LOG_NETWORK, 5, "Started receiving incoming HTTP request, size so far %u", size);
1973 if (!startReceive)
1974 startReceive = GetTimeNow();
1975 req = new HTTPRequest(con->getRemoteAddress(), startReceive);
1976 //utils::PrintBinary(buffer, size, false, "HTTP: ");
1977 if (req->processHeader(buffer, size, isInvalid)) {
1978 //printf("HTTP Done\n");
1979 break;
1980 }
1981 else if (isInvalid) {
1982 LogPrint(0, LOG_NETWORK, 2, "Received invalid incoming HTTP request, size %u", size);
1983 //utils::WriteAFile("d:\\http_invalid.bin", buffer, size, true);
1984 //DataMessage* test = new DataMessage(buffer);
1985 delete [] buffer;
1986 con->discard(size);
1987 return req;
1988 }
1989 else if (size == maxSize) {
1990 LogPrint(0, LOG_NETWORK, 2, "Received incoming HTTP request, buffer needs resizing %u", size);
1991 // maxSize isn't big enough to satisfy processHeader
1992 //printf("HTTP MaxSize, waiting for more...\n");
1993 //utils::PrintBinary(buffer, size, false, "HTTP MaxSize: ");
1994 //utils::WriteAFile("d:\\http_maxsize.bin", buffer, size, true);
1995 utils::Sleep(10);
1996 }
1997 else {
1998 LogPrint(0, LOG_NETWORK, 5, "Received incoming HTTP request, got %u, waiting for more...", size);
1999 //utils::PrintBinary(buffer, size, false, "HTTP WaitMore: ");
2000 //printf("HTTP waiting for more...\n");
2001 //utils::WriteAFile("d:\\http_notenough.bin", buffer, size, true);
2002 utils::Sleep(10);
2003 }
2004 delete(req);
2005 req = NULL;
2006 }
2007 if (GetTimeAgeMS(start) < (int32)timeout) {
2008 delete [] buffer;
2009 return NULL;
2010 }
2011 wait = 20;
2012 }
2013
2014 // Now we know how much to actually expect
2015 // First read the header proper and ignore
2016 if (!con->discard(req->headerLength)) {
2017 delete [] buffer;
2018 delete(req);
2019 return NULL;
2020 }
2021 // Then read the rest of the content
2022 if (req->contentLength) {
2023 if (req->contentLength > maxSize) {
2024 delete [] buffer;
2025 buffer = new char[req->contentLength];
2026 }
2027 // We have to read the rest, otherwise we will mess up the connection
2028 if (!con->receive(buffer, req->contentLength, 30000)) {
2029 LogPrint(0, LOG_NETWORK, 2, "Received HTTP header of %u, couldn't read the content of size %u", req->headerLength, req->contentLength);
2030 delete [] buffer;
2031 delete(req);
2032 return NULL;
2033 }
2034
2035 if (!req->processContent(buffer, req->contentLength)) {
2036 LogPrint(0, LOG_NETWORK, 2, "Received HTTP header of %u, content of size %u, failed processing the content buffer", req->headerLength, req->contentLength);
2037 delete [] buffer;
2038 delete(req);
2039 return NULL;
2040 }
2041 }
2042
2043 delete [] buffer;
2044 return req;
2045}
2046
2047
2048
2050
2051 if (!con || !con->waitForDataToRead(timeout)) {
2052 return NULL;
2053 }
2054
2055 uint32 maxSize = 4096, size;
2056 char* buffer = new char[maxSize];
2057
2058 uint64 start = GetTimeNow();
2059 uint64 startReceive = 0;
2060 uint32 wait = 0;
2061 uint64 remoteAddress;
2062 bool isInvalid;
2063 WebsocketData* wsData = NULL;
2064 while (!wsData || !wsData->isComplete()) {
2065 while (true) {
2066 if (!wsData && !con->receiveAvailable(buffer, size, maxSize, wait, true)) {
2067 delete[] buffer;
2068 return NULL;
2069 }
2070 else
2071 con->receiveAvailable(buffer, size, maxSize, wait, true);
2072 if (size > 0) {
2073 //utils::WriteAFile("d:/c/ws.bin", buffer, size, true);
2074 if (!wsData) {
2075 if (!startReceive)
2076 startReceive = GetTimeNow();
2077 wsData = new WebsocketData(con->getRemoteAddress(), startReceive);
2078 //utils::PrintBinary(buffer, size, false, "HTTP: ");
2079 }
2080
2081 if (wsData->processHeader(buffer, size, isInvalid)) {
2082 //printf("HTTP Done\n");
2083 break;
2084 }
2085 else if (isInvalid) {
2086 remoteAddress = con->getRemoteAddress();
2087 LogPrint(0, LOG_NETWORK, 1, "Invalid data received on Websocket from %u.%u.%u.%u", GETIPADDRESSQUAD(remoteAddress));
2088 //utils::WriteAFile("d:\\http_invalid.bin", buffer, size, true);
2089 //DataMessage* test = new DataMessage(buffer);
2090 delete[] buffer;
2091 con->discard(size);
2092 return wsData;
2093 }
2094 else if (size == maxSize) {
2095 // maxSize isn't big enough to satisfy processHeader
2096 //printf("HTTP MaxSize, waiting for more...\n");
2097 //utils::PrintBinary(buffer, size, false, "HTTP MaxSize: ");
2098 //utils::WriteAFile("d:\\http_maxsize.bin", buffer, size, true);
2099 utils::Sleep(10);
2100 }
2101 else {
2102 //utils::PrintBinary(buffer, size, false, "HTTP WaitMore: ");
2103 //printf("HTTP waiting for more...\n");
2104 //utils::WriteAFile("d:\\http_notenough.bin", buffer, size, true);
2105 utils::Sleep(10);
2106 }
2107 delete(wsData);
2108 wsData = NULL;
2109 }
2110 if (!wsData && (GetTimeAgeMS(start) < (int32)timeout)) {
2111 remoteAddress = con->getRemoteAddress();
2112 LogPrint(0, LOG_NETWORK, 1, "Timeout receiving data on Websocket from %u.%u.%u.%u", GETIPADDRESSQUAD(remoteAddress));
2113 delete[] buffer;
2114 return NULL;
2115 }
2116 wait = 20;
2117 }
2118
2119 // Now we know how much to actually expect
2120 // First read the header proper and ignore
2121 if (!con->discard(wsData->headerLength)) {
2122 delete[] buffer;
2123 delete(wsData);
2124 return NULL;
2125 }
2126 size -= wsData->headerLength;
2127 // Then read the rest of the content
2128 if (wsData->payloadSize) {
2129 if (wsData->payloadSize > maxSize) {
2130 delete[] buffer;
2131 buffer = new char[(uint32)(wsData->payloadSize)];
2132 }
2133 // We have to read the rest, otherwise we will mess up the connection
2134 if (!con->receive(buffer, (uint32)wsData->payloadSize, 30000)) {
2135 remoteAddress = con->getRemoteAddress();
2136 LogPrint(0, LOG_NETWORK, 1, "Unable to receive payload data on Websocket from %u.%u.%u.%u", GETIPADDRESSQUAD(remoteAddress));
2137 delete[] buffer;
2138 delete(wsData);
2139 return NULL;
2140 }
2141
2142 if (!wsData->processContent(buffer, wsData->payloadSize)) {
2143 remoteAddress = con->getRemoteAddress();
2144 LogPrint(0, LOG_NETWORK, 1, "Unable to process payload data on Websocket from %u.%u.%u.%u", GETIPADDRESSQUAD(remoteAddress));
2145 delete[] buffer;
2146 delete(wsData);
2147 return NULL;
2148 }
2149 size -= (uint32)(wsData->payloadSize);
2150 }
2151 if (wsData->isComplete()) {
2152 break;
2153 }
2154 }
2155
2156 delete[] buffer;
2157 return wsData;
2158}
2159
2161 if (!con || !wsData)
2162 return false;
2163 uint64 size = 0;
2164 const char* data = wsData->getRawData(size);
2165 if (!data || !size)
2166 return false;
2167 //return con->send((char*)reply->data, reply->getSize());
2168 if (con->send(data, (uint32)size)) {
2169 // printf("--- HTTP sent reply (%u)...\n", reply->contentLength);
2170 return true;
2171 }
2172 else {
2173 // printf("--- HTTP failed to send reply (%u)...\n", reply->contentLength);
2174 return false;
2175 }
2176
2177}
2178
2179
2180
2181
2182
2184// HTTP Client Protocol
2186
2187bool HTTPClientProtocol::CheckBufferForCompatibility(const char* buffer, uint32 length) {
2188 return false;
2189}
2190
2194
2196 return false;
2197}
2198
2200 return NULL;
2201}
2202
2204 return false;
2205}
2206
2208 return NULL;
2209}
2210
2211
2212
2214// Message Protocol
2216
2217bool MessageProtocol::CheckBufferForCompatibility(const char* buffer, uint32 length) {
2218 if (length < 2*sizeof(uint32))
2219 return false;
2220 return (GetObjID(buffer) == DATAMESSAGEID);
2221}
2222
2226
2228 if (!con || !msg)
2229 return false;
2230// uint64 addr = con->getRemoteAddress();
2231// LogPrint(0,0,0,"Protocol sending msg %llu to %u.%u.%u.%u:%u", msg->getType(), GETIPADDRESSQUAD(addr), GETIPPORT(addr));
2232// LogPrint(0,0,0,"Protocol sending msg: %u", msg->getType()[15]);
2233 return con->send((char*)msg->data, msg->getSize(), receiver);
2234}
2235
2237// uint64 t = GetTimeNow();
2238 if (!con) {
2239// if (!con || !con->waitForDataToRead(timeout)) {
2240// printf("Failed ReceiveMessage: %ld\n", GetTimeAgeMS(t));
2241 return NULL;
2242 }
2243
2244 // Peek just the header (size + object id) into a small stack buffer so the
2245 // body allocation can be sized exactly once. The previous code malloc'd a 1KB
2246 // scratch buffer for every message and then freed and re-malloc'd it for any
2247 // message bigger than 1KB - two allocations plus a free per large message.
2248 char header[2*sizeof(uint32)];
2249 if (!con->receive(header, sizeof(header), timeout, true))
2250 return NULL;
2251 if (GetObjID(header) != DATAMESSAGEID)
2252 return NULL;
2253 uint32 size = *(uint32*)header;
2254 if (size < sizeof(header))
2255 return NULL; // implausible size for a DataMessage header
2256
2257 char* buffer = (char*) malloc(size);
2258 if (!buffer)
2259 return NULL;
2260 if (!con->receive(buffer, size, timeout)) {
2261 free(buffer);
2262 return NULL;
2263 }
2264 // DataMessage adopts the buffer (copy=false) and frees it on destruction.
2265 DataMessage* msg = new DataMessage(buffer);
2266// uint64 addr = con->getRemoteAddress();
2267// LogPrint(0,0,0,"Protocol received msg %llu from %u.%u.%u.%u:%u", msg->getType(), GETIPADDRESSQUAD(addr), GETIPPORT(addr));
2268 //printf("Protocol received msg: %u.%u.%u (%p)...\n", msg->getType()[0], msg->getType()[1], msg->getType()[2], msg);
2269 return msg;
2270}
2271
2272
2274// Telnet Protocol
2276
2277bool TelnetProtocol::CheckBufferForCompatibility(const char* buffer, uint32 length) {
2278 if (length < 1)
2279 return false;
2280 return ((buffer[0] == 13) || (buffer[0] == 10));
2281}
2282
2286
2288 if (!con || !line)
2289 return false;
2290 return con->send((char*)line->data, line->getSize());
2291}
2292
2294 if (!con || !con->waitForDataToRead(timeout))
2295 return NULL;
2296
2297 uint32 maxSize = 1024, size;
2298 char* buffer = new char[maxSize];
2299 TelnetLine* line = NULL;
2300
2301 uint64 start = GetTimeNow();
2302 uint32 wait = 0, n;
2303
2304 while (GetTimeAgeMS(start) < (int32)timeout) {
2305 if (!con->receiveAvailable(buffer, size, maxSize, wait, true)) {
2306 delete [] buffer;
2307 return NULL;
2308 }
2309 for ( n=0; n<size; n++) {
2310 if (buffer[n] == 13) {
2311 line = new TelnetLine(con->getRemoteAddress());
2312 line->setCR(TELNET_WINDOWS);
2313 line->setLine(buffer, n);
2314 if ( (n < size-1) && (buffer[n+1] == 10) )
2315 n++;
2316 con->discard(n+1);
2317 delete [] buffer;
2318 // printf("\n");
2319 return line;
2320 }
2321 else if (buffer[n] == 10) {
2322 line = new TelnetLine(con->getRemoteAddress());
2323 line->setCR(TELNET_UNIX);
2324 line->setLine(buffer, n);
2325 if ( (n < size-1) && (buffer[n+1] == 13) )
2326 n++;
2327 con->discard(n+1);
2328 delete [] buffer;
2329 // printf("\n");
2330 return line;
2331 }
2332 //else
2333 // printf("[%c]", buffer[n]);
2334 }
2335 if (size == maxSize) {
2336 delete [] buffer;
2337 maxSize *= 4;
2338 buffer = new char[maxSize];
2339 wait = 0;
2340 }
2341 else
2342 wait = 20;
2343 }
2344 delete [] buffer;
2345 return NULL;
2346}
2347
2349 if (!con || !con->waitForDataToRead(timeout))
2350 return NULL;
2351
2352 char* buffer = new char[size];
2353
2354 if (!con->receive(buffer, size, timeout)) {
2355 delete [] buffer;
2356 return NULL;
2357 }
2358
2359 TelnetLine* reply = new TelnetLine(con->getRemoteAddress());
2360 reply->data = buffer;
2361 reply->size = size;
2362 return reply;
2363}
2364
2365
2366
2367
2368
2369
2371 jmData = NULL;
2372 jmSize = mOffset = 0;
2373}
2374
2375JSONM::JSONM(const char* data, uint64 size) {
2376 jmSize = size;
2377 if (jmSize) {
2378 jmData = new char[(uint32)jmSize];
2379 memcpy(jmData, data, (uint32)jmSize);
2380 mOffset = strlen(data) + 1;
2381 extractMultipartInfo();
2382 }
2383 else
2384 jmData = NULL;
2385}
2386
2387JSONM::JSONM(const char* json) {
2388 jmData = NULL;
2389 jmSize = 0;
2390 if (json)
2391 setJSON(json);
2392 else
2393 setJSON("{}");
2394}
2395
2397 mutex.enter(1000);
2398 if (jmData)
2399 delete[] jmData;
2400 jmData = NULL;
2401 jmSize = 0;
2402 mOffset = 0;
2403}
2404
2406 if (!mutex.enter(1000)) return 0;
2407 uint64 s = jmSize;
2408 mutex.leave();
2409 return s;
2410}
2412 if (!mutex.enter(1000)) return 0;
2413 uint32 s = (uint32)entries.size();
2414 mutex.leave();
2415 return s;
2416}
2417
2419 if (!jmData || !jmSize)
2420 return false;
2421 if (!strchr(jmData, '{') || !utils::laststrstr(jmData, "}"))
2422 return false;
2423 // Check that the sizes add up to the correct full size
2424 uint64 jsonLength = strlen(jmData);
2425 uint64 multipartLength = 0;
2426 std::map<uint32, JSONMEntry>::iterator i = entries.begin(), e = entries.end();
2427 while (i != e) {
2428 multipartLength += i->second.size;
2429 i++;
2430 }
2431 if (jsonLength + 1 + multipartLength != jmSize) {
2432 LogPrint(0, LOG_NETWORK, 0, "Invalid JSONM structure: JSON(%llu) + 1 + multiparts(%llu) != %llu", jsonLength, multipartLength, jmSize);
2433 return false;
2434 }
2435 return true;
2436}
2437
2438std::string JSONM::getJSON() {
2439 if (!jmData || !jmSize)
2440 return "";
2441 if (!strchr(jmData, '{') || !utils::laststrstr(jmData, "}"))
2442 return "";
2443 if (strlen(jmData) > jmSize)
2444 return std::string(jmData, jmSize);
2445 return jmData;
2446}
2447
2448const char* JSONM::getData(const char* name, uint64& size) {
2449 if (!mutex.enter(1000)) return NULL;
2450 size = 0;
2451 if (!name || !jmData || !jmSize) {
2452 mutex.leave();
2453 return NULL;
2454 }
2455 std::map<uint32, JSONMEntry>::iterator i = entries.begin(), e = entries.end();
2456 while (i != e) {
2457 if (stricmp(i->second.name.c_str(), name) == 0) {
2458 if (mOffset + i->second.offset + i->second.size > jmSize) {
2459 LogPrint(0, LOG_NETWORK, 0, "Invalid JSONM structure: Entry '%s' offset %llu size %llu exceeds the JSONM size %llu",
2460 name, i->second.offset, i->second.size, jmSize);
2461 mutex.leave();
2462 return NULL;
2463 }
2464 size = i->second.size;
2465 mutex.leave();
2466 return jmData + mOffset + i->second.offset;
2467 }
2468 i++;
2469 }
2470 mutex.leave();
2471 return NULL;
2472}
2473
2474std::string JSONM::getDataType(const char* name) {
2475 if (!mutex.enter(1000)) return "";
2476 if (!name || !jmData || !jmSize) {
2477 mutex.leave();
2478 return NULL;
2479 }
2480 std::map<uint32, JSONMEntry>::iterator i = entries.begin(), e = entries.end();
2481 while (i != e) {
2482 if (stricmp(i->second.name.c_str(), name) == 0) {
2483 mutex.leave();
2484 return i->second.type;
2485 }
2486 i++;
2487 }
2488 mutex.leave();
2489 return "";
2490}
2491
2492std::string JSONM::getDataType(uint32 chunk) {
2493 if (!mutex.enter(1000)) return NULL;
2494 if (!jmData || !jmSize || !chunk)
2495 return "";
2496
2497 std::map<uint32, JSONMEntry>::iterator i = entries.find(chunk);
2498 if (i == entries.end()) {
2499 mutex.leave();
2500 return "";
2501 }
2502
2503 mutex.leave();
2504 return i->second.type;
2505}
2506
2507std::string JSONM::getDataName(uint32 chunk) {
2508 if (!mutex.enter(1000)) return NULL;
2509 if (!jmData || !jmSize || !chunk) {
2510 mutex.leave();
2511 return "";
2512 }
2513
2514 std::map<uint32, JSONMEntry>::iterator i = entries.find(chunk);
2515 if (i == entries.end()) {
2516 mutex.leave();
2517 return "";
2518 }
2519
2520 mutex.leave();
2521 return i->second.name;
2522}
2523
2524
2525
2526const char* JSONM::getData(uint32 chunk, uint64& size) {
2527 if (!mutex.enter(1000)) return NULL;
2528 size = 0;
2529 if (!jmData || !jmSize) {
2530 mutex.leave();
2531 return NULL;
2532 }
2533
2534 std::map<uint32, JSONMEntry>::iterator i = entries.find(chunk);
2535 if (i == entries.end()) {
2536 mutex.leave();
2537 return NULL;
2538 }
2539
2540 if (mOffset + i->second.offset + i->second.size > jmSize) {
2541 LogPrint(0, LOG_NETWORK, 0, "Invalid JSONM structure: Entry %u '%s' offset %llu size %llu exceeds the JSONM size %llu",
2542 chunk, i->second.name.c_str(), i->second.offset, i->second.size, jmSize);
2543 mutex.leave();
2544 return NULL;
2545 }
2546
2547 size = i->second.size;
2548 mutex.leave();
2549 return jmData + mOffset + i->second.offset;
2550}
2551
2552bool JSONM::setRawJSON(const char* json) {
2553 if (!json)
2554 return false;
2555 if (!jmData) {
2556 jmSize = mOffset = strlen(json) + 1;
2557 jmData = new char[(uint32)jmSize];
2558 utils::strcpyavail(jmData, json, (uint32)jmSize, true);
2559 return true;
2560 }
2561 // else we have JSON and possibly data already
2562 uint64 newMOffset = strlen(json) + 1;
2563 uint64 newSize = newMOffset + (jmSize - mOffset);
2564 char* newData = new char[(uint32)newSize];
2565 utils::strcpyavail(newData, json, (uint32)newMOffset, true);
2566 // copy in old data
2567 memcpy(newData + (uint32)newMOffset, jmData + (uint32)mOffset, (uint32)(jmSize - mOffset));
2568 delete[] jmData;
2569 jmData = newData;
2570 mOffset = newMOffset;
2571 jmSize = newSize;
2572 return true;
2573}
2574
2575bool JSONM::setJSON(const char* json) {
2576 if (!json)
2577 return false;
2578 if (!mutex.enter(1000)) return false;
2579 if (setRawJSON(json))
2580 updateMultipartJSON();
2581 mutex.leave();
2582 return true;
2583}
2584
2585uint32 JSONM::addData(const char* name, const char* data, uint64 size, const char* type) {
2586 if (!mutex.enter(1000)) return 0;
2587 // check for name in use
2588 uint64 s;
2589 if (getData(name, s)) {
2590 mutex.leave();
2591 return 0;
2592 }
2593 if (!jmData)
2594 setJSON("{}");
2595 JSONMEntry entry;
2596 entry.chunk = getCount() + 1;
2597 entry.name = name;
2598 entry.size = size;
2599 entry.type = type;
2600 entry.offset = jmSize - this->mOffset;
2601 uint64 newSize = jmSize + size;
2602 char* newData = new char[(uint32)newSize];
2603 memcpy(newData, jmData, (uint32)jmSize);
2604 memcpy(newData + (uint32)jmSize, data, (uint32)size);
2605 entries[entry.chunk] = entry;
2606 delete[] jmData;
2607 jmData = newData;
2608 jmSize = newSize;
2609 updateMultipartJSON();
2610 mutex.leave();
2611 return entry.chunk;
2612}
2613
2615 if (!mutex.enter(1000)) return "";
2616 if (!jmData || !jmSize) {
2617 mutex.leave();
2618 return "";
2619 }
2620 std::string info = utils::StringFormat("JSON length: %u\nTotal size: %s\nChunks: %u total: %s\n%s\n",
2621 mOffset - 1,
2622 utils::BytifySize((uint32)jmSize).c_str(),
2623 getCount(),
2624 utils::BytifySize((uint32)(jmSize - mOffset)).c_str(),
2625 jmData);
2626 std::map<uint32, JSONMEntry>::iterator i = entries.begin(), e = entries.end();
2627 while (i != e) {
2628 info += i->second.getInfoString();
2629 i++;
2630 }
2631 mutex.leave();
2632 return info;
2633}
2634
2635
2636
2637
2638bool JSONM::extractMultipartInfo() {
2639 // Assume multipart info is in JSON
2640 // read and create entries from this
2641 // also assume mutex is already locked
2642
2643 if (!jmData || !jmSize || (jmSize - mOffset < 10))
2644 return false;
2645
2646 jsmn_parser parser;
2647 jsmntok_t* tokens = new jsmntok_t[1024];
2648 int tokenCount = 0;
2649 //const char* val;
2650 //int valSize;
2651 jsmn_init(&parser);
2652 if ((tokenCount = jsmn_parse(&parser, jmData, strlen(jmData), tokens, 1024)) <= 0) {
2653 // No JSON found
2654 delete[] tokens;
2655 return false;
2656 }
2657
2658 JSONMEntry entry;
2659 //int token;
2660 std::vector<int> jsonArrayIdx;
2661 std::vector<int>::iterator i, e;
2662 jsonArrayIdx = GetJSONChildArrayIndexes(tokens, tokenCount, jmData, "_multipart_", 0, JSMN_UNDEFINED);
2663 if (!jsonArrayIdx.size()) {
2664 // No info found
2665 delete[] tokens;
2666 return false;
2667 }
2668 i = jsonArrayIdx.begin();
2669 e = jsonArrayIdx.end();
2670
2671 uint64 offset = 0;
2672 while (i != e) {
2673 if ((entry.name = GetJSONChildValueString(tokens, tokenCount, jmData, "name", *i)).length()) {
2674 entry.type = GetJSONChildValueString(tokens, tokenCount, jmData, "mimetype", *i);
2675 entry.size = GetJSONChildValueUint64(tokens, tokenCount, jmData, "bytesize", *i);
2676 entry.chunk = (uint32)entries.size() + 1;
2677 entry.offset = offset;
2678 entries[entry.chunk] = entry;
2679 offset += entry.size;
2680 }
2681 i++;
2682 }
2683 return true;
2684}
2685
2686bool JSONM::updateMultipartJSON() {
2687 // Assume entries and binary chunks updated
2688 // create new JSON entry and replace old one, if present
2689 // also assume mutex is already locked
2690
2691 std::string newJSON;
2692
2693 std::map<uint32, JSONMEntry>::iterator i = entries.begin(), e = entries.end();
2694 while (i != e) {
2695 if (newJSON.length())
2696 newJSON += ",";
2697 newJSON += i->second.toJSON();
2698 i++;
2699 }
2700
2701 jsmn_parser parser;
2702 jsmntok_t* tokens = new jsmntok_t[1024];
2703 int tokenCount = 0;
2704 //const char* val;
2705 //int valSize;
2706 jsmn_init(&parser);
2707 if ((tokenCount = jsmn_parse(&parser, jmData, strlen(jmData), tokens, 1024)) <= 0) {
2708 // No JSON found
2709 delete[] tokens;
2710 return false;
2711 }
2712
2713 std::string fullJSON = jmData;
2714 JSONMEntry entry;
2715 int token = GetJSONToken(tokens, tokenCount, jmData, "_multipart_", 0);
2716 if (token > 0) {
2717 uint32 begin = tokens[token].start;
2718 uint32 end = tokens[token].end;
2719 //uint32 end = tokens[tokens[tokens[token].parent].parent].end;
2720 fullJSON = utils::StringFormat("%s[%s]%s",
2721 fullJSON.substr(0, begin).c_str(),
2722 newJSON.c_str(),
2723 fullJSON.substr(end).c_str()
2724 );
2725 }
2726 else {
2727 // just add at the end
2728 const char* lastBracket = utils::laststrstr(jmData, "}");
2729 if (!lastBracket) {
2730 // Broken JSON found
2731 delete[] tokens;
2732 return false;
2733 }
2734 fullJSON = utils::StringFormat("%s,\"_multipart_\": [%s]}", fullJSON.substr(0, lastBracket-jmData).c_str(), newJSON.c_str());
2735 }
2736 this->setRawJSON(fullJSON.c_str());
2737 return true;
2738}
2739
2741 DataMessage* msg = new DataMessage();
2742 uint64 now = GetTimeNow();
2743
2744 msg->setString("POST", this->getJSON().c_str());
2745 msg->setString("POST_CONTENT_TYPE_", "application/json");
2746
2747 std::map<uint32, JSONMEntry>::iterator i = entries.begin(), e = entries.end();
2748 while (i != e) {
2749 msg->setData(i->second.name.c_str(), jmData + mOffset + i->second.offset, (uint32)i->second.size);
2750 msg->setString((i->second.name + "_CONTENT_TYPE_").c_str(), i->second.type.c_str());
2751 i++;
2752 }
2753 msg->setCreatedTime(now);
2754 msg->setRecvTime(now);
2755 return msg;
2756}
2757
2758
2759// ################# Unit Test #################
2760
2761// Encode a WebsocketData frame, then decode it back through the
2762// processHeader/processContent path and verify the recovered payload matches.
2763// All in-memory, no sockets. Returns true on success.
2764static bool TestWebsocketRoundTrip(WebsocketData::DataType type, bool maskData,
2765 const char* payload, uint64 payloadSize) {
2766
2767 WebsocketData enc;
2768 if (!enc.setData(type, maskData, payload, payloadSize)) {
2769 unittest::fail("WebsocketData::setData returned false (type %d, mask %d, size %llu)",
2770 (int)type, (int)maskData, payloadSize);
2771 return false;
2772 }
2773 if (!enc.isComplete()) {
2774 unittest::fail("encoded WebsocketData is not COMPLETE");
2775 return false;
2776 }
2777
2778 uint64 rawSize = 0;
2779 const char* raw = enc.getRawData(rawSize);
2780 if (!raw || !rawSize) {
2781 unittest::fail("getRawData returned no bytes");
2782 return false;
2783 }
2784 if (rawSize != enc.headerLength + payloadSize) {
2785 unittest::fail("raw size %llu != header %u + payload %llu",
2786 rawSize, enc.headerLength, payloadSize);
2787 return false;
2788 }
2789
2790 // First byte must carry FIN + opcode; opcode must equal the data type.
2791 uint8 b0 = *(const unsigned char*)raw;
2792 if (!(b0 & WebsocketData::BHB0_FIN)) {
2793 unittest::fail("encoded frame missing FIN bit");
2794 return false;
2795 }
2796 if ((b0 & WebsocketData::BHB0_OPCODE) != (uint8)type) {
2797 unittest::fail("encoded opcode %u != type %u", (uint8)(b0 & WebsocketData::BHB0_OPCODE), (uint8)type);
2798 return false;
2799 }
2800
2801 // Decode: split the raw bytes into header + payload, exactly as the socket
2802 // reader would, but feeding from the in-memory buffer.
2803 WebsocketData dec;
2804 bool isInvalid = false;
2805 if (!dec.processHeader(raw, rawSize, isInvalid) || isInvalid) {
2806 unittest::fail("processHeader failed (invalid=%d)", (int)isInvalid);
2807 return false;
2808 }
2809 if (dec.dataType != type) {
2810 unittest::fail("decoded dataType %d != %d", (int)dec.dataType, (int)type);
2811 return false;
2812 }
2813 if (dec.getPayloadSize() != payloadSize) {
2814 unittest::fail("decoded payloadSize %llu != %llu", dec.getPayloadSize(), payloadSize);
2815 return false;
2816 }
2817
2818 if (payloadSize) {
2819 if (!dec.processContent(raw + dec.headerLength, payloadSize)) {
2820 unittest::fail("processContent failed");
2821 return false;
2822 }
2823 uint64 outSize = 0;
2824 const char* out = dec.getContent(outSize);
2825 if (!out || (outSize != payloadSize)) {
2826 unittest::fail("getContent size %llu != %llu", outSize, payloadSize);
2827 return false;
2828 }
2829 if (memcmp(out, payload, (size_t)payloadSize) != 0) {
2830 unittest::fail("decoded payload bytes differ from original");
2831 return false;
2832 }
2833 }
2834 return true;
2835}
2836
2838
2839 // ---- Phase 1: WebsocketData encode/decode round-trips ----
2840 unittest::progress(5, "websocket frames");
2841
2842 const char* shortText = "hello websocket";
2843 if (!TestWebsocketRoundTrip(WebsocketData::TEXT, false, shortText, strlen(shortText)))
2844 return false;
2845 unittest::detail("websocket short unmasked TEXT round-trip ok (%zu bytes)", strlen(shortText));
2846
2847 // Masked frame: exercises the client->server masking path.
2848 if (!TestWebsocketRoundTrip(WebsocketData::TEXT, true, shortText, strlen(shortText)))
2849 return false;
2850 unittest::detail("websocket short masked TEXT round-trip ok");
2851
2852 if (!TestWebsocketRoundTrip(WebsocketData::BINARY, false, shortText, strlen(shortText)))
2853 return false;
2854
2855 unittest::progress(25, "websocket extended frames");
2856
2857 // Extended (16-bit length) payload: > 125 bytes forces the 2-byte length field.
2858 std::string big;
2859 big.reserve(4096);
2860 for (uint32 i = 0; i < 4000; i++)
2861 big.push_back((char)('A' + (i % 26)));
2862 if (!TestWebsocketRoundTrip(WebsocketData::BINARY, false, big.data(), big.size()))
2863 return false;
2864 if (!TestWebsocketRoundTrip(WebsocketData::BINARY, true, big.data(), big.size()))
2865 return false;
2866 unittest::detail("websocket extended (16-bit length) masked+unmasked round-trip ok (%zu bytes)", big.size());
2867
2868 // ---- Phase 2: HTTP request build/parse round-trip ----
2869 unittest::progress(50, "http request");
2870
2871 const char* body = "field1=value1&field2=value2";
2872 uint32 bodyLen = (uint32)strlen(body);
2873
2874 // Put query parameters in the URI so processHeader -> parseURIParameters
2875 // populates params; the urlencoded body is carried verbatim and checked
2876 // byte-for-byte below.
2877 const char* fullURI = "/path/script.cgi?home=Cosby&favorite=flies";
2878
2879 HTTPRequest reqEnc;
2880 if (!reqEnc.createRequest(HTTP_POST, "example.com", fullURI,
2881 body, bodyLen, true, 0)) {
2882 unittest::fail("HTTPRequest::createRequest returned false");
2883 return false;
2884 }
2885
2886 uint32 rawSize = 0;
2887 const char* raw = reqEnc.getRawContent(rawSize);
2888 if (!raw || !rawSize) {
2889 unittest::fail("HTTPRequest::getRawContent returned no bytes");
2890 return false;
2891 }
2892
2893 // Parse it back through a fresh request, feeding header then content
2894 // from the in-memory buffer (header ends at reqEnc.headerLength).
2895 HTTPRequest reqDec;
2896 bool isInvalid = false;
2897 if (!reqDec.processHeader(raw, rawSize, isInvalid) || isInvalid) {
2898 unittest::fail("HTTPRequest::processHeader failed (invalid=%d)", (int)isInvalid);
2899 return false;
2900 }
2901 if (reqDec.type != HTTP_POST) {
2902 unittest::fail("parsed method %u != HTTP_POST", reqDec.type);
2903 return false;
2904 }
2905 const char* uri = reqDec.getURI();
2906 if (!uri || strcmp(uri, fullURI) != 0) {
2907 unittest::fail("parsed URI '%s' != '%s'", uri ? uri : "(null)", fullURI);
2908 return false;
2909 }
2910 const char* request = reqDec.getRequest();
2911 if (!request || strcmp(request, "path/script.cgi") != 0) {
2912 unittest::fail("parsed request '%s' != 'path/script.cgi'", request ? request : "(null)");
2913 return false;
2914 }
2915 if (reqDec.contentLength != bodyLen) {
2916 unittest::fail("parsed Content-Length %u != %u", reqDec.contentLength, bodyLen);
2917 return false;
2918 }
2919
2920 unittest::progress(70, "http content");
2921
2922 // Feed the body (located right after the header in the same buffer).
2923 if (!reqDec.processContent(raw + reqDec.headerLength, bodyLen)) {
2924 unittest::fail("HTTPRequest::processContent failed");
2925 return false;
2926 }
2927 uint32 outSize = 0;
2928 const char* outBody = reqDec.getContent(outSize);
2929 if (!outBody || (outSize != bodyLen) || (memcmp(outBody, body, bodyLen) != 0)) {
2930 unittest::fail("parsed body differs from original (size %u != %u)", outSize, bodyLen);
2931 return false;
2932 }
2933 // query-string params (from the URI) should have been parsed.
2934 const char* p = reqDec.getParameter("home");
2935 if (!p || strcmp(p, "Cosby") != 0) {
2936 unittest::fail("parsed URI param home='%s' != 'Cosby'", p ? p : "(null)");
2937 return false;
2938 }
2939 unittest::detail("http POST build/parse round-trip ok (body %u bytes)", bodyLen);
2940
2941 // ---- Phase 3: parse throughput metric ----
2942 unittest::progress(85, "throughput");
2943
2944 const uint32 iters = 2000;
2945 uint64 t0 = GetTimeNow();
2946 for (uint32 i = 0; i < iters; i++) {
2947 HTTPRequest r;
2948 bool inv = false;
2949 if (!r.processHeader(raw, reqEnc.headerLength, inv) || inv) {
2950 unittest::fail("throughput loop processHeader failed at iter %u", i);
2951 return false;
2952 }
2953 }
2954 double us = (double)(GetTimeNow() - t0);
2955 if (us > 0.0)
2956 unittest::metric("http_header_parse_throughput", (double)iters / us * 1e6, "ops/s", true);
2957
2958 unittest::progress(100, "done");
2959 return true;
2960}
2961
2964 "networkprotocols", NetworkProtocols_UnitTest,
2965 "HTTP request and WebSocket frame build/parse round-trips", "network");
2966}
2967
2968}
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 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.
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 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
#define GETIPADDRESSQUAD(a)
Definition Utils.h:1446
std::string ReadAFileString(std::string filename)
Read an entire file into a std::string.
Definition Utils.cpp:6797
const char * laststrstr(const char *str1, const char *str2)
Find the last occurrence of str2 in str1.
Definition Utils.cpp:7183
bool TextStartsWith(const char *str, const char *start, bool caseSensitive=true)
Test whether str starts with start.
Definition Utils.cpp:7240
bool Sleep(uint32 ms)
Suspend the calling thread.
Definition Utils.cpp:2802
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:6511
FileDetails GetFileDetails(const char *filename)
Stat a file.
Definition Utils.cpp:7364
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:6184
std::string TextTrimQuotes(const char *text)
Strip a single pair of surrounding quotes if present.
Definition Utils.cpp:6151
const char * stristr(const char *str, const char *substr, uint32 len=0)
Case-insensitive strstr.
Definition Utils.cpp:6035
std::string BytifySize(double val)
Format a byte count with binary units, e.g.
Definition Utils.cpp:7724
bool GetNextLineEnd(const char *str, uint32 size, uint32 &len, uint32 &crSize)
Find the end of the current line.
Definition Utils.cpp:6074
uint64 ntoh64(const uint64 *input)
Convert a 64-bit value from network to host byte order.
Definition Utils.cpp:2414
std::string StringFormat(const char *format,...)
printf into a std::string.
Definition Utils.cpp:6626
char * ReadAFile(const char *filename, uint32 &length, bool binary=false)
Read an entire file into a new buffer.
Definition Utils.cpp:6818
uint32 strcpyavail(char *dst, const char *src, uint32 maxlen, bool copyAvailable)
Bounded strcpy that always NUL-terminates.
Definition Utils.cpp:6056
int64 RandomInt(int64 from=0, int64 to=100)
Uniform random integer in [from,to].
Definition Utils.cpp:7715
@ 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.
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:1666
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