diff options
Diffstat (limited to 'src/pmwebapi/pmresapi.c')
-rw-r--r-- | src/pmwebapi/pmresapi.c | 162 |
1 files changed, 162 insertions, 0 deletions
diff --git a/src/pmwebapi/pmresapi.c b/src/pmwebapi/pmresapi.c new file mode 100644 index 0000000..0585646 --- /dev/null +++ b/src/pmwebapi/pmresapi.c @@ -0,0 +1,162 @@ +/* + * JSON web bridge for PMAPI. + * + * Copyright (c) 2011-2013 Red Hat Inc. + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "pmwebapi.h" +#include "config.h" + + +/* ------------------------------------------------------------------------ */ + +static const char *guess_content_type (const char* filename) +{ + const char *extension = rindex (filename, '.'); + if (extension == NULL) return NULL; + + /* One could go all out and parse /etc/mime.types, or one can do this ... */ + if (0 == strcasecmp (extension, "html")) return "text/html"; + if (0 == strcasecmp (extension, "js")) return "text/javascript"; + if (0 == strcasecmp (extension, "json")) return "application/json"; + if (0 == strcasecmp (extension, "txt")) return "text/plain"; + if (0 == strcasecmp (extension, "xml")) return "text/xml"; + if (0 == strcasecmp (extension, "svg")) return "image/svg+xml"; + if (0 == strcasecmp (extension, "png")) return "image/png"; + if (0 == strcasecmp (extension, "jpg")) return "image/jpg"; + + return NULL; +} + + +static const char *create_rfc822_date (time_t t) +{ + static char datebuf[80]; /* if-threaded: unstaticify */ + struct tm *now = gmtime (& t); + size_t rc = strftime (datebuf, sizeof(datebuf), "%a, %d %b %Y %T %z", now); + if (rc <= 0 || rc >= sizeof(datebuf)) return NULL; + return datebuf; +} + + + +/* Respond to a GET request, not under the pmwebapi URL prefix. This + is a mini fileserver, just for small standalone installations of + pmwebapi-based web front-ends. */ +int pmwebres_respond (void *cls, struct MHD_Connection *connection, + const char* url) +{ + int fd = -1; + int rc; + char filename [PATH_MAX]; + struct stat fds; + unsigned int resp_code = MHD_HTTP_OK; + struct MHD_Response *resp; + const char *ctype = NULL; + static const char error_page[] = + "PMRESAPI error"; /* could also be an actual error page... */ + + (void) cls; + assert (resourcedir != NULL); /* facility is enabled at all */ + + assert (url[0] == '/'); + rc = snprintf (filename, sizeof(filename), "%s%s", resourcedir, url); + if (rc < 0 || rc >= (int)sizeof(filename)) + goto error_response; + + /* Reject some obvious ways of escaping resourcedir. */ + if (NULL != strstr (filename, "/../")) { + pmweb_notify (LOG_ERR, connection, "pmwebres suspicious url %s\n", url); + goto error_response; + } + + fd = open (filename, O_RDONLY); + if (fd < 0) { + pmweb_notify (LOG_ERR, connection, "pmwebres open %s failed (%d)\n", filename, fd); + resp_code = MHD_HTTP_NOT_FOUND; + goto error_response; + } + + rc = fstat (fd, &fds); + if (rc < 0) { + pmweb_notify (LOG_ERR, connection, "pmwebres stat %s failed (%d)\n", filename, rc); + close (fd); + goto error_response; + } + + /* XXX: handle if-modified-since */ + + if (! S_ISREG (fds.st_mode)) { + pmweb_notify (LOG_ERR, connection, "pmwebres non-file %s attempted\n", filename); + close (fd); + + /* XXX: list directory, or redirect to index.html instead? */ + resp_code = MHD_HTTP_FORBIDDEN; + goto error_response; + } + + if (verbosity > 2) + pmweb_notify (LOG_INFO, connection, "pmwebres serving file %s.\n", filename); + + resp = MHD_create_response_from_fd_at_offset (fds.st_size, fd, 0); /* auto-closes fd */ + if (resp == NULL) { + pmweb_notify (LOG_ERR, connection, "MHD_create_response_from_callback failed\n"); + goto error_response; + } + + /* Guess at a suitable MIME content-type. */ + ctype = guess_content_type (filename); + if (ctype) + (void) MHD_add_response_header (resp, "Content-Type", ctype); + + /* And since we're generous to a fault, supply a timestamp field to + assist caching. */ + ctype = create_rfc822_date (fds.st_mtime); + if (ctype) + (void) MHD_add_response_header (resp, "Last-Modified", ctype); + + /* Add a 5-minute expiry. */ + ctype = create_rfc822_date (time(0) + 300); /* XXX: configure */ + if (ctype) + (void) MHD_add_response_header (resp, "Expires", ctype); + + (void) MHD_add_response_header (resp, "Cache-Control", "public"); + + rc = MHD_queue_response (connection, resp_code, resp); + MHD_destroy_response (resp); + return rc; + + error_response: + resp = MHD_create_response_from_buffer (strlen(error_page), + (char*)error_page, + MHD_RESPMEM_PERSISTENT); + if (resp == NULL) { + pmweb_notify (LOG_ERR, connection, "MHD_create_response_from_callback failed\n"); + return MHD_NO; + } + + (void) MHD_add_response_header (resp, "Content-Type", "text/plain"); + + rc = MHD_queue_response (connection, resp_code, resp); + MHD_destroy_response (resp); + if (rc != MHD_YES) { + pmweb_notify (LOG_ERR, connection, "MHD_queue_response failed\n"); + return MHD_NO; + } + + return rc; +} |