static void process_request (CockpitRequest *request, const gchar *path, GHashTable *headers) { gboolean claimed = FALSE; const gchar *actual_path; if (request->web_server->url_root->len && !path_has_prefix (path, request->web_server->url_root)) { request->delayed_reply = 404; } /* * If redirecting to TLS, check the path. Certain paths * don't require us to redirect. */ if (request->delayed_reply == 301 && path_has_prefix (path, request->web_server->ssl_exception_prefix)) { request->delayed_reply = 0; } if (request->delayed_reply) { process_delayed_reply (request, path, headers); return; } actual_path = path + request->web_server->url_root->len; /* See if we have any takers... */ g_signal_emit (request->web_server, sig_handle_stream, 0, path, actual_path, request->io, headers, request->buffer, &claimed); if (!claimed) g_critical ("no handler responded to request: %s", actual_path); }
gboolean g_mount_spec_match_with_path (GMountSpec *mount, GMountSpec *spec, const char *path) { if (items_equal (mount->items, spec->items) && path_has_prefix (path, mount->mount_prefix)) return TRUE; return FALSE; }
/** * cockpit_web_response_file: * @response: the response * @path: escaped path, or NULL to get from response * @roots: directories to look for file in * * Serve a file from disk as an HTTP response. */ void cockpit_web_response_file (CockpitWebResponse *response, const gchar *escaped, const gchar **roots) { const gchar *csp_header; GError *error = NULL; gchar *unescaped = NULL; gchar *path = NULL; GMappedFile *file = NULL; const gchar *root; GBytes *body; g_return_if_fail (COCKPIT_IS_WEB_RESPONSE (response)); if (!escaped) escaped = cockpit_web_response_get_path (response); g_return_if_fail (escaped != NULL); /* Someone is trying to escape the root directory, or access hidden files? */ unescaped = g_uri_unescape_string (escaped, NULL); if (strstr (unescaped, "/.") || strstr (unescaped, "../") || strstr (unescaped, "//")) { g_debug ("%s: invalid path request", escaped); cockpit_web_response_error (response, 404, NULL, "Not Found"); goto out; } again: root = *(roots++); if (root == NULL) { cockpit_web_response_error (response, 404, NULL, "Not Found"); goto out; } g_free (path); path = g_build_filename (root, unescaped, NULL); if (g_file_test (path, G_FILE_TEST_IS_DIR)) { cockpit_web_response_error (response, 403, NULL, "Directory Listing Denied"); goto out; } /* As a double check of above behavior */ g_assert (path_has_prefix (path, root)); g_clear_error (&error); file = g_mapped_file_new (path, FALSE, &error); if (file == NULL) { if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT) || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NAMETOOLONG)) { g_debug ("%s: file not found in root: %s", escaped, root); goto again; } else if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_PERM) || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_ACCES) || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_ISDIR)) { cockpit_web_response_error (response, 403, NULL, "Access denied"); goto out; } else { g_warning ("%s: %s", path, error->message); cockpit_web_response_error (response, 500, NULL, "Internal server error"); goto out; } } body = g_mapped_file_get_bytes (file); /* * The default Content-Security-Policy for .html files allows * the site to have inline <script> and <style> tags. This code * is not used when serving resources once logged in, only for * static resources when we don't yet have a session. */ csp_header = NULL; if (g_str_has_suffix (unescaped, ".html")) csp_header = "Content-Security-Policy"; cockpit_web_response_headers (response, 200, "OK", g_bytes_get_size (body), csp_header, "default-src 'self' 'unsafe-inline'; connect-src 'self' ws: wss:", NULL); if (cockpit_web_response_queue (response, body)) cockpit_web_response_complete (response); g_bytes_unref (body); out: g_free (unescaped); g_clear_error (&error); g_free (path); if (file) g_mapped_file_unref (file); }
/** * cockpit_web_response_file: * @response: the response * @path: escaped path, or NULL to get from response * @roots: directories to look for file in * * Serve a file from disk as an HTTP response. */ void cockpit_web_response_file (CockpitWebResponse *response, const gchar *escaped, gboolean cache_forever, const gchar **roots) { const gchar *cache_control; GError *error = NULL; gchar *unescaped; char *path = NULL; gchar *built = NULL; GMappedFile *file = NULL; const gchar *root; GBytes *body; g_return_if_fail (COCKPIT_IS_WEB_RESPONSE (response)); if (!escaped) escaped = cockpit_web_response_get_path (response); g_return_if_fail (escaped != NULL); again: root = *(roots++); if (root == NULL) { cockpit_web_response_error (response, 404, NULL, "Not Found"); goto out; } unescaped = g_uri_unescape_string (escaped, NULL); built = g_build_filename (root, unescaped, NULL); g_free (unescaped); path = realpath (built, NULL); g_free (built); if (path == NULL) { if (errno == ENOENT || errno == ENOTDIR || errno == ELOOP || errno == ENAMETOOLONG) { g_debug ("%s: file not found in root: %s", escaped, root); goto again; } else if (errno == EACCES) { cockpit_web_response_error (response, 403, NULL, "Access Denied"); goto out; } else { g_warning ("%s: resolving path failed: %m", escaped); cockpit_web_response_error (response, 500, NULL, "Internal Server Error"); goto out; } } /* Double check that realpath() did the right thing */ g_return_if_fail (strstr (path, "../") == NULL); g_return_if_fail (!g_str_has_suffix (path, "/..")); /* Someone is trying to escape the root directory */ if (!path_has_prefix (path, root) && !path_has_prefix (path, cockpit_web_exception_escape_root)) { g_debug ("%s: request tried to escape the root directory: %s: %s", escaped, root, path); cockpit_web_response_error (response, 404, NULL, "Not Found"); goto out; } if (g_file_test (path, G_FILE_TEST_IS_DIR)) { cockpit_web_response_error (response, 403, NULL, "Directory Listing Denied"); goto out; } file = g_mapped_file_new (path, FALSE, &error); if (file == NULL) { if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_PERM) || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_ACCES) || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_ISDIR)) { cockpit_web_response_error (response, 403, NULL, "Access denied"); g_clear_error (&error); goto out; } else { g_warning ("%s: %s", path, error->message); cockpit_web_response_error (response, 500, NULL, "Internal server error"); g_clear_error (&error); goto out; } } body = g_mapped_file_get_bytes (file); cache_control = cache_forever ? "max-age=31556926, public" : NULL; cockpit_web_response_headers (response, 200, "OK", g_bytes_get_size (body), "Cache-Control", cache_control, NULL); if (cockpit_web_response_queue (response, body)) cockpit_web_response_complete (response); g_bytes_unref (body); out: free (path); if (file) g_mapped_file_unref (file); }
/** * cockpit_web_response_file: * @response: the response * @path: escaped path, or NULL to get from response * @roots: directories to look for file in * * Serve a file from disk as an HTTP response. */ void cockpit_web_response_file (CockpitWebResponse *response, const gchar *escaped, gboolean cache_forever, const gchar **roots) { const gchar *cache_control; GError *error = NULL; gchar *unescaped = NULL; gchar *path = NULL; GMappedFile *file = NULL; const gchar *root; GBytes *body; g_return_if_fail (COCKPIT_IS_WEB_RESPONSE (response)); if (!escaped) escaped = cockpit_web_response_get_path (response); g_return_if_fail (escaped != NULL); /* Someone is trying to escape the root directory, or access hidden files? */ unescaped = g_uri_unescape_string (escaped, NULL); if (strstr (unescaped, "/.") || strstr (unescaped, "../") || strstr (unescaped, "//")) { g_debug ("%s: invalid path request", escaped); cockpit_web_response_error (response, 404, NULL, "Not Found"); goto out; } again: root = *(roots++); if (root == NULL) { cockpit_web_response_error (response, 404, NULL, "Not Found"); goto out; } g_free (path); path = g_build_filename (root, unescaped, NULL); if (g_file_test (path, G_FILE_TEST_IS_DIR)) { cockpit_web_response_error (response, 403, NULL, "Directory Listing Denied"); goto out; } /* As a double check of above behavior */ g_assert (path_has_prefix (path, root)); g_clear_error (&error); file = g_mapped_file_new (path, FALSE, &error); if (file == NULL) { if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT) || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NAMETOOLONG)) { g_debug ("%s: file not found in root: %s", escaped, root); goto again; } else if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_PERM) || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_ACCES) || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_ISDIR)) { cockpit_web_response_error (response, 403, NULL, "Access denied"); goto out; } else { g_warning ("%s: %s", path, error->message); cockpit_web_response_error (response, 500, NULL, "Internal server error"); goto out; } } body = g_mapped_file_get_bytes (file); cache_control = cache_forever ? "max-age=31556926, public" : NULL; cockpit_web_response_headers (response, 200, "OK", g_bytes_get_size (body), "Cache-Control", cache_control, NULL); if (cockpit_web_response_queue (response, body)) cockpit_web_response_complete (response); g_bytes_unref (body); out: g_free (unescaped); g_clear_error (&error); g_free (path); if (file) g_mapped_file_unref (file); }