mirror of
https://github.com/ostreedev/ostree.git
synced 2024-12-22 17:35:55 +03:00
Split trivial-httpd into separate binary
Working on the libcurl backend, I hit the issue that the trivial-httpd program depends on libsoup. I briefly considered having two versions, but libcurl is client only, and moreover trivial-httpd is no longer trivial - it has various features which are used by the test suite extensively. Hence, what we'll do is build it as a separate binary which links to libsoup, and use it during the tests. We *also* currently still provide `ostree trivial-httpd` since some things use it like `rpm-ostree-toolbox` and the Cockpit tests. After those are ported to use some other webserver, I plan to add a build-time option to drop it. Closes: #636 Approved by: jlebon
This commit is contained in:
parent
c051ee4a35
commit
ced22f6c9b
@ -103,7 +103,8 @@ EXTRA_DIST += src/ostree/parse-datetime.y
|
|||||||
CLEANFILES += src/ostree/parse-datetime.c
|
CLEANFILES += src/ostree/parse-datetime.c
|
||||||
|
|
||||||
ostree_bin_shared_cflags = $(AM_CFLAGS) -I$(srcdir)/src/libotutil -I$(srcdir)/src/libostree \
|
ostree_bin_shared_cflags = $(AM_CFLAGS) -I$(srcdir)/src/libotutil -I$(srcdir)/src/libostree \
|
||||||
-I$(srcdir)/src/ostree -I$(srcdir)/libglnx $(OT_INTERNAL_GIO_UNIX_CFLAGS)
|
-I$(srcdir)/src/ostree -I$(srcdir)/libglnx $(OT_INTERNAL_GIO_UNIX_CFLAGS) \
|
||||||
|
-DPKGLIBEXECDIR=\"$(pkglibexecdir)\"
|
||||||
ostree_bin_shared_ldadd = $(AM_LDFLAGS) libglnx.la libotutil.la libostree-1.la \
|
ostree_bin_shared_ldadd = $(AM_LDFLAGS) libglnx.la libotutil.la libostree-1.la \
|
||||||
$(OT_INTERNAL_GIO_UNIX_LIBS)
|
$(OT_INTERNAL_GIO_UNIX_LIBS)
|
||||||
|
|
||||||
@ -112,12 +113,14 @@ ostree_LDADD = $(ostree_bin_shared_ldadd) libbsdiff.la libostree-kernel-args.la
|
|||||||
|
|
||||||
|
|
||||||
if USE_LIBSOUP
|
if USE_LIBSOUP
|
||||||
ostree_SOURCES += \
|
ostree_SOURCES += src/ostree/ot-builtin-pull.c src/ostree/ot-builtin-trivial-httpd.c
|
||||||
src/ostree/ot-builtin-pull.c \
|
|
||||||
src/ostree/ot-builtin-trivial-httpd.c \
|
|
||||||
$(NULL)
|
|
||||||
ostree_CFLAGS += $(OT_INTERNAL_SOUP_CFLAGS)
|
ostree_CFLAGS += $(OT_INTERNAL_SOUP_CFLAGS)
|
||||||
ostree_LDADD += $(OT_INTERNAL_SOUP_LIBS)
|
ostree_LDADD += $(OT_INTERNAL_SOUP_LIBS)
|
||||||
|
|
||||||
|
pkglibexec_PROGRAMS += ostree-trivial-httpd
|
||||||
|
ostree_trivial_httpd_SOURCES = src/ostree/ostree-trivial-httpd.c
|
||||||
|
ostree_trivial_httpd_CFLAGS = $(ostree_bin_shared_cflags) $(OT_INTERNAL_SOUP_CFLAGS)
|
||||||
|
ostree_trivial_httpd_LDADD = $(ostree_bin_shared_ldadd) $(OT_INTERNAL_SOUP_LIBS)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if USE_LIBARCHIVE
|
if USE_LIBARCHIVE
|
||||||
|
@ -28,6 +28,7 @@ EXTRA_DIST += \
|
|||||||
# include the builddir in $PATH so we find our just-built ostree
|
# include the builddir in $PATH so we find our just-built ostree
|
||||||
# binary.
|
# binary.
|
||||||
TESTS_ENVIRONMENT += OT_TESTS_DEBUG=1 \
|
TESTS_ENVIRONMENT += OT_TESTS_DEBUG=1 \
|
||||||
|
OSTREE_UNINSTALLED=$(abs_top_builddir) \
|
||||||
G_DEBUG=fatal-warnings \
|
G_DEBUG=fatal-warnings \
|
||||||
GI_TYPELIB_PATH=$$(cd $(top_builddir) && pwd)$${GI_TYPELIB_PATH:+:$$GI_TYPELIB_PATH} \
|
GI_TYPELIB_PATH=$$(cd $(top_builddir) && pwd)$${GI_TYPELIB_PATH:+:$$GI_TYPELIB_PATH} \
|
||||||
LD_LIBRARY_PATH=$$(cd $(top_builddir)/.libs && pwd)$${LD_LIBRARY_PATH:+:$${LD_LIBRARY_PATH}} \
|
LD_LIBRARY_PATH=$$(cd $(top_builddir)/.libs && pwd)$${LD_LIBRARY_PATH:+:$${LD_LIBRARY_PATH}} \
|
||||||
|
683
src/ostree/ostree-trivial-httpd.c
Normal file
683
src/ostree/ostree-trivial-httpd.c
Normal file
@ -0,0 +1,683 @@
|
|||||||
|
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
||||||
|
*
|
||||||
|
* Copyright (C) 2011,2013 Colin Walters <walters@verbum.org>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library 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
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the
|
||||||
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||||
|
* Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <libsoup/soup.h>
|
||||||
|
|
||||||
|
#include <gio/gunixoutputstream.h>
|
||||||
|
|
||||||
|
#include "ot-main.h"
|
||||||
|
#include "ot-builtins.h"
|
||||||
|
#include "ostree.h"
|
||||||
|
#include "otutil.h"
|
||||||
|
|
||||||
|
#include <locale.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
static char *opt_port_file = NULL;
|
||||||
|
static char *opt_log = NULL;
|
||||||
|
static gboolean opt_daemonize;
|
||||||
|
static gboolean opt_autoexit;
|
||||||
|
static gboolean opt_force_ranges;
|
||||||
|
static int opt_random_500s_percentage;
|
||||||
|
/* We have a strong upper bound for any unlikely
|
||||||
|
* cases involving repeated random 500s. */
|
||||||
|
static int opt_random_500s_max = 100;
|
||||||
|
static gint opt_port = 0;
|
||||||
|
static gchar **opt_expected_cookies;
|
||||||
|
static gchar **opt_expected_headers;
|
||||||
|
|
||||||
|
static guint emitted_random_500s_count = 0;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int root_dfd;
|
||||||
|
gboolean running;
|
||||||
|
GOutputStream *log;
|
||||||
|
} OtTrivialHttpd;
|
||||||
|
|
||||||
|
static GOptionEntry options[] = {
|
||||||
|
{ "daemonize", 'd', 0, G_OPTION_ARG_NONE, &opt_daemonize, "Fork into background when ready", NULL },
|
||||||
|
{ "autoexit", 0, 0, G_OPTION_ARG_NONE, &opt_autoexit, "Automatically exit when directory is deleted", NULL },
|
||||||
|
{ "port", 'P', 0, G_OPTION_ARG_INT, &opt_port, "Use the specified TCP port", NULL },
|
||||||
|
{ "port-file", 'p', 0, G_OPTION_ARG_FILENAME, &opt_port_file, "Write port number to PATH (- for standard output)", "PATH" },
|
||||||
|
{ "force-range-requests", 0, 0, G_OPTION_ARG_NONE, &opt_force_ranges, "Force range requests by only serving half of files", NULL },
|
||||||
|
{ "random-500s", 0, 0, G_OPTION_ARG_INT, &opt_random_500s_percentage, "Generate random HTTP 500 errors approximately for PERCENTAGE requests", "PERCENTAGE" },
|
||||||
|
{ "random-500s-max", 0, 0, G_OPTION_ARG_INT, &opt_random_500s_max, "Limit HTTP 500 errors to MAX (default 100)", "MAX" },
|
||||||
|
{ "log-file", 0, 0, G_OPTION_ARG_FILENAME, &opt_log, "Put logs here", "PATH" },
|
||||||
|
{ "expected-cookies", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_expected_cookies, "Expect given cookies in the http request", "KEY=VALUE" },
|
||||||
|
{ "expected-header", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_expected_headers, "Expect given headers in the http request", "KEY=VALUE" },
|
||||||
|
{ NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
httpd_log (OtTrivialHttpd *httpd, const gchar *format, ...) __attribute__ ((format(printf, 2, 3)));
|
||||||
|
|
||||||
|
static void
|
||||||
|
httpd_log (OtTrivialHttpd *httpd, const gchar *format, ...)
|
||||||
|
{
|
||||||
|
g_autoptr(GString) str = NULL;
|
||||||
|
va_list args;
|
||||||
|
gsize written;
|
||||||
|
|
||||||
|
if (!httpd->log)
|
||||||
|
return;
|
||||||
|
|
||||||
|
{
|
||||||
|
g_autoptr(GDateTime) now = g_date_time_new_now_local ();
|
||||||
|
g_autofree char *timestamp = g_date_time_format (now, "%F %T");
|
||||||
|
str = g_string_new (timestamp);
|
||||||
|
g_string_append_printf (str, ".%06d - ", g_date_time_get_microsecond (now));
|
||||||
|
}
|
||||||
|
|
||||||
|
va_start (args, format);
|
||||||
|
g_string_append_vprintf (str, format, args);
|
||||||
|
va_end (args);
|
||||||
|
|
||||||
|
g_output_stream_write_all (httpd->log, str->str, str->len, &written, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
compare_strings (gconstpointer a, gconstpointer b)
|
||||||
|
{
|
||||||
|
const char **sa = (const char **)a;
|
||||||
|
const char **sb = (const char **)b;
|
||||||
|
|
||||||
|
return strcmp (*sa, *sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static GString *
|
||||||
|
get_directory_listing (int dfd,
|
||||||
|
const char *path)
|
||||||
|
{
|
||||||
|
g_autoptr(GPtrArray) entries = g_ptr_array_new_with_free_func (g_free);
|
||||||
|
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
|
||||||
|
g_autoptr(GError) local_error = NULL;
|
||||||
|
GError **error = &local_error;
|
||||||
|
guint i;
|
||||||
|
char *escaped;
|
||||||
|
GString *listing;
|
||||||
|
|
||||||
|
listing = g_string_new ("<html>\r\n");
|
||||||
|
|
||||||
|
if (!glnx_dirfd_iterator_init_at (dfd, path, FALSE, &dfd_iter, error))
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
while (TRUE)
|
||||||
|
{
|
||||||
|
struct dirent *dent;
|
||||||
|
|
||||||
|
if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, NULL, error))
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
if (dent == NULL)
|
||||||
|
break;
|
||||||
|
|
||||||
|
escaped = g_markup_escape_text (dent->d_name, -1);
|
||||||
|
g_ptr_array_add (entries, escaped);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_ptr_array_sort (entries, (GCompareFunc)compare_strings);
|
||||||
|
|
||||||
|
escaped = g_markup_escape_text (strchr (path, '/'), -1);
|
||||||
|
g_string_append_printf (listing, "<head><title>Index of %s</title></head>\r\n", escaped);
|
||||||
|
g_string_append_printf (listing, "<body><h1>Index of %s</h1>\r\n<p>\r\n", escaped);
|
||||||
|
g_free (escaped);
|
||||||
|
for (i = 0; i < entries->len; i++)
|
||||||
|
{
|
||||||
|
g_string_append_printf (listing, "<a href=\"%s\">%s</a><br>\r\n",
|
||||||
|
(char *)entries->pdata[i],
|
||||||
|
(char *)entries->pdata[i]);
|
||||||
|
g_free (g_steal_pointer (&entries->pdata[i]));
|
||||||
|
}
|
||||||
|
g_string_append (listing, "</body>\r\n</html>\r\n");
|
||||||
|
out:
|
||||||
|
if (local_error)
|
||||||
|
g_printerr ("%s\n", local_error->message);
|
||||||
|
return listing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Only allow reading files that have o+r, and for directories, o+x.
|
||||||
|
* This makes this server relatively safe to use on multiuser
|
||||||
|
* machines.
|
||||||
|
*/
|
||||||
|
static gboolean
|
||||||
|
is_safe_to_access (struct stat *stbuf)
|
||||||
|
{
|
||||||
|
/* Only regular files or directores */
|
||||||
|
if (!(S_ISREG (stbuf->st_mode) || S_ISDIR (stbuf->st_mode)))
|
||||||
|
return FALSE;
|
||||||
|
/* Must be o+r */
|
||||||
|
if (!(stbuf->st_mode & S_IROTH))
|
||||||
|
return FALSE;
|
||||||
|
/* For directories, must be o+x */
|
||||||
|
if (S_ISDIR (stbuf->st_mode) && !(stbuf->st_mode & S_IXOTH))
|
||||||
|
return FALSE;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
close_socket (SoupMessage *msg, gpointer user_data)
|
||||||
|
{
|
||||||
|
SoupSocket *sock = user_data;
|
||||||
|
int sockfd;
|
||||||
|
|
||||||
|
/* Actually calling soup_socket_disconnect() here would cause
|
||||||
|
* us to leak memory, so just shutdown the socket instead.
|
||||||
|
*/
|
||||||
|
sockfd = soup_socket_get_fd (sock);
|
||||||
|
#ifdef G_OS_WIN32
|
||||||
|
shutdown (sockfd, SD_SEND);
|
||||||
|
#else
|
||||||
|
shutdown (sockfd, SHUT_WR);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
do_get (OtTrivialHttpd *self,
|
||||||
|
SoupServer *server,
|
||||||
|
SoupMessage *msg,
|
||||||
|
const char *path,
|
||||||
|
SoupClientContext *context)
|
||||||
|
{
|
||||||
|
char *slash;
|
||||||
|
int ret;
|
||||||
|
struct stat stbuf;
|
||||||
|
|
||||||
|
httpd_log (self, "serving %s\n", path);
|
||||||
|
|
||||||
|
if (opt_expected_cookies)
|
||||||
|
{
|
||||||
|
GSList *cookies = soup_cookies_from_request (msg);
|
||||||
|
GSList *l;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0 ; opt_expected_cookies[i] != NULL; i++)
|
||||||
|
{
|
||||||
|
gboolean found = FALSE;
|
||||||
|
gchar *k = opt_expected_cookies[i];
|
||||||
|
gchar *v = strchr (k, '=') + 1;
|
||||||
|
|
||||||
|
for (l = cookies; l != NULL ; l = g_slist_next (l))
|
||||||
|
{
|
||||||
|
SoupCookie *c = l->data;
|
||||||
|
|
||||||
|
if (!strncmp (k, soup_cookie_get_name (c), v - k - 1) &&
|
||||||
|
!strcmp (v, soup_cookie_get_value (c)))
|
||||||
|
{
|
||||||
|
found = TRUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
httpd_log (self, "Expected cookie not found %s\n", k);
|
||||||
|
soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
|
||||||
|
soup_cookies_free (cookies);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
soup_cookies_free (cookies);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt_expected_headers)
|
||||||
|
{
|
||||||
|
for (int i = 0 ; opt_expected_headers[i] != NULL; i++)
|
||||||
|
{
|
||||||
|
const gchar *kv = opt_expected_headers[i];
|
||||||
|
const gchar *eq = strchr (kv, '=');
|
||||||
|
|
||||||
|
g_assert (eq);
|
||||||
|
|
||||||
|
{
|
||||||
|
g_autofree char *k = g_strndup (kv, eq - kv);
|
||||||
|
const gchar *expected_v = eq + 1;
|
||||||
|
const gchar *found_v = soup_message_headers_get_one (msg->request_headers, k);
|
||||||
|
|
||||||
|
if (!found_v)
|
||||||
|
{
|
||||||
|
httpd_log (self, "Expected header not found %s\n", k);
|
||||||
|
soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (strcmp (found_v, expected_v) != 0)
|
||||||
|
{
|
||||||
|
httpd_log (self, "Expected header %s: %s but found %s\n", k, expected_v, found_v);
|
||||||
|
soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstr (path, "../") != NULL)
|
||||||
|
{
|
||||||
|
soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt_random_500s_percentage > 0 &&
|
||||||
|
emitted_random_500s_count < opt_random_500s_max &&
|
||||||
|
g_random_int_range (0, 100) < opt_random_500s_percentage)
|
||||||
|
{
|
||||||
|
emitted_random_500s_count++;
|
||||||
|
soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (path[0] == '/')
|
||||||
|
path++;
|
||||||
|
|
||||||
|
do
|
||||||
|
ret = fstatat (self->root_dfd, path, &stbuf, 0);
|
||||||
|
while (ret == -1 && errno == EINTR);
|
||||||
|
if (ret == -1)
|
||||||
|
{
|
||||||
|
if (errno == EPERM)
|
||||||
|
soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
|
||||||
|
else if (errno == ENOENT)
|
||||||
|
soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
|
||||||
|
else
|
||||||
|
soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_safe_to_access (&stbuf))
|
||||||
|
{
|
||||||
|
soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (S_ISDIR (stbuf.st_mode))
|
||||||
|
{
|
||||||
|
slash = strrchr (path, '/');
|
||||||
|
if (!slash || slash[1])
|
||||||
|
{
|
||||||
|
g_autofree char *redir_uri = NULL;
|
||||||
|
|
||||||
|
redir_uri = g_strdup_printf ("%s/", soup_message_get_uri (msg)->path);
|
||||||
|
soup_message_set_redirect (msg, SOUP_STATUS_MOVED_PERMANENTLY,
|
||||||
|
redir_uri);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_autofree char *index_realpath = g_strconcat (path, "/index.html", NULL);
|
||||||
|
if (fstatat (self->root_dfd, index_realpath, &stbuf, 0) != -1)
|
||||||
|
{
|
||||||
|
g_autofree char *index_path = g_strconcat (path, "/index.html", NULL);
|
||||||
|
do_get (self, server, msg, index_path, context);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GString *listing = get_directory_listing (self->root_dfd, path);
|
||||||
|
soup_message_set_response (msg, "text/html",
|
||||||
|
SOUP_MEMORY_TAKE,
|
||||||
|
listing->str, listing->len);
|
||||||
|
soup_message_set_status (msg, SOUP_STATUS_OK);
|
||||||
|
g_string_free (listing, FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!S_ISREG (stbuf.st_mode))
|
||||||
|
{
|
||||||
|
soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg->method == SOUP_METHOD_GET)
|
||||||
|
{
|
||||||
|
glnx_fd_close int fd = -1;
|
||||||
|
g_autoptr(GMappedFile) mapping = NULL;
|
||||||
|
gsize buffer_length, file_size;
|
||||||
|
SoupRange *ranges;
|
||||||
|
int ranges_length;
|
||||||
|
gboolean have_ranges;
|
||||||
|
|
||||||
|
fd = openat (self->root_dfd, path, O_RDONLY | O_CLOEXEC);
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
mapping = g_mapped_file_new_from_fd (fd, FALSE, NULL);
|
||||||
|
if (!mapping)
|
||||||
|
{
|
||||||
|
soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
(void) close (fd); fd = -1;
|
||||||
|
|
||||||
|
file_size = g_mapped_file_get_length (mapping);
|
||||||
|
have_ranges = soup_message_headers_get_ranges(msg->request_headers, file_size, &ranges, &ranges_length);
|
||||||
|
if (opt_force_ranges && !have_ranges && g_strrstr (path, "/objects") != NULL)
|
||||||
|
{
|
||||||
|
SoupSocket *sock;
|
||||||
|
buffer_length = file_size/2;
|
||||||
|
soup_message_headers_set_content_length (msg->response_headers, file_size);
|
||||||
|
soup_message_headers_append (msg->response_headers,
|
||||||
|
"Connection", "close");
|
||||||
|
|
||||||
|
/* soup-message-io will wait for us to add
|
||||||
|
* another chunk after the first, to fill out
|
||||||
|
* the declared Content-Length. Instead, we
|
||||||
|
* forcibly close the socket at that point.
|
||||||
|
*/
|
||||||
|
sock = soup_client_context_get_socket (context);
|
||||||
|
g_signal_connect (msg, "wrote-chunk", G_CALLBACK (close_socket), sock);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
buffer_length = file_size;
|
||||||
|
|
||||||
|
if (have_ranges)
|
||||||
|
{
|
||||||
|
if (ranges_length > 0 && ranges[0].start >= file_size)
|
||||||
|
{
|
||||||
|
soup_message_set_status (msg, SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE);
|
||||||
|
soup_message_headers_free_ranges (msg->request_headers, ranges);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
soup_message_headers_free_ranges (msg->request_headers, ranges);
|
||||||
|
}
|
||||||
|
if (buffer_length > 0)
|
||||||
|
{
|
||||||
|
SoupBuffer *buffer;
|
||||||
|
|
||||||
|
buffer = soup_buffer_new_with_owner (g_mapped_file_get_contents (mapping),
|
||||||
|
buffer_length,
|
||||||
|
g_mapped_file_ref (mapping),
|
||||||
|
(GDestroyNotify)g_mapped_file_unref);
|
||||||
|
soup_message_body_append_buffer (msg->response_body, buffer);
|
||||||
|
soup_buffer_free (buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else /* msg->method == SOUP_METHOD_HEAD */
|
||||||
|
{
|
||||||
|
g_autofree char *length = NULL;
|
||||||
|
|
||||||
|
/* We could just use the same code for both GET and
|
||||||
|
* HEAD (soup-message-server-io.c will fix things up).
|
||||||
|
* But we'll optimize and avoid the extra I/O.
|
||||||
|
*/
|
||||||
|
length = g_strdup_printf ("%lu", (gulong)stbuf.st_size);
|
||||||
|
soup_message_headers_append (msg->response_headers,
|
||||||
|
"Content-Length", length);
|
||||||
|
}
|
||||||
|
soup_message_set_status (msg, SOUP_STATUS_OK);
|
||||||
|
}
|
||||||
|
out:
|
||||||
|
{
|
||||||
|
guint status = 0;
|
||||||
|
g_autofree gchar *reason = NULL;
|
||||||
|
|
||||||
|
g_object_get (msg,
|
||||||
|
"status-code", &status,
|
||||||
|
"reason-phrase", &reason,
|
||||||
|
NULL);
|
||||||
|
httpd_log (self, " status: %s (%u)\n", reason, status);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
httpd_callback (SoupServer *server, SoupMessage *msg,
|
||||||
|
const char *path, GHashTable *query,
|
||||||
|
SoupClientContext *context, gpointer data)
|
||||||
|
{
|
||||||
|
OtTrivialHttpd *self = data;
|
||||||
|
|
||||||
|
if (msg->method == SOUP_METHOD_GET || msg->method == SOUP_METHOD_HEAD)
|
||||||
|
do_get (self, server, msg, path, context);
|
||||||
|
else
|
||||||
|
soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_dir_changed (GFileMonitor *mon,
|
||||||
|
GFile *file,
|
||||||
|
GFile *other,
|
||||||
|
GFileMonitorEvent event,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
OtTrivialHttpd *self = user_data;
|
||||||
|
|
||||||
|
if (event == G_FILE_MONITOR_EVENT_DELETED)
|
||||||
|
{
|
||||||
|
self->running = FALSE;
|
||||||
|
g_main_context_wakeup (NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
run (int argc, char **argv, GCancellable *cancellable, GError **error)
|
||||||
|
{
|
||||||
|
gboolean ret = FALSE;
|
||||||
|
g_autoptr(GOptionContext) context = NULL;
|
||||||
|
const char *dirpath;
|
||||||
|
OtTrivialHttpd appstruct = { 0, };
|
||||||
|
OtTrivialHttpd *app = &appstruct;
|
||||||
|
glnx_unref_object SoupServer *server = NULL;
|
||||||
|
g_autoptr(GFileMonitor) dirmon = NULL;
|
||||||
|
|
||||||
|
context = g_option_context_new ("[DIR] - Simple webserver");
|
||||||
|
g_option_context_add_main_entries (context, options, NULL);
|
||||||
|
|
||||||
|
app->root_dfd = -1;
|
||||||
|
|
||||||
|
if (!g_option_context_parse (context, &argc, &argv, error))
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
if (argc > 1)
|
||||||
|
dirpath = argv[1];
|
||||||
|
else
|
||||||
|
dirpath = ".";
|
||||||
|
|
||||||
|
if (!glnx_opendirat (AT_FDCWD, dirpath, TRUE, &app->root_dfd, error))
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
if (!(opt_random_500s_percentage >= 0 && opt_random_500s_percentage <= 99))
|
||||||
|
{
|
||||||
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||||
|
"Invalid --random-500s=%u", opt_random_500s_percentage);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt_log)
|
||||||
|
{
|
||||||
|
GOutputStream *stream = NULL;
|
||||||
|
|
||||||
|
if (g_strcmp0 (opt_log, "-") == 0)
|
||||||
|
{
|
||||||
|
if (opt_daemonize)
|
||||||
|
{
|
||||||
|
ot_util_usage_error (context, "Cannot use --log-file=- and --daemonize at the same time", error);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
stream = G_OUTPUT_STREAM (g_unix_output_stream_new (STDOUT_FILENO, FALSE));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_autoptr(GFile) log_file;
|
||||||
|
GFileOutputStream* log_stream;
|
||||||
|
|
||||||
|
log_file = g_file_new_for_path (opt_log);
|
||||||
|
log_stream = g_file_create (log_file,
|
||||||
|
G_FILE_CREATE_PRIVATE,
|
||||||
|
cancellable,
|
||||||
|
error);
|
||||||
|
if (!log_stream)
|
||||||
|
goto out;
|
||||||
|
stream = G_OUTPUT_STREAM (log_stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
app->log = stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if SOUP_CHECK_VERSION(2, 48, 0)
|
||||||
|
server = soup_server_new (SOUP_SERVER_SERVER_HEADER, "ostree-httpd ", NULL);
|
||||||
|
if (!soup_server_listen_all (server, opt_port, 0, error))
|
||||||
|
goto out;
|
||||||
|
#else
|
||||||
|
server = soup_server_new (SOUP_SERVER_PORT, opt_port,
|
||||||
|
SOUP_SERVER_SERVER_HEADER, "ostree-httpd ",
|
||||||
|
NULL);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
soup_server_add_handler (server, NULL, httpd_callback, app, NULL);
|
||||||
|
if (opt_port_file)
|
||||||
|
{
|
||||||
|
g_autofree char *portstr = NULL;
|
||||||
|
#if SOUP_CHECK_VERSION(2, 48, 0)
|
||||||
|
GSList *listeners = soup_server_get_listeners (server);
|
||||||
|
g_autoptr(GSocket) listener = NULL;
|
||||||
|
g_autoptr(GSocketAddress) addr = NULL;
|
||||||
|
|
||||||
|
g_assert (listeners);
|
||||||
|
listener = g_object_ref (listeners->data);
|
||||||
|
g_slist_free (listeners);
|
||||||
|
listeners = NULL;
|
||||||
|
addr = g_socket_get_local_address (listener, error);
|
||||||
|
if (!addr)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
g_assert (G_IS_INET_SOCKET_ADDRESS (addr));
|
||||||
|
|
||||||
|
portstr = g_strdup_printf ("%u\n", g_inet_socket_address_get_port ((GInetSocketAddress*)addr));
|
||||||
|
#else
|
||||||
|
portstr = g_strdup_printf ("%u\n", soup_server_get_port (server));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (g_strcmp0 ("-", opt_port_file) == 0)
|
||||||
|
{
|
||||||
|
fputs (portstr, stdout); // not g_print - this must go to stdout, not a handler
|
||||||
|
fflush (stdout);
|
||||||
|
}
|
||||||
|
else if (!g_file_set_contents (opt_port_file, portstr, strlen (portstr), error))
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
#if !SOUP_CHECK_VERSION(2, 48, 0)
|
||||||
|
soup_server_run_async (server);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (opt_daemonize)
|
||||||
|
{
|
||||||
|
pid_t pid = fork();
|
||||||
|
if (pid == -1)
|
||||||
|
{
|
||||||
|
int errsv = errno;
|
||||||
|
g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
|
||||||
|
g_strerror (errsv));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
else if (pid > 0)
|
||||||
|
{
|
||||||
|
ret = TRUE;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
/* Child, continue */
|
||||||
|
/* Daemonising: close stdout/stderr so $() et al work on us */
|
||||||
|
fclose (stdout);
|
||||||
|
fclose (stdin);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Since we're used for testing purposes, let's just do this by
|
||||||
|
* default. This ensures we exit when our parent does.
|
||||||
|
*/
|
||||||
|
if (prctl (PR_SET_PDEATHSIG, SIGTERM) != 0)
|
||||||
|
{
|
||||||
|
if (errno != ENOSYS)
|
||||||
|
{
|
||||||
|
glnx_set_error_from_errno (error);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app->running = TRUE;
|
||||||
|
if (opt_autoexit)
|
||||||
|
{
|
||||||
|
gboolean is_symlink = FALSE;
|
||||||
|
g_autoptr(GFile) root = NULL;
|
||||||
|
g_autoptr(GFileInfo) info = NULL;
|
||||||
|
|
||||||
|
root = g_file_new_for_path (dirpath);
|
||||||
|
info = g_file_query_info (root,
|
||||||
|
G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK,
|
||||||
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||||
|
cancellable, error);
|
||||||
|
if (!info)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
is_symlink = g_file_info_get_is_symlink (info);
|
||||||
|
|
||||||
|
if (is_symlink)
|
||||||
|
dirmon = g_file_monitor_file (root, 0, cancellable, error);
|
||||||
|
else
|
||||||
|
dirmon = g_file_monitor_directory (root, 0, cancellable, error);
|
||||||
|
|
||||||
|
if (!dirmon)
|
||||||
|
goto out;
|
||||||
|
g_signal_connect (dirmon, "changed", G_CALLBACK (on_dir_changed), app);
|
||||||
|
}
|
||||||
|
httpd_log (app, "serving at root %s\n", dirpath);
|
||||||
|
while (app->running)
|
||||||
|
g_main_context_iteration (NULL, TRUE);
|
||||||
|
|
||||||
|
ret = TRUE;
|
||||||
|
out:
|
||||||
|
if (app->root_dfd != -1)
|
||||||
|
(void) close (app->root_dfd);
|
||||||
|
g_clear_object (&app->log);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc,
|
||||||
|
char **argv)
|
||||||
|
{
|
||||||
|
g_autoptr(GError) error = NULL;
|
||||||
|
g_autoptr(GCancellable) cancellable = NULL;
|
||||||
|
|
||||||
|
setlocale (LC_ALL, "");
|
||||||
|
|
||||||
|
g_set_prgname (argv[0]);
|
||||||
|
|
||||||
|
if (!run (argc, argv, cancellable, &error))
|
||||||
|
{
|
||||||
|
int is_tty = isatty (1);
|
||||||
|
const char *prefix = "";
|
||||||
|
const char *suffix = "";
|
||||||
|
if (is_tty)
|
||||||
|
{
|
||||||
|
prefix = "\x1b[31m\x1b[1m"; /* red, bold */
|
||||||
|
suffix = "\x1b[22m\x1b[0m"; /* bold off, color reset */
|
||||||
|
}
|
||||||
|
g_printerr ("%serror: %s%s\n", prefix, suffix, error->message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
||||||
*
|
*
|
||||||
* Copyright (C) 2011,2013 Colin Walters <walters@verbum.org>
|
* Copyright (C) 2016 Colin Walters <walters@verbum.org>
|
||||||
*
|
*
|
||||||
* This library is free software; you can redistribute it and/or
|
* This library is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
@ -20,634 +20,22 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <libsoup/soup.h>
|
|
||||||
|
|
||||||
#include <gio/gunixoutputstream.h>
|
|
||||||
|
|
||||||
#include "ot-main.h"
|
#include "ot-main.h"
|
||||||
#include "ot-builtins.h"
|
#include "ot-builtins.h"
|
||||||
#include "ostree.h"
|
#include "ostree.h"
|
||||||
#include "otutil.h"
|
#include "otutil.h"
|
||||||
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/prctl.h>
|
|
||||||
#include <signal.h>
|
|
||||||
|
|
||||||
static char *opt_port_file = NULL;
|
|
||||||
static char *opt_log = NULL;
|
|
||||||
static gboolean opt_daemonize;
|
|
||||||
static gboolean opt_autoexit;
|
|
||||||
static gboolean opt_force_ranges;
|
|
||||||
static int opt_random_500s_percentage;
|
|
||||||
/* We have a strong upper bound for any unlikely
|
|
||||||
* cases involving repeated random 500s. */
|
|
||||||
static int opt_random_500s_max = 100;
|
|
||||||
static gint opt_port = 0;
|
|
||||||
static gchar **opt_expected_cookies;
|
|
||||||
static gchar **opt_expected_headers;
|
|
||||||
|
|
||||||
static guint emitted_random_500s_count = 0;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int root_dfd;
|
|
||||||
gboolean running;
|
|
||||||
GOutputStream *log;
|
|
||||||
} OtTrivialHttpd;
|
|
||||||
|
|
||||||
static GOptionEntry options[] = {
|
|
||||||
{ "daemonize", 'd', 0, G_OPTION_ARG_NONE, &opt_daemonize, "Fork into background when ready", NULL },
|
|
||||||
{ "autoexit", 0, 0, G_OPTION_ARG_NONE, &opt_autoexit, "Automatically exit when directory is deleted", NULL },
|
|
||||||
{ "port", 'P', 0, G_OPTION_ARG_INT, &opt_port, "Use the specified TCP port", NULL },
|
|
||||||
{ "port-file", 'p', 0, G_OPTION_ARG_FILENAME, &opt_port_file, "Write port number to PATH (- for standard output)", "PATH" },
|
|
||||||
{ "force-range-requests", 0, 0, G_OPTION_ARG_NONE, &opt_force_ranges, "Force range requests by only serving half of files", NULL },
|
|
||||||
{ "random-500s", 0, 0, G_OPTION_ARG_INT, &opt_random_500s_percentage, "Generate random HTTP 500 errors approximately for PERCENTAGE requests", "PERCENTAGE" },
|
|
||||||
{ "random-500s-max", 0, 0, G_OPTION_ARG_INT, &opt_random_500s_max, "Limit HTTP 500 errors to MAX (default 100)", "MAX" },
|
|
||||||
{ "log-file", 0, 0, G_OPTION_ARG_FILENAME, &opt_log, "Put logs here", "PATH" },
|
|
||||||
{ "expected-cookies", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_expected_cookies, "Expect given cookies in the http request", "KEY=VALUE" },
|
|
||||||
{ "expected-header", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_expected_headers, "Expect given headers in the http request", "KEY=VALUE" },
|
|
||||||
{ NULL }
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
|
||||||
httpd_log (OtTrivialHttpd *httpd, const gchar *format, ...) __attribute__ ((format(printf, 2, 3)));
|
|
||||||
|
|
||||||
static void
|
|
||||||
httpd_log (OtTrivialHttpd *httpd, const gchar *format, ...)
|
|
||||||
{
|
|
||||||
g_autoptr(GString) str = NULL;
|
|
||||||
va_list args;
|
|
||||||
gsize written;
|
|
||||||
|
|
||||||
if (!httpd->log)
|
|
||||||
return;
|
|
||||||
|
|
||||||
{
|
|
||||||
g_autoptr(GDateTime) now = g_date_time_new_now_local ();
|
|
||||||
g_autofree char *timestamp = g_date_time_format (now, "%F %T");
|
|
||||||
str = g_string_new (timestamp);
|
|
||||||
g_string_append_printf (str, ".%06d - ", g_date_time_get_microsecond (now));
|
|
||||||
}
|
|
||||||
|
|
||||||
va_start (args, format);
|
|
||||||
g_string_append_vprintf (str, format, args);
|
|
||||||
va_end (args);
|
|
||||||
|
|
||||||
g_output_stream_write_all (httpd->log, str->str, str->len, &written, NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
compare_strings (gconstpointer a, gconstpointer b)
|
|
||||||
{
|
|
||||||
const char **sa = (const char **)a;
|
|
||||||
const char **sb = (const char **)b;
|
|
||||||
|
|
||||||
return strcmp (*sa, *sb);
|
|
||||||
}
|
|
||||||
|
|
||||||
static GString *
|
|
||||||
get_directory_listing (int dfd,
|
|
||||||
const char *path)
|
|
||||||
{
|
|
||||||
g_autoptr(GPtrArray) entries = g_ptr_array_new_with_free_func (g_free);
|
|
||||||
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
|
|
||||||
g_autoptr(GError) local_error = NULL;
|
|
||||||
GError **error = &local_error;
|
|
||||||
guint i;
|
|
||||||
char *escaped;
|
|
||||||
GString *listing;
|
|
||||||
|
|
||||||
listing = g_string_new ("<html>\r\n");
|
|
||||||
|
|
||||||
if (!glnx_dirfd_iterator_init_at (dfd, path, FALSE, &dfd_iter, error))
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
while (TRUE)
|
|
||||||
{
|
|
||||||
struct dirent *dent;
|
|
||||||
|
|
||||||
if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, NULL, error))
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
if (dent == NULL)
|
|
||||||
break;
|
|
||||||
|
|
||||||
escaped = g_markup_escape_text (dent->d_name, -1);
|
|
||||||
g_ptr_array_add (entries, escaped);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_ptr_array_sort (entries, (GCompareFunc)compare_strings);
|
|
||||||
|
|
||||||
escaped = g_markup_escape_text (strchr (path, '/'), -1);
|
|
||||||
g_string_append_printf (listing, "<head><title>Index of %s</title></head>\r\n", escaped);
|
|
||||||
g_string_append_printf (listing, "<body><h1>Index of %s</h1>\r\n<p>\r\n", escaped);
|
|
||||||
g_free (escaped);
|
|
||||||
for (i = 0; i < entries->len; i++)
|
|
||||||
{
|
|
||||||
g_string_append_printf (listing, "<a href=\"%s\">%s</a><br>\r\n",
|
|
||||||
(char *)entries->pdata[i],
|
|
||||||
(char *)entries->pdata[i]);
|
|
||||||
g_free (g_steal_pointer (&entries->pdata[i]));
|
|
||||||
}
|
|
||||||
g_string_append (listing, "</body>\r\n</html>\r\n");
|
|
||||||
out:
|
|
||||||
if (local_error)
|
|
||||||
g_printerr ("%s\n", local_error->message);
|
|
||||||
return listing;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Only allow reading files that have o+r, and for directories, o+x.
|
|
||||||
* This makes this server relatively safe to use on multiuser
|
|
||||||
* machines.
|
|
||||||
*/
|
|
||||||
static gboolean
|
|
||||||
is_safe_to_access (struct stat *stbuf)
|
|
||||||
{
|
|
||||||
/* Only regular files or directores */
|
|
||||||
if (!(S_ISREG (stbuf->st_mode) || S_ISDIR (stbuf->st_mode)))
|
|
||||||
return FALSE;
|
|
||||||
/* Must be o+r */
|
|
||||||
if (!(stbuf->st_mode & S_IROTH))
|
|
||||||
return FALSE;
|
|
||||||
/* For directories, must be o+x */
|
|
||||||
if (S_ISDIR (stbuf->st_mode) && !(stbuf->st_mode & S_IXOTH))
|
|
||||||
return FALSE;
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
close_socket (SoupMessage *msg, gpointer user_data)
|
|
||||||
{
|
|
||||||
SoupSocket *sock = user_data;
|
|
||||||
int sockfd;
|
|
||||||
|
|
||||||
/* Actually calling soup_socket_disconnect() here would cause
|
|
||||||
* us to leak memory, so just shutdown the socket instead.
|
|
||||||
*/
|
|
||||||
sockfd = soup_socket_get_fd (sock);
|
|
||||||
#ifdef G_OS_WIN32
|
|
||||||
shutdown (sockfd, SD_SEND);
|
|
||||||
#else
|
|
||||||
shutdown (sockfd, SHUT_WR);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
do_get (OtTrivialHttpd *self,
|
|
||||||
SoupServer *server,
|
|
||||||
SoupMessage *msg,
|
|
||||||
const char *path,
|
|
||||||
SoupClientContext *context)
|
|
||||||
{
|
|
||||||
char *slash;
|
|
||||||
int ret;
|
|
||||||
struct stat stbuf;
|
|
||||||
|
|
||||||
httpd_log (self, "serving %s\n", path);
|
|
||||||
|
|
||||||
if (opt_expected_cookies)
|
|
||||||
{
|
|
||||||
GSList *cookies = soup_cookies_from_request (msg);
|
|
||||||
GSList *l;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0 ; opt_expected_cookies[i] != NULL; i++)
|
|
||||||
{
|
|
||||||
gboolean found = FALSE;
|
|
||||||
gchar *k = opt_expected_cookies[i];
|
|
||||||
gchar *v = strchr (k, '=') + 1;
|
|
||||||
|
|
||||||
for (l = cookies; l != NULL ; l = g_slist_next (l))
|
|
||||||
{
|
|
||||||
SoupCookie *c = l->data;
|
|
||||||
|
|
||||||
if (!strncmp (k, soup_cookie_get_name (c), v - k - 1) &&
|
|
||||||
!strcmp (v, soup_cookie_get_value (c)))
|
|
||||||
{
|
|
||||||
found = TRUE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found)
|
|
||||||
{
|
|
||||||
httpd_log (self, "Expected cookie not found %s\n", k);
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
|
|
||||||
soup_cookies_free (cookies);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
soup_cookies_free (cookies);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opt_expected_headers)
|
|
||||||
{
|
|
||||||
for (int i = 0 ; opt_expected_headers[i] != NULL; i++)
|
|
||||||
{
|
|
||||||
const gchar *kv = opt_expected_headers[i];
|
|
||||||
const gchar *eq = strchr (kv, '=');
|
|
||||||
|
|
||||||
g_assert (eq);
|
|
||||||
|
|
||||||
{
|
|
||||||
g_autofree char *k = g_strndup (kv, eq - kv);
|
|
||||||
const gchar *expected_v = eq + 1;
|
|
||||||
const gchar *found_v = soup_message_headers_get_one (msg->request_headers, k);
|
|
||||||
|
|
||||||
if (!found_v)
|
|
||||||
{
|
|
||||||
httpd_log (self, "Expected header not found %s\n", k);
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if (strcmp (found_v, expected_v) != 0)
|
|
||||||
{
|
|
||||||
httpd_log (self, "Expected header %s: %s but found %s\n", k, expected_v, found_v);
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strstr (path, "../") != NULL)
|
|
||||||
{
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opt_random_500s_percentage > 0 &&
|
|
||||||
emitted_random_500s_count < opt_random_500s_max &&
|
|
||||||
g_random_int_range (0, 100) < opt_random_500s_percentage)
|
|
||||||
{
|
|
||||||
emitted_random_500s_count++;
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (path[0] == '/')
|
|
||||||
path++;
|
|
||||||
|
|
||||||
do
|
|
||||||
ret = fstatat (self->root_dfd, path, &stbuf, 0);
|
|
||||||
while (ret == -1 && errno == EINTR);
|
|
||||||
if (ret == -1)
|
|
||||||
{
|
|
||||||
if (errno == EPERM)
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
|
|
||||||
else if (errno == ENOENT)
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
|
|
||||||
else
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_safe_to_access (&stbuf))
|
|
||||||
{
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (S_ISDIR (stbuf.st_mode))
|
|
||||||
{
|
|
||||||
slash = strrchr (path, '/');
|
|
||||||
if (!slash || slash[1])
|
|
||||||
{
|
|
||||||
g_autofree char *redir_uri = NULL;
|
|
||||||
|
|
||||||
redir_uri = g_strdup_printf ("%s/", soup_message_get_uri (msg)->path);
|
|
||||||
soup_message_set_redirect (msg, SOUP_STATUS_MOVED_PERMANENTLY,
|
|
||||||
redir_uri);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
g_autofree char *index_realpath = g_strconcat (path, "/index.html", NULL);
|
|
||||||
if (fstatat (self->root_dfd, index_realpath, &stbuf, 0) != -1)
|
|
||||||
{
|
|
||||||
g_autofree char *index_path = g_strconcat (path, "/index.html", NULL);
|
|
||||||
do_get (self, server, msg, index_path, context);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GString *listing = get_directory_listing (self->root_dfd, path);
|
|
||||||
soup_message_set_response (msg, "text/html",
|
|
||||||
SOUP_MEMORY_TAKE,
|
|
||||||
listing->str, listing->len);
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_OK);
|
|
||||||
g_string_free (listing, FALSE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!S_ISREG (stbuf.st_mode))
|
|
||||||
{
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg->method == SOUP_METHOD_GET)
|
|
||||||
{
|
|
||||||
glnx_fd_close int fd = -1;
|
|
||||||
g_autoptr(GMappedFile) mapping = NULL;
|
|
||||||
gsize buffer_length, file_size;
|
|
||||||
SoupRange *ranges;
|
|
||||||
int ranges_length;
|
|
||||||
gboolean have_ranges;
|
|
||||||
|
|
||||||
fd = openat (self->root_dfd, path, O_RDONLY | O_CLOEXEC);
|
|
||||||
if (fd < 0)
|
|
||||||
{
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
mapping = g_mapped_file_new_from_fd (fd, FALSE, NULL);
|
|
||||||
if (!mapping)
|
|
||||||
{
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
(void) close (fd); fd = -1;
|
|
||||||
|
|
||||||
file_size = g_mapped_file_get_length (mapping);
|
|
||||||
have_ranges = soup_message_headers_get_ranges(msg->request_headers, file_size, &ranges, &ranges_length);
|
|
||||||
if (opt_force_ranges && !have_ranges && g_strrstr (path, "/objects") != NULL)
|
|
||||||
{
|
|
||||||
SoupSocket *sock;
|
|
||||||
buffer_length = file_size/2;
|
|
||||||
soup_message_headers_set_content_length (msg->response_headers, file_size);
|
|
||||||
soup_message_headers_append (msg->response_headers,
|
|
||||||
"Connection", "close");
|
|
||||||
|
|
||||||
/* soup-message-io will wait for us to add
|
|
||||||
* another chunk after the first, to fill out
|
|
||||||
* the declared Content-Length. Instead, we
|
|
||||||
* forcibly close the socket at that point.
|
|
||||||
*/
|
|
||||||
sock = soup_client_context_get_socket (context);
|
|
||||||
g_signal_connect (msg, "wrote-chunk", G_CALLBACK (close_socket), sock);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
buffer_length = file_size;
|
|
||||||
|
|
||||||
if (have_ranges)
|
|
||||||
{
|
|
||||||
if (ranges_length > 0 && ranges[0].start >= file_size)
|
|
||||||
{
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE);
|
|
||||||
soup_message_headers_free_ranges (msg->request_headers, ranges);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
soup_message_headers_free_ranges (msg->request_headers, ranges);
|
|
||||||
}
|
|
||||||
if (buffer_length > 0)
|
|
||||||
{
|
|
||||||
SoupBuffer *buffer;
|
|
||||||
|
|
||||||
buffer = soup_buffer_new_with_owner (g_mapped_file_get_contents (mapping),
|
|
||||||
buffer_length,
|
|
||||||
g_mapped_file_ref (mapping),
|
|
||||||
(GDestroyNotify)g_mapped_file_unref);
|
|
||||||
soup_message_body_append_buffer (msg->response_body, buffer);
|
|
||||||
soup_buffer_free (buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else /* msg->method == SOUP_METHOD_HEAD */
|
|
||||||
{
|
|
||||||
g_autofree char *length = NULL;
|
|
||||||
|
|
||||||
/* We could just use the same code for both GET and
|
|
||||||
* HEAD (soup-message-server-io.c will fix things up).
|
|
||||||
* But we'll optimize and avoid the extra I/O.
|
|
||||||
*/
|
|
||||||
length = g_strdup_printf ("%lu", (gulong)stbuf.st_size);
|
|
||||||
soup_message_headers_append (msg->response_headers,
|
|
||||||
"Content-Length", length);
|
|
||||||
}
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_OK);
|
|
||||||
}
|
|
||||||
out:
|
|
||||||
{
|
|
||||||
guint status = 0;
|
|
||||||
g_autofree gchar *reason = NULL;
|
|
||||||
|
|
||||||
g_object_get (msg,
|
|
||||||
"status-code", &status,
|
|
||||||
"reason-phrase", &reason,
|
|
||||||
NULL);
|
|
||||||
httpd_log (self, " status: %s (%u)\n", reason, status);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
httpd_callback (SoupServer *server, SoupMessage *msg,
|
|
||||||
const char *path, GHashTable *query,
|
|
||||||
SoupClientContext *context, gpointer data)
|
|
||||||
{
|
|
||||||
OtTrivialHttpd *self = data;
|
|
||||||
|
|
||||||
if (msg->method == SOUP_METHOD_GET || msg->method == SOUP_METHOD_HEAD)
|
|
||||||
do_get (self, server, msg, path, context);
|
|
||||||
else
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
on_dir_changed (GFileMonitor *mon,
|
|
||||||
GFile *file,
|
|
||||||
GFile *other,
|
|
||||||
GFileMonitorEvent event,
|
|
||||||
gpointer user_data)
|
|
||||||
{
|
|
||||||
OtTrivialHttpd *self = user_data;
|
|
||||||
|
|
||||||
if (event == G_FILE_MONITOR_EVENT_DELETED)
|
|
||||||
{
|
|
||||||
self->running = FALSE;
|
|
||||||
g_main_context_wakeup (NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
ostree_builtin_trivial_httpd (int argc, char **argv, GCancellable *cancellable, GError **error)
|
ostree_builtin_trivial_httpd (int argc, char **argv, GCancellable *cancellable, GError **error)
|
||||||
{
|
{
|
||||||
gboolean ret = FALSE;
|
g_autoptr(GPtrArray) new_argv = g_ptr_array_new ();
|
||||||
g_autoptr(GOptionContext) context = NULL;
|
|
||||||
const char *dirpath;
|
|
||||||
OtTrivialHttpd appstruct = { 0, };
|
|
||||||
OtTrivialHttpd *app = &appstruct;
|
|
||||||
glnx_unref_object SoupServer *server = NULL;
|
|
||||||
g_autoptr(GFileMonitor) dirmon = NULL;
|
|
||||||
|
|
||||||
context = g_option_context_new ("[DIR] - Simple webserver");
|
g_ptr_array_add (new_argv, PKGLIBEXECDIR "/ostree-trivial-httpd");
|
||||||
|
for (int i = 1; i < argc; i++)
|
||||||
app->root_dfd = -1;
|
g_ptr_array_add (new_argv, argv[i]);
|
||||||
|
g_ptr_array_add (new_argv, NULL);
|
||||||
if (!ostree_option_context_parse (context, options, &argc, &argv, OSTREE_BUILTIN_FLAG_NO_REPO, NULL, cancellable, error))
|
execvp (new_argv->pdata[0], (char**)new_argv->pdata);
|
||||||
goto out;
|
/* Fall through on error */
|
||||||
|
glnx_set_error_from_errno (error);
|
||||||
if (argc > 1)
|
return FALSE;
|
||||||
dirpath = argv[1];
|
|
||||||
else
|
|
||||||
dirpath = ".";
|
|
||||||
|
|
||||||
if (!glnx_opendirat (AT_FDCWD, dirpath, TRUE, &app->root_dfd, error))
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
if (!(opt_random_500s_percentage >= 0 && opt_random_500s_percentage <= 99))
|
|
||||||
{
|
|
||||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
||||||
"Invalid --random-500s=%u", opt_random_500s_percentage);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opt_log)
|
|
||||||
{
|
|
||||||
GOutputStream *stream = NULL;
|
|
||||||
|
|
||||||
if (g_strcmp0 (opt_log, "-") == 0)
|
|
||||||
{
|
|
||||||
if (opt_daemonize)
|
|
||||||
{
|
|
||||||
ot_util_usage_error (context, "Cannot use --log-file=- and --daemonize at the same time", error);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
stream = G_OUTPUT_STREAM (g_unix_output_stream_new (STDOUT_FILENO, FALSE));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
g_autoptr(GFile) log_file;
|
|
||||||
GFileOutputStream* log_stream;
|
|
||||||
|
|
||||||
log_file = g_file_new_for_path (opt_log);
|
|
||||||
log_stream = g_file_create (log_file,
|
|
||||||
G_FILE_CREATE_PRIVATE,
|
|
||||||
cancellable,
|
|
||||||
error);
|
|
||||||
if (!log_stream)
|
|
||||||
goto out;
|
|
||||||
stream = G_OUTPUT_STREAM (log_stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
app->log = stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if SOUP_CHECK_VERSION(2, 48, 0)
|
|
||||||
server = soup_server_new (SOUP_SERVER_SERVER_HEADER, "ostree-httpd ", NULL);
|
|
||||||
if (!soup_server_listen_all (server, opt_port, 0, error))
|
|
||||||
goto out;
|
|
||||||
#else
|
|
||||||
server = soup_server_new (SOUP_SERVER_PORT, opt_port,
|
|
||||||
SOUP_SERVER_SERVER_HEADER, "ostree-httpd ",
|
|
||||||
NULL);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
soup_server_add_handler (server, NULL, httpd_callback, app, NULL);
|
|
||||||
if (opt_port_file)
|
|
||||||
{
|
|
||||||
g_autofree char *portstr = NULL;
|
|
||||||
#if SOUP_CHECK_VERSION(2, 48, 0)
|
|
||||||
GSList *listeners = soup_server_get_listeners (server);
|
|
||||||
g_autoptr(GSocket) listener = NULL;
|
|
||||||
g_autoptr(GSocketAddress) addr = NULL;
|
|
||||||
|
|
||||||
g_assert (listeners);
|
|
||||||
listener = g_object_ref (listeners->data);
|
|
||||||
g_slist_free (listeners);
|
|
||||||
listeners = NULL;
|
|
||||||
addr = g_socket_get_local_address (listener, error);
|
|
||||||
if (!addr)
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
g_assert (G_IS_INET_SOCKET_ADDRESS (addr));
|
|
||||||
|
|
||||||
portstr = g_strdup_printf ("%u\n", g_inet_socket_address_get_port ((GInetSocketAddress*)addr));
|
|
||||||
#else
|
|
||||||
portstr = g_strdup_printf ("%u\n", soup_server_get_port (server));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (g_strcmp0 ("-", opt_port_file) == 0)
|
|
||||||
{
|
|
||||||
fputs (portstr, stdout); // not g_print - this must go to stdout, not a handler
|
|
||||||
fflush (stdout);
|
|
||||||
}
|
|
||||||
else if (!g_file_set_contents (opt_port_file, portstr, strlen (portstr), error))
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
#if !SOUP_CHECK_VERSION(2, 48, 0)
|
|
||||||
soup_server_run_async (server);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (opt_daemonize)
|
|
||||||
{
|
|
||||||
pid_t pid = fork();
|
|
||||||
if (pid == -1)
|
|
||||||
{
|
|
||||||
int errsv = errno;
|
|
||||||
g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
|
|
||||||
g_strerror (errsv));
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
else if (pid > 0)
|
|
||||||
{
|
|
||||||
ret = TRUE;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
/* Child, continue */
|
|
||||||
/* Daemonising: close stdout/stderr so $() et al work on us */
|
|
||||||
fclose (stdout);
|
|
||||||
fclose (stdin);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* Since we're used for testing purposes, let's just do this by
|
|
||||||
* default. This ensures we exit when our parent does.
|
|
||||||
*/
|
|
||||||
if (prctl (PR_SET_PDEATHSIG, SIGTERM) != 0)
|
|
||||||
{
|
|
||||||
if (errno != ENOSYS)
|
|
||||||
{
|
|
||||||
glnx_set_error_from_errno (error);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
app->running = TRUE;
|
|
||||||
if (opt_autoexit)
|
|
||||||
{
|
|
||||||
gboolean is_symlink = FALSE;
|
|
||||||
g_autoptr(GFile) root = NULL;
|
|
||||||
g_autoptr(GFileInfo) info = NULL;
|
|
||||||
|
|
||||||
root = g_file_new_for_path (dirpath);
|
|
||||||
info = g_file_query_info (root,
|
|
||||||
G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK,
|
|
||||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
||||||
cancellable, error);
|
|
||||||
if (!info)
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
is_symlink = g_file_info_get_is_symlink (info);
|
|
||||||
|
|
||||||
if (is_symlink)
|
|
||||||
dirmon = g_file_monitor_file (root, 0, cancellable, error);
|
|
||||||
else
|
|
||||||
dirmon = g_file_monitor_directory (root, 0, cancellable, error);
|
|
||||||
|
|
||||||
if (!dirmon)
|
|
||||||
goto out;
|
|
||||||
g_signal_connect (dirmon, "changed", G_CALLBACK (on_dir_changed), app);
|
|
||||||
}
|
|
||||||
httpd_log (app, "serving at root %s\n", dirpath);
|
|
||||||
while (app->running)
|
|
||||||
g_main_context_iteration (NULL, TRUE);
|
|
||||||
|
|
||||||
ret = TRUE;
|
|
||||||
out:
|
|
||||||
if (app->root_dfd != -1)
|
|
||||||
(void) close (app->root_dfd);
|
|
||||||
g_clear_object (&app->log);
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
@ -102,6 +102,12 @@ else
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if test -n "${OSTREE_UNINSTALLED:-}"; then
|
||||||
|
OSTREE_HTTPD=${OSTREE_UNINSTALLED}/ostree-trivial-httpd
|
||||||
|
else
|
||||||
|
OSTREE_HTTPD="${CMD_PREFIX} ostree trivial-httpd"
|
||||||
|
fi
|
||||||
|
|
||||||
assert_streq () {
|
assert_streq () {
|
||||||
test "$1" = "$2" || (echo 1>&2 "$1 != $2"; exit 1)
|
test "$1" = "$2" || (echo 1>&2 "$1 != $2"; exit 1)
|
||||||
}
|
}
|
||||||
@ -257,7 +263,7 @@ setup_fake_remote_repo1() {
|
|||||||
mkdir ${test_tmpdir}/httpd
|
mkdir ${test_tmpdir}/httpd
|
||||||
cd httpd
|
cd httpd
|
||||||
ln -s ${test_tmpdir}/ostree-srv ostree
|
ln -s ${test_tmpdir}/ostree-srv ostree
|
||||||
${CMD_PREFIX} ostree trivial-httpd --autoexit --daemonize -p ${test_tmpdir}/httpd-port $args
|
${OSTREE_HTTPD} --autoexit --daemonize -p ${test_tmpdir}/httpd-port $args
|
||||||
port=$(cat ${test_tmpdir}/httpd-port)
|
port=$(cat ${test_tmpdir}/httpd-port)
|
||||||
echo "http://127.0.0.1:${port}" > ${test_tmpdir}/httpd-address
|
echo "http://127.0.0.1:${port}" > ${test_tmpdir}/httpd-address
|
||||||
cd ${oldpwd}
|
cd ${oldpwd}
|
||||||
@ -379,7 +385,7 @@ EOF
|
|||||||
mkdir ${test_tmpdir}/httpd
|
mkdir ${test_tmpdir}/httpd
|
||||||
cd httpd
|
cd httpd
|
||||||
ln -s ${test_tmpdir} ostree
|
ln -s ${test_tmpdir} ostree
|
||||||
${CMD_PREFIX} ostree trivial-httpd --autoexit --daemonize -p ${test_tmpdir}/httpd-port
|
${OSTREE_HTTPD} --autoexit --daemonize -p ${test_tmpdir}/httpd-port
|
||||||
port=$(cat ${test_tmpdir}/httpd-port)
|
port=$(cat ${test_tmpdir}/httpd-port)
|
||||||
echo "http://127.0.0.1:${port}" > ${test_tmpdir}/httpd-address
|
echo "http://127.0.0.1:${port}" > ${test_tmpdir}/httpd-address
|
||||||
cd ${oldpwd}
|
cd ${oldpwd}
|
||||||
|
@ -53,7 +53,7 @@ cd ${test_tmpdir}
|
|||||||
mkdir ${test_tmpdir}/httpd
|
mkdir ${test_tmpdir}/httpd
|
||||||
cd httpd
|
cd httpd
|
||||||
ln -s ${test_tmpdir}/ostree-srv ostree
|
ln -s ${test_tmpdir}/ostree-srv ostree
|
||||||
${CMD_PREFIX} ostree trivial-httpd --autoexit --daemonize -P 18081 -p ${test_tmpdir}/httpd-port
|
${OSTREE_HTTPD} --autoexit --daemonize -P 18081 -p ${test_tmpdir}/httpd-port
|
||||||
port=$(cat ${test_tmpdir}/httpd-port)
|
port=$(cat ${test_tmpdir}/httpd-port)
|
||||||
assert_streq $port 18081
|
assert_streq $port 18081
|
||||||
echo "http://127.0.0.1:${port}" > ${test_tmpdir}/httpd-address
|
echo "http://127.0.0.1:${port}" > ${test_tmpdir}/httpd-address
|
||||||
|
@ -51,8 +51,7 @@ fi
|
|||||||
find ${test_tmpdir}/ostree-srv/gnomerepo/objects \
|
find ${test_tmpdir}/ostree-srv/gnomerepo/objects \
|
||||||
! -name '*.commitmeta' -type f | xargs rm
|
! -name '*.commitmeta' -type f | xargs rm
|
||||||
|
|
||||||
${CMD_PREFIX} ostree trivial-httpd --autoexit --daemonize \
|
${OSTREE_HTTPD} --autoexit --daemonize -p ${test_tmpdir}/httpd-content-port
|
||||||
-p ${test_tmpdir}/httpd-content-port
|
|
||||||
content_port=$(cat ${test_tmpdir}/httpd-content-port)
|
content_port=$(cat ${test_tmpdir}/httpd-content-port)
|
||||||
echo "http://127.0.0.1:${content_port}" > ${test_tmpdir}/httpd-content-address
|
echo "http://127.0.0.1:${content_port}" > ${test_tmpdir}/httpd-content-address
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ echo '1..9'
|
|||||||
cd ${test_tmpdir}
|
cd ${test_tmpdir}
|
||||||
mkdir metalink-data
|
mkdir metalink-data
|
||||||
cd metalink-data
|
cd metalink-data
|
||||||
${CMD_PREFIX} ostree trivial-httpd --autoexit --daemonize -p ${test_tmpdir}/metalink-httpd-port
|
${OSTREE_HTTPD} --autoexit --daemonize -p ${test_tmpdir}/metalink-httpd-port
|
||||||
metalink_port=$(cat ${test_tmpdir}/metalink-httpd-port)
|
metalink_port=$(cat ${test_tmpdir}/metalink-httpd-port)
|
||||||
echo "http://127.0.0.1:${metalink_port}" > ${test_tmpdir}/metalink-httpd-address
|
echo "http://127.0.0.1:${metalink_port}" > ${test_tmpdir}/metalink-httpd-address
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ setup_mirror () {
|
|||||||
cd $name
|
cd $name
|
||||||
cp -a ${test_tmpdir}/ostree-srv ostree
|
cp -a ${test_tmpdir}/ostree-srv ostree
|
||||||
|
|
||||||
${CMD_PREFIX} ostree trivial-httpd --autoexit --daemonize \
|
${OSTREE_HTTPD} --autoexit --daemonize \
|
||||||
-p ${test_tmpdir}/${name}-port
|
-p ${test_tmpdir}/${name}-port
|
||||||
port=$(cat ${test_tmpdir}/${name}-port)
|
port=$(cat ${test_tmpdir}/${name}-port)
|
||||||
echo "http://127.0.0.1:${port}" > ${test_tmpdir}/${name}-address
|
echo "http://127.0.0.1:${port}" > ${test_tmpdir}/${name}-address
|
||||||
|
@ -50,7 +50,7 @@ mkdir mirror-httpd
|
|||||||
cd mirror-httpd
|
cd mirror-httpd
|
||||||
ln -s ${test_tmpdir}/mirror-srv ostree
|
ln -s ${test_tmpdir}/mirror-srv ostree
|
||||||
mirror_log="${test_tmpdir}/mirror_log"
|
mirror_log="${test_tmpdir}/mirror_log"
|
||||||
${CMD_PREFIX} ostree trivial-httpd --log-file=${mirror_log} --autoexit --daemonize -p ${test_tmpdir}/mirror-httpd-port
|
${OSTREE_HTTPD} --log-file=${mirror_log} --autoexit --daemonize -p ${test_tmpdir}/mirror-httpd-port
|
||||||
port=$(cat ${test_tmpdir}/mirror-httpd-port)
|
port=$(cat ${test_tmpdir}/mirror-httpd-port)
|
||||||
echo "http://127.0.0.1:${port}" > ${test_tmpdir}/mirror-httpd-address
|
echo "http://127.0.0.1:${port}" > ${test_tmpdir}/mirror-httpd-address
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user