Added Session Management
[src/app-framework-binder.git] / src / http-svc.c
index b29a4bb..ac5cd78 100644 (file)
 
 
 #include <microhttpd.h>
+
 #include <sys/stat.h>
 #include "../include/local-def.h"
 
+// let's compute fixed URL length only once
+static apiUrlLen=0;
+static baseUrlLen=0;
+static rootUrlLen=0;
+
 // proto missing from GCC
 char *strcasestr(const char *haystack, const char *needle);
 
 static int rqtcount = 0;  // dummy request rqtcount to make each message be different
 static int postcount = 0;
-static int aipUrlLen=0;  // do not compute apiurl for each call
-static int baseUrlLen=0; // do not compute baseurl for each call
+
+// try to open libmagic to handle mime types
+static AFB_error initLibMagic (AFB_session *session) {
+  
+    /*MAGIC_MIME tells magic to return a mime of the file, but you can specify different things*/
+    if (verbose) printf("Loading mimetype default magic database\n");
+  
+    session->magic = magic_open(MAGIC_MIME_TYPE);
+    if (session->magic == NULL) {
+        fprintf(stderr,"ERROR: unable to initialize magic library\n");
+        return AFB_FAIL;
+    }
+    
+    // Warning: should not use NULL for DB [libmagic bug wont pass efence check]
+    if (magic_load(session->magic, MAGIC_DB) != 0) {
+        fprintf(stderr,"cannot load magic database - %s\n", magic_error(session->magic));
+        magic_close(session->magic);
+        return AFB_FAIL;
+    }
+
+    return AFB_SUCCESS;
+}
 
 // Because of POST call multiple time requestApi we need to free POST handle here
 static void endRequest (void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe) {
@@ -62,125 +88,138 @@ STATIC void computeEtag(char *etag, int maxlen, struct stat *sbuf) {
     snprintf(etag, maxlen, "%d", time);
 }
 
-STATIC int servFile (struct MHD_Connection *connection, AFB_session *session, const char *url, char *filepath, int fd) {
-    const char *etagCache;
+STATIC int servFile (struct MHD_Connection *connection, AFB_session *session, const char *url, AFB_staticfile *staticfile) {
+    const char *etagCache, *mimetype; 
     char etagValue[15];
     struct MHD_Response *response;
     struct stat sbuf; 
     int ret;
 
-    if (fstat (fd, &sbuf) != 0) {
-        fprintf(stderr, "Fail to stat file: [%s] error:%s\n", filepath, strerror(errno));
-        return (FAILED);
+    if (fstat (staticfile->fd, &sbuf) != 0) {
+        fprintf(stderr, "Fail to stat file: [%s] error:%s\n", staticfile->path, strerror(errno));
+        goto abortRequest;
     }
-    
+       
     // if url is a directory let's add index.html and redirect client
     if (S_ISDIR (sbuf.st_mode)) {
-        strncpy (filepath, url, sizeof (filepath));
-
-        if (url [strlen (url) -1] != '/') strncat (filepath, "/", sizeof (filepath));
-        strncat (filepath, "index.html", sizeof (filepath));
-        close (fd);
-        response = MHD_create_response_from_buffer (0,"", MHD_RESPMEM_PERSISTENT);
-        MHD_add_response_header (response,MHD_HTTP_HEADER_LOCATION, filepath);
-        ret = MHD_queue_response (connection, MHD_HTTP_MOVED_PERMANENTLY, response);
-
-    } else  if (! S_ISREG (sbuf.st_mode)) { // only standard file any other one including symbolic links are refused.
-
-        fprintf (stderr, "Fail file: [%s] is not a regular file\n", filepath);
-        const char *errorstr = "<html><body>Alsa-Json-Gateway Invalid file type</body></html>";
+        close (staticfile->fd); // close directory check for Index
+       
+        // No trailing '/'. Let's add one and redirect for relative paths to work
+        if (url [strlen (url) -1] != '/') {
+            response = MHD_create_response_from_buffer(0,"", MHD_RESPMEM_PERSISTENT);
+            strncat(staticfile->path, "/", sizeof (staticfile->path));
+            MHD_add_response_header (response, "Location", staticfile->path);
+            MHD_queue_response (connection, MHD_HTTP_MOVED_PERMANENTLY, response);
+            if (verbose) fprintf (stderr,"Adding trailing '/' [%s]\n",staticfile->path);      
+            goto sendRequest;
+        }
+        
+        strncat (staticfile->path, OPA_INDEX, sizeof (staticfile->path));
+        if (-1 == (staticfile->fd = open(staticfile->path, O_RDONLY)) || (fstat (staticfile->fd, &sbuf) != 0)) {
+           fprintf(stderr, "No Index.html in direcory [%s]\n", staticfile->path);
+           goto abortRequest;  
+        } 
+    } else if (! S_ISREG (sbuf.st_mode)) { // only standard file any other one including symbolic links are refused.
+        close (staticfile->fd); // nothing useful to do with this file
+        fprintf (stderr, "Fail file: [%s] is not a regular file\n", staticfile->path);
+        const char *errorstr = "<html><body>Application Framework Binder Invalid file type</body></html>";
         response = MHD_create_response_from_buffer (strlen (errorstr),
                      (void *) errorstr,         MHD_RESPMEM_PERSISTENT);
-        ret = MHD_queue_response (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response);
-
-    } else {
+        MHD_queue_response (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response);
+        goto sendRequest;
+    } 
     
-        // https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=fr
-        // ftp://ftp.heanet.ie/disk1/www.gnu.org/software/libmicrohttpd/doxygen/dc/d0c/microhttpd_8h.html
-
-        // Check etag value and load file only when modification date changes
-        etagCache = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_NONE_MATCH);
-        computeEtag(etagValue, sizeof (etagValue), &sbuf);
-
-        if (etagCache != NULL && strcmp(etagValue, etagCache) == 0) {
-            close(fd); // file did not change since last upload
-            if (verbose) fprintf(stderr, "Not Modify: [%s]\n", filepath);
-            response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT);
-            MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL, session->cacheTimeout); // default one hour cache
-            MHD_add_response_header(response, MHD_HTTP_HEADER_ETAG, etagValue);
-            ret = MHD_queue_response(connection, MHD_HTTP_NOT_MODIFIED, response);
-
-        } else { // it's a new file, we need to upload it to client
-            if (verbose) fprintf(stderr, "Serving: [%s]\n", filepath);
-            response = MHD_create_response_from_fd(sbuf.st_size, fd);
-            MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL, session->cacheTimeout); // default one hour cache
-            MHD_add_response_header(response, MHD_HTTP_HEADER_ETAG, etagValue);
-            ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
-        }
+    // https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=fr
+    // ftp://ftp.heanet.ie/disk1/www.gnu.org/software/libmicrohttpd/doxygen/dc/d0c/microhttpd_8h.html
+
+    // Check etag value and load file only when modification date changes
+    etagCache = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_NONE_MATCH);
+    computeEtag(etagValue, sizeof (etagValue), &sbuf);
+
+    if (etagCache != NULL && strcmp(etagValue, etagCache) == 0) {
+        close(staticfile->fd); // file did not change since last upload
+        if (verbose) fprintf(stderr, "Not Modify: [%s]\n", staticfile->path);
+        response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT);
+        MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL, session->cacheTimeout); // default one hour cache
+        MHD_add_response_header(response, MHD_HTTP_HEADER_ETAG, etagValue);
+        MHD_queue_response(connection, MHD_HTTP_NOT_MODIFIED, response);
+
+    } else { // it's a new file, we need to upload it to client
+        // if we have magic let's try to guest mime type
+        if (session->magic) {          
+           mimetype= magic_descriptor(session->magic, staticfile->fd);
+           if (mimetype != NULL)  MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_TYPE, mimetype);
+        } else mimetype="application/unknown";
+        
+        if (verbose) fprintf(stderr, "Serving: [%s] mime=%s\n", staticfile->path, mimetype);
+        response = MHD_create_response_from_fd(sbuf.st_size, staticfile->fd);
+        MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL, session->cacheTimeout); // default one hour cache
+        MHD_add_response_header(response, MHD_HTTP_HEADER_ETAG, etagValue);
+        MHD_queue_response(connection, MHD_HTTP_OK, response);
     }
+    
+sendRequest:    
     MHD_destroy_response(response);
-    return (ret);
+    return (MHD_YES);
 
+abortRequest:
+    return (FAILED);
 }
 
-// minimal httpd file server for static HTML,JS,CSS,etc...
-STATIC int requestFile(struct MHD_Connection *connection, AFB_session *session, const char* url) {
+
+// this function return either Index.htlm or a redirect to /#!route to make angular happy
+STATIC int redirectHTML5(struct MHD_Connection *connection, AFB_session *session, const char* url) {
+
     int fd;
     int ret;
+    struct MHD_Response *response;
+    AFB_staticfile staticfile;
+
+    // Url match /opa/xxxx should redirect to "/opa/#!page" to force index.html reload
+    strncpy(staticfile.path, session->config->rootbase, sizeof (staticfile.path));
+    strncat(staticfile.path, "/#!", sizeof (staticfile.path));
+    strncat(staticfile.path, &url[1], sizeof (staticfile.path));
+    response = MHD_create_response_from_buffer(0,"", MHD_RESPMEM_PERSISTENT);
+    MHD_add_response_header (response, "Location", staticfile.path);
+    MHD_queue_response (connection, MHD_HTTP_MOVED_PERMANENTLY, response);
+    if (verbose) fprintf (stderr,"checkHTML5 redirect to [%s]\n",staticfile.path);
+    return (MHD_YES);
+}
 
-    char filepath [512];
 
+// minimal httpd file server for static HTML,JS,CSS,etc...
+STATIC int requestFile(struct MHD_Connection *connection, AFB_session *session, const char* url) {
+    int fd, ret, idx;
+    AFB_staticfile staticfile;
+    char *requestdir, *requesturl;
+   
+    // default search for file is rootdir base
+    requestdir= session->config->rootdir;
+    requesturl=(char*)url;
+    
+    // Check for optional aliases
+    for (idx=0; session->config->aliasdir[idx].url != NULL; idx++) {
+        if (0 == strncmp(url, session->config->aliasdir[idx].url, session->config->aliasdir[idx].len)) {
+             requestdir = session->config->aliasdir[idx].path;
+             requesturl=(char*)&url[session->config->aliasdir[idx].len];
+             break;
+        }
+    }
+    
     // build full path from rootdir + url
-    strncpy(filepath, session->config->rootdir, sizeof (filepath));
-    strncat(filepath, url, 511);
+    strncpy(staticfile.path, requestdir, sizeof (staticfile.path));   
+    strncat(staticfile.path, requesturl, sizeof (staticfile.path));
 
     // try to open file and get its size
-    if (-1 == (fd = open(filepath, O_RDONLY))) {
-        fprintf(stderr, "Fail to open file: [%s] error:%s\n", filepath, strerror(errno));
+    if (-1 == (staticfile.fd = open(staticfile.path, O_RDONLY))) {
+        fprintf(stderr, "Fail to open file: [%s] error:%s\n", staticfile.path, strerror(errno));
         return (FAILED);
-
     }
     // open file is OK let use it
-    ret = servFile (connection, session, url, filepath, fd);
+    ret = servFile (connection, session, url, &staticfile);
     return ret;
 }
 
-// this function return either Index.htlm or a redirect to /#!route to make angular happy
-STATIC int checkHTML5(struct MHD_Connection *connection, AFB_session *session, const char* url) {
-
-    int fd;
-    int ret;
-    struct MHD_Response *response;
-    char filepath [512];
-
-    // if requesting '/' serve index.html
-    if (strlen (url) == 0) {
-        strncpy(filepath, session->config->rootdir, sizeof (filepath));
-        strncat(filepath, "/index.html", sizeof (filepath));
-        // try to open file and get its size
-        if (-1 == (fd = open(filepath, O_RDONLY))) {
-            fprintf(stderr, "Fail to open file: [%s] error:%s\n", filepath, strerror(errno));
-            // Nothing respond to this request Files, API, Angular Base
-            const char *errorstr = "<html><body>Alsa-Json-Gateway Unknown or Not readable file</body></html>";
-            response = MHD_create_response_from_buffer(strlen(errorstr),(void *)errorstr, MHD_RESPMEM_PERSISTENT);
-            ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response);
-            ret = MHD_YES;
-            return (FAILED);
-       } else {
-            ret = servFile (connection, session, url, filepath, fd);
-            return ret;
-       }
-    }
-
-    // we are facing a internal route within the HTML5 OnePageApp let's redirect ex: /myapp/#!user/login
-    strncpy(filepath, session->config->rootbase, sizeof (filepath));
-    strncat(filepath, "#!", sizeof (filepath));
-    strncat(filepath, url, sizeof (filepath));
-    response = MHD_create_response_from_buffer(session->config->html5.len,(void *)session->config->html5.msg, MHD_RESPMEM_PERSISTENT);
-    MHD_add_response_header (response, "Location", "http://somesite.com/page.html");
-    MHD_queue_response (connection, MHD_HTTP_OK, response);
-}
-
 // Check and Dispatch HTTP request
 STATIC int newRequest(void *cls,
         struct MHD_Connection *connection,
@@ -193,9 +232,9 @@ STATIC int newRequest(void *cls,
     struct MHD_Response *response;
     int ret;
     
-    // this is an Angular request we change URL /!#xxxxx   
-    if (0 == strncmp(url, session->config->rootapi, baseUrlLen)) {
-        ret = doRestApi(connection, session, method, &url[baseUrlLen]);
+    // this is a REST API let's check for plugins
+    if (0 == strncmp(url, session->config->rootapi, apiUrlLen)) {
+        ret = doRestApi(connection, session, &url[apiUrlLen+1], method, upload_data, upload_data_size, con_cls);
         return ret;
     }
     
@@ -206,9 +245,9 @@ STATIC int newRequest(void *cls,
     ret = requestFile(connection, session, url);
     if (ret != FAILED) return ret;
     
-    // no static was served let check for Angular redirect
+    // no static was served let's try HTML5 OPA redirect
     if (0 == strncmp(url, session->config->rootbase, baseUrlLen)) {
-        ret = checkHTML5(connection, session, &url[baseUrlLen]);
+        ret = redirectHTML5(connection, session, &url[baseUrlLen]);
         return ret;
     }
 
@@ -220,17 +259,22 @@ STATIC int newRequest(void *cls,
 }
 
 STATIC int newClient(void *cls, const struct sockaddr * addr, socklen_t addrlen) {
-    // check if client is comming from an acceptable IP
+    // check if client is coming from an acceptable IP
     return (MHD_YES); // MHD_NO
 }
 
 
-PUBLIC AFB_ERROR httpdStart(AFB_session *session) {
-  
-    // do this only once
-    aipUrlLen  = strlen (session->config->rootapi);
-    baseUrlLen = strlen (session->config->rootbase);
-
+PUBLIC AFB_error httpdStart(AFB_session *session) {
+    
+    // compute fixed URL length at startup time
+    apiUrlLen = strlen (session->config->rootapi);
+    baseUrlLen= strlen (session->config->rootbase);
+    rootUrlLen= strlen (session->config->rootdir);
+     
+    // TBD open libmagic cache [fail to pass EFENCE check]
+    // initLibMagic (session);
+    
+    
     if (verbose) {
         printf("AFB:notice Waiting port=%d rootdir=%s\n", session->config->httpdPort, session->config->rootdir);
         printf("AFB:notice Browser URL= http://localhost:%d\n", session->config->httpdPort);
@@ -253,14 +297,14 @@ PUBLIC AFB_ERROR httpdStart(AFB_session *session) {
 }
 
 // infinite loop
-PUBLIC AFB_ERROR httpdLoop(AFB_session *session) {
+PUBLIC AFB_error httpdLoop(AFB_session *session) {
     static int  count = 0;
 
     if (verbose) fprintf(stderr, "AFB:notice entering httpd waiting loop\n");
     if (session->foreground) {
 
         while (TRUE) {
-            fprintf(stderr, "AFB:notice Use Ctrl-C to quit");
+            fprintf(stderr, "AFB:notice Use Ctrl-C to quit\n");
             (void) getc(stdin);
         }
     } else {