rsvg-io.c 9.78 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
/*
   Copyright (C) 2000 Eazel, Inc.
   Copyright (C) 2002, 2003, 2004, 2005 Dom Lachowicz <cinamod@hotmail.com>
   Copyright (C) 2003, 2004, 2005 Caleb Moore <c.moore@student.unsw.edu.au>
   Copyright © 2011, 2012 Christian Persch

   This program 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 program 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 program; if not, write to the
   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
*/

#include "config.h"

#include "rsvg-io.h"
#include "rsvg-private.h"

#include <string.h>

30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
/* Copied from soup-request-data.c (LGPL2+):
 * Copyright (C) 2009, 2010 Red Hat, Inc.
 * Copyright (C) 2010 Igalia, S.L.
 * and from soup-uri.c:
 * Copyright 1999-2003 Ximian, Inc.
 */

#define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10)
#define HEXCHAR(s) ((XDIGIT (s[1]) << 4) + XDIGIT (s[2]))

static char *
uri_decoded_copy (const char *part, 
                  gsize length)
{
    unsigned char *s, *d;
    char *decoded = g_strndup (part, length);

    s = d = (unsigned char *)decoded;
    do {
        if (*s == '%') {
            if (!g_ascii_isxdigit (s[1]) ||
                !g_ascii_isxdigit (s[2])) {
                *d++ = *s;
                continue;
            }
            *d++ = HEXCHAR (s);
            s += 2;
        } else {
            *d++ = *s;
        }
    } while (*s++);

    return decoded;
}

#define BASE64_INDICATOR     ";base64"
#define BASE64_INDICATOR_LEN (sizeof (";base64") - 1)

68
static char *
69 70
rsvg_acquire_data_data (const char *uri,
                        const char *base_uri, 
71
                        char **out_mime_type,
72
                        gsize *out_len,
73
                        GError **error)
74
{
75
    const char *comma, *start, *end;
76
    char *mime_type;
77 78 79 80 81
    char *data;
    gsize data_len;
    gboolean base64 = FALSE;

    g_assert (out_len != NULL);
82
    g_assert (strncmp (uri, "data:", 5) == 0);
83 84

    mime_type = NULL;
85 86 87 88 89 90 91 92 93 94 95 96 97
    start = uri + 5;
    comma = strchr (start, ',');

    if (comma && comma != start) {
        /* Deal with MIME type / params */
        if (comma > start + BASE64_INDICATOR_LEN && 
            !g_ascii_strncasecmp (comma - BASE64_INDICATOR_LEN, BASE64_INDICATOR, BASE64_INDICATOR_LEN)) {
            end = comma - BASE64_INDICATOR_LEN;
            base64 = TRUE;
        } else {
            end = comma;
        }

98
        if (end != start) {
99
            mime_type = uri_decoded_copy (start, end - start);
100
        }
101 102 103 104
    }

    if (comma)
        start = comma + 1;
105

106
    if (*start) {
107
	data = uri_decoded_copy (start, strlen (start));
108 109

        if (base64)
110
            data = (char *) g_base64_decode_inplace (data, &data_len);
111
        else
112
            data_len = strlen (data);
113 114 115 116
    } else {
        data = NULL;
        data_len = 0;
    }
117

118 119 120 121
    if (out_mime_type)
        *out_mime_type = mime_type;
    else
        g_free (mime_type);
122

123 124
    *out_len = data_len;
    return data;
125 126 127
}

gchar *
128 129
_rsvg_io_get_file_path (const gchar * filename,
                        const gchar * base_uri)
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
{
    gchar *absolute_filename;

    if (g_file_test (filename, G_FILE_TEST_EXISTS) || g_path_is_absolute (filename)) {
        absolute_filename = g_strdup (filename);
    } else {
        gchar *tmpcdir;
        gchar *base_filename;

        if (base_uri) {
            base_filename = g_filename_from_uri (base_uri, NULL, NULL);
            if (base_filename != NULL) {
                tmpcdir = g_path_get_dirname (base_filename);
                g_free (base_filename);
            } else 
                return NULL;
        } else
            tmpcdir = g_get_current_dir ();

        absolute_filename = g_build_filename (tmpcdir, filename, NULL);
        g_free (tmpcdir);
    }

    return absolute_filename;
}

156
static char *
157 158
rsvg_acquire_file_data (const char *filename,
                        const char *base_uri,
159
                        char **out_mime_type,
160
                        gsize *out_len,
161
                        GCancellable *cancellable,
162 163 164
                        GError **error)
{
    gchar *path, *data;
165
    gsize len;
166
    char *content_type;
167 168

    rsvg_return_val_if_fail (filename != NULL, NULL, error);
169
    g_assert (out_len != NULL);
170

171
    path = _rsvg_io_get_file_path (filename, base_uri);
172 173 174
    if (path == NULL)
        return NULL;

175 176 177 178 179
    if (!g_file_get_contents (path, &data, &len, error)) {
        g_free (path);
        return NULL;
    }

180
    if (out_mime_type &&
181
        (content_type = g_content_type_guess (path, (guchar *) data, len, NULL))) {
182 183
        *out_mime_type = g_content_type_get_mime_type (content_type);
        g_free (content_type);
184 185
    }

Christian Persch's avatar
Christian Persch committed
186
    g_free (path);
187

188 189
    *out_len = len;
    return data;
190 191 192 193 194
}

static GInputStream *
rsvg_acquire_gvfs_stream (const char *uri, 
                          const char *base_uri, 
195
                          char **out_mime_type,
196
                          GCancellable *cancellable,
197 198 199
                          GError **error)
{
    GFile *base, *file;
200
    GFileInputStream *stream;
201 202 203 204
    GError *err = NULL;

    file = g_file_new_for_uri (uri);

205
    stream = g_file_read (file, cancellable, &err);
206 207 208 209 210 211 212 213 214 215
    g_object_unref (file);

    if (stream == NULL &&
        g_error_matches (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
        g_clear_error (&err);

        base = g_file_new_for_uri (base_uri);
        file = g_file_resolve_relative_path (base, uri);
        g_object_unref (base);

216
        stream = g_file_read (file, cancellable, &err);
217 218 219
        g_object_unref (file);
    }

220
    if (stream == NULL) {
221
        g_propagate_error (error, err);
222 223 224
        return NULL;
    }

225
    if (out_mime_type) {
226
        GFileInfo *file_info;
227
        const char *content_type;
228 229 230

        file_info = g_file_input_stream_query_info (stream, 
                                                    G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
231
                                                    cancellable,
232
                                                    NULL /* error */);
233 234 235 236 237 238 239
        if (file_info &&
            (content_type = g_file_info_get_content_type (file_info)))
            *out_mime_type = g_content_type_get_mime_type (content_type);
        else
            *out_mime_type = NULL;

        if (file_info)
240 241
            g_object_unref (file_info);
    }
242

243
    return G_INPUT_STREAM (stream);
244 245
}

246
static char *
247 248
rsvg_acquire_gvfs_data (const char *uri,
                        const char *base_uri,
249
                        char **out_mime_type,
250
                        gsize *out_len,
251
                        GCancellable *cancellable,
252 253 254 255
                        GError **error)
{
    GFile *base, *file;
    GError *err;
256
    char *data;
257
    gsize len;
258
    char *content_type;
259 260 261 262 263 264
    gboolean res;

    file = g_file_new_for_uri (uri);

    err = NULL;
    data = NULL;
265
    if (!(res = g_file_load_contents (file, cancellable, &data, &len, NULL, &err)) &&
266 267 268
        g_error_matches (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) &&
        base_uri != NULL) {
        g_clear_error (&err);
Christian Persch's avatar
Christian Persch committed
269
        g_object_unref (file);
270 271 272 273 274

        base = g_file_new_for_uri (base_uri);
        file = g_file_resolve_relative_path (base, uri);
        g_object_unref (base);

275
        res = g_file_load_contents (file, cancellable, &data, &len, NULL, &err);
276 277 278 279
    }

    g_object_unref (file);

280 281 282 283
    if (err) {
        g_propagate_error (error, err);
        return NULL;
    }
284

285
    if (out_mime_type &&
286
        (content_type = g_content_type_guess (uri, (guchar *) data, len, NULL))) {
287 288
        *out_mime_type = g_content_type_get_mime_type (content_type);
        g_free (content_type);
289 290 291 292
    }

    *out_len = len;
    return data;
293 294
}

295
char *
296 297
_rsvg_io_acquire_data (const char *href, 
                       const char *base_uri, 
298
                       char **mime_type,
299
                       gsize *len,
300
                       GCancellable *cancellable,
301 302
                       GError **error)
{
303
    char *data;
304
    gsize llen;
305

306 307 308
    if (!(href && *href)) {
        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                            "Invalid URI");
309
        return NULL;
310
    }
311

312 313 314
    if (!len)
        len = &llen;

315
    if (strncmp (href, "data:", 5) == 0)
316
      return rsvg_acquire_data_data (href, NULL, mime_type, len, error);
317

318
    if ((data = rsvg_acquire_file_data (href, base_uri, mime_type, len, cancellable, NULL)))
319 320
      return data;

321
    if ((data = rsvg_acquire_gvfs_data (href, base_uri, mime_type, len, cancellable, error)))
322 323 324 325 326 327 328 329
      return data;

    return NULL;
}

GInputStream *
_rsvg_io_acquire_stream (const char *href, 
                         const char *base_uri, 
330
                         char **mime_type,
331
                         GCancellable *cancellable,
332 333 334
                         GError **error)
{
    GInputStream *stream;
335
    char *data;
336 337
    gsize len;

338 339 340
    if (!(href && *href)) {
        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                            "Invalid URI");
341
        return NULL;
342
    }
343

344
    if (strncmp (href, "data:", 5) == 0) {
345
        if (!(data = rsvg_acquire_data_data (href, NULL, mime_type, &len, error)))
346 347 348 349
            return NULL;

        return g_memory_input_stream_new_from_data (data, len, (GDestroyNotify) g_free);
    }
350

351
    if ((data = rsvg_acquire_file_data (href, base_uri, mime_type, &len, cancellable, NULL)))
352 353
      return g_memory_input_stream_new_from_data (data, len, (GDestroyNotify) g_free);

354
    if ((stream = rsvg_acquire_gvfs_stream (href, base_uri, mime_type, cancellable, error)))
355 356 357 358
      return stream;

    return NULL;
}