/*
* This file is part of Cockpit.
*
* Copyright (C) 2015 Red Hat, Inc.
*
* Cockpit 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.1 of the License, or
* (at your option) any later version.
*
* Cockpit 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 Cockpit; If not, see .
*/
#include "config.h"
#include "cockpitwebinject.h"
#include
/**
* CockpitWebInject
*
* This is a CockpitWebFilter which looks for a marker data
* and inject additional data after that point. The data is
* not injected more than the specified number of times.
*/
struct _CockpitWebInject {
GObject parent;
/* partial_matches stores the lengths of partial matches, size is (marker length) - 1
* e.g. "ABA" with marker "ABAC" will result in [TRUE, FALSE, TRUE]
*/
GArray *partial_matches;
GBytes *marker;
GBytes *inject;
guint maximum;
guint injected;
};
typedef struct _CockpitInjectClass {
GObjectClass parent_class;
} CockpitWebInjectClass;
static void cockpit_web_filter_inject_iface (CockpitWebFilterIface *iface);
G_DEFINE_TYPE_WITH_CODE (CockpitWebInject, cockpit_web_inject, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (COCKPIT_TYPE_WEB_FILTER, cockpit_web_filter_inject_iface)
)
static void
cockpit_web_inject_init (CockpitWebInject *self)
{
}
static void
cockpit_web_inject_finalize (GObject *object)
{
CockpitWebInject *self = COCKPIT_WEB_INJECT (object);
if (self->partial_matches)
g_array_unref (self->partial_matches);
if (self->marker)
g_bytes_unref (self->marker);
if (self->inject)
g_bytes_unref (self->inject);
G_OBJECT_CLASS (cockpit_web_inject_parent_class)->finalize (object);
}
static void
cockpit_web_inject_class_init (CockpitWebInjectClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = cockpit_web_inject_finalize;
}
static void
cockpit_web_inject_push (CockpitWebFilter *filter,
GBytes *block,
void (* function) (gpointer, GBytes *),
gpointer func_data)
{
CockpitWebInject *self = (CockpitWebInject *)filter;
const gchar *mark, *data, *pos = NULL;
gsize mark_len, data_len, at;
GBytes *bytes;
gsize written;
gint partial_len, remaining_len;
mark = g_bytes_get_data (self->marker, &mark_len);
data = g_bytes_get_data (block, &data_len);
if (data_len == 0)
return;
written = at = 0;
/* look at our partial matches first
* longest partial matches have precedence (they either get longer or don't pan out)
* only do this if we haven't reached the maximum yet
*/
if (self->injected < self->maximum)
{
for (partial_len = self->partial_matches->len - 1; partial_len >= 0; --partial_len)
{
if (g_array_index (self->partial_matches, gboolean, partial_len))
{
/* our match can only grow longer */
g_array_index (self->partial_matches, gboolean, partial_len) = FALSE;
remaining_len = mark_len - partial_len;
/* our current block might be too short */
if (remaining_len > data_len)
{
if (memcmp (mark + partial_len, data, data_len) == 0)
g_array_index (self->partial_matches, gboolean, partial_len + data_len) = TRUE;
}
else if (memcmp (mark + partial_len, data, remaining_len) == 0)
{
/* we have a match */
at = remaining_len;
pos = data + at;
/* reset partials */
g_array_set_size(self->partial_matches, 0);
g_array_set_size(self->partial_matches, mark_len);
break;
}
}
}
}
/* keep searching until we have found the maximum number of allowed matches or reached the end */
for(;;)
{
if (at != written)
{
bytes = g_bytes_new_from_bytes (block, written, at - written);
function (func_data, bytes);
g_bytes_unref (bytes);
written = at;
/* did we have a match? */
if (pos == (data + at) && self->injected < self->maximum)
{
function (func_data, self->inject);
self->injected++;
}
}
if (at >= data_len)
break;
/* if there are enough chars left, try to find a complete match */
if (self->injected < self->maximum &&
data_len >= (at + mark_len) &&
(pos = memmem (data + at, data_len - at, mark, mark_len)))
{
/* we found a match, but we want to write out the mark also before we inject */
pos += mark_len;
at = pos - data;
}
else
{
/* nothing found, forward */
at = data_len;
pos = NULL;
}
}
/* if we haven't reached our max number of injections, look for partial matches at the end */
partial_len = mark_len - 1;
if (partial_len > data_len)
partial_len = data_len;
while (partial_len > 0)
{
if (memcmp (mark, data + data_len - partial_len, partial_len) == 0)
g_array_index (self->partial_matches, gboolean, partial_len) = TRUE;
partial_len--;
}
}
static void
cockpit_web_filter_inject_iface (CockpitWebFilterIface *iface)
{
iface->push = cockpit_web_inject_push;
}
/**
* cockpit_web_filter_new:
* @marker: marker to search for
* @inject: bytes to inject after marker
* @count: number of times to inject
*
* Create a new CockpitWebFilter which injects @inject bytes
* after the @marker. It injects the data once.
*
* Returns: A new CockpitWebFilter
*/
CockpitWebFilter *
cockpit_web_inject_new (const gchar *marker,
GBytes *inject,
guint count)
{
CockpitWebInject *self;
gsize len;
g_return_val_if_fail (marker != NULL, NULL);
g_return_val_if_fail (inject != NULL, NULL);
len = strlen (marker);
g_return_val_if_fail (len > 0, NULL);
self = g_object_new (COCKPIT_TYPE_WEB_INJECT, NULL);
self->marker = g_bytes_new (marker, len);
self->partial_matches = g_array_sized_new(FALSE, TRUE, sizeof(gboolean), len);
g_array_set_size(self->partial_matches, len);
self->inject = g_bytes_ref (inject);
self->maximum = count;
return COCKPIT_WEB_FILTER (self);
}