* Copyright (C) 2016 Red Hat, Inc.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "config.h"
#include "cockpitwebserver.h"
#include "cockpittransport.h"
#include "cockpitsystem.h"
#include "common/cockpitconf.h"
#include "cockpitauth.h"
#include "cockpitbranding.h"
#include "cockpitwebservice.h"
static void
add_config_branding_dirs (GPtrArray *dirs)
{
const gchar * const* config_dirs;
config_dirs = cockpit_conf_get_dirs ();
while (config_dirs && config_dirs[0])
{
g_autofree gchar *config_path = g_build_filename (config_dirs[0], "cockpit", "branding", NULL);
if (g_file_test (config_path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
g_ptr_array_add (dirs, g_steal_pointer (&config_path));
config_dirs++;
}
}
static const gchar * const*
get_system_data_dirs (void)
{
const gchar *env;
env = g_getenv ("XDG_DATA_DIRS");
if (env && env[0])
return g_get_system_data_dirs ();
return NULL;
}
static void
add_system_dirs (GPtrArray *dirs)
{
const gchar * const* system;
system = get_system_data_dirs ();
while (system && system[0])
{
g_ptr_array_add (dirs, g_build_filename (system[0], "cockpit", "static", NULL));
system++;
}
}
static void
add_system_branding_dirs (GPtrArray *dirs, const gchar *branding_path)
{
const gchar * const* system;
system = get_system_data_dirs ();
while (system && system[0])
{
g_ptr_array_add (dirs, g_build_filename (system[0], "cockpit", "branding", branding_path, NULL));
system++;
}
}
static gboolean
is_valid_path_component (const gchar *str)
{
return str && str[0] != '\0' && str[0] != '.' && !strchr (str, '/');
}
gchar **
cockpit_branding_calculate_static_roots (const gchar *os_id,
const gchar *os_variant_id,
const gchar *os_version_id,
const gchar *os_id_like,
gboolean is_local)
{
GPtrArray *dirs;
gchar **roots;
const gchar *static_dir;
dirs = g_ptr_array_new_with_free_func (g_free);
add_config_branding_dirs (dirs);
if (is_local)
add_system_dirs (dirs);
if (is_valid_path_component (os_id))
{
if (is_valid_path_component (os_variant_id))
{
g_autofree gchar *variant_path = g_strdup_printf ("%s-%s", os_id, os_variant_id);
add_system_branding_dirs (dirs, variant_path);
g_ptr_array_add (dirs, g_strdup_printf (DATADIR "/cockpit/branding/%s", variant_path));
}
if (is_valid_path_component (os_version_id))
{
g_autofree gchar *version_path = g_strdup_printf ("%s-%s", os_id, os_version_id);
add_system_branding_dirs (dirs, version_path);
g_ptr_array_add (dirs, g_strdup_printf (DATADIR "/cockpit/branding/%s", version_path));
}
add_system_branding_dirs (dirs, os_id);
g_ptr_array_add (dirs, g_strdup_printf (DATADIR "/cockpit/branding/%s", os_id));
}
if (os_id_like)
{
gchar **ids;
ids = g_strsplit_set (os_id_like, " ", -1);
for (gint i = 0; ids[i]; i += 1)
{
if (!is_valid_path_component (ids[i]))
continue;
add_system_branding_dirs (dirs, ids[i]);
g_ptr_array_add (dirs, g_strdup_printf (DATADIR "/cockpit/branding/%s", ids[i]));
}
g_strfreev (ids);
}
if (!is_local)
add_system_dirs (dirs);
add_system_branding_dirs (dirs, "default");
g_ptr_array_add (dirs, g_strdup (DATADIR "/cockpit/branding/default"));
static_dir = cockpit_conf_string ("WebService", "CustomLoginPage");
if (static_dir)
g_ptr_array_add (dirs, g_strdup (static_dir));
g_ptr_array_add (dirs, g_strdup (DATADIR "/cockpit/static"));
g_ptr_array_add (dirs, NULL);
roots = cockpit_web_response_resolve_roots ((const gchar **)dirs->pdata);
g_ptr_array_free (dirs, TRUE);
return roots;
}
static void
serve_branding_css_file (CockpitWebResponse *response,
const gchar *path,
const gchar **roots,
GHashTable *os_release)
{
if (os_release)
cockpit_web_response_template (response, path, roots, os_release);
else
cockpit_web_response_file (response, path, roots);
}
typedef struct {
const gchar *path;
CockpitWebResponse *response;
} CockpitBrandingData;
static void
serve_branding_css_with_init_data (CockpitWebService *service,
CockpitWebResponse *response,
const gchar *path)
{
CockpitTransport *transport = NULL;
GHashTable *os_release = NULL;
gchar **roots = NULL;
JsonObject *os = NULL;
gboolean responded = FALSE;
JsonObject *init = NULL;
init = cockpit_web_service_get_init (service);
if (!init)
goto out;
transport = cockpit_web_service_get_transport (service);
if (!transport)
goto out;
roots = g_object_get_data (G_OBJECT (transport), "static-roots");
if (!roots)
{
if (cockpit_json_get_object (init, "os-release", NULL, &os) && os)
os_release = cockpit_json_to_hash_table (os, cockpit_system_os_release_fields ());
if (os_release)
{
roots = cockpit_branding_calculate_static_roots (g_hash_table_lookup (os_release, "ID"),
g_hash_table_lookup (os_release, "VARIANT_ID"),
g_hash_table_lookup (os_release, "VERSION_ID"),
g_hash_table_lookup (os_release, "ID_LIKE"),
FALSE);
g_object_set_data_full (G_OBJECT (transport), "os-release", os_release,
(GDestroyNotify) g_hash_table_unref);
}
else
{
roots = cockpit_branding_calculate_static_roots (NULL, NULL, NULL, NULL, FALSE);
}
g_object_set_data_full (G_OBJECT (transport), "static-roots", roots,
(GDestroyNotify) g_strfreev);
}
else
{
os_release = g_object_get_data (G_OBJECT (transport), "os-release");
}
serve_branding_css_file (response, path, (const gchar **)roots, os_release);
responded = TRUE;
out:
if (!responded)
cockpit_web_response_error (response, 502, NULL, NULL);
}
void
cockpit_branding_serve (CockpitWebService *service,
CockpitWebResponse *response,
const gchar *full_path,
const gchar *static_path,
GHashTable *local_os_release,
const gchar **local_roots)
{
gboolean is_host = FALSE;
gchar *application = cockpit_auth_parse_application (full_path, &is_host);
if (is_host && !service)
{
cockpit_web_response_error (response, 403, NULL, NULL);
goto out;
}
cockpit_web_response_set_cache_type (response, COCKPIT_WEB_RESPONSE_CACHE);
if (g_str_has_suffix (static_path, ".css"))
{
if (!is_host)
{
serve_branding_css_file (response, static_path, local_roots, local_os_release);
}
else
{
serve_branding_css_with_init_data (service, response, static_path);
}
}
else
{
cockpit_web_response_file (response, static_path, local_roots);
}
out:
g_free (application);
}