work in progress (tbf)
[src/app-framework-binder.git] / src / afb-rest-api.c
1 /*
2  * Copyright (C) 2016 "IoT.bzh"
3  * Author "Fulup Ar Foll"
4  * Author José Bollo <jose.bollo@iot.bzh>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  * 
19  * Contain all generic part to handle REST/API
20  * 
21  *  https://www.gnu.org/software/libmicrohttpd/tutorial.html [search 'largepost.c']
22  */
23
24 #define _GNU_SOURCE
25
26 #include "../include/local-def.h"
27
28 #include <dirent.h>
29 #include <dlfcn.h>
30 #include <setjmp.h>
31 #include <signal.h>
32
33 #include "afb-apis.h"
34 #include "session.h"
35
36 #define AFB_MSG_JTYPE "AJB_reply"
37
38 #define JSON_CONTENT  "application/json"
39 #define FORM_CONTENT  MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA
40
41 static json_object *afbJsonType;
42
43 // Because of POST call multiple time requestApi we need to free POST handle here
44 // Note this method is called from http-svc just before closing session
45 PUBLIC void endPostRequest(AFB_PostHandle * postHandle)
46 {
47
48         if (postHandle->type == AFB_POST_JSON) {
49                 // if (verbose) fprintf(stderr, "End PostJson Request UID=%d\n", postHandle->uid);
50         }
51
52         if (postHandle->type == AFB_POST_FORM) {
53                 if (verbose)
54                         fprintf(stderr, "End PostForm Request UID=%d\n", postHandle->uid);
55         }
56         if (postHandle->privatebuf)
57                 free(postHandle->privatebuf);
58         free(postHandle);
59 }
60
61 // Check of apiurl is declare in this plugin and call it
62 static AFB_error doCallPluginApi(AFB_request * request, int apiidx, int verbidx, void *context)
63 {
64         enum AFB_sessionE session;
65         json_object *jresp, *jcall, *jreqt;
66         AFB_clientCtx *clientCtx = NULL;
67
68         // Request was found and at least partially executed
69         jreqt = json_object_new_object();
70         json_object_object_add(jreqt, "jtype", json_object_get(afbJsonType));
71
72         // prepare an object to store calling values
73         jcall = json_object_new_object();
74         json_object_object_add(jcall, "prefix", json_object_new_string(request->prefix));
75         json_object_object_add(jcall, "api", json_object_new_string(request->method));
76
77         // Out of SessionNone every call get a client context session
78         session = afb_apis_get(apiidx, verbidx)->session;
79         if (AFB_SESSION_NONE != session) {
80
81                 // add client context to request
82                 clientCtx = ctxClientGet(request);
83                 if (clientCtx == NULL) {
84                         request->errcode = MHD_HTTP_INSUFFICIENT_STORAGE;
85                         json_add_status(jcall, "fail", "Client Session Context Full !!!");
86                         json_object_object_add(jreqt, "request", jcall);
87                         goto ExitOnDone;
88                 }
89                 request->context = clientCtx->contexts[apiidx];
90                 request->uuid = clientCtx->uuid;
91
92                 if (verbose)
93                         fprintf(stderr, "Plugin=[%s] Api=[%s] Middleware=[%d] Client=[%p] Uuid=[%s] Token=[%s]\n", request->prefix, request->method, session, clientCtx, clientCtx->uuid, clientCtx->token);
94
95                 switch (session) {
96
97                 case AFB_SESSION_CREATE:
98                         if (clientCtx->token[0] != '\0' && request->config->token[0] != '\0') {
99                                 request->errcode = MHD_HTTP_UNAUTHORIZED;
100                                 json_add_status(jcall, "exist", "AFB_SESSION_CREATE Session already exist");
101                                 json_object_object_add(jreqt, "request", jcall);
102                                 goto ExitOnDone;
103                         }
104
105                         if (AFB_SUCCESS != ctxTokenCreate(clientCtx, request)) {
106                                 request->errcode = MHD_HTTP_UNAUTHORIZED;
107                                 json_add_status(jcall, "fail", "AFB_SESSION_CREATE Invalid Initial Token");
108                                 json_object_object_add(jreqt, "request", jcall);
109                                 goto ExitOnDone;
110                         } else {
111                                 json_object_object_add(jcall, "uuid", json_object_new_string(clientCtx->uuid));
112                                 json_object_object_add(jcall, "token", json_object_new_string(clientCtx->token));
113                                 json_object_object_add(jcall, "timeout", json_object_new_int(request->config->cntxTimeout));
114                         }
115                         break;
116
117                 case AFB_SESSION_RENEW:
118                         if (AFB_SUCCESS != ctxTokenRefresh(clientCtx, request)) {
119                                 request->errcode = MHD_HTTP_UNAUTHORIZED;
120                                 json_add_status(jcall, "fail", "AFB_SESSION_REFRESH Broken Exchange Token Chain");
121                                 json_object_object_add(jreqt, "request", jcall);
122                                 goto ExitOnDone;
123                         } else {
124                                 json_object_object_add(jcall, "uuid", json_object_new_string(clientCtx->uuid));
125                                 json_object_object_add(jcall, "token", json_object_new_string(clientCtx->token));
126                                 json_object_object_add(jcall, "timeout", json_object_new_int(request->config->cntxTimeout));
127                         }
128                         break;
129
130                 case AFB_SESSION_CLOSE:
131                         if (AFB_SUCCESS != ctxTokenCheck(clientCtx, request)) {
132                                 request->errcode = MHD_HTTP_UNAUTHORIZED;
133                                 json_add_status(jcall, "fail", "AFB_SESSION_CLOSE Not a Valid Access Token"));
134                                 json_object_object_add(jreqt, "request", jcall);
135                                 goto ExitOnDone;
136                         } else {
137                                 json_object_object_add(jcall, "uuid", json_object_new_string(clientCtx->uuid));
138                         }
139                         break;
140
141                 case AFB_SESSION_CHECK:
142                 default:
143                         // default action is check
144                         if (AFB_SUCCESS != ctxTokenCheck(clientCtx, request)) {
145                                 request->errcode = MHD_HTTP_UNAUTHORIZED;
146                                 json_add_status(jcall, "fail", "AFB_SESSION_CHECK Invalid Active Token"));
147                                 json_object_object_add(jreqt, "request", jcall);
148                                 goto ExitOnDone;
149                         }
150                         break;
151                 }
152         }
153
154         // Effectively CALL PLUGIN API with a subset of the context
155         jresp = afb_apis_get(apiidx, verbidx)->callback(request, context);
156
157         // Store context in case it was updated by plugins
158         if (request->context != NULL)
159                 clientCtx->contexts[apiidx] = request->context;
160
161         // handle intermediary Post Iterates out of band
162         if ((jresp == NULL) && (request->errcode == MHD_HTTP_OK))
163                 return AFB_SUCCESS;
164
165         // Session close is done after the API call so API can still use session in closing API
166         if (AFB_SESSION_CLOSE == session)
167                 ctxTokenReset(clientCtx, request);
168
169         // API should return NULL of a valid Json Object
170         if (jresp == NULL) {
171                 json_object_object_add(jcall, "status", json_object_new_string("null"));
172                 json_object_object_add(jreqt, "request", jcall);
173                 request->errcode = MHD_HTTP_NO_RESPONSE;
174
175         } else {
176                 json_object_object_add(jcall, "status", json_object_new_string("processed"));
177                 json_object_object_add(jreqt, "request", jcall);
178                 json_object_object_add(jreqt, "response", jresp);
179         }
180
181 ExitOnDone:
182         request->jresp = jreqt;
183         return AFB_DONE;
184 }
185
186 // Check of apiurl is declare in this plugin and call it
187 extern __thread sigjmp_buf *error_handler;
188 static AFB_error callPluginApi(AFB_request * request, int apiidx, int verbidx, void *context)
189 {
190         sigjmp_buf jmpbuf, *older;
191
192         json_object *jcall, *jreqt;
193         int status;
194
195         // save context before calling the API
196         status = setjmp(jmpbuf);
197         if (status != 0) {
198
199                 // Request was found and at least partially executed
200                 jreqt = json_object_new_object();
201                 json_object_object_add(jreqt, "jtype", json_object_get(afbJsonType));
202
203                 // prepare an object to store calling values
204                 jcall = json_object_new_object();
205                 json_object_object_add(jcall, "prefix", json_object_new_string(request->prefix));
206                 json_object_object_add(jcall, "api", json_object_new_string(request->method));
207
208                 // Plugin aborted somewhere during its execution
209                 json_object_object_add(jcall, "status", json_object_new_string("abort"));
210                 json_object_object_add(jcall, "info", json_object_new_string("Plugin broke during execution"));
211                 json_object_object_add(jreqt, "request", jcall);
212                 request->jresp = jreqt;
213         } else {
214
215                 // Trigger a timer to protect from unacceptable long time execution
216                 if (request->config->apiTimeout > 0)
217                         alarm((unsigned)request->config->apiTimeout);
218
219                 older = error_handler;
220                 error_handler = &jmpbuf;
221                 doCallPluginApi(request, apiidx, verbidx, context);
222                 error_handler = older;
223
224                 // cancel timeout and plugin signal handle before next call
225                 alarm(0);
226         }
227         return AFB_DONE;
228 }
229
230 STATIC AFB_error findAndCallApi(AFB_request * request, void *context)
231 {
232         int apiidx, verbidx;
233         AFB_error status;
234
235         if (!request->method || !request->prefix)
236                 return AFB_FAIL;
237
238         /* get the plugin if any */
239         apiidx = afb_apis_get_apiidx(request->prefix, 0);
240         if (apiidx < 0) {
241                 request->jresp = jsonNewMessage(AFB_FATAL, "No Plugin=[%s] Url=%s", request->prefix, request->url);
242                 request->errcode = MHD_HTTP_UNPROCESSABLE_ENTITY;
243                 return AFB_FAIL;
244         }
245
246         /* get the verb if any */
247         verbidx = afb_apis_get_verbidx(apiidx, request->method);
248         if (verbidx < 0) {
249                 request->jresp = jsonNewMessage(AFB_FATAL, "No API=[%s] for Plugin=[%s] url=[%s]", request->method, request->prefix, request->url);
250                 request->errcode = MHD_HTTP_UNPROCESSABLE_ENTITY;
251                 return AFB_FAIL;
252         }
253
254         /* Search for a plugin with this urlpath */
255         status = callPluginApi(request, apiidx, verbidx, context);
256
257         /* plugin callback did not return a valid Json Object */
258         if (status == AFB_FAIL) {
259                 request->jresp = jsonNewMessage(AFB_FATAL, "No API=[%s] for Plugin=[%s] url=[%s]", request->method, request->prefix, request->url);
260                 request->errcode = MHD_HTTP_UNPROCESSABLE_ENTITY;
261                 return AFB_FAIL;
262         }
263         // Everything look OK
264         return status;
265 }
266
267 // This CB is call for every item with a form post it reformat iterator values
268 // and callback Plugin API for each Item within PostForm.
269 STATIC int doPostIterate(void *cls, enum MHD_ValueKind kind, const char *key, const char *filename, const char *mimetype, const char *encoding, const char *data, uint64_t offset, size_t size)
270 {
271
272         AFB_error status;
273         AFB_PostItem item;
274
275         // retrieve API request from Post iterator handle  
276         AFB_PostHandle *postHandle = (AFB_PostHandle *) cls;
277         AFB_request *request = (AFB_request *) postHandle->privatebuf;
278         AFB_PostRequest postRequest;
279
280         if (verbose)
281                 fprintf(stderr, "postHandle key=%s filename=%s len=%zu mime=%s\n", key, filename, size, mimetype);
282
283         // Create and Item value for Plugin API
284         item.kind = kind;
285         item.key = key;
286         item.filename = filename;
287         item.mimetype = mimetype;
288         item.encoding = encoding;
289         item.len = size;
290         item.data = data;
291         item.offset = offset;
292
293         // Reformat Request to make it somehow similar to GET/PostJson case
294         postRequest.data = (char *)postHandle;
295         postRequest.len = size;
296         postRequest.type = AFB_POST_FORM;;
297         request->post = &postRequest;
298
299         // effectively call plugin API                 
300         status = findAndCallApi(request, &item);
301         // when returning no processing of postform stop
302         if (status != AFB_SUCCESS)
303                 return MHD_NO;
304
305         // let's allow iterator to move to next item
306         return MHD_YES;
307 }
308
309 STATIC void freeRequest(AFB_request * request)
310 {
311
312         free((void*)request->prefix);
313         free((void*)request->method);
314         free(request);
315 }
316
317 STATIC AFB_request *createRequest(struct MHD_Connection *connection, AFB_session * session, const char *url)
318 {
319
320         AFB_request *request;
321
322         // Start with a clean request
323         request = calloc(1, sizeof(AFB_request));
324         char *urlcpy1, *urlcpy2;
325         char *baseapi, *baseurl;
326
327         // Extract plugin urlpath from request and make two copy because strsep overload copy
328         urlcpy1 = urlcpy2 = strdup(url);
329         baseurl = strsep(&urlcpy2, "/");
330         if (baseurl == NULL) {
331                 request->jresp = jsonNewMessage(AFB_FATAL, "Invalid API call url=[%s]", url);
332                 request->errcode = MHD_HTTP_BAD_REQUEST;
333                 goto Done;
334         }
335         // let's compute URL and call API
336         baseapi = strsep(&urlcpy2, "/");
337         if (baseapi == NULL) {
338                 request->jresp = jsonNewMessage(AFB_FATAL, "Invalid API call plugin=[%s] url=[%s]", baseurl, url);
339                 request->errcode = MHD_HTTP_BAD_REQUEST;
340                 goto Done;
341         }
342         // build request structure
343 //      request->connection = connection;
344         request->config = session->config;
345         request->url = url;
346         request->prefix = strdup(baseurl);
347         request->method = strdup(baseapi);
348
349  Done:
350         free(urlcpy1);
351         return (request);
352 }
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368 static int doRestApiPost(struct MHD_Connection *connection, AFB_session * session, const char *url, const char *method, const char *upload_data, size_t * upload_data_size, void **con_cls)
369 {
370
371         static int postcount = 0;       // static counter to debug POST protocol
372         json_object *errMessage;
373         AFB_error status;
374         struct MHD_Response *webResponse;
375         const char *serialized;
376         AFB_request *request = NULL;
377         AFB_PostHandle *postHandle;
378         AFB_PostRequest postRequest;
379         int ret;
380
381         // fprintf (stderr, "doRestAPI method=%s posthandle=%p\n", method, con_cls);
382
383         // if post data may come in multiple calls
384         const char *encoding, *param;
385         int contentlen = -1;
386         postHandle = *con_cls;
387
388         // This is the initial post event let's create form post structure POST data come in multiple events
389         if (postHandle == NULL) {
390
391                 // allocate application POST processor handle to zero
392                 postHandle = calloc(1, sizeof(AFB_PostHandle));
393                 postHandle->uid = postcount++;  // build a UID for DEBUG
394                 *con_cls = postHandle;  // update context with posthandle
395
396                 // Let make sure we have the right encoding and a valid length
397                 encoding = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE);
398
399                 // We are facing an empty post let's process it as a get
400                 if (encoding == NULL) {
401                         postHandle->type = AFB_POST_EMPTY;
402                         return MHD_YES;
403                 }
404
405                 // Form post is handle through a PostProcessor and call API once per form key
406                 if (strcasestr(encoding, FORM_CONTENT) != NULL) {
407                         if (verbose)
408                                 fprintf(stderr, "Create doPostIterate[uid=%d posthandle=%p]\n", postHandle->uid, postHandle);
409
410                         request = createRequest(connection, session, url);
411                         if (request->jresp != NULL)
412                                 goto ProcessApiCall;
413                         postHandle->type = AFB_POST_FORM;
414                         postHandle->privatebuf = (void *)request;
415                         postHandle->pp = MHD_create_post_processor(connection, MAX_POST_SIZE, &doPostIterate, postHandle);
416
417                         if (NULL == postHandle->pp) {
418                                 fprintf(stderr, "OOPS: Internal error fail to allocate MHD_create_post_processor\n");
419                                 free(postHandle);
420                                 return MHD_NO;
421                         }
422                         return MHD_YES;
423                 }
424                 // POST json is store into a buffer and present in one piece to API
425                 if (strcasestr(encoding, JSON_CONTENT) != NULL) {
426
427                         param = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH);
428                         if (param)
429                                 sscanf(param, "%i", &contentlen);
430
431                         // Because PostJson are build in RAM size is constrained
432                         if (contentlen > MAX_POST_SIZE) {
433                                 errMessage = jsonNewMessage(AFB_FATAL, "Post Date to big %d > %d", contentlen, MAX_POST_SIZE);
434                                 goto ExitOnError;
435                         }
436                         // Size is OK, let's allocate a buffer to hold post data
437                         postHandle->type = AFB_POST_JSON;
438                         postHandle->privatebuf = malloc((unsigned)contentlen + 1);      // allocate memory for full POST data + 1 for '\0' enf of string
439
440                         // if (verbose) fprintf(stderr, "Create PostJson[uid=%d] Size=%d\n", postHandle->uid, contentlen);
441                         return MHD_YES;
442
443                 }
444                 // We only support Json and Form Post format
445                 errMessage = jsonNewMessage(AFB_FATAL, "Post Date wrong type encoding=%s != %s", encoding, JSON_CONTENT);
446                 goto ExitOnError;
447         }
448
449         // This time we receive partial/all Post data. Note that even if we get all POST data. We should nevertheless
450         // return MHD_YES and not process the request directly. Otherwise Libmicrohttpd is unhappy and fails with
451         // 'Internal application error, closing connection'.            
452         if (*upload_data_size) {
453
454                 if (postHandle->type == AFB_POST_FORM) {
455                         // if (verbose) fprintf(stderr, "Processing PostForm[uid=%d]\n", postHandle->uid);
456                         MHD_post_process(postHandle->pp, upload_data, *upload_data_size);
457                 }
458                 // Process JsonPost request when buffer is completed let's call API    
459                 if (postHandle->type == AFB_POST_JSON) {
460                         // if (verbose) fprintf(stderr, "Updating PostJson[uid=%d]\n", postHandle->uid);
461                         memcpy(&postHandle->privatebuf[postHandle->len], upload_data, *upload_data_size);
462                         postHandle->len = postHandle->len + *upload_data_size;
463                 }
464
465                 *upload_data_size = 0;
466                 return MHD_YES;
467
468         }
469
470         if (postHandle->type == AFB_POST_FORM)
471                 request = postHandle->privatebuf;
472         else
473                 // Create a request structure to finalise the request
474                 request = createRequest(connection, session, url);
475
476         if (request->jresp != NULL) {
477                 errMessage = request->jresp;
478                 goto ExitOnError;
479         }
480         postRequest.type = postHandle->type;
481
482         // Postform add application context handle to request
483         if (postHandle->type == AFB_POST_FORM) {
484                 postRequest.data = (char *)postHandle;
485                 request->post = &postRequest;
486         }
487
488         if (postHandle->type == AFB_POST_JSON) {
489                 // if (verbose) fprintf(stderr, "Processing PostJson[uid=%d]\n", postHandle->uid);
490
491                 param = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH);
492                 if (param)
493                         sscanf(param, "%i", &contentlen);
494
495                 // At this level we're may verify that we got everything and process DATA
496                 if (postHandle->len != contentlen) {
497                         errMessage = jsonNewMessage(AFB_FATAL, "Post Data Incomplete UID=%d Len %d != %d", postHandle->uid, contentlen, postHandle->len);
498                         goto ExitOnError;
499                 }
500                 // Before processing data, make sure buffer string is properly ended
501                 postHandle->privatebuf[postHandle->len] = '\0';
502                 postRequest.data = postHandle->privatebuf;
503                 request->post = &postRequest;
504
505                 // if (verbose) fprintf(stderr, "Close Post[%d] Buffer=%s\n", postHandle->uid, request->post->data);
506         }
507
508  ProcessApiCall:
509         // Request is ready let's call API without any extra handle
510         status = findAndCallApi(request, NULL);
511
512         serialized = json_object_to_json_string(request->jresp);
513         webResponse = MHD_create_response_from_buffer(strlen(serialized), (void *)serialized, MHD_RESPMEM_MUST_COPY);
514
515         // client did not pass token on URI let's use cookies 
516         if ((!request->restfull) && (request->context != NULL)) {
517                 char cookie[256];
518                 snprintf(cookie, sizeof(cookie), "%s-%d=%s; Path=%s; Max-Age=%d; HttpOnly", COOKIE_NAME, request->config->httpdPort, request->uuid, request->config->rootapi, request->config->cntxTimeout);
519                 MHD_add_response_header(webResponse, MHD_HTTP_HEADER_SET_COOKIE, cookie);
520         }
521         // if requested add an error status
522         if (request->errcode != 0)
523                 ret = MHD_queue_response(connection, request->errcode, webResponse);
524         else
525                 MHD_queue_response(connection, MHD_HTTP_OK, webResponse);
526
527         MHD_destroy_response(webResponse);
528         json_object_put(request->jresp);        // decrease reference rqtcount to free the json object
529         freeRequest(request);
530         return MHD_YES;
531
532  ExitOnError:
533         freeRequest(request);
534         serialized = json_object_to_json_string(errMessage);
535         webResponse = MHD_create_response_from_buffer(strlen(serialized), (void *)serialized, MHD_RESPMEM_MUST_COPY);
536         MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, webResponse);
537         MHD_destroy_response(webResponse);
538         json_object_put(errMessage);    // decrease reference rqtcount to free the json object
539         return MHD_YES;
540 }
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561 static int doRestApiGet(struct MHD_Connection *connection, AFB_session * session, const char *url, const char *method, const char *upload_data, size_t * upload_data_size, void **con_cls)
562 {
563         AFB_error status;
564         struct MHD_Response *webResponse;
565         const char *serialized;
566         AFB_request *request = NULL;
567         int ret;
568
569         // fprintf (stderr, "doRestAPI method=%s posthandle=%p\n", method, con_cls);
570
571         // if post data may come in multiple calls
572         // this is a get we only need a request
573         request = createRequest(connection, session, url);
574
575         // Request is ready let's call API without any extra handle
576         status = findAndCallApi(request, NULL);
577
578         serialized = json_object_to_json_string(request->jresp);
579         webResponse = MHD_create_response_from_buffer(strlen(serialized), (void *)serialized, MHD_RESPMEM_MUST_COPY);
580
581         // client did not pass token on URI let's use cookies 
582         if ((!request->restfull) && (request->context != NULL)) {
583                 char cookie[256];
584                 snprintf(cookie, sizeof(cookie), "%s-%d=%s; Path=%s; Max-Age=%d; HttpOnly", COOKIE_NAME, request->config->httpdPort, request->uuid, request->config->rootapi, request->config->cntxTimeout);
585                 MHD_add_response_header(webResponse, MHD_HTTP_HEADER_SET_COOKIE, cookie);
586         }
587         // if requested add an error status
588         if (request->errcode != 0)
589                 ret = MHD_queue_response(connection, request->errcode, webResponse);
590         else
591                 MHD_queue_response(connection, MHD_HTTP_OK, webResponse);
592
593         MHD_destroy_response(webResponse);
594         json_object_put(request->jresp);        // decrease reference rqtcount to free the json object
595         freeRequest(request);
596         return MHD_YES;
597 }
598
599 int doRestApi(struct MHD_Connection *connection, AFB_session * session, const char *url, const char *method, const char *upload_data, size_t * upload_data_size, void **con_cls)
600 {
601         int rc;
602
603         if (afbJsonType == NULL)
604                 afbJsonType = json_object_new_string (AFB_MSG_JTYPE);
605
606         if (0 == strcmp(method, MHD_HTTP_METHOD_POST)) {
607                 rc = doRestApiPost(connection, session, url, method, upload_data, upload_data_size, con_cls);
608         } else {
609                 rc = doRestApiGet(connection, session, url, method, upload_data, upload_data_size, con_cls);
610         }
611         return rc;
612 }
613