web_response_file (CockpitWebResponse *response,
                   const gchar *escaped,
                   const gchar **roots,
                   CockpitTemplateFunc template_func,
                   gpointer user_data)
{
  const gchar *default_policy = "default-src 'self' 'unsafe-inline';";

  const gchar *headers[5] = { NULL };
  GError *error = NULL;
  gchar *unescaped = NULL;
  gchar *path = NULL;
  gchar *alloc = NULL;
  GMappedFile *file = NULL;
  const gchar *root;
  GBytes *body;
  GList *output = NULL;
  GList *l = NULL;
  gint content_length = -1;
  gint at = 0;

  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, "/");
  if (!unescaped || 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);
  if (template_func)
    {
      output = cockpit_template_expand (body, "${", "}", template_func, user_data);
    }
  else
    {
      output = g_list_prepend (output, g_bytes_ref (body));
      content_length = g_bytes_get_size (body);
    }
  g_bytes_unref (body);

  if (response->origin)
    {
      headers[at++] = "Access-Control-Allow-Origin";
      headers[at++] = response->origin;
    }

  /*
   * The default Content-Security-Policy for .html files allows
   * the site to have inline <script> and <style> tags. This code
   * is only used for static resources that do not use the session.
   */
  if (g_str_has_suffix (unescaped, ".html"))
    {
      headers[at++] = "Content-Security-Policy";
      headers[at++] = alloc = cockpit_web_response_security_policy (default_policy, response->origin);
    }

  cockpit_web_response_headers (response, 200, "OK", content_length,
                                headers[0], headers[1], headers[2], headers[3], NULL);

  for (l = output; l != NULL; l = g_list_next (l))
    {
      if (!cockpit_web_response_queue (response, l->data))
        break;
    }
  if (l == NULL)
    cockpit_web_response_complete (response);

out:
  g_free (alloc);
  g_free (unescaped);
  g_clear_error (&error);
  g_free (path);
  if (file)
    g_mapped_file_unref (file);

  if (output)
    g_list_free_full (output, (GDestroyNotify)g_bytes_unref);
}
web_response_file (CockpitWebResponse *response,
                   const gchar *escaped,
                   const gchar **roots,
                   CockpitTemplateFunc template_func,
                   gpointer user_data)
{
  const gchar *default_policy = "default-src 'self' 'unsafe-inline';";

  const gchar *headers[5] = { NULL };
  GError *error = NULL;
  gchar *unescaped = NULL;
  gchar *path = NULL;
  gchar *alloc = NULL;
  GMappedFile *file = NULL;
  const gchar *root;
  GBytes *body;
  GList *output = NULL;
  GList *l = NULL;
  gint content_length = -1;
  gint at = 0;

  g_return_if_fail (COCKPIT_IS_WEB_RESPONSE (response));

  if (!escaped)
    escaped = cockpit_web_response_get_path (response);

  g_return_if_fail (escaped != NULL);

   
  unescaped = g_uri_unescape_string (escaped, "/");
  if (!unescaped || 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;
    }

   
  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);
  if (template_func)
    {
      output = cockpit_template_expand (body, "${", "}", template_func, user_data);
    }
  else
    {
      output = g_list_prepend (output, g_bytes_ref (body));
      content_length = g_bytes_get_size (body);
    }
  g_bytes_unref (body);

  if (response->origin)
    {
      headers[at++] = "Access-Control-Allow-Origin";
      headers[at++] = response->origin;
    }

   
  if (g_str_has_suffix (unescaped, ".html"))
    {
      headers[at++] = "Content-Security-Policy";
      headers[at++] = alloc = cockpit_web_response_security_policy (default_policy, response->origin);
    }

  cockpit_web_response_headers (response, 200, "OK", content_length,
                                headers[0], headers[1], headers[2], headers[3], NULL);

  for (l = output; l != NULL; l = g_list_next (l))
    {
      if (!cockpit_web_response_queue (response, l->data))
        break;
    }
  if (l == NULL)
    cockpit_web_response_complete (response);

out:
  g_free (alloc);
  g_free (unescaped);
  g_clear_error (&error);
  g_free (path);
  if (file)
    g_mapped_file_unref (file);

  if (output)
    g_list_free_full (output, (GDestroyNotify)g_bytes_unref);
}