CMSDK 2.0.1
Cross-platform C++ base library and SDK for the Psyclone AIOS platform
Loading...
Searching...
No Matches
RESTAPI.cpp
Go to the documentation of this file.
1
8#include "RESTAPI.h"
9#include "UnitTestFramework.h"
10
11namespace cmlabs {
12
14 mainNode = XMLNode::emptyXMLNode;
15}
17 std::map<std::string, RESTHTTPCall*>::iterator i = httpCalls.begin(), e = httpCalls.end();
18 while (i != e) {
19 delete(i->second);
20 i++;
21 }
22 httpCalls.clear();
23}
24
25
26bool RESTParser::init(const char* restDefinition) {
27
28 lastErrorString.clear();
29 XMLResults xmlResults;
30 mainNode=XMLNode::parseString(restDefinition,"restapi", &xmlResults);
31
32 if (xmlResults.error != eXMLErrorNone) {
33 if (xmlResults.error == eXMLErrorFirstTagNotFound) {
34 lastErrorString = utils::StringFormat("REST API Definition format error, main tag <restapi> not found");
35 }
36 else {
37 lastErrorString = utils::StringFormat("REST API Definition parsing error at line %i, column %i:\n %s\n",
38 xmlResults.nLine,xmlResults.nColumn, XMLNode::getError(xmlResults.error));
39 }
40 mainNode = XMLNode::emptyXMLNode;
41 return false;
42 }
43 if(mainNode.isEmpty()) {
44 lastErrorString = utils::StringFormat("REST API Definition format error, main tag <restapi> empty");
45 mainNode = XMLNode::emptyXMLNode;
46 return false;
47 }
48
49 // read any defined objects
50 std::map<std::string, RESTParam> paramMap;
51 XMLNode node;
52 const char* name;
53 uint32 i, count = mainNode.nChildNode("paramset");
54 for (i = 0; i < count; i++) {
55 if (!(node = mainNode.getChildNode("paramset", i)).isEmpty()) {
56 if (!(name = node.getAttribute("name")) || !strlen(name))
57 continue;
58 paramMap = parseParamSet(node);
59 if (paramMap.size())
60 paramSets[name] = paramMap;
61 }
62 }
63
64 // read any defined HTTPCalls
65 RESTHTTPCall* httpCall;
66 count = mainNode.nChildNode("httpcall");
67 for (i = 0; i < count; i++) {
68 if (!(node = mainNode.getChildNode("httpcall", i)).isEmpty()) {
69 if (!(name = node.getAttribute("name")) || !strlen(name))
70 continue;
71 httpCall = new RESTHTTPCall();
72 if (!httpCall->init(node)) {
73 lastErrorString = utils::StringFormat("REST API Definition format error, <httpcall> tag name or url error: %s", name);
74 delete(httpCall);
75 break;
76 }
77 httpCalls[name] = httpCall;
78 }
79 }
80
81 return (lastErrorString.length() == 0);
82}
83
84
85std::map<std::string, RESTParam> RESTParser::parseParamSet(XMLNode objectNode) {
86 std::map<std::string, RESTParam> paramMap;
87 const char* optional, *type, *name, *objects, *allowEmpty;
88 RESTParam restParam;
89 XMLNode node;
90 uint32 i, count = objectNode.nChildNode("param");
91 for (i = 0; i < count; i++) {
92 if (!(node = objectNode.getChildNode("param", i)).isEmpty()) {
93 type = node.getAttribute("type");
94 name = node.getAttribute("name");
95 optional = node.getAttribute("optional");
96 allowEmpty = node.getAttribute("allowempty");
97 objects = node.getAttribute("objects");
98
99 if (!type || !name || !strlen(name)) {
100 lastErrorString = utils::StringFormat("REST API Definition format error, <param> tag name or type error: %s", name);
101 break;
102 }
103
104 if (strcmp(type, "integer") == 0) restParam.type = RESTParam::INTEGER;
105 else if (strcmp(type, "text") == 0) restParam.type = RESTParam::TEXT;
106 else if (strcmp(type, "float") == 0) restParam.type = RESTParam::FLOAT;
107 else if (strcmp(type, "double") == 0) restParam.type = RESTParam::FLOAT;
108 else if (strcmp(type, "array") == 0) restParam.type = RESTParam::ARRAY;
109 else if (strcmp(type, "image") == 0) restParam.type = RESTParam::IMAGE;
110 else if (strcmp(type, "video") == 0) restParam.type = RESTParam::VIDEO;
111 else if (strcmp(type, "binary") == 0) restParam.type = RESTParam::BINARY;
112 else {
113 lastErrorString = utils::StringFormat("REST API Definition format error, <param> type error: %s", type);
114 break;
115 }
116
117 restParam.optional = false | (optional && strcmp(optional, "yes") == 0);
118 restParam.allowEmpty = false | (allowEmpty && strcmp(allowEmpty, "yes") == 0);
119 if (objects && strlen(objects)) restParam.objects = objects; else restParam.objects.clear();
120 restParam.name = name;
121 paramMap[restParam.name] = restParam;
122 }
123 }
124 return paramMap;
125}
126
128 RESTRequest* restReq = new RESTRequest();
129 std::map<std::string, RESTParam> paramSet;
130 std::string signature;
131
132// XMLNode node = parseRequest(req->getRequest(), paramSet, restReq, signature);
133 XMLNode node = parseRequest(req->getURI(), paramSet, restReq, signature);
134 if (node.isEmpty())
135 return restReq;
136
137 const char* auth;
138 if (restReq->getRequireAuthentication()) {
139 if (!(auth = req->getHeaderEntry("Authorization")) || !strlen(auth)) {
140 restReq->addToErrorString(utils::StringFormat("No authentication provided, required for this operation").c_str());
142 return restReq;
143 }
144 restReq->setString("Authentication", auth);
145 }
146
147 const char* httpCallName = node.getAttribute("httpcall");
148 if (httpCallName && strlen(httpCallName)) {
149 std::map<std::string, RESTHTTPCall*>::iterator iHC = httpCalls.find(httpCallName);
150 if (iHC != httpCalls.end()) {
151 restReq->httpCall = iHC->second;
152 }
153 }
154
155 // Now the node is the one at the correct level
156 // and if an id we have the id (int or text) in the restReq already
157 const char* ops = node.getAttribute("ops");
158
159 if ( (req->type == HTTP_GET) && (utils::stristr(ops, "get")) ) {
160 // Dont expect any more parameters, we are fine now
161 signature = utils::StringFormat("GET%s", signature.c_str());
162 restReq->setRequestSignature(signature.c_str());
164 return restReq;
165 }
166 else if ( (req->type == HTTP_POST) && (utils::stristr(ops, "post")) ) {
167 // A post needs the parameters described in paramSet, check if they are there
168 if (!extractParameters(req, paramSet, restReq)) {
170 return restReq;
171 }
172 signature = utils::StringFormat("POST%s", signature.c_str());
173 restReq->setRequestSignature(signature.c_str());
175 return restReq;
176 }
177 else if ( (req->type == HTTP_PUT) && (utils::stristr(ops, "put")) ) {
178 // A put needs an ID (already there) and the parameters described in paramSet, check if they are there
179 if (!extractParameters(req, paramSet, restReq)) {
180 // If they are not all there, that's ok, only the updating ones need be there
181 //restReq->setStatus(RESTRequest::FAILED_PARAM);
182 //return restReq;
183 }
184 signature = utils::StringFormat("PUT%s", signature.c_str());
185 restReq->setRequestSignature(signature.c_str());
187 return restReq;
188 }
189 else if ( (req->type == HTTP_DELETE) && (utils::stristr(ops, "delete")) ) {
190 // Dont expect any more parameters, we are fine now
191 signature = utils::StringFormat("DELETE%s", signature.c_str());
192 restReq->setRequestSignature(signature.c_str());
194 return restReq;
195 }
196 else if (req->type == HTTP_OPTIONS) {
197 //restReq->addToErrorString(utils::StringFormat("Operation type %s not supported by the requested API", HTTP_Type[req->type]).c_str());
198 signature = ops;
200 return restReq;
201 }
202 else {
203 restReq->addToErrorString(utils::StringFormat("Operation type %s not supported by the requested API", HTTP_Type[req->type]).c_str());
205 return restReq;
206 }
207}
208
210 RESTRequest* restReq = new RESTRequest();
211 std::map<std::string, RESTParam> paramSet;
212 std::string signature;
213
214// XMLNode node = parseRequest(msg->getString("REQUEST"), paramSet, restReq, signature);
215 XMLNode node = parseRequest(msg->getString("URI"), paramSet, restReq, signature);
216 if (node.isEmpty())
217 return restReq;
218
219 const char* httpCallName = node.getAttribute("httpcall");
220 if (httpCallName && strlen(httpCallName)) {
221 std::map<std::string, RESTHTTPCall*>::iterator iHC = httpCalls.find(httpCallName);
222 if (iHC != httpCalls.end()) {
223 restReq->httpCall = iHC->second;
224 }
225 }
226
227 int64 httpOperation = 0;
228 msg->getInt("HTTP_OPERATION", httpOperation);
229
230 const char* auth;
231 if (restReq->getRequireAuthentication()) {
232 if (!(auth = msg->getString("Authorization")) || !strlen(auth)) {
233 restReq->addToErrorString(utils::StringFormat("No authentication provided, required for this operation").c_str());
235 return restReq;
236 }
237 restReq->setString("Authentication", auth);
238 }
239
240 // Now the node is the one at the correct level
241 // and if an id we have the id (int or text) in the restReq already
242 const char* ops = node.getAttribute("ops");
243
244 if ( (httpOperation == HTTP_GET) && (utils::stristr(ops, "get")) ) {
245 // Dont expect any more parameters, we are fine now
246 signature = utils::StringFormat("GET%s", signature.c_str());
247 restReq->setRequestSignature(signature.c_str());
249 return restReq;
250 }
251 else if ( (httpOperation == HTTP_POST) && (utils::stristr(ops, "post")) ) {
252 // A post needs the parameters described in paramSet, check if they are there
253 if (!extractParameters(msg, paramSet, restReq)) {
255 return restReq;
256 }
257 signature = utils::StringFormat("POST%s", signature.c_str());
258 restReq->setRequestSignature(signature.c_str());
260 return restReq;
261 }
262 else if ( (httpOperation == HTTP_PUT) && (utils::stristr(ops, "put")) ) {
263 // A put needs an ID (already there) and the parameters described in paramSet, check if they are there
264 if (!extractParameters(msg, paramSet, restReq)) {
265 // If they are not all there, that's ok, only the updating ones need be there
266 //restReq->setStatus(RESTRequest::FAILED_PARAM);
267 //return restReq;
268 }
269 signature = utils::StringFormat("PUT%s", signature.c_str());
270 restReq->setRequestSignature(signature.c_str());
272 return restReq;
273 }
274 else if ( (httpOperation == HTTP_DELETE) && (utils::stristr(ops, "delete")) ) {
275 // Dont expect any more parameters, we are fine now
276 signature = utils::StringFormat("DELETE%s", signature.c_str());
277 restReq->setRequestSignature(signature.c_str());
279 return restReq;
280 }
281 else if (httpOperation == HTTP_OPTIONS) {
282 //restReq->addToErrorString(utils::StringFormat("Operation type %s not supported by the requested API", HTTP_Type[req->type]).c_str());
283 restReq->setRequestSignature(ops);
285 return restReq;
286 }
287 else {
288 restReq->addToErrorString(utils::StringFormat("Operation type %s not supported by the requested API", HTTP_Type[httpOperation]).c_str());
290 return restReq;
291 }
292}
293
294
295
296
297
298XMLNode RESTParser::parseRequest(const char* requestString, std::map<std::string, RESTParam> &paramSet, RESTRequest* restReq, std::string &signature) {
299
300 XMLNode emptyNode = XMLNode::emptyNode();
301
302 if (mainNode.isEmpty()) {
303 restReq->addToErrorString(utils::StringFormat("API not initialised").c_str());
305 return emptyNode;
306 }
307
308 if (!requestString || !strlen(requestString)) {
309 restReq->addToErrorString(utils::StringFormat("Empty request provided").c_str());
311 return emptyNode;
312 }
313
314 std::string filterString;
315 std::string requestPart;
316 const char* filterDiv = strstr(requestString, "?");
317 if (filterDiv) {
318 filterString = filterDiv+1;
319 requestPart = utils::TextListSplit(requestString, "?", true, true).at(0);
320 }
321 else
322 requestPart = requestString;
323
324 std::vector<std::string> reqParts = utils::TextListSplit(requestPart.c_str(), "/", false, true);
325
326 XMLNode topNode = findTopNodeForRequest(reqParts.at(0).c_str());
327 if (topNode.isEmpty()) {
328 restReq->addToErrorString(utils::StringFormat("Requested API is not known: %s", reqParts.at(0).c_str()).c_str());
330 return emptyNode;
331 }
332
333 if (filterString.length()) {
334 std::multimap<std::string, std::string> paramMap = utils::TextMultiMapSplit(filterString.c_str(), "&", "=");
335 std::multimap<std::string, std::string>::iterator ip = paramMap.begin(), ep = paramMap.end();
336 while (ip != ep) {
337 //restReq->setFilter(ip->first.c_str(), ip->second.c_str());
338 restReq->setFilter(utils::TextTrimQuotes(html::DecodeHTML(ip->first).c_str()).c_str(), utils::TextTrimQuotes(html::DecodeHTML(ip->second).c_str()).c_str());
339 ip++;
340 }
341 }
342
343 const char* tag, *type, *name, *subName, *set, *auth, *subapi;
344 XMLNode node = topNode, subNode;
345 uint32 level = 0;
346 int subNodeCount = 0, i;
347
348 //signature.clear();
349
350 while (level < reqParts.size()) {
351 // first read any params available
352 if (!(name = node.getAttribute("name")) || !strlen(name)) {
353 restReq->addToErrorString(utils::StringFormat("API description error: Node name not provided").c_str());
355 return emptyNode;
356 }
357
358 if (set = node.getAttribute("paramset")) {
359 if (!strlen(set))
360 paramSet.clear();
361 else if (paramSets.find(set) != paramSets.end())
362 paramSet = paramSets[set];
363 else {
364 restReq->addToErrorString(utils::StringFormat("API description error: Paramset %s unknown", set).c_str());
366 return emptyNode;
367 }
368 }
369
370 if ((auth = node.getAttribute("auth")) && stricmp(auth, "yes") == 0)
371 restReq->setRequireAuthentication(true);
372
373 if (!(tag = node.getName()) || !strlen(tag)) {
374 restReq->addToErrorString(utils::StringFormat("API description error: Empty tag name").c_str());
376 return emptyNode;
377 }
378
379 if (strcmp(tag, "id") == 0) {
380 if (type = node.getAttribute("type")) {
381 if (strcmp(type, "integer") == 0) {
382 restReq->setInt(name, utils::Ascii2Int64(reqParts.at(level).c_str()));
383 signature = utils::StringFormat("%s_IDINTEGER", signature.c_str());
384 }
385 else if (strcmp(type, "text") == 0) {
386 restReq->setString(name, reqParts.at(level).c_str());
387 signature = utils::StringFormat("%s_IDTEXT", signature.c_str());
388 }
389 }
390 }
391 else if (strcmp(tag, "api") == 0) {
392 signature = utils::StringFormat("%s_%s", signature.c_str(), reqParts.at(level).c_str());
393 }
394
395 // Check for subapi nodes
396 // For now we assume subapi="*", may in the future restrict to list of subapis
397 if ((subapi = node.getAttribute("subapi")) && strlen(subapi)) {
398 // create a new request with just the sub part from here
399 std::string newRequest;
400 std::string oldSignature = signature;
401 for (uint32 n=level+1; n<reqParts.size(); n++) {
402 if (n == level+1)
403 newRequest = reqParts.at(n);
404 else
405 newRequest = utils::StringFormat("%s/%s", newRequest.c_str(), reqParts.at(n).c_str());
406 }
407 XMLNode subapiNode = parseRequest(newRequest.c_str(), paramSet, restReq, signature);
408 if (!subapiNode.isEmpty())
409 return subapiNode;
410 else {
411 signature = oldSignature;
412 restReq->clearErrorState();
413 }
414 }
415
416 if (level == reqParts.size() - 1)
417 break;
418
419 if (node.nChildNode("id") > 0) {
420 if (!(subNode=node.getChildNode("id")).isEmpty()) {
421 if (!(type = subNode.getAttribute("type"))) {
422 restReq->addToErrorString(utils::StringFormat("API description error: ID type not provided").c_str());
424 return emptyNode;
425 }
426 else if (strcmp(type, "integer") == 0) {
427 // check if next part is integer
428 if (!utils::IsTextNumeric(reqParts.at(level+1).c_str())) {
429 restReq->addToErrorString(utils::StringFormat("API ID %s provided is not numeric", subNode.getAttribute("name")).c_str());
431 return emptyNode;
432 }
433 level++;
434 node = subNode;
435 continue;
436 }
437 else if (strcmp(type, "text") == 0) {
438 // check if next part is text
439 if (!reqParts.at(level+1).length()) {
440 restReq->addToErrorString(utils::StringFormat("API ID %s provided is empty", subNode.getAttribute("name")).c_str());
442 return emptyNode;
443 }
444 level++;
445 node = subNode;
446 continue;
447 }
448 else {
449 restReq->addToErrorString(utils::StringFormat("API description error: Type %s not supported", type).c_str());
451 return emptyNode;
452 }
453 }
454 else {
455 restReq->addToErrorString(utils::StringFormat("API description error: No ID node found").c_str());
457 return emptyNode;
458 }
459 }
460 else if ( (subNodeCount = node.nChildNode("api")) > 0) {
461 // There might be multiple api child nodes, we need to find the one with name = reqParts.at(level)
462 for (i = 0; i < subNodeCount; i++) {
463 if (!(subNode=node.getChildNode("api", i)).isEmpty() && (subName = subNode.getAttribute("name")) && (stricmp(reqParts.at(level+1).c_str(), subName) == 0)) {
464 level++;
465 node = subNode;
466 break;
467 }
468 }
469 if (i == subNodeCount) {
470 // If we haven't found a matching subnode
471 restReq->addToErrorString(utils::StringFormat("API description error: No API node found").c_str());
473 return emptyNode;
474 }
475 else {
476 // We did find a matching subnode
477 continue;
478 }
479 }
480 else {
481 restReq->addToErrorString(utils::StringFormat("API error: No API found for signature %s_%s", signature.c_str(), reqParts.at(level+1).c_str()).c_str());
483 return emptyNode;
484 }
485 }
486 return node;
487}
488
489
490
491
492
493
494
495
496
497bool RESTParser::extractParameters(HTTPRequest* req, std::map<std::string, RESTParam>& paramSet, RESTRequest* restReq) {
498 // First find out whether the parameters are stored inside the HTTPRequest structure or as JSON/XML content
499 uint32 size;
500 const char* postType = req->getPostDataType("POST");
501 if (!postType) {
502 postType = req->getHeaderEntry("Content-Type");
503 }
504 const char* val, *data, *json = NULL, *xml = NULL;
505
506 jsmn_parser parser;
507 jsmntok_t tokens[256];
508 int tokenCount = 0;
509 int valSize;
510 XMLResults xmlResults;
511 XMLNode paramNode, node;
512
513 //if (utils::stristr(req->getHeaderEntry("Content-Type"), "multipart/form-data")) {
514 // // parse all non-POST entries by name
515 // #############################
516
517 //}
518
519 // POST entry contains JSON
520 if (utils::stristr(postType, "json")) {
521 if (!(json = req->getPostData("POST", size)) || !size) {
522 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: No JSON POST found").c_str());
523 return false;
524 }
525 jsmn_init(&parser);
526 if ( (tokenCount = jsmn_parse(&parser, json, size, tokens, 256)) <= 0) {
527 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Error parsing JSON").c_str());
528 return false;
529 }
530 }
531 // JSON entry contains JSON
532 else if ((json = req->getPostData("JSON", size)) && size) {
533 jsmn_init(&parser);
534 if ( (tokenCount = jsmn_parse(&parser, json, size, tokens, 256)) <= 0) {
535 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Error parsing JSON").c_str());
536 return false;
537 }
538 }
539 // POST entry contains XML
540 else if (utils::stristr(postType, "xml")) {
541 if (!(xml = req->getPostData("POST", size)) || !size) {
542 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: No XML POST found").c_str());
543 return false;
544 }
545 paramNode=XMLNode::parseString(xml, NULL, &xmlResults);
546 if ((xmlResults.error != eXMLErrorNone) || (paramNode.isEmpty())) {
547 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Error parsing XML").c_str());
548 return false;
549 }
550 }
551 // XML entry contains XML
552 else if ((xml = req->getPostData("XML", size)) && size) {
553 paramNode=XMLNode::parseString(xml, NULL, &xmlResults);
554 if ((xmlResults.error != eXMLErrorNone) || (paramNode.isEmpty())) {
555 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Error parsing XML").c_str());
556 return false;
557 }
558 }
559 // POST entry contains something and content type doesn't specify
560 else if ((val = req->getPostData("POST", size)) && size) {
561 // first try to see if it is JSON
562 jsmn_init(&parser);
563 if ( (tokenCount = jsmn_parse(&parser, val, size, tokens, 256)) > 0) {
564 // great, we found valid JSON
565 json = val;
566 }
567 // and if not then try XML
568 else {
569 paramNode=XMLNode::parseString(val, NULL, &xmlResults);
570 if ((xmlResults.error != eXMLErrorNone) || (paramNode.isEmpty())) {
571 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Cannot find either JSON or XML content").c_str());
572 return false;
573 }
574 else {
575 // great, we found valid XML
576 xml = val;
577 }
578 }
579 }
580
581 uint32 errorCount = 0;
582 std::string value;
583 std::map<std::string, RESTParam>::iterator i = paramSet.begin(), e = paramSet.end();
584 while (i != e) {
585 value.clear();
586 if (json) {
587 if (!(val = GetJSONValue(tokens, tokenCount, json, i->second.name.c_str(), JSMN_UNDEFINED, valSize))) {
588 if (!i->second.optional) {
589 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Parameter %s not found in JSON", i->second.name.c_str()).c_str());
590 errorCount++;
591 i++;
592 continue;
593 }
594 }
595 else if (!valSize && !i->second.allowEmpty) {
596 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Parameter %s empty in JSON, not allowed", i->second.name.c_str()).c_str());
597 // Continue anyway, but log the error
598 value.clear();
599 errorCount++;
600 }
601 else
602 value = std::string(val, valSize);
603 }
604 else if (xml) {
605 // #############################
606 return false;
607 //node = paramNode.
608 //if (!(val = extractXMLValue(req->getPostData("POST", size), i->second.name.c_str(), size)) || !size)
609 // return false;
610 }
611 else if (utils::stristr(postType, "form")) { // both multipart/form-data and application/x-www-form-urlencoded
612 if (i->second.type == RESTParam::IMAGE || i->second.type == RESTParam::VIDEO || i->second.type == RESTParam::BINARY) {
613 if (!(data = req->getPostData(i->second.name.c_str(), size)) || !size) {
614 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Binary parameter %s not found in form", i->second.name.c_str()).c_str());
615 errorCount++;
616 i++;
617 continue;
618 }
619 // and now get the content-type
620 if (!(val = req->getPostDataType(i->second.name.c_str()))) {
621 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Binary parameter %s content type not found in form", i->second.name.c_str()).c_str());
622 errorCount++;
623 i++;
624 continue;
625 }
626 restReq->setBinary(i->second.name.c_str(), data, size, val);
627 i++;
628 continue;
629 }
630 else {
631 if (!(val = req->getPostData(i->second.name.c_str(), size)) || !size) {
632 if (!i->second.optional && (i->second.type != RESTParam::ARRAY)) {
633 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Parameter %s not found in form", i->second.name.c_str()).c_str());
634 errorCount++;
635 i++;
636 continue;
637 }
638 else if (i->second.type != RESTParam::ARRAY) {
639 i++;
640 continue;
641 }
642 }
643 else
644 value = val;
645 }
646 }
647 else {
648 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: No form, JSON or XML found in request", i->second.name.c_str()).c_str());
649 return false;
650 }
651 uint32 num;
652 if (i->second.type == RESTParam::ARRAY) {
653 std::map<std::string, RESTParam> arrayParamSet = paramSets[i->second.objects];
654 if (!arrayParamSet.size()) {
655 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Array objects %s unknown", i->second.objects.c_str()).c_str());
656 return false;
657 }
658 if (json) {
659 // call with sub node 1, 2, ...
660 num = 1;
661 std::vector<int> jsonArrayIdx = GetJSONArrayIndexes(tokens, tokenCount, json, i->second.name.c_str(), JSMN_UNDEFINED);
662 std::vector<int>::iterator ia = jsonArrayIdx.begin(), ea = jsonArrayIdx.end();
663 while (ia != ea) {
664 value = std::string(json+tokens[*ia].start, tokens[*ia].end - tokens[*ia].start);
665 // call array function...
666 extractArrayParameters(req, arrayParamSet, restReq, num, value.c_str(), (uint32)value.length(), "json");
667 num++;
668 ia++;
669 }
670 }
671 else if (xml) {
672 // call with subnode 1, 2, ...
673 return false;
674 }
675 else if (utils::stristr(postType, "form")) { // both multipart/form-data and application/x-www-form-urlencoded
676 num = 1;
677 while (extractArrayParameters(req, arrayParamSet, restReq, num, NULL, 0, NULL))
678 num++;
679 if (num > 1)
680 restReq->clearErrorState();
681 // call self with param?
682 // setArrayValuesPOST();
683 }
684 }
685 else if (value.length()) {
686 if (!setParameterValue(req, i->second, value, restReq))
687 return false;
688 }
689 i++;
690 }
691 return (errorCount == 0);
692}
693
694
695
696
697
698
699bool RESTParser::extractParameters(DataMessage* msg, std::map<std::string, RESTParam>& paramSet, RESTRequest* restReq) {
700 // First find out whether the parameters are stored inside the HTTPRequest structure or as JSON/XML content
701 uint32 size, size2;
702
703 const char* postType = msg->getString("POST_CONTENT_TYPE_");
704 const char* val, *data, *json = NULL, *xml = NULL;
705
706 jsmn_parser parser;
707 jsmntok_t tokens[256];
708 int tokenCount = 0;
709 int valSize;
710 XMLResults xmlResults;
711 XMLNode paramNode, node;
712
713 // POST entry contains JSON
714 if (utils::stristr(postType, "json")) {
715 if (!(json = msg->getString("POST", size)) || !size) {
716 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: No JSON POST found").c_str());
717 return false;
718 }
719 jsmn_init(&parser);
720 if ( (tokenCount = jsmn_parse(&parser, json, size, tokens, 256)) <= 0) {
721 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Error parsing JSON").c_str());
722 return false;
723 }
724 }
725 // JSON entry contains JSON
726 else if ((json = msg->getString("JSON", size)) && size) {
727 jsmn_init(&parser);
728 if ( (tokenCount = jsmn_parse(&parser, json, size, tokens, 256)) <= 0) {
729 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Error parsing JSON").c_str());
730 return false;
731 }
732 }
733 // POST entry contains XML
734 else if (utils::stristr(postType, "xml")) {
735 if (!(xml = msg->getString("POST", size)) || !size) {
736 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: No XML POST found").c_str());
737 return false;
738 }
739 paramNode=XMLNode::parseString(xml, NULL, &xmlResults);
740 if ((xmlResults.error != eXMLErrorNone) || (paramNode.isEmpty())) {
741 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Error parsing XML").c_str());
742 return false;
743 }
744 }
745 // XML entry contains XML
746 else if ((xml = msg->getString("XML", size)) && size) {
747 paramNode=XMLNode::parseString(xml, NULL, &xmlResults);
748 if ((xmlResults.error != eXMLErrorNone) || (paramNode.isEmpty())) {
749 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Error parsing XML").c_str());
750 return false;
751 }
752 }
753 // POST entry contains something and content type doesn't specify
754 else if ((val = msg->getString("POST", size)) && size) {
755 // first try to see if it is JSON
756 jsmn_init(&parser);
757 if ( (tokenCount = jsmn_parse(&parser, val, size, tokens, 256)) > 0) {
758 // great, we found valid JSON
759 json = val;
760 }
761 // and if not then try XML
762 else {
763 paramNode=XMLNode::parseString(val, NULL, &xmlResults);
764 if ((xmlResults.error != eXMLErrorNone) || (paramNode.isEmpty())) {
765 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Cannot find either JSON or XML content").c_str());
766 return false;
767 }
768 else {
769 // great, we found valid XML
770 xml = val;
771 }
772 }
773 }
774
775 uint32 errorCount = 0;
776 std::string value;
777 std::map<std::string, RESTParam>::iterator i = paramSet.begin(), e = paramSet.end();
778 while (i != e) {
779 value.clear();
780 if (json) {
781 if (!(val = GetJSONValue(tokens, tokenCount, json, i->second.name.c_str(), JSMN_UNDEFINED, valSize))) {
782 if (!i->second.optional) {
783 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Parameter %s not found in JSON", i->second.name.c_str()).c_str());
784 errorCount++;
785 i++;
786 continue;
787 }
788 }
789 else if (!valSize && !i->second.allowEmpty) {
790 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Parameter %s empty in JSON, not allowed", i->second.name.c_str()).c_str());
791 // Continue anyway, but log the error
792 value.clear();
793 errorCount++;
794 }
795 else
796 value = std::string(val, valSize);
797 }
798 else if (xml) {
799 // #############################
800 return false;
801 //node = paramNode.
802 //if (!(val = extractXMLValue(req->getPostData("POST", size), i->second.name.c_str(), size)) || !size)
803 // return false;
804 }
805 else if (!postType || utils::stristr(postType, "form")) { // both multipart/form-data and application/x-www-form-urlencoded
806 if (i->second.type == RESTParam::IMAGE || i->second.type == RESTParam::VIDEO || i->second.type == RESTParam::BINARY) {
807 if (!(data = msg->getData(i->second.name.c_str(), size)) || !size) {
808 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Binary parameter %s not found in form", i->second.name.c_str()).c_str());
809 errorCount++;
810 i++;
811 continue;
812 }
813 // and now get the content-type
814 if (!(val = msg->getString((i->second.name + "_CONTENT_TYPE_").c_str(), size2)) || !size2) {
815 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Binary parameter %s content type not found in form", i->second.name.c_str()).c_str());
816 errorCount++;
817 i++;
818 continue;
819 }
820 restReq->setBinary(i->second.name.c_str(), data, size, val);
821 i++;
822 continue;
823 }
824 else {
825 if (!(val = msg->getString(i->second.name.c_str(), size)) || !size) {
826 if (!i->second.optional && (i->second.type != RESTParam::ARRAY)) {
827 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Parameter %s not found in form", i->second.name.c_str()).c_str());
828 errorCount++;
829 i++;
830 continue;
831 }
832 else if (i->second.type != RESTParam::ARRAY) {
833 i++;
834 continue;
835 }
836 }
837 else
838 value = val;
839 }
840 }
841 else {
842 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: No form, JSON or XML found in request", i->second.name.c_str()).c_str());
843 return false;
844 }
845
846 uint32 num;
847 if (i->second.type == RESTParam::ARRAY) {
848 std::map<std::string, RESTParam> arrayParamSet = paramSets[i->second.objects];
849 if (!arrayParamSet.size()) {
850 restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Array objects %s unknown", i->second.objects.c_str()).c_str());
851 return false;
852 }
853 if (json) {
854 // call with sub node 1, 2, ...
855 num = 1;
856 std::vector<int> jsonArrayIdx = GetJSONArrayIndexes(tokens, tokenCount, json, i->second.name.c_str(), JSMN_UNDEFINED);
857 std::vector<int>::iterator ia = jsonArrayIdx.begin(), ea = jsonArrayIdx.end();
858 while (ia != ea) {
859 value = std::string(json+tokens[*ia].start, tokens[*ia].end - tokens[*ia].start);
860 // call array function...
861 extractArrayParameters(msg, arrayParamSet, restReq, num, value.c_str(), (uint32)value.length(), "json");
862 num++;
863 ia++;
864 }
865 }
866 else if (xml) {
867 // call with subnode 1, 2, ...
868 return false;
869 }
870 else if (!postType || utils::stristr(postType, "form")) { // both multipart/form-data and application/x-www-form-urlencoded
871 num = 1;
872 while (extractArrayParameters(msg, arrayParamSet, restReq, num, NULL, 0, NULL))
873 num++;
874 if (num > 1)
875 restReq->clearErrorState();
876 // call self with param?
877 // setArrayValuesPOST();
878 }
879 }
880 else if (value.length()) {
881 if (!setParameterValue(msg, i->second, value, restReq))
882 return false;
883 }
884 else if (val) {
885 if (!setParameterValue(msg, i->second, "", restReq))
886 return false;
887 }
888 i++;
889 }
890 return (errorCount == 0);
891}
892
893
894
895
896
897
898
899bool RESTParser::extractArrayParameters(HTTPRequest* req, std::map<std::string, RESTParam>& paramSet, RESTRequest* restReq, int entryNum, const char* content, uint32 contentSize, const char* contentType) {
900 // Called from extractParameters if any parameter is of type ARRAY
901 // content can either be JSON or XML or NULL if parameters are posted as PARAM-1, PARAM-2, etc.
902 // paramSet is the set inside the ARRAY
903
904 uint32 size;
905 const char* val, *data, *json = NULL, *xml = NULL;
906
907 jsmn_parser parser;
908 jsmntok_t tokens[256];
909 int tokenCount = 0;
910 int valSize;
911 XMLResults xmlResults;
912 XMLNode paramNode, node;
913
914 if (utils::stristr(contentType, "json")) {
915 if (!(json = content) || !contentSize) {
916 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: No JSON found").c_str());
917 return false;
918 }
919 jsmn_init(&parser);
920 if ( (tokenCount = jsmn_parse(&parser, json, contentSize, tokens, 256)) <= 0) {
921 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Error parsing JSON").c_str());
922 return false;
923 }
924 }
925 else if (utils::stristr(contentType, "xml")) {
926 if (!(xml = content) || !contentSize) {
927 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: No XML found").c_str());
928 return false;
929 }
930 paramNode=XMLNode::parseString(xml, NULL, &xmlResults);
931 if ((xmlResults.error != eXMLErrorNone) || (paramNode.isEmpty())) {
932 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Error parsing XML").c_str());
933 return false;
934 }
935 }
936
937 std::string paddedParamName;
938 std::string value;
939 std::map<std::string, RESTParam>::iterator i = paramSet.begin(), e = paramSet.end();
940 while (i != e) {
941 paddedParamName = utils::StringFormat("%s_%u", i->second.name.c_str(), entryNum);
942 value.clear();
943 if (json) {
944 // First look for the raw parameter name
945 if (!(val = GetJSONValue(tokens, tokenCount, json, i->second.name.c_str(), JSMN_UNDEFINED, valSize))) {
946 // then look for the padded PARAM_N name
947 if (!(val = GetJSONValue(tokens, tokenCount, json, paddedParamName.c_str(), JSMN_UNDEFINED, valSize))) {
948 if (!i->second.optional) {
949 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Parameter %s not found in JSON", i->second.name.c_str()).c_str());
950 return false;
951 }
952 }
953 }
954 else {
955 if (!valSize && !i->second.allowEmpty) {
956 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Parameter %s empty in JSON, not allowed", i->second.name.c_str()).c_str());
957 return false;
958 }
959 value = std::string(val, valSize);
960 // if this is a binary reference, val has the name of the binary attachment
961 if (i->second.type == RESTParam::IMAGE || i->second.type == RESTParam::VIDEO || i->second.type == RESTParam::BINARY) {
962 data = req->getPostData(value.c_str(), size);
963 if (!data || !size) {
964 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Parameter %s (from %s) not found in form", value.c_str(), paddedParamName.c_str()).c_str());
965 return false;
966 }
967 // and now get the content-type
968 if (!(val = req->getPostDataType(i->second.name.c_str()))) {
969 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Binary parameter %s content type not found in form", i->second.name.c_str()).c_str());
970 return false;
971 }
972 restReq->setBinary(paddedParamName.c_str(), data, size, val);
973 i++;
974 continue;
975 }
976 // else leave value as text
977 }
978 }
979 else if (xml) {
980 // #############################
981 return false;
982 //node = paramNode.
983 //if (!(val = extractXMLValue(req->getPostData("POST", size), i->second.name.c_str(), size)) || !size)
984 // return false;
985 }
986 else if (utils::stristr(contentType, "form")) { // both multipart/form-data and application/x-www-form-urlencoded
987 if (!(data = req->getPostData(paddedParamName.c_str(), size)) || !size) {
988 if (!i->second.optional) {
989 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Parameter %s not found in form", paddedParamName.c_str()).c_str());
990 return false;
991 }
992 }
993 else {
994 // if this is a binary reference, val has the name of the binary attachment
995 if (i->second.type == RESTParam::IMAGE || i->second.type == RESTParam::VIDEO || i->second.type == RESTParam::BINARY) {
996 // and now get the content-type
997 if (!(val = req->getPostDataType(i->second.name.c_str()))) {
998 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Binary parameter %s content type not found in form", i->second.name.c_str()).c_str());
999 return false;
1000 }
1001 // for this don't go into the string-based setParameterValue
1002 restReq->setBinary(paddedParamName.c_str(), data, size, val);
1003 i++;
1004 continue;
1005 }
1006 // else leave value as text
1007 else
1008 value = data;
1009 }
1010 }
1011 else {
1012 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: No form, JSON or XML found in request", i->second.name.c_str()).c_str());
1013 return false;
1014 }
1015
1016 if (i->second.type == RESTParam::ARRAY) {
1017 // For now we do not support nested arrays
1018 }
1019 else if (value.length()) {
1020 if (!setParameterValue(req, i->second, value, restReq, entryNum))
1021 return false;
1022 }
1023 i++;
1024 }
1025 return true;
1026}
1027
1028
1029
1030bool RESTParser::extractArrayParameters(DataMessage* msg, std::map<std::string, RESTParam>& paramSet, RESTRequest* restReq, int entryNum, const char* content, uint32 contentSize, const char* contentType) {
1031 // Called from extractParameters if any parameter is of type ARRAY
1032 // content can either be JSON or XML or NULL if parameters are posted as PARAM-1, PARAM-2, etc.
1033 // paramSet is the set inside the ARRAY
1034
1035 uint32 size, size2;
1036 const char* val, *data, *json = NULL, *xml = NULL;
1037
1038 jsmn_parser parser;
1039 jsmntok_t tokens[256];
1040 int tokenCount = 0;
1041 int valSize;
1042 XMLResults xmlResults;
1043 XMLNode paramNode, node;
1044
1045 if (utils::stristr(contentType, "json")) {
1046 if (!(json = content) || !contentSize) {
1047 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: No JSON found").c_str());
1048 return false;
1049 }
1050 jsmn_init(&parser);
1051 if ( (tokenCount = jsmn_parse(&parser, json, contentSize, tokens, 256)) <= 0) {
1052 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Error parsing JSON").c_str());
1053 return false;
1054 }
1055 }
1056 else if (utils::stristr(contentType, "xml")) {
1057 if (!(xml = content) || !contentSize) {
1058 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: No XML found").c_str());
1059 return false;
1060 }
1061 paramNode=XMLNode::parseString(xml, NULL, &xmlResults);
1062 if ((xmlResults.error != eXMLErrorNone) || (paramNode.isEmpty())) {
1063 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Error parsing XML").c_str());
1064 return false;
1065 }
1066 }
1067
1068 std::string paddedParamName;
1069 std::string value;
1070 std::map<std::string, RESTParam>::iterator i = paramSet.begin(), e = paramSet.end();
1071 while (i != e) {
1072 paddedParamName = utils::StringFormat("%s_%u", i->second.name.c_str(), entryNum);
1073 value.clear();
1074 if (json) {
1075 // First look for the raw parameter name
1076 if (!(val = GetJSONValue(tokens, tokenCount, json, i->second.name.c_str(), JSMN_UNDEFINED, valSize))) {
1077 // then look for the padded PARAM_N name
1078 if (!(val = GetJSONValue(tokens, tokenCount, json, paddedParamName.c_str(), JSMN_UNDEFINED, valSize))) {
1079 if (!i->second.optional) {
1080 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Parameter %s not found in JSON", i->second.name.c_str()).c_str());
1081 return false;
1082 }
1083 }
1084 }
1085 else {
1086 if (!valSize && !i->second.allowEmpty) {
1087 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Parameter %s empty in JSON, not allowed", i->second.name.c_str()).c_str());
1088 return false;
1089 }
1090 value = std::string(val, valSize);
1091 // if this is a binary reference, val has the name of the binary attachment
1092 if (i->second.type == RESTParam::IMAGE || i->second.type == RESTParam::VIDEO || i->second.type == RESTParam::BINARY) {
1093 data = msg->getData(value.c_str(), size);
1094 if (!data || !size) {
1095 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Parameter %s (from %s) not found in form", value.c_str(), paddedParamName.c_str()).c_str());
1096 return false;
1097 }
1098 // and now get the content-type
1099 if (!(val = msg->getString((value + "_CONTENT_TYPE_").c_str(), size2)) || !size2) {
1100 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Binary parameter %s content type not found in form", i->second.name.c_str()).c_str());
1101 return false;
1102 }
1103 restReq->setBinary(paddedParamName.c_str(), data, size, val);
1104 i++;
1105 continue;
1106 }
1107 // else leave value as text
1108 }
1109 }
1110 else if (xml) {
1111 // #############################
1112 return false;
1113 //node = paramNode.
1114 //if (!(val = extractXMLValue(req->getPostData("POST", size), i->second.name.c_str(), size)) || !size)
1115 // return false;
1116 }
1117 else if (!contentType || utils::stristr(contentType, "form")) { // both multipart/form-data and application/x-www-form-urlencoded
1118 if (i->second.type == RESTParam::IMAGE || i->second.type == RESTParam::VIDEO || i->second.type == RESTParam::BINARY) {
1119 if (!(data = msg->getData(paddedParamName.c_str(), size)) || !size) {
1120 if (!i->second.optional) {
1121 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Parameter %s not found in form", paddedParamName.c_str()).c_str());
1122 return false;
1123 }
1124 else {
1125 i++;
1126 continue;
1127 }
1128 }
1129 // and now get the content-type
1130 if (!(val = msg->getString((paddedParamName + "_CONTENT_TYPE_").c_str(), size2)) || !size2) {
1131 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Binary parameter %s content type not found in form", i->second.name.c_str()).c_str());
1132 return false;
1133 }
1134 restReq->setBinary(paddedParamName.c_str(), data, size, val);
1135 i++;
1136 continue;
1137 }
1138 else {
1139 if (!(val = msg->getString(paddedParamName.c_str(), size)) || !size) {
1140 if (!i->second.optional) {
1141 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Parameter %s not found in form", i->second.name.c_str()).c_str());
1142 return false;
1143 }
1144 else {
1145 i++;
1146 continue;
1147 }
1148 }
1149 value = val;
1150 }
1151 }
1152 else {
1153 restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: No form, JSON or XML found in request", i->second.name.c_str()).c_str());
1154 return false;
1155 }
1156
1157 if (i->second.type == RESTParam::ARRAY) {
1158 // For now we do not support nested arrays
1159 }
1160 else if (value.length()) {
1161 if (!setParameterValue(msg, i->second, value, restReq, entryNum))
1162 return false;
1163 }
1164 i++;
1165 }
1166 return true;
1167}
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201bool RESTParser::setParameterValue(HTTPRequest* req, RESTParam &param, std::string value, RESTRequest* restReq, uint32 entryNum) {
1202
1203 // if entryName = 0 this is a normal parameter, if not, it is an array parameter
1204
1205 uint32 size;
1206 const char* data, *val;
1207 utils::StringSingleReplace(value, "\"", "", false);
1208
1209 std::string paddedParamName;
1210 if (entryNum)
1211 paddedParamName = utils::StringFormat("%s_%u", param.name.c_str(), entryNum);
1212 else
1213 paddedParamName = param.name;
1214
1215 switch(param.type) {
1216 case RESTParam::INTEGER:
1217 if (!utils::IsTextNumeric(value.c_str())) {
1218 restReq->addToErrorString(utils::StringFormat("Parameter error: %s = %s is not numeric", paddedParamName.c_str(), value.c_str()).c_str());
1219 return false;
1220 }
1221 restReq->setInt(paddedParamName.c_str(), utils::Ascii2Int64(value.c_str()));
1222 break;
1223 case RESTParam::FLOAT:
1224 if (!utils::IsTextNumeric(value.c_str())) {
1225 restReq->addToErrorString(utils::StringFormat("Parameter error: %s = %s is not numeric", paddedParamName.c_str(), value.c_str()).c_str());
1226 return false;
1227 }
1228 restReq->setDouble(paddedParamName.c_str(), (double)utils::Ascii2Float64(value.c_str()));
1229 break;
1230 case RESTParam::TEXT:
1231 restReq->setString(paddedParamName.c_str(), value.c_str());
1232 break;
1233 case RESTParam::IMAGE:
1234 case RESTParam::VIDEO:
1235 case RESTParam::BINARY:
1236 // Check that post contains an entry of value
1237 if (!(data = req->getPostData(value.c_str(), size))) {
1238 restReq->addToErrorString(utils::StringFormat("Parameter error: cannot find post entry called %s for parameter %s", value.c_str(), paddedParamName.c_str()).c_str());
1239 return false;
1240 }
1241 if (!(val = req->getPostDataType(value.c_str()))) {
1242 restReq->addToErrorString(utils::StringFormat("Parameter error: cannot find post type for entry called %s for parameter %s", value.c_str(), paddedParamName.c_str()).c_str());
1243 return false;
1244 }
1245 restReq->setBinary(paddedParamName.c_str(), data, size, val);
1246 break;
1247 //case RESTParam::ARRAY:
1248 // ###################
1249 // break;
1250 //return false;
1251 default:
1252 restReq->addToErrorString(utils::StringFormat("Parameter error: %s type is not supported", paddedParamName.c_str(), value.c_str()).c_str());
1253 return false;
1254 }
1255 return true;
1256}
1257
1258
1259
1260
1261
1262bool RESTParser::setParameterValue(DataMessage* msg, RESTParam &param, std::string value, RESTRequest* restReq, uint32 entryNum) {
1263
1264 // if entryName = 0 this is a normal parameter, if not, it is an array parameter
1265
1266 uint32 size, size2;
1267 const char* data, *val;
1268 utils::StringSingleReplace(value, "\"", "", false);
1269
1270 std::string paddedParamName;
1271 if (entryNum)
1272 paddedParamName = utils::StringFormat("%s_%u", param.name.c_str(), entryNum);
1273 else
1274 paddedParamName = param.name;
1275
1276 switch(param.type) {
1277 case RESTParam::INTEGER:
1278 if (!utils::IsTextNumeric(value.c_str())) {
1279 restReq->addToErrorString(utils::StringFormat("Parameter error: %s = %s is not numeric", paddedParamName.c_str(), value.c_str()).c_str());
1280 return false;
1281 }
1282 restReq->setInt(paddedParamName.c_str(), utils::Ascii2Int64(value.c_str()));
1283 break;
1284 case RESTParam::FLOAT:
1285 if (!utils::IsTextNumeric(value.c_str())) {
1286 restReq->addToErrorString(utils::StringFormat("Parameter error: %s = %s is not numeric", paddedParamName.c_str(), value.c_str()).c_str());
1287 return false;
1288 }
1289 restReq->setDouble(paddedParamName.c_str(), (double)utils::Ascii2Float64(value.c_str()));
1290 break;
1291 case RESTParam::TEXT:
1292 restReq->setString(paddedParamName.c_str(), value.c_str());
1293 break;
1294 case RESTParam::IMAGE:
1295 case RESTParam::VIDEO:
1296 case RESTParam::BINARY:
1297 // Check that post contains an entry of value
1298 if (!(data = msg->getData(value.c_str(), size))) {
1299 restReq->addToErrorString(utils::StringFormat("Parameter error: cannot find post entry called %s for parameter %s", value.c_str(), paddedParamName.c_str()).c_str());
1300 return false;
1301 }
1302 // and now get the content-type
1303 if (!(val = msg->getString((value + "_CONTENT_TYPE_").c_str(), size2)) || !size2) {
1304 restReq->addToErrorString(utils::StringFormat("Parameter error: cannot find post type for entry called %s for parameter %s", value.c_str(), paddedParamName.c_str()).c_str());
1305 return false;
1306 }
1307 restReq->setBinary(paddedParamName.c_str(), data, size, val);
1308 break;
1309 //case RESTParam::ARRAY:
1310 // ###################
1311 // break;
1312 //return false;
1313 default:
1314 restReq->addToErrorString(utils::StringFormat("Parameter error: %s type is not supported", paddedParamName.c_str(), value.c_str()).c_str());
1315 return false;
1316 }
1317 return true;
1318}
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339XMLNode RESTParser::findTopNodeForRequest(const char* topNodeString) {
1340 const char* name;
1341 XMLNode node;
1342 uint32 i, count = mainNode.nChildNode("api");
1343 for(i=0; i<count; i++) {
1344 if (!(node = mainNode.getChildNode("api", i)).isEmpty()) {
1345 name = node.getAttribute("name");
1346 if (strcmp(topNodeString, name) == 0)
1347 return node;
1348 }
1349 }
1350 return XMLNode::emptyXMLNode;
1351}
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1367 bool ok;
1368 createTime = msg->getTime("BUILTIN_CREATETIME", ok);
1369 status = (Status)msg->getInt("BUILTIN_STATUS", ok);
1370 signature = getString("BUILTIN_REQUEST_SIGNATURE", ok);
1371 requireAuthentication = (msg->getInt("BUILTIN_REQUIRE_AUTHENTICATION", ok) != 0);
1372 std::map<std::string, std::string> filterMapTemp = utils::TextMapSplit(msg->getString("BUILTIN_FILTERMAP"), ";;;", "=");
1373 std::map<std::string, std::string>::iterator i = filterMapTemp.begin(), e = filterMapTemp.end();
1374 while (i != e) {
1375 filterMap[html::DecodeHTML(i->first)] = html::DecodeHTML(i->second);
1376 i++;
1377 }
1378 storage = msg;
1379 httpCall = NULL;
1380}
1381
1383 delete(storage);
1384 // we don't own httpCall, just remove link
1385 httpCall = NULL;
1386}
1387
1388
1390 DataMessage* msg = new DataMessage(*storage);
1391 msg->setTime("BUILTIN_CREATETIME", createTime);
1392 msg->setInt("BUILTIN_STATUS", status);
1394 msg->setInt("BUILTIN_REQUIRE_AUTHENTICATION", 1);
1395 std::string filterString;
1396 std::multimap<std::string, std::string>::iterator i = filterMap.begin(), e = filterMap.end();
1397 while (i != e) {
1398 if (filterString.length())
1399 filterString = utils::StringFormat("%s;;;%s=%s", filterString.c_str(), html::EncodeHTML(i->first).c_str(), html::EncodeHTML(i->second).c_str());
1400 else
1401 filterString = utils::StringFormat("%s=%s", html::EncodeHTML(i->first).c_str(), html::EncodeHTML(i->second).c_str());
1402 i++;
1403 }
1404 msg->setString("BUILTIN_FILTERMAP", filterString.c_str());
1405 msg->setString("BUILTIN_REQUEST_SIGNATURE", signature.c_str());
1406 return msg;
1407}
1408
1409
1411 if (!httpCall)
1412 return NULL;
1413 return httpCall->generateHTTPRequest(this);
1414}
1415
1417 if (!httpCall)
1418 return "";
1419 return httpCall->url;
1420}
1421
1422
1423
1424
1425
1427
1428 // A self-contained REST API definition exercising paramsets, an http call,
1429 // a top-level api with an integer id child and a nested sub-api.
1430 const char* restDesc =
1431 "<restapi>"
1432 " <api name=\"users\" ops=\"get,post\">"
1433 " <id name=\"userid\" type=\"integer\" ops=\"get,put,delete\" />"
1434 " <api name=\"profile\" ops=\"get\" />"
1435 " </api>"
1436 "</restapi>";
1437
1438 // 1. init with a valid definition
1439 unittest::progress(10, "init valid definition");
1440 RESTParser parser;
1441 if (!parser.init(restDesc)) {
1442 unittest::fail("RESTParser test: init on valid definition failed: %s", parser.getLastErrorString());
1443 return false;
1444 }
1445 unittest::detail("init succeeded, lastError='%s'", parser.getLastErrorString());
1446
1447 // 2. init with malformed definitions must fail and report an error
1448 unittest::progress(25, "init invalid definitions");
1449 {
1450 RESTParser bad;
1451 if (bad.init("<notrestapi></notrestapi>")) {
1452 unittest::fail("RESTParser test: init accepted definition without <restapi> tag");
1453 return false;
1454 }
1455 if (!strlen(bad.getLastErrorString())) {
1456 unittest::fail("RESTParser test: missing-tag init did not set an error string");
1457 return false;
1458 }
1459 unittest::detail("missing-tag init error: %s", bad.getLastErrorString());
1460
1461 RESTParser malformed;
1462 if (malformed.init("<restapi><api name=")) {
1463 unittest::fail("RESTParser test: init accepted malformed XML");
1464 return false;
1465 }
1466 }
1467
1468 // 3. parse a valid GET request via an HTTPRequest
1469 unittest::progress(45, "parse GET request");
1470 {
1471 bool isInvalid = false;
1472 const char* raw =
1473 "GET /users HTTP/1.1\r\n"
1474 "Host: localhost\r\n"
1475 "\r\n";
1476 HTTPRequest httpReq;
1477 if (!httpReq.processHeader(raw, (uint32)strlen(raw), isInvalid) || isInvalid) {
1478 unittest::fail("RESTParser test: processHeader on GET failed (isInvalid=%d)", (int)isInvalid);
1479 return false;
1480 }
1481 if (httpReq.type != HTTP_GET) {
1482 unittest::fail("RESTParser test: parsed request type != HTTP_GET (got %u)", (unsigned)httpReq.type);
1483 return false;
1484 }
1485 RESTRequest* restReq = parser.parseRequest(&httpReq);
1486 if (!restReq) {
1487 unittest::fail("RESTParser test: parseRequest(HTTPRequest) returned NULL");
1488 return false;
1489 }
1490 if (restReq->getStatus() != RESTRequest::SUCCESS) {
1491 unittest::fail("RESTParser test: GET /users status %d != SUCCESS (err='%s')",
1492 (int)restReq->getStatus(), restReq->getErrorString());
1493 delete(restReq);
1494 return false;
1495 }
1496 unittest::detail("GET /users parsed, status=%d", (int)restReq->getStatus());
1497 delete(restReq);
1498 }
1499
1500 // 4. parse a request for an unknown API: must not crash and must report failure
1501 unittest::progress(65, "parse unknown request");
1502 {
1503 bool isInvalid = false;
1504 const char* raw =
1505 "GET /doesnotexist HTTP/1.1\r\n"
1506 "Host: localhost\r\n"
1507 "\r\n";
1508 HTTPRequest httpReq;
1509 if (!httpReq.processHeader(raw, (uint32)strlen(raw), isInvalid) || isInvalid) {
1510 unittest::fail("RESTParser test: processHeader on unknown GET failed");
1511 return false;
1512 }
1513 RESTRequest* restReq = parser.parseRequest(&httpReq);
1514 if (!restReq) {
1515 unittest::fail("RESTParser test: parseRequest(unknown) returned NULL");
1516 return false;
1517 }
1518 if (restReq->getStatus() == RESTRequest::SUCCESS) {
1519 unittest::fail("RESTParser test: unknown request unexpectedly reported SUCCESS");
1520 delete(restReq);
1521 return false;
1522 }
1523 unittest::detail("unknown request status=%d err='%s'",
1524 (int)restReq->getStatus(), restReq->getErrorString());
1525 delete(restReq);
1526 }
1527
1528 // 5. parse a request expressed as a DataMessage (the message-based entry point)
1529 unittest::progress(80, "parse request from message");
1530 {
1531 DataMessage msg(CTRL_TEST, 0);
1532 msg.setString("URI", "/users/42");
1533 msg.setInt("HTTP_OPERATION", HTTP_GET);
1534 RESTRequest* restReq = parser.parseRequest(&msg);
1535 if (!restReq) {
1536 unittest::fail("RESTParser test: parseRequest(DataMessage) returned NULL");
1537 return false;
1538 }
1539 // Either SUCCESS (id matched) or a defined failure status; just must not crash.
1540 unittest::detail("message request /users/42 status=%d", (int)restReq->getStatus());
1541 delete(restReq);
1542 }
1543
1544 // 6. throughput: repeatedly init + parse to produce a stable metric
1545 unittest::progress(90, "throughput measurement");
1546 {
1547 const int N = 2000;
1548 const char* raw =
1549 "GET /users HTTP/1.1\r\n"
1550 "Host: localhost\r\n"
1551 "\r\n";
1552 uint32 rawLen = (uint32)strlen(raw);
1553 uint64 t0 = GetTimeNow();
1554 for (int n = 0; n < N; n++) {
1555 bool isInvalid = false;
1556 HTTPRequest httpReq;
1557 if (!httpReq.processHeader(raw, rawLen, isInvalid)) {
1558 unittest::fail("RESTParser test: processHeader failed during throughput loop at %d", n);
1559 return false;
1560 }
1561 RESTRequest* restReq = parser.parseRequest(&httpReq);
1562 if (!restReq) {
1563 unittest::fail("RESTParser test: parseRequest returned NULL during throughput loop at %d", n);
1564 return false;
1565 }
1566 delete(restReq);
1567 }
1568 double us = (double)(GetTimeNow() - t0);
1569 if (us > 0) {
1570 unittest::metric("parse_throughput", (double)N / us * 1e6, "req/s", true);
1571 unittest::metric("avg_parse_latency", us / N, "us", false);
1572 }
1573 }
1574
1575 unittest::progress(100, "done");
1576 return true;
1577}
1578
1581 "REST API definition parsing, request matching and parameter extraction", "network");
1582}
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1601
1604
1605bool RESTHTTPCall::init(XMLNode objectNode) {
1606
1607 name = objectNode.getAttribute("name");
1608 url = objectNode.getAttribute("url");
1609 contentType = objectNode.getAttribute("contenttype");
1610 cacheControl = objectNode.getAttribute("cachecontrol");
1611
1612 const char* opsString = objectNode.getAttribute("ops");
1613
1614 if (!opsString || !strlen(opsString))
1615 ops = HTTP_GET;
1616 else if (stricmp(opsString, "post") == 0)
1617 ops = HTTP_POST;
1618 else if (stricmp(opsString, "put") == 0)
1619 ops = HTTP_PUT;
1620 else if (stricmp(opsString, "delete") == 0)
1621 ops = HTTP_DELETE;
1622 else
1623 ops = HTTP_GET;
1624
1625 if (!name.length() || !url.length())
1626 return false;
1627
1628 if (!contentType.length())
1629 contentType = "form-data";
1630
1631 if (!cacheControl.length())
1632 cacheControl = "no-cache";
1633
1634 const char* entryName, *entryValue, *entryParam, *entryContentType;
1635
1636 XMLNode node;
1637 uint32 i, count = objectNode.nChildNode("header");
1638 for (i = 0; i < count; i++) {
1639 if (!(node = objectNode.getChildNode("header", i)).isEmpty()) {
1640 entryName = node.getAttribute("name");
1641 entryValue = node.getAttribute("value");
1642 entryParam = node.getAttribute("param");
1643 if (entryName && strlen(entryName)) {
1644 if (entryValue && strlen(entryValue)) {
1645 headerEntries[entryName] = entryValue;
1646 }
1647 else if (entryParam && strlen(entryParam)) {
1648 headerParams[entryName] = entryParam;
1649 }
1650 }
1651 }
1652 }
1653
1654 count = objectNode.nChildNode("body");
1655 for (i = 0; i < count; i++) {
1656 if (!(node = objectNode.getChildNode("body", i)).isEmpty()) {
1657 entryName = node.getAttribute("name");
1658 entryValue = node.getAttribute("value");
1659 entryParam = node.getAttribute("param");
1660 entryContentType = node.getAttribute("contenttype");
1661 if (entryName && strlen(entryName)) {
1662 if (entryValue && strlen(entryValue)) {
1663 bodyEntries[entryName] = entryValue;
1664 }
1665 else if (entryParam && strlen(entryParam)) {
1666 bodyParams[entryName] = entryParam;
1667 }
1668
1669 if (entryContentType && strlen(entryContentType)) {
1670 bodyContentTypes[entryName] = entryContentType;
1671 }
1672 }
1673 }
1674 }
1675
1676 //if (!headerEntries.size() && !headerParams.size() && !bodyEntries.size() && !bodyParams.size())
1677 // return false;
1678
1679 return true;
1680}
1681
1682
1683
1685
1686 HTTPRequest* req = new HTTPRequest();
1687
1688 std::map<std::string, std::string> allHeaderEntries;
1689
1690 // Copy all the static headers
1691 allHeaderEntries = headerEntries;
1692
1693 bool success;
1694 const char* str;
1695 std::map<std::string, std::string>::iterator hI = headerParams.begin(), hE = headerParams.end();
1696 // Look for all the parameter headers
1697 while (hI != hE) {
1698 // If the param string is provided in the restReq add it as the value of a header entry
1699 if ((str = restReq->getString(hI->second.c_str(), success)) && success)
1700 allHeaderEntries[hI->first] = str;
1701 hI++;
1702 }
1703
1704 std::map<std::string, HTTPPostEntry*> allBodyEntries;
1705 HTTPPostEntry* postEntry;
1706
1707 std::map<std::string, std::string>::iterator sI, sE = bodyContentTypes.end();
1708 // Now iterate through the static body entries
1709 std::map<std::string, std::string>::iterator bI = bodyEntries.begin(), bE = bodyEntries.end();
1710 while (bI != bE) {
1711 postEntry = new HTTPPostEntry();
1712 postEntry->name = bI->first;
1713 if ((sI = bodyContentTypes.find(bI->first)) != sE)
1714 postEntry->type = sI->second;
1715 else
1716 postEntry->type = "text/plain";
1717 postEntry->contentSize = (uint32)bI->second.length();
1718 postEntry->content = new char[postEntry->contentSize + 1];
1719 memcpy(postEntry->content, bI->second.c_str(), postEntry->contentSize + 1);
1720 allBodyEntries[postEntry->name] = postEntry;
1721 bI++;
1722 }
1723
1724 // Now iterate through the parameter body entries
1725 bI = bodyParams.begin(), bE = bodyParams.end();
1726 while (bI != bE) {
1727 postEntry = new HTTPPostEntry();
1728 postEntry->name = bI->first;
1729 if ((sI = bodyContentTypes.find(bI->first)) != sE)
1730 postEntry->type = sI->second;
1731 else
1732 postEntry->type = "text/plain";
1733
1734 if (html::IsMimeBinary(postEntry->type.c_str())) {
1735 // Copy as binary content
1736 postEntry->content = restReq->getDataCopy(bI->second.c_str(), postEntry->contentSize, success);
1737 }
1738 else {
1739 // Copy as text content
1740 if ((str = restReq->getString(bI->second.c_str(), success)) && success) {
1741 postEntry->contentSize = (uint32)strlen(str);
1742 postEntry->content = new char[postEntry->contentSize + 1];
1743 memcpy(postEntry->content, str, postEntry->contentSize + 1);
1744 }
1745 }
1746
1747 allBodyEntries[postEntry->name] = postEntry;
1748 bI++;
1749 }
1750
1751 req->createMultipartRequest(ops, html::GetHostFromURL(url).c_str(), html::GetURIFromURL(url).c_str(), allHeaderEntries, allBodyEntries, true, 0);
1752
1753 std::map<std::string, HTTPPostEntry*>::iterator i = allBodyEntries.begin(), e = allBodyEntries.end();
1754 while (i != e) {
1755 delete(i->second);
1756 i++;
1757 }
1758
1759 return req;
1760
1761
1762
1763 //if (!headerEntries.size() && !headerParams.size() && !bodyParams.size() && !bodyParams.size()) {
1764 // req->createRequest(ops, "", url.c_str(), NULL, 0, false, 0);
1765 //}
1766
1767 //if (content && contentSize)
1768 // req->createRequest(HTTP_POST, "", uriString.c_str(), content, contentSize, false, 0);
1769 //else
1770 // req->createRequest(HTTP_GET, "", uriString.c_str(), NULL, 0, false, 0);
1771 //HTTPReply* reply = makeHTTPRequest(req, hostString.c_str(), port, encryption, timeout);
1772
1773
1774
1775
1776}
1777
1778
1779} // namespace cmlabs
1780
#define HTTP_OPTIONS
OPTIONS.
#define HTTP_DELETE
DELETE.
#define HTTP_GET
GET.
#define HTTP_PUT
PUT.
#define HTTP_POST
POST.
XML-defined REST API parsing: turn HTTP requests (or DataMessages) into validated RESTRequest objects...
Small, dependency-free unit test harness used by all CMSDK object tests.
#define stricmp
Definition Utils.h:132
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 getInt(const char *key, int64 &value)
getInt(const char* key, int64& value)
bool setInt(const char *key, int64 value)
setInt(const char* key, int64 value)
const char * getData(const char *key, uint32 &size)
getData(const char* key, uint32& size)
uint64 getTime(const char *key)
getTime(const char* key)
const char * getString(const char *key)
getString(const char* key)
One named entry of an HTTP POST body (form field or uploaded file part).
std::string type
MIME Content-Type of this part.
uint32 contentSize
Size of content in bytes.
std::string name
Form field name (Content-Disposition name attribute).
char * content
Owned content bytes (NUL-terminated for convenience).
A parsed or generated HTTP request (also used for WebSocket upgrade handshakes).
const char * getHeaderEntry(const char *entry)
Look up a header field (case-insensitive).
const char * getPostData(const char *entry, uint32 &size, const char **type)
Get a multipart POST part's content, size and MIME type.
uint8 type
HTTP_* method id.
bool processHeader(const char *buffer, uint32 size, bool &isInvalid)
Parse the HTTP header block from raw bytes.
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.
const char * getPostDataType(const char *entry)
Definition of an outbound HTTP call template from the REST API XML.
Definition RESTAPI.h:211
std::map< std::string, std::string > headerParams
Headers filled from request parameters.
Definition RESTAPI.h:237
~RESTHTTPCall()
Destructor.
Definition RESTAPI.cpp:1602
HTTPRequest * generateHTTPRequest(RESTRequest *restReq)
Build the outbound HTTP request for a parsed REST request, substituting parameters into URL,...
Definition RESTAPI.cpp:1684
uint8 ops
Allowed HTTP operations bitmask (GET/POST/...).
Definition RESTAPI.h:232
std::string name
Call name from the API definition.
Definition RESTAPI.h:230
std::map< std::string, std::string > headerEntries
Fixed header key/values.
Definition RESTAPI.h:236
bool init(XMLNode objectNode)
Populate this call definition from its XML node in the API definition.
Definition RESTAPI.cpp:1605
std::string contentType
Content-Type for the request body.
Definition RESTAPI.h:233
std::map< std::string, std::string > bodyContentTypes
Per-body-part content types (e.g.
Definition RESTAPI.h:240
std::string cacheControl
Cache-Control header value, if any.
Definition RESTAPI.h:234
RESTHTTPCall()
Create an empty call definition; populate with init().
Definition RESTAPI.cpp:1599
std::map< std::string, std::string > bodyParams
Body fields filled from request parameters.
Definition RESTAPI.h:239
std::map< std::string, std::string > bodyEntries
Fixed body key/values.
Definition RESTAPI.h:238
std::string url
URL template (with parameter placeholders).
Definition RESTAPI.h:231
RESTParser()
Create an uninitialised parser; call init() before use.
Definition RESTAPI.cpp:13
std::map< std::string, RESTHTTPCall * > httpCalls
Definition RESTAPI.h:187
virtual ~RESTParser()
Destructor; deletes the owned RESTHTTPCall objects.
Definition RESTAPI.cpp:16
bool init(const char *restDefinition)
Load and validate the XML REST API definition.
Definition RESTAPI.cpp:26
const char * getLastErrorString()
Definition RESTAPI.h:181
XMLNode findTopNodeForRequest(const char *requestString)
Definition RESTAPI.cpp:1339
bool extractParameters(HTTPRequest *req, std::map< std::string, RESTParam > &paramSet, RESTRequest *restReq)
Definition RESTAPI.cpp:497
std::map< std::string, std::map< std::string, RESTParam > > paramSets
Definition RESTAPI.h:186
bool extractArrayParameters(HTTPRequest *req, std::map< std::string, RESTParam > &paramSet, RESTRequest *restReq, int entryNum, const char *content, uint32 contentSize, const char *contentType)
Definition RESTAPI.cpp:899
std::string lastErrorString
Definition RESTAPI.h:185
bool setParameterValue(HTTPRequest *req, RESTParam &param, std::string value, RESTRequest *restReq, uint32 entryNum=0)
Definition RESTAPI.cpp:1201
RESTRequest * parseRequest(HTTPRequest *req)
Parse an incoming HTTP request against the API definition.
Definition RESTAPI.cpp:127
static bool UnitTest()
Run the RESTParser unit tests.
Definition RESTAPI.cpp:1426
std::map< std::string, RESTParam > parseParamSet(XMLNode objectNode)
Definition RESTAPI.cpp:85
A parsed and validated REST request with typed access to its parameters.
Definition RESTAPI.h:42
virtual ~RESTRequest()
Destructor; deletes the internal storage message.
Definition RESTAPI.cpp:1382
bool addToErrorString(const char *errorLine)
Definition RESTAPI.h:74
RESTHTTPCall * httpCall
Definition RESTAPI.h:56
bool setRequestSignature(const char *signature)
Definition RESTAPI.h:61
RESTRequest()
Create an empty request (status FAILED_EMPTY) with fresh internal storage.
Definition RESTAPI.h:83
std::string signature
Definition RESTAPI.h:50
bool setBinary(const char *key, const char *data, uint32 size, const char *contentType)
Definition RESTAPI.h:68
bool setDouble(const char *key, double val)
Definition RESTAPI.h:66
bool requireAuthentication
Definition RESTAPI.h:53
enum cmlabs::RESTRequest::Status status
bool setString(const char *key, const char *val)
Definition RESTAPI.h:67
char * getDataCopy(const char *key, uint32 &size, bool &success)
Definition RESTAPI.h:121
bool setInt(const char *key, int64 val)
Definition RESTAPI.h:65
std::string getHTTPCallURL()
Definition RESTAPI.cpp:1416
bool setStatus(Status status)
Definition RESTAPI.h:58
bool setFilter(const char *key, const char *val)
Definition RESTAPI.h:136
Status getStatus()
Definition RESTAPI.h:97
std::map< std::string, std::string > filterMap
Definition RESTAPI.h:54
const char * getString(const char *key, bool &success)
Definition RESTAPI.h:117
bool setRequireAuthentication(bool auth)
Definition RESTAPI.h:62
bool clearErrorState()
Definition RESTAPI.h:75
HTTPRequest * generateHTTPCallRequest()
Generate the outbound HTTPRequest for an HTTP-call request.
Definition RESTAPI.cpp:1410
bool getRequireAuthentication()
Definition RESTAPI.h:124
DataMessage * storage
Definition RESTAPI.h:52
const char * getErrorString()
Definition RESTAPI.h:127
DataMessage * getStorageMessageCopy()
Definition RESTAPI.cpp:1389
Status
Parse/validation outcome of the request.
Definition RESTAPI.h:46
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.
uint64 GetTimeNow()
Return the current absolute time (µs since year 0) according to the TMC.
Definition PsyTime.cpp:69
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
bool IsTextNumeric(const char *ascii, uint32 start=0, uint32 end=0)
Test whether a (sub)string is a valid number.
Definition Utils.cpp:7450
std::map< std::string, std::string > TextMapSplit(const char *text, const char *outersplit, const char *innersplit)
Split "k=v<sep>k=v..." text into a map (later duplicates overwrite earlier ones).
Definition Utils.cpp:6211
uint32 StringSingleReplace(std::string &text, std::string key, std::string value, bool onlyFirst)
Replace occurrences of key with value in text.
Definition Utils.cpp:6644
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 StringFormat(const char *format,...)
printf into a std::string.
Definition Utils.cpp:6626
int64 Ascii2Int64(const char *ascii, uint32 start=0, uint32 end=0)
Parse a signed 64-bit decimal integer from a substring.
Definition Utils.cpp:7462
float64 Ascii2Float64(const char *ascii, uint32 start=0, uint32 end=0)
Parse a 64-bit float from a substring (decimal point, not locale dependent).
Definition Utils.cpp:7546
const char * GetJSONValue(jsmntok_t *tokens, int tokenCount, const char *json, int token, int &valLength)
Definition jsmn.cpp:347
@ JSMN_UNDEFINED
Definition jsmn.h:29
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
std::vector< int > GetJSONArrayIndexes(jsmntok_t *tokens, int tokenCount, const char *json, const char *key, jsmntype_t type=JSMN_UNDEFINED, int level=0, int instance=0)
Definition jsmn.cpp:464
void jsmn_init(jsmn_parser *parser)
Create JSON parser over an array of tokens.
Definition jsmn.cpp:311
std::string GetURIFromURL(std::string url)
Extract the URI (path plus query) from a URL.
Definition HTML.cpp:348
bool IsMimeBinary(const char *type)
Determine whether a MIME type denotes binary content.
Definition HTML.cpp:130
std::string EncodeHTML(std::string str)
Encode a plain string for safe embedding in HTML (e.g.
Definition HTML.cpp:51
std::string GetHostFromURL(std::string url)
Extract the host name (or IP literal) from a URL.
Definition HTML.cpp:291
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.
@ eXMLErrorFirstTagNotFound
Definition xml_parser.h:142
@ eXMLErrorNone
Definition xml_parser.h:131
static char HTTP_Type[][8]
HTTP method name strings, indexed by the HTTP_* method ids (index 0 unused).
void Register_RESTParser_Tests()
Definition RESTAPI.cpp:1579
static struct PsyType CTRL_TEST
Definition ObjectIDs.h:82
Definition of one parameter of a REST request, as declared in the API XML.
Definition RESTAPI.h:21
enum cmlabs::RESTParam::Type type
Expected data type of the parameter.
bool allowEmpty
true if an empty value is acceptable.
Definition RESTAPI.h:24
std::string name
Parameter name.
Definition RESTAPI.h:25
std::string objects
For ARRAY parameters: the nested object/param-set name.
Definition RESTAPI.h:26
bool optional
true if the parameter may be omitted.
Definition RESTAPI.h:23
XMLCSTR getName() const
XMLAttribute getAttribute(int i=0) const
XMLNode getChildNode(int i=0) const
int nChildNode(XMLCSTR name) const
char isEmpty() const
enum XMLError error
Definition xml_parser.h:168
JSON parser.
Definition jsmn.h:65
JSON token description.
Definition jsmn.h:51