css: Add gtk_css_data_url_parse()

This surprisingly decodes data URLs.
This commit is contained in:
Benjamin Otte 2019-05-09 03:04:21 +02:00
parent 4505f4f17b
commit 5da58ba47d
5 changed files with 327 additions and 0 deletions

175
gtk/css/gtkcssdataurl.c Normal file
View File

@ -0,0 +1,175 @@
/* GStreamer data:// uri source element
* Copyright (C) 2009 Igalia S.L
* Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/*<private>
* SECTION:data-url
* @title: data: URLs
*
* These function allow encoding and decoding of data: URLs, see
* [RFC 2397](http://tools.ietf.org/html/rfc2397) for more information.
*/
#include "config.h"
#include "gtkcssdataurlprivate.h"
#include "../gtkintl.h"
#include <string.h>
/*<private>
* gtk_css_data_url_parse:
* @url: the URL to parse
* @out_mimetype: (out nullable optional): Return location to set the contained
* mime type to. If no mime type was specified, this value is set to %NULL.
* @error: error location or %NULL for none
*
* Decodes a data URL according to RFC2397 and returns the decoded data.
*
* Returns: a new #GBytes with the decoded data or %NULL on error
**/
GBytes *
gtk_css_data_url_parse (const char *url,
char **out_mimetype,
GError **error)
{
char *mimetype = NULL;
const char *parameters_start;
const char *data_start;
GBytes *bytes;
gboolean base64 = FALSE;
char *charset = NULL;
gpointer bdata;
gsize bsize;
/* url must be an URI as defined in RFC 2397
* data:[<mediatype>][;base64],<data>
*/
if (g_ascii_strncasecmp ("data:", url, 5) != 0)
{
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_FILENAME,
_("Not a data: URL"));
return NULL;
}
url += 5;
parameters_start = strchr (url, ';');
data_start = strchr (url, ',');
if (data_start == NULL)
{
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_FILENAME,
_("Malformed data: URL"));
return NULL;
}
if (parameters_start > data_start)
parameters_start = NULL;
if (data_start != url && parameters_start != url)
{
mimetype = g_strndup (url,
(parameters_start ? parameters_start
: data_start) - url);
}
else
{
mimetype = NULL;
}
if (parameters_start != NULL)
{
char *parameters_str;
char **parameters;
guint i;
parameters_str = g_strndup (parameters_start + 1, data_start - parameters_start - 1);
parameters = g_strsplit (parameters_str, ";", -1);
for (i = 0; parameters[i] != NULL; i++)
{
if (g_ascii_strcasecmp ("base64", parameters[i]) == 0)
{
base64 = TRUE;
}
else if (g_ascii_strncasecmp ("charset=", parameters[i], 8) == 0)
{
g_free (charset);
charset = g_strdup (parameters[i] + 8);
}
}
g_free (parameters_str);
g_strfreev (parameters);
}
/* Skip comma */
data_start += 1;
if (base64)
{
bdata = g_base64_decode (data_start, &bsize);
}
else
{
/* URI encoded, i.e. "percent" encoding */
/* XXX: This doesn't allow nul bytes */
bdata = g_uri_unescape_string (data_start, NULL);
if (bdata == NULL)
{
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_FILENAME,
_("Could not unescape string"));
g_free (mimetype);
return NULL;
}
bsize = strlen (bdata);
}
/* Convert to UTF8 */
if ((mimetype == NULL || g_ascii_strcasecmp ("text/plain", mimetype) == 0) &&
charset && g_ascii_strcasecmp ("US-ASCII", charset) != 0
&& g_ascii_strcasecmp ("UTF-8", charset) != 0)
{
gsize read;
gsize written;
gpointer data;
data = g_convert_with_fallback (bdata, bsize,
"UTF-8", charset,
(char *) "*",
&read, &written, NULL);
g_free (bdata);
bdata = data;
bsize = written;
}
bytes = g_bytes_new_take (bdata, bsize);
g_free (charset);
if (out_mimetype)
*out_mimetype = mimetype;
else
g_free (mimetype);
return bytes;
}

View File

@ -0,0 +1,35 @@
/*
* Copyright © 2019 Benjamin Otte
*
* 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.1 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, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_CSS_DATA_URL_PRIVATE_H__
#define __GTK_CSS_DATA_URL_PRIVATE_H__
#include <gio/gio.h>
G_BEGIN_DECLS
GBytes * gtk_css_data_url_parse (const char *url,
char **out_mimetype,
GError **error);
G_END_DECLS
#endif /* __GTK_CSS_DATA_URL_PRIVATE_H__ */

View File

@ -5,6 +5,7 @@ gtk_css_public_sources = files([
])
gtk_css_private_sources = files([
'gtkcssdataurl.c',
'gtkcssparser.c',
'gtkcsstokenizer.c',
])

100
testsuite/css/data.c Normal file
View File

@ -0,0 +1,100 @@
/*
* Copyright © 2019 Benjamin Otte
*
* 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.1 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, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "../../gtk/css/gtkcssdataurlprivate.h"
#include <locale.h>
typedef struct _Test Test;
struct _Test
{
const char *name;
const char *url;
const char *mimetype;
const char *contents;
gsize contents_len;
};
#define CONTENTS(data) (data), sizeof(data) - 1
Test tests[] = {
{ "simple",
"data:,HelloWorld",
NULL, CONTENTS("HelloWorld") },
{ "nodata",
"data:,",
NULL, CONTENTS("") },
{ "case_sensitive",
"dATa:,HelloWorld",
NULL, CONTENTS("HelloWorld") },
{ "semicolon_after_comma",
"data:,;base64",
NULL, CONTENTS(";base64") },
{ "mimetype",
"data:image/png,nopng",
"image/png", CONTENTS("nopng") },
{ "charset",
"data:text/plain;charset=ISO-8859-1,Timm B\344der",
"text/plain", CONTENTS("Timm Bäder") },
{ "charset_base64",
"data:text/plain;charset=ISO-8859-5;base64,wOPh29DdILjW0ePb0OLe0g==",
"text/plain", CONTENTS("Руслан Ижбулатов") },
};
static void
test_parse (gconstpointer data)
{
const Test *test = data;
GError *error = NULL;
char *mimetype = NULL;
GBytes *bytes;
bytes = gtk_css_data_url_parse (test->url, &mimetype, &error);
g_assert (bytes != NULL);
g_assert_no_error (error);
if (test->mimetype == NULL)
g_assert (mimetype == NULL);
else
g_assert_cmpstr (mimetype, ==, test->mimetype);
g_assert_cmpmem (g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes),
test->contents, test->contents_len);
g_bytes_unref (bytes);
}
int
main (int argc, char *argv[])
{
guint i;
g_test_init (&argc, &argv, NULL);
setlocale (LC_ALL, "C");
for (i = 0; i < G_N_ELEMENTS (tests); i++)
{
char *name = g_strdup_printf ("/css/data/load/%s", tests[i].name);
g_test_add_data_func (name, &tests[i], test_parse);
g_free (name);
}
return g_test_run ();
}

View File

@ -20,6 +20,22 @@ test('api', test_api,
],
suite: 'css')
test_data = executable('data', ['data.c', '../../gtk/css/gtkcssdataurl.c'],
include_directories: [confinc, ],
dependencies: gtk_deps,
install: get_option('install-tests'),
install_dir: testexecdir)
test('data', test_data,
args: ['--tap', '-k' ],
env: [ 'GIO_USE_VOLUME_MONITOR=unix',
'GSETTINGS_BACKEND=memory',
'GTK_CSD=1',
'G_ENABLE_DIAGNOSTIC=0',
'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir())
],
suite: 'css')
if get_option('install-tests')
conf = configuration_data()
conf.set('libexecdir', gtk_libexecdir)