diff options
-rw-r--r-- | include/afb-req-itf.h | 31 | ||||
-rw-r--r-- | plugins/samples/ClientCtx.c | 76 | ||||
-rw-r--r-- | plugins/samples/HelloWorld.c | 82 | ||||
-rw-r--r-- | plugins/session/token-api.c | 64 | ||||
-rw-r--r-- | src/afb-apis.c | 20 | ||||
-rw-r--r-- | src/afb-rest-api.c | 613 | ||||
-rw-r--r-- | src/local-def.h (renamed from include/local-def.h) | 0 | ||||
-rw-r--r-- | src/proto-def.h (renamed from include/proto-def.h) | 0 | ||||
-rw-r--r-- | test/hello-world.html | 1 |
9 files changed, 153 insertions, 734 deletions
diff --git a/include/afb-req-itf.h b/include/afb-req-itf.h index eea78317..c593440b 100644 --- a/include/afb-req-itf.h +++ b/include/afb-req-itf.h @@ -15,6 +15,8 @@ * limitations under the License. */ +struct json_object; + struct afb_arg { const char *name; const char *value; @@ -26,12 +28,13 @@ struct afb_req_itf { struct afb_arg (*get)(void *data, const char *name); void (*iterate)(void *data, int (*iterator)(void *closure, struct afb_arg arg), void *closure); void (*fail)(void *data, const char *status, const char *info); - void (*success)(void *data, json_object *obj, const char *info); + void (*success)(void *data, struct json_object *obj, const char *info); }; struct afb_req { const struct afb_req_itf *itf; void *data; + void **context; }; static inline struct afb_arg afb_req_get(struct afb_req req, const char *name) @@ -54,15 +57,24 @@ static inline void afb_req_iterate(struct afb_req req, int (*iterator)(void *clo req.itf->iterate(req.data, iterator, closure); } -#include <stdarg.h> -#include <stdlib.h> -#include <stdio.h> - static inline void afb_req_fail(struct afb_req req, const char *status, const char *info) { req.itf->fail(req.data, status, info); } +static inline void afb_req_success(struct afb_req req, struct json_object *obj, const char *info) +{ + req.itf->success(req.data, obj, info); +} + +#if !defined(_GNU_SOURCE) +# error "_GNU_SOURCE must be defined for using vasprintf" +#endif + +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> + static inline void afb_req_fail_v(struct afb_req req, const char *status, const char *info, va_list args) { char *message; @@ -80,12 +92,7 @@ static inline void afb_req_fail_f(struct afb_req req, const char *status, const va_end(args); } -static inline void afb_req_success(struct afb_req req, json_object *obj, const char *info) -{ - req.itf->success(req.data, obj, info); -} - -static inline void afb_req_success_v(struct afb_req req, json_object *obj, const char *info, va_list args) +static inline void afb_req_success_v(struct afb_req req, struct json_object *obj, const char *info, va_list args) { char *message; if (info == NULL || vasprintf(&message, info, args) < 0) @@ -94,7 +101,7 @@ static inline void afb_req_success_v(struct afb_req req, json_object *obj, const free(message); } -static inline void afb_req_success_f(struct afb_req req, json_object *obj, const char *info, ...) +static inline void afb_req_success_f(struct afb_req req, struct json_object *obj, const char *info, ...) { va_list args; va_start(args, info); diff --git a/plugins/samples/ClientCtx.c b/plugins/samples/ClientCtx.c index 175b9387..cda6abbb 100644 --- a/plugins/samples/ClientCtx.c +++ b/plugins/samples/ClientCtx.c @@ -16,9 +16,12 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#define _GNU_SOURCE #include <stdio.h> +#include <json.h> -#include "local-def.h" +#include "afb-plugin.h" +#include "afb-req-itf.h" typedef struct { /* @@ -58,61 +61,53 @@ typedef struct { // Plugin handle should not be in stack (malloc or static) -STATIC MyPluginHandleT global_handle; +static MyPluginHandleT global_handle; // This function is call at session open time. Any client trying to // call it with an already open session will be denied. // Ex: http://localhost:1234/api/context/create?token=123456789 -STATIC json_object* myCreate (AFB_request *request) { - json_object *jresp; - - MyClientContextT *ctx= malloc (sizeof (MyClientContextT)); +static void myCreate (struct afb_req request) +{ + MyClientContextT *ctx = malloc (sizeof (MyClientContextT)); MyPluginHandleT *handle = (MyPluginHandleT*) &global_handle; // store something in our plugin private client context ctx->count = 0; ctx->abcd = "SomeThingUseful"; - request->context = ctx; - jresp = jsonNewMessage(AFB_SUCCESS, "SUCCESS: create client context for plugin [%s]", handle->anythingYouWant); - - return jresp; + *request.context = ctx; + afb_req_success_f(request, NULL, "SUCCESS: create client context for plugin [%s]", handle->anythingYouWant); } // This function can only be called with a valid token. Token should be renew before // session timeout a standard renew api is avaliable at /api/token/renew this API // can be called automatically with <token-renew> HTML5 widget. // ex: http://localhost:1234/api/context/action?token=xxxxxx-xxxxxx-xxxxx-xxxxx-xxxxxx -STATIC json_object* myAction (AFB_request *request) { - json_object* jresp; +static void myAction (struct afb_req request) +{ MyPluginHandleT *handle = (MyPluginHandleT*) &global_handle; - MyClientContextT *ctx= (MyClientContextT*) request->context; + MyClientContextT *ctx = (MyClientContextT*) *request.context; // store something in our plugin private client context ctx->count++; - jresp = jsonNewMessage(AFB_SUCCESS, "SUCCESS: plugin [%s] Check=[%d]\n", handle->anythingYouWant, ctx->count); - - return jresp; + afb_req_success_f(request, NULL, "SUCCESS: plugin [%s] Check=[%d]\n", handle->anythingYouWant, ctx->count); } // After execution of this function, client session will be close and if they // created a context [request->context != NULL] every plugins will be notified // that they should free context resources. // ex: http://localhost:1234/api/context/close?token=xxxxxx-xxxxxx-xxxxx-xxxxx-xxxxxx -STATIC json_object* myClose (AFB_request *request) { - json_object* jresp; +static void myClose (struct afb_req request) +{ MyPluginHandleT *handle = (MyPluginHandleT*) &global_handle; - MyClientContextT *ctx= (MyClientContextT*) request->context; + MyClientContextT *ctx = (MyClientContextT*) *request.context; // store something in our plugin private client context ctx->count++; - jresp = jsonNewMessage(AFB_SUCCESS, "SUCCESS: plugin [%s] Close=[%d]\n", handle->anythingYouWant, ctx->count); - - // Note Context resource should be free in FreeCtxCB and not here in case session timeout. - return jresp; + afb_req_success_f(request, NULL, "SUCCESS: plugin [%s] Close=[%d]\n", handle->anythingYouWant, ctx->count); } -STATIC void freeCtxCB (MyClientContextT *ctx) { +static void freeCtxCB (MyClientContextT *ctx) { MyPluginHandleT *handle = (MyPluginHandleT*) &global_handle; fprintf (stderr, "FreeCtxCB Plugin=[%s] count=[%d]", (char*)handle->anythingYouWant, ctx->count); free (ctx); @@ -122,24 +117,23 @@ STATIC void freeCtxCB (MyClientContextT *ctx) { // NOTE: this sample does not use session to keep test a basic as possible // in real application most APIs should be protected with AFB_SESSION_CHECK -STATIC AFB_restapi pluginApis[]= { - {"create", AFB_SESSION_CREATE, (AFB_apiCB)myCreate , "Create a new session"}, - {"action", AFB_SESSION_CHECK , (AFB_apiCB)myAction , "Use Session Context"}, - {"close" , AFB_SESSION_CLOSE , (AFB_apiCB)myClose , "Free Context"}, +static const struct AFB_restapi pluginApis[]= { + {"create", AFB_SESSION_CREATE, myCreate , "Create a new session"}, + {"action", AFB_SESSION_CHECK , myAction , "Use Session Context"}, + {"close" , AFB_SESSION_CLOSE , myClose , "Free Context"}, {NULL} }; -PUBLIC AFB_plugin *pluginRegister () { - - AFB_plugin *plugin = malloc (sizeof (AFB_plugin)); - plugin->type = AFB_PLUGIN_JSON; - plugin->info = "Sample of Client Context Usage"; - plugin->prefix = "context"; - plugin->apis = pluginApis; - plugin->freeCtxCB= (AFB_freeCtxCB) freeCtxCB; - - // feed plugin handle before returning from registration - global_handle.anythingYouWant = "My Plugin Handle"; - - return (plugin); +static const struct AFB_plugin plugin_desc = { + .type = AFB_PLUGIN_JSON, + .info = "Sample of Client Context Usage", + .prefix = "context", + .apis = pluginApis, + .freeCtxCB = (void*)freeCtxCB }; + +const struct AFB_plugin *pluginRegister () +{ + return &plugin_desc; +} + diff --git a/plugins/samples/HelloWorld.c b/plugins/samples/HelloWorld.c index 01275aa3..4f0af71f 100644 --- a/plugins/samples/HelloWorld.c +++ b/plugins/samples/HelloWorld.c @@ -16,14 +16,41 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #define _GNU_SOURCE - #include <stdio.h> #include <string.h> +#include <json.h> -#include "local-def.h" +#include "afb-plugin.h" #include "afb-req-itf.h" -static json_object* ping (AFB_request *request, json_object *jresp) { +typedef struct queryHandleT { + char *msg; + size_t idx; + size_t len; +} queryHandleT; + +static int getQueryCB (queryHandleT *query, struct afb_arg arg) { + if (query->idx >= query->len) + return 0; + query->idx += (unsigned)snprintf (&query->msg[query->idx], query->len-query->idx, " %s: %s\'%s\',", arg.name, arg.is_file?"FILE=":"", arg.value); + return 1; /* continue to iterate */ +} + +// Helper to retrieve argument from connection +static size_t getQueryAll(struct afb_req request, char *buffer, size_t len) { + queryHandleT query; + buffer[0] = '\0'; // start with an empty string + query.msg = buffer; + query.len = len; + query.idx = 0; + + afb_req_iterate(request, (void*)getQueryCB, &query); + buffer[len-1] = 0; + return query.idx >= len ? len - 1 : query.idx; +} + +static void ping (struct afb_req request, json_object *jresp) +{ static int pingcount = 0; char query [512]; size_t len; @@ -34,21 +61,28 @@ static json_object* ping (AFB_request *request, json_object *jresp) { // return response to caller // response = jsonNewMessage(AFB_SUCCESS, "Ping Binder Daemon %d query={%s}", pingcount++, query); - afb_req_success_f(*request->areq, jresp, "Ping Binder Daemon %d query={%s}", pingcount++, query); + afb_req_success_f(request, jresp, "Ping Binder Daemon %d query={%s}", pingcount++, query); - if (verbose) fprintf(stderr, "%d: \n", pingcount); - return jresp; + fprintf(stderr, "%d: \n", pingcount); +} + +static void pingSample (struct afb_req request) +{ + ping(request, json_object_new_string ("Some String")); } -static json_object* pingSample (AFB_request *request) { - return ping(request, json_object_new_string ("Some String")); +static void pingFail (struct afb_req request) +{ + afb_req_fail(request, "failed", "Ping Binder Daemon fails"); } -static json_object* pingFail (AFB_request *request) { - return ping(request, NULL); +static void pingNull (struct afb_req request) +{ + ping(request, NULL); } -static json_object* pingBug (AFB_request *request) { +static void pingBug (struct afb_req request) +{ int a,b,c; fprintf (stderr, "Use --timeout=10 to trap error\n"); @@ -56,13 +90,11 @@ static json_object* pingBug (AFB_request *request) { c=0; a=b/c; - // should never return - return NULL; } // For samples https://linuxprograms.wordpress.com/2010/05/20/json-c-libjson-tutorial/ -static json_object* pingJson (AFB_request *request) { +static void pingJson (struct afb_req request) { json_object *jresp, *embed; jresp = json_object_new_object(); @@ -75,26 +107,28 @@ static json_object* pingJson (AFB_request *request) { json_object_object_add(jresp,"eobj", embed); - return ping(request, jresp); + ping(request, jresp); } // NOTE: this sample does not use session to keep test a basic as possible // in real application most APIs should be protected with AFB_SESSION_CHECK -static AFB_restapi pluginApis[]= { - {"ping" , AFB_SESSION_NONE, (AFB_apiCB)pingSample , "Ping Application Framework"}, - {"pingnull" , AFB_SESSION_NONE, (AFB_apiCB)pingFail , "Return NULL"}, - {"pingbug" , AFB_SESSION_NONE, (AFB_apiCB)pingBug , "Do a Memory Violation"}, - {"pingJson" , AFB_SESSION_NONE, (AFB_apiCB)pingJson , "Return a JSON object"}, +static const struct AFB_restapi pluginApis[]= { + {"ping" , AFB_SESSION_NONE, pingSample , "Ping Application Framework"}, + {"pingfail" , AFB_SESSION_NONE, pingFail , "Fails"}, + {"pingnull" , AFB_SESSION_NONE, pingNull , "Return NULL"}, + {"pingbug" , AFB_SESSION_NONE, pingBug , "Do a Memory Violation"}, + {"pingJson" , AFB_SESSION_NONE, pingJson , "Return a JSON object"}, {NULL} }; -static const AFB_plugin plugin = { +static const struct AFB_plugin plugin_desc = { .type = AFB_PLUGIN_JSON, .info = "Minimal Hello World Sample", .prefix = "hello", .apis = pluginApis }; -const AFB_plugin *pluginRegister () { - return &plugin; -}; +const struct AFB_plugin *pluginRegister () +{ + return &plugin_desc; +} diff --git a/plugins/session/token-api.c b/plugins/session/token-api.c index feb269dd..65c18d26 100644 --- a/plugins/session/token-api.c +++ b/plugins/session/token-api.c @@ -16,9 +16,12 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#define _GNU_SOURCE #include <stdio.h> +#include <json.h> -#include "local-def.h" +#include "afb-plugin.h" +#include "afb-req-itf.h" // Dummy sample of Client Application Context typedef struct { @@ -28,43 +31,44 @@ typedef struct { // Request Creation of new context if it does not exist -STATIC json_object* clientContextCreate (AFB_request *request) { +static void clientContextCreate (struct afb_req request) +{ json_object *jresp; // add an application specific client context to session - request->context = malloc (sizeof (MyClientApplicationHandle)); + *request.context = malloc (sizeof (MyClientApplicationHandle)); // Send response to UI jresp = json_object_new_object(); json_object_object_add(jresp, "token", json_object_new_string ("A New Token and Session Context Was Created")); - return (jresp); + afb_req_success(request, jresp, NULL); } // Before entering here token will be check and renew -STATIC json_object* clientContextRefresh (AFB_request *request) { +static void clientContextRefresh (struct afb_req request) { json_object *jresp; jresp = json_object_new_object(); json_object_object_add(jresp, "token", json_object_new_string ("Token was refreshed")); - return (jresp); + afb_req_success(request, jresp, NULL); } // Session token will we verified before entering here -STATIC json_object* clientContextCheck (AFB_request *request) { +static void clientContextCheck (struct afb_req request) { json_object *jresp = json_object_new_object(); json_object_object_add(jresp, "isvalid", json_object_new_boolean (TRUE)); - return (jresp); + afb_req_success(request, jresp, NULL); } // Close and Free context -STATIC json_object* clientContextReset (AFB_request *request) { +static void clientContextReset (struct afb_req request) { json_object *jresp; /* after this call token will be reset @@ -75,44 +79,46 @@ STATIC json_object* clientContextReset (AFB_request *request) { jresp = json_object_new_object(); json_object_object_add(jresp, "info", json_object_new_string ("Token and all resources are released")); - // WARNING: if you free context resource manually here do not forget to set request->context=NULL; - return (jresp); + // WARNING: if you free context resource manually here do not forget to set *request.context=NULL; + afb_req_success(request, jresp, NULL); } // Close and Free context -STATIC json_object* clientGetPing (AFB_request *request) { +static void clientGetPing (struct afb_req request) { static int count=0; json_object *jresp; jresp = json_object_new_object(); json_object_object_add(jresp, "count", json_object_new_int (count ++)); - return (jresp); + afb_req_success(request, jresp, NULL); } // This function is call when Client Session Context is removed // Note: when freeCtxCB==NULL standard free/malloc is called -STATIC void clientContextFree(void *context) { +static void clientContextFree(void *context) { fprintf (stderr,"Plugin[token] Closing Session\n"); free (context); } -STATIC AFB_restapi pluginApis[]= { - {"ping" , AFB_SESSION_NONE , (AFB_apiCB)clientGetPing ,"Ping Rest Test Service"}, - {"create" , AFB_SESSION_CREATE, (AFB_apiCB)clientContextCreate ,"Request Client Context Creation"}, - {"refresh" , AFB_SESSION_RENEW , (AFB_apiCB)clientContextRefresh,"Refresh Client Context Token"}, - {"check" , AFB_SESSION_CHECK , (AFB_apiCB)clientContextCheck ,"Check Client Context Token"}, - {"reset" , AFB_SESSION_CLOSE , (AFB_apiCB)clientContextReset ,"Close Client Context and Free resources"}, +static const struct AFB_restapi pluginApis[]= { + {"ping" , AFB_SESSION_NONE , clientGetPing ,"Ping Rest Test Service"}, + {"create" , AFB_SESSION_CREATE, clientContextCreate ,"Request Client Context Creation"}, + {"refresh" , AFB_SESSION_RENEW , clientContextRefresh,"Refresh Client Context Token"}, + {"check" , AFB_SESSION_CHECK , clientContextCheck ,"Check Client Context Token"}, + {"reset" , AFB_SESSION_CLOSE , clientContextReset ,"Close Client Context and Free resources"}, {NULL} }; -PUBLIC AFB_plugin *pluginRegister () { - AFB_plugin *plugin = malloc (sizeof (AFB_plugin)); - plugin->type = AFB_PLUGIN_JSON; - plugin->info = "Application Framework Binder Service"; - plugin->prefix= "token"; // url base - plugin->apis = pluginApis; - plugin->freeCtxCB= (void*) clientContextFree; - - return (plugin); +static const struct AFB_plugin plugin_desc = { + .type = AFB_PLUGIN_JSON, + .info = "Application Framework Binder Service", + .prefix = "token", + .apis = pluginApis, + .freeCtxCB = clientContextFree }; + +const struct AFB_plugin *pluginRegister () +{ + return &plugin_desc; +} diff --git a/src/afb-apis.c b/src/afb-apis.c index 8fbd9fc9..7dcb101a 100644 --- a/src/afb-apis.c +++ b/src/afb-apis.c @@ -286,7 +286,7 @@ int afb_apis_add_pathset(const char *pathset) // Check of apiurl is declare in this plugin and call it extern __thread sigjmp_buf *error_handler; -static void trapping_handle(AFB_request * request, struct json_object *(*cb)(AFB_request *,void*)) +static void trapping_handle(struct afb_req req, void(*cb)(struct afb_req)) { volatile int signum, timerset; timer_t timerid; @@ -299,7 +299,7 @@ static void trapping_handle(AFB_request * request, struct json_object *(*cb)(AFB older = error_handler; signum = setjmp(jmpbuf); if (signum != 0) { - afb_req_fail_f(*request->areq, "aborted", "signal %d caught", signum); + afb_req_fail_f(req, "aborted", "signal %d caught", signum); } else { error_handler = &jmpbuf; @@ -320,7 +320,7 @@ static void trapping_handle(AFB_request * request, struct json_object *(*cb)(AFB timer_settime(timerid, 0, &its, NULL); } - cb(request, NULL); + cb(req); } if (timerset) timer_delete(timerid); @@ -329,17 +329,6 @@ static void trapping_handle(AFB_request * request, struct json_object *(*cb)(AFB static void handle(struct afb_req req, const struct api_desc *api, const struct AFB_restapi *verb) { - AFB_request request; - - request.uuid = request.url = "fake"; - request.prefix = api->prefix; - request.method = verb->name; - request.context = NULL; - request.restfull = 0; - request.errcode = 0; - request.config = NULL; - request.areq = &req; - switch(verb->session) { case AFB_SESSION_CREATE: case AFB_SESSION_RENEW: @@ -351,9 +340,10 @@ static void handle(struct afb_req req, const struct api_desc *api, const struct break; case AFB_SESSION_NONE: default: + req.context = NULL; break; } - trapping_handle(&request, verb->callback); + trapping_handle(req, verb->callback); if (verb->session == AFB_SESSION_CLOSE) /*close*/; diff --git a/src/afb-rest-api.c b/src/afb-rest-api.c deleted file mode 100644 index 28346b19..00000000 --- a/src/afb-rest-api.c +++ /dev/null @@ -1,613 +0,0 @@ -/* - * Copyright (C) 2016 "IoT.bzh" - * Author "Fulup Ar Foll" - * Author José Bollo <jose.bollo@iot.bzh> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * Contain all generic part to handle REST/API - * - * https://www.gnu.org/software/libmicrohttpd/tutorial.html [search 'largepost.c'] - */ - -#define _GNU_SOURCE - -#include "../include/local-def.h" - -#include <dirent.h> -#include <dlfcn.h> -#include <setjmp.h> -#include <signal.h> - -#include "afb-apis.h" -#include "session.h" - -#define AFB_MSG_JTYPE "AJB_reply" - -#define JSON_CONTENT "application/json" -#define FORM_CONTENT MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA - -static json_object *afbJsonType; - -// Because of POST call multiple time requestApi we need to free POST handle here -// Note this method is called from http-svc just before closing session -PUBLIC void endPostRequest(AFB_PostHandle * postHandle) -{ - - if (postHandle->type == AFB_POST_JSON) { - // if (verbose) fprintf(stderr, "End PostJson Request UID=%d\n", postHandle->uid); - } - - if (postHandle->type == AFB_POST_FORM) { - if (verbose) - fprintf(stderr, "End PostForm Request UID=%d\n", postHandle->uid); - } - if (postHandle->privatebuf) - free(postHandle->privatebuf); - free(postHandle); -} - -// Check of apiurl is declare in this plugin and call it -static AFB_error doCallPluginApi(AFB_request * request, int apiidx, int verbidx, void *context) -{ - enum AFB_sessionE session; - json_object *jresp, *jcall, *jreqt; - AFB_clientCtx *clientCtx = NULL; - - // Request was found and at least partially executed - jreqt = json_object_new_object(); - json_object_object_add(jreqt, "jtype", json_object_get(afbJsonType)); - - // prepare an object to store calling values - jcall = json_object_new_object(); - json_object_object_add(jcall, "prefix", json_object_new_string(request->prefix)); - json_object_object_add(jcall, "api", json_object_new_string(request->method)); - - // Out of SessionNone every call get a client context session - session = afb_apis_get(apiidx, verbidx)->session; - if (AFB_SESSION_NONE != session) { - - // add client context to request - clientCtx = ctxClientGet(request); - if (clientCtx == NULL) { - request->errcode = MHD_HTTP_INSUFFICIENT_STORAGE; - json_add_status(jcall, "fail", "Client Session Context Full !!!"); - json_object_object_add(jreqt, "request", jcall); - goto ExitOnDone; - } - request->context = clientCtx->contexts[apiidx]; - request->uuid = clientCtx->uuid; - - if (verbose) - 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); - - switch (session) { - - case AFB_SESSION_CREATE: - if (clientCtx->token[0] != '\0' && request->config->token[0] != '\0') { - request->errcode = MHD_HTTP_UNAUTHORIZED; - json_add_status(jcall, "exist", "AFB_SESSION_CREATE Session already exist"); - json_object_object_add(jreqt, "request", jcall); - goto ExitOnDone; - } - - if (AFB_SUCCESS != ctxTokenCreate(clientCtx, request)) { - request->errcode = MHD_HTTP_UNAUTHORIZED; - json_add_status(jcall, "fail", "AFB_SESSION_CREATE Invalid Initial Token"); - json_object_object_add(jreqt, "request", jcall); - goto ExitOnDone; - } else { - json_object_object_add(jcall, "uuid", json_object_new_string(clientCtx->uuid)); - json_object_object_add(jcall, "token", json_object_new_string(clientCtx->token)); - json_object_object_add(jcall, "timeout", json_object_new_int(request->config->cntxTimeout)); - } - break; - - case AFB_SESSION_RENEW: - if (AFB_SUCCESS != ctxTokenRefresh(clientCtx, request)) { - request->errcode = MHD_HTTP_UNAUTHORIZED; - json_add_status(jcall, "fail", "AFB_SESSION_REFRESH Broken Exchange Token Chain"); - json_object_object_add(jreqt, "request", jcall); - goto ExitOnDone; - } else { - json_object_object_add(jcall, "uuid", json_object_new_string(clientCtx->uuid)); - json_object_object_add(jcall, "token", json_object_new_string(clientCtx->token)); - json_object_object_add(jcall, "timeout", json_object_new_int(request->config->cntxTimeout)); - } - break; - - case AFB_SESSION_CLOSE: - if (AFB_SUCCESS != ctxTokenCheck(clientCtx, request)) { - request->errcode = MHD_HTTP_UNAUTHORIZED; - json_add_status(jcall, "fail", "AFB_SESSION_CLOSE Not a Valid Access Token")); - json_object_object_add(jreqt, "request", jcall); - goto ExitOnDone; - } else { - json_object_object_add(jcall, "uuid", json_object_new_string(clientCtx->uuid)); - } - break; - - case AFB_SESSION_CHECK: - default: - // default action is check - if (AFB_SUCCESS != ctxTokenCheck(clientCtx, request)) { - request->errcode = MHD_HTTP_UNAUTHORIZED; - json_add_status(jcall, "fail", "AFB_SESSION_CHECK Invalid Active Token")); - json_object_object_add(jreqt, "request", jcall); - goto ExitOnDone; - } - break; - } - } - - // Effectively CALL PLUGIN API with a subset of the context - jresp = afb_apis_get(apiidx, verbidx)->callback(request, context); - - // Store context in case it was updated by plugins - if (request->context != NULL) - clientCtx->contexts[apiidx] = request->context; - - // handle intermediary Post Iterates out of band - if ((jresp == NULL) && (request->errcode == MHD_HTTP_OK)) - return AFB_SUCCESS; - - // Session close is done after the API call so API can still use session in closing API - if (AFB_SESSION_CLOSE == session) - ctxTokenReset(clientCtx, request); - - // API should return NULL of a valid Json Object - if (jresp == NULL) { - json_object_object_add(jcall, "status", json_object_new_string("null")); - json_object_object_add(jreqt, "request", jcall); - request->errcode = MHD_HTTP_NO_RESPONSE; - - } else { - json_object_object_add(jcall, "status", json_object_new_string("processed")); - json_object_object_add(jreqt, "request", jcall); - json_object_object_add(jreqt, "response", jresp); - } - -ExitOnDone: - request->jresp = jreqt; - return AFB_DONE; -} - -// Check of apiurl is declare in this plugin and call it -extern __thread sigjmp_buf *error_handler; -static AFB_error callPluginApi(AFB_request * request, int apiidx, int verbidx, void *context) -{ - sigjmp_buf jmpbuf, *older; - - json_object *jcall, *jreqt; - int status; - - // save context before calling the API - status = setjmp(jmpbuf); - if (status != 0) { - - // Request was found and at least partially executed - jreqt = json_object_new_object(); - json_object_object_add(jreqt, "jtype", json_object_get(afbJsonType)); - - // prepare an object to store calling values - jcall = json_object_new_object(); - json_object_object_add(jcall, "prefix", json_object_new_string(request->prefix)); - json_object_object_add(jcall, "api", json_object_new_string(request->method)); - - // Plugin aborted somewhere during its execution - json_object_object_add(jcall, "status", json_object_new_string("abort")); - json_object_object_add(jcall, "info", json_object_new_string("Plugin broke during execution")); - json_object_object_add(jreqt, "request", jcall); - request->jresp = jreqt; - } else { - - // Trigger a timer to protect from unacceptable long time execution - if (request->config->apiTimeout > 0) - alarm((unsigned)request->config->apiTimeout); - - older = error_handler; - error_handler = &jmpbuf; - doCallPluginApi(request, apiidx, verbidx, context); - error_handler = older; - - // cancel timeout and plugin signal handle before next call - alarm(0); - } - return AFB_DONE; -} - -STATIC AFB_error findAndCallApi(AFB_request * request, void *context) -{ - int apiidx, verbidx; - AFB_error status; - - if (!request->method || !request->prefix) - return AFB_FAIL; - - /* get the plugin if any */ - apiidx = afb_apis_get_apiidx(request->prefix, 0); - if (apiidx < 0) { - request->jresp = jsonNewMessage(AFB_FATAL, "No Plugin=[%s] Url=%s", request->prefix, request->url); - request->errcode = MHD_HTTP_UNPROCESSABLE_ENTITY; - return AFB_FAIL; - } - - /* get the verb if any */ - verbidx = afb_apis_get_verbidx(apiidx, request->method); - if (verbidx < 0) { - request->jresp = jsonNewMessage(AFB_FATAL, "No API=[%s] for Plugin=[%s] url=[%s]", request->method, request->prefix, request->url); - request->errcode = MHD_HTTP_UNPROCESSABLE_ENTITY; - return AFB_FAIL; - } - - /* Search for a plugin with this urlpath */ - status = callPluginApi(request, apiidx, verbidx, context); - - /* plugin callback did not return a valid Json Object */ - if (status == AFB_FAIL) { - request->jresp = jsonNewMessage(AFB_FATAL, "No API=[%s] for Plugin=[%s] url=[%s]", request->method, request->prefix, request->url); - request->errcode = MHD_HTTP_UNPROCESSABLE_ENTITY; - return AFB_FAIL; - } - // Everything look OK - return status; -} - -// This CB is call for every item with a form post it reformat iterator values -// and callback Plugin API for each Item within PostForm. -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) -{ - - AFB_error status; - AFB_PostItem item; - - // retrieve API request from Post iterator handle - AFB_PostHandle *postHandle = (AFB_PostHandle *) cls; - AFB_request *request = (AFB_request *) postHandle->privatebuf; - AFB_PostRequest postRequest; - - if (verbose) - fprintf(stderr, "postHandle key=%s filename=%s len=%zu mime=%s\n", key, filename, size, mimetype); - - // Create and Item value for Plugin API - item.kind = kind; - item.key = key; - item.filename = filename; - item.mimetype = mimetype; - item.encoding = encoding; - item.len = size; - item.data = data; - item.offset = offset; - - // Reformat Request to make it somehow similar to GET/PostJson case - postRequest.data = (char *)postHandle; - postRequest.len = size; - postRequest.type = AFB_POST_FORM;; - request->post = &postRequest; - - // effectively call plugin API - status = findAndCallApi(request, &item); - // when returning no processing of postform stop - if (status != AFB_SUCCESS) - return MHD_NO; - - // let's allow iterator to move to next item - return MHD_YES; -} - -STATIC void freeRequest(AFB_request * request) -{ - - free((void*)request->prefix); - free((void*)request->method); - free(request); -} - -STATIC AFB_request *createRequest(struct MHD_Connection *connection, AFB_session * session, const char *url) -{ - - AFB_request *request; - - // Start with a clean request - request = calloc(1, sizeof(AFB_request)); - char *urlcpy1, *urlcpy2; - char *baseapi, *baseurl; - - // Extract plugin urlpath from request and make two copy because strsep overload copy - urlcpy1 = urlcpy2 = strdup(url); - baseurl = strsep(&urlcpy2, "/"); - if (baseurl == NULL) { - request->jresp = jsonNewMessage(AFB_FATAL, "Invalid API call url=[%s]", url); - request->errcode = MHD_HTTP_BAD_REQUEST; - goto Done; - } - // let's compute URL and call API - baseapi = strsep(&urlcpy2, "/"); - if (baseapi == NULL) { - request->jresp = jsonNewMessage(AFB_FATAL, "Invalid API call plugin=[%s] url=[%s]", baseurl, url); - request->errcode = MHD_HTTP_BAD_REQUEST; - goto Done; - } - // build request structure -// request->connection = connection; - request->config = session->config; - request->url = url; - request->prefix = strdup(baseurl); - request->method = strdup(baseapi); - - Done: - free(urlcpy1); - return (request); -} - - - - - - - - - - - - - - - -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) -{ - - static int postcount = 0; // static counter to debug POST protocol - json_object *errMessage; - AFB_error status; - struct MHD_Response *webResponse; - const char *serialized; - AFB_request *request = NULL; - AFB_PostHandle *postHandle; - AFB_PostRequest postRequest; - int ret; - - // fprintf (stderr, "doRestAPI method=%s posthandle=%p\n", method, con_cls); - - // if post data may come in multiple calls - const char *encoding, *param; - int contentlen = -1; - postHandle = *con_cls; - - // This is the initial post event let's create form post structure POST data come in multiple events - if (postHandle == NULL) { - - // allocate application POST processor handle to zero - postHandle = calloc(1, sizeof(AFB_PostHandle)); - postHandle->uid = postcount++; // build a UID for DEBUG - *con_cls = postHandle; // update context with posthandle - - // Let make sure we have the right encoding and a valid length - encoding = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE); - - // We are facing an empty post let's process it as a get - if (encoding == NULL) { - postHandle->type = AFB_POST_EMPTY; - return MHD_YES; - } - - // Form post is handle through a PostProcessor and call API once per form key - if (strcasestr(encoding, FORM_CONTENT) != NULL) { - if (verbose) - fprintf(stderr, "Create doPostIterate[uid=%d posthandle=%p]\n", postHandle->uid, postHandle); - - request = createRequest(connection, session, url); - if (request->jresp != NULL) - goto ProcessApiCall; - postHandle->type = AFB_POST_FORM; - postHandle->privatebuf = (void *)request; - postHandle->pp = MHD_create_post_processor(connection, MAX_POST_SIZE, &doPostIterate, postHandle); - - if (NULL == postHandle->pp) { - fprintf(stderr, "OOPS: Internal error fail to allocate MHD_create_post_processor\n"); - free(postHandle); - return MHD_NO; - } - return MHD_YES; - } - // POST json is store into a buffer and present in one piece to API - if (strcasestr(encoding, JSON_CONTENT) != NULL) { - - param = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH); - if (param) - sscanf(param, "%i", &contentlen); - - // Because PostJson are build in RAM size is constrained - if (contentlen > MAX_POST_SIZE) { - errMessage = jsonNewMessage(AFB_FATAL, "Post Date to big %d > %d", contentlen, MAX_POST_SIZE); - goto ExitOnError; - } - // Size is OK, let's allocate a buffer to hold post data - postHandle->type = AFB_POST_JSON; - postHandle->privatebuf = malloc((unsigned)contentlen + 1); // allocate memory for full POST data + 1 for '\0' enf of string - - // if (verbose) fprintf(stderr, "Create PostJson[uid=%d] Size=%d\n", postHandle->uid, contentlen); - return MHD_YES; - - } - // We only support Json and Form Post format - errMessage = jsonNewMessage(AFB_FATAL, "Post Date wrong type encoding=%s != %s", encoding, JSON_CONTENT); - goto ExitOnError; - } - - // This time we receive partial/all Post data. Note that even if we get all POST data. We should nevertheless - // return MHD_YES and not process the request directly. Otherwise Libmicrohttpd is unhappy and fails with - // 'Internal application error, closing connection'. - if (*upload_data_size) { - - if (postHandle->type == AFB_POST_FORM) { - // if (verbose) fprintf(stderr, "Processing PostForm[uid=%d]\n", postHandle->uid); - MHD_post_process(postHandle->pp, upload_data, *upload_data_size); - } - // Process JsonPost request when buffer is completed let's call API - if (postHandle->type == AFB_POST_JSON) { - // if (verbose) fprintf(stderr, "Updating PostJson[uid=%d]\n", postHandle->uid); - memcpy(&postHandle->privatebuf[postHandle->len], upload_data, *upload_data_size); - postHandle->len = postHandle->len + *upload_data_size; - } - - *upload_data_size = 0; - return MHD_YES; - - } - - if (postHandle->type == AFB_POST_FORM) - request = postHandle->privatebuf; - else - // Create a request structure to finalise the request - request = createRequest(connection, session, url); - - if (request->jresp != NULL) { - errMessage = request->jresp; - goto ExitOnError; - } - postRequest.type = postHandle->type; - - // Postform add application context handle to request - if (postHandle->type == AFB_POST_FORM) { - postRequest.data = (char *)postHandle; - request->post = &postRequest; - } - - if (postHandle->type == AFB_POST_JSON) { - // if (verbose) fprintf(stderr, "Processing PostJson[uid=%d]\n", postHandle->uid); - - param = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH); - if (param) - sscanf(param, "%i", &contentlen); - - // At this level we're may verify that we got everything and process DATA - if (postHandle->len != contentlen) { - errMessage = jsonNewMessage(AFB_FATAL, "Post Data Incomplete UID=%d Len %d != %d", postHandle->uid, contentlen, postHandle->len); - goto ExitOnError; - } - // Before processing data, make sure buffer string is properly ended - postHandle->privatebuf[postHandle->len] = '\0'; - postRequest.data = postHandle->privatebuf; - request->post = &postRequest; - - // if (verbose) fprintf(stderr, "Close Post[%d] Buffer=%s\n", postHandle->uid, request->post->data); - } - - ProcessApiCall: - // Request is ready let's call API without any extra handle - status = findAndCallApi(request, NULL); - - serialized = json_object_to_json_string(request->jresp); - webResponse = MHD_create_response_from_buffer(strlen(serialized), (void *)serialized, MHD_RESPMEM_MUST_COPY); - - // client did not pass token on URI let's use cookies - if ((!request->restfull) && (request->context != NULL)) { - char cookie[256]; - 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); - MHD_add_response_header(webResponse, MHD_HTTP_HEADER_SET_COOKIE, cookie); - } - // if requested add an error status - if (request->errcode != 0) - ret = MHD_queue_response(connection, request->errcode, webResponse); - else - MHD_queue_response(connection, MHD_HTTP_OK, webResponse); - - MHD_destroy_response(webResponse); - json_object_put(request->jresp); // decrease reference rqtcount to free the json object - freeRequest(request); - return MHD_YES; - - ExitOnError: - freeRequest(request); - serialized = json_object_to_json_string(errMessage); - webResponse = MHD_create_response_from_buffer(strlen(serialized), (void *)serialized, MHD_RESPMEM_MUST_COPY); - MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, webResponse); - MHD_destroy_response(webResponse); - json_object_put(errMessage); // decrease reference rqtcount to free the json object - return MHD_YES; -} - - - - - - - - - - - - - - - - - - - - -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) -{ - AFB_error status; - struct MHD_Response *webResponse; - const char *serialized; - AFB_request *request = NULL; - int ret; - - // fprintf (stderr, "doRestAPI method=%s posthandle=%p\n", method, con_cls); - - // if post data may come in multiple calls - // this is a get we only need a request - request = createRequest(connection, session, url); - - // Request is ready let's call API without any extra handle - status = findAndCallApi(request, NULL); - - serialized = json_object_to_json_string(request->jresp); - webResponse = MHD_create_response_from_buffer(strlen(serialized), (void *)serialized, MHD_RESPMEM_MUST_COPY); - - // client did not pass token on URI let's use cookies - if ((!request->restfull) && (request->context != NULL)) { - char cookie[256]; - 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); - MHD_add_response_header(webResponse, MHD_HTTP_HEADER_SET_COOKIE, cookie); - } - // if requested add an error status - if (request->errcode != 0) - ret = MHD_queue_response(connection, request->errcode, webResponse); - else - MHD_queue_response(connection, MHD_HTTP_OK, webResponse); - - MHD_destroy_response(webResponse); - json_object_put(request->jresp); // decrease reference rqtcount to free the json object - freeRequest(request); - return MHD_YES; -} - -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) -{ - int rc; - - if (afbJsonType == NULL) - afbJsonType = json_object_new_string (AFB_MSG_JTYPE); - - if (0 == strcmp(method, MHD_HTTP_METHOD_POST)) { - rc = doRestApiPost(connection, session, url, method, upload_data, upload_data_size, con_cls); - } else { - rc = doRestApiGet(connection, session, url, method, upload_data, upload_data_size, con_cls); - } - return rc; -} - diff --git a/include/local-def.h b/src/local-def.h index f46dd97c..f46dd97c 100644 --- a/include/local-def.h +++ b/src/local-def.h diff --git a/include/proto-def.h b/src/proto-def.h index e2190eb1..e2190eb1 100644 --- a/include/proto-def.h +++ b/src/proto-def.h diff --git a/test/hello-world.html b/test/hello-world.html index 88f2c95b..67464ff5 100644 --- a/test/hello-world.html +++ b/test/hello-world.html @@ -5,6 +5,7 @@ <h1>Hello world test</h1> <ol> <li><a href="api/hello/ping?toto=1">ping</a> + <li><a href="api/hello/pingfail">ping fail</a> <li><a href="api/hello/pingnull">ping null</a> <li><a href="api/hello/pingbug">ping bug</a> <li><a href="api/hello/pingJson?toto&tata&titi=u">ping json</a> |