/* * 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; }