summaryrefslogtreecommitdiff
path: root/src/pmwebapi/pmresapi.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pmwebapi/pmresapi.c')
-rw-r--r--src/pmwebapi/pmresapi.c162
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;
+}