xslt.c 11.7 KB
Newer Older
1 2 3 4 5
/* Icecast
 *
 * This program is distributed under the GNU General Public License, version 2.
 * A copy of this license is included with this source.
 *
6
 * Copyright 2000-2004, Jack Moffitt <jack@xiph.org,
7 8 9 10
 *                      Michael Smith <msmith@xiph.org>,
 *                      oddsock <oddsock@xiph.org>,
 *                      Karl Heyes <karl@xiph.org>
 *                      and others (see AUTHORS for details).
11
 * Copyright 2012-2018, Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>,
12 13
 */

14 15 16 17
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

18 19 20 21 22 23 24
#include <string.h>
#include <libxml/xmlmemory.h>
#include <libxml/debugXML.h>
#include <libxml/HTMLtree.h>
#include <libxml/xmlIO.h>
#include <libxml/xinclude.h>
#include <libxml/catalog.h>
25
#include <libxml/uri.h>
26 27 28 29
#include <libxslt/xslt.h>
#include <libxslt/xsltInternals.h>
#include <libxslt/transform.h>
#include <libxslt/xsltutils.h>
30
#include <libxslt/documents.h>
31

32 33 34 35
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>

36
#ifdef  HAVE_SYS_TIME_H
37 38 39
#include <sys/time.h>
#endif

40 41 42
#ifdef WIN32
#define snprintf _snprintf
#endif
43

Marvin Scholz's avatar
Marvin Scholz committed
44 45 46 47
#include "common/thread/thread.h"
#include "common/avl/avl.h"
#include "common/httpp/httpp.h"
#include "common/net/sock.h"
48 49 50 51 52 53

#include "connection.h"

#include "global.h"
#include "refbuf.h"
#include "client.h"
54
#include "errors.h"
55
#include "config.h"
56
#include "stats.h"
57
#include "fserve.h"
Philipp Schafft's avatar
Philipp Schafft committed
58
#include "util.h"
59

60
#define CATMODULE "xslt"
Karl Heyes's avatar
Karl Heyes committed
61

62 63 64
#include "logging.h"

typedef struct {
65 66 67 68
    char *filename;
    time_t last_modified;
    time_t cache_age;
    xsltStylesheetPtr stylesheet;
69 70
} stylesheet_cache_t;

71 72 73 74 75 76 77
#ifndef HAVE_XSLTSAVERESULTTOSTRING
int xsltSaveResultToString(xmlChar **doc_txt_ptr, int * doc_txt_len, xmlDocPtr result, xsltStylesheetPtr style) {
    xmlOutputBufferPtr buf;

    *doc_txt_ptr = NULL;
    *doc_txt_len = 0;
    if (result->children == NULL)
78
        return (0);
79

80
    buf = xmlAllocOutputBuffer(NULL);
81 82

    if (buf == NULL)
83
        return (-1);
84 85
    xsltSaveResultTo(buf, result, style);
    if (buf->conv != NULL) {
86 87
        *doc_txt_len = buf->conv->use;
        *doc_txt_ptr = xmlStrndup(buf->conv->content, *doc_txt_len);
88
    } else {
89 90
        *doc_txt_len = buf->buffer->use;
        *doc_txt_ptr = xmlStrndup(buf->buffer->content, *doc_txt_len);
91
    }
92
    (void) xmlOutputBufferClose(buf);
93 94 95 96
    return 0;
}
#endif

97 98 99
/* Keep it small... */
#define CACHESIZE 3

100 101
static stylesheet_cache_t cache[CACHESIZE];
static mutex_t xsltlock;
102

103 104 105 106 107
/* Reference to the original xslt loader func */
static xsltDocLoaderFunc xslt_loader;
/* Admin path cache */
static xmlChar *admin_path = NULL;

108
void xslt_initialize(void)
109
{
110
    memset(cache, 0, sizeof(stylesheet_cache_t) * CACHESIZE);
111
    thread_mutex_create(&xsltlock);
112 113
    xmlInitParser();
    LIBXML_TEST_VERSION
114 115
    xmlSubstituteEntitiesDefault(1);
    xmlLoadExtDtdDefaultValue = 1;
116
    xslt_loader = xsltDocDefaultLoader;
117 118
}

119
void xslt_shutdown(void) {
120 121 122 123 124 125 126 127 128
    int i;

    for(i=0; i < CACHESIZE; i++) {
        if(cache[i].filename)
            free(cache[i].filename);
        if(cache[i].stylesheet)
            xsltFreeStylesheet(cache[i].stylesheet);
    }

129
    thread_mutex_destroy (&xsltlock);
130
    xmlCleanupParser();
131
    xsltCleanupGlobals();
132 133
    if (admin_path)
        xmlFree(admin_path);
134 135
}

136
static int evict_cache_entry(void) {
brendan's avatar
brendan committed
137
    int i, age=0, oldest=0;
138 139 140 141 142 143 144 145 146 147 148 149 150 151

    for(i=0; i < CACHESIZE; i++) {
        if(cache[i].cache_age > age) {
            age = cache[i].cache_age;
            oldest = i;
        }
    }

    xsltFreeStylesheet(cache[oldest].stylesheet);
    free(cache[oldest].filename);

    return oldest;
}

152
static xsltStylesheetPtr xslt_get_stylesheet(const char *fn) {
153 154 155 156 157
    int i;
    int empty = -1;
    struct stat file;

    if(stat(fn, &file)) {
158
        ICECAST_LOG_WARN("Error checking for stylesheet file \"%s\": %s", fn,
159
                strerror(errno));
160 161 162 163 164 165
        return NULL;
    }

    for(i=0; i < CACHESIZE; i++) {
        if(cache[i].filename)
        {
166 167 168
#ifdef _WIN32
            if(!stricmp(fn, cache[i].filename))
#else
169
            if(!strcmp(fn, cache[i].filename))
170
#endif
171 172 173 174
            {
                if(file.st_mtime > cache[i].last_modified)
                {
                    xsltFreeStylesheet(cache[i].stylesheet);
175

176
                    cache[i].last_modified = file.st_mtime;
177
                    cache[i].stylesheet = xsltParseStylesheetFile(XMLSTR(fn));
178 179
                    cache[i].cache_age = time(NULL);
                }
180
                ICECAST_LOG_DEBUG("Using cached sheet %i", i);
181 182 183 184 185 186 187 188 189 190 191 192 193 194
                return cache[i].stylesheet;
            }
        }
        else
            empty = i;
    }

    if(empty>=0)
        i = empty;
    else
        i = evict_cache_entry();

    cache[i].last_modified = file.st_mtime;
    cache[i].filename = strdup(fn);
195
    cache[i].stylesheet = xsltParseStylesheetFile(XMLSTR(fn));
196 197 198
    cache[i].cache_age = time(NULL);
    return cache[i].stylesheet;
}
199

200 201 202 203 204 205 206 207
/* Custom xslt loader */
static xmlDocPtr custom_loader(const        xmlChar *URI,
                               xmlDictPtr   dict,
                               int          options,
                               void        *ctxt,
                               xsltLoadType type)
{
    xmlDocPtr ret;
208
    xmlChar *rel_path, *fn, *final_URI = NULL;
209
    char *path_URI = NULL;
210
    xsltStylesheet *c;
211 212
    ice_config_t *config;

213 214 215
    switch (type) {
        /* In case an include is loaded */
        case XSLT_LOAD_STYLESHEET:
216 217 218 219 220 221
            /* URI is an escaped URI, make an unescaped version */
            path_URI = util_url_unescape((const char*)URI);
            /* Error if we can't unescape */
            if (path_URI == NULL)
                return NULL;

222
            /* Not look in admindir if the include file exists */
223 224
            if (access(path_URI, F_OK) == 0) {
                free(path_URI);
225
                break;
226 227
            }
            free(path_URI);
228

229 230 231 232
            c = (xsltStylesheet *) ctxt;
            /* Check if we actually have context/path */
            if (ctxt == NULL || c->doc->URL == NULL)
                break;
233

234
            /* Construct the right path */
235 236 237
            rel_path = xmlBuildRelativeURI(URI, c->doc->URL);
            if (rel_path != NULL && admin_path != NULL) {
                fn = xmlBuildURI(rel_path, admin_path);
238 239 240
                final_URI = fn;
                xmlFree(rel_path);
            }
241

242 243 244 245 246
            /* Fail if there was an error constructing the path */
            if (final_URI == NULL) {
                if (rel_path)
                    xmlFree(rel_path);
                return NULL;
247 248 249 250
            }
        break;
        /* In case a top stylesheet is loaded */
        case XSLT_LOAD_START:
251
            config = config_get_config();
252 253 254 255 256
            /* Admin path is cached, so that we don't need to get it from
             * the config every time we load a xsl include.
             * Whenever a new top stylesheet is loaded, we check here
             * if the path in the config has changed and adjust it, if needed.
             */
257
            if (admin_path != NULL &&
258
                strcmp(config->adminroot_dir, (char *)admin_path) != 0) {
259 260 261
                xmlFree(admin_path);
                admin_path = NULL;
            }
262 263 264 265
            /* Do we need to load the admin path? */
            if (!admin_path) {
                size_t len = strlen(config->adminroot_dir);

266
                admin_path = xmlMalloc(len+2);
267
                if (!admin_path)
268
                    return NULL;
269 270 271

                /* Copy over admin path and add a tailing slash. */
                xmlStrPrintf(admin_path, len+2, XMLSTR("%s/"), XMLSTR(config->adminroot_dir));
272
            }
273 274 275
            config_release_config();
        break;

276
        /* Avoid warnings about other events we don't care for */
277 278 279
        default:
        break;
    }
280

281
    /* Get the actual xmlDoc */
282 283 284 285 286 287
    if (final_URI) {
        ret = xslt_loader(final_URI, dict, options, ctxt, type);
        xmlFree(final_URI);
    } else {
        ret = xslt_loader(URI, dict, options, ctxt, type);
    }
288 289 290
    return ret;
}

291
void xslt_transform(xmlDocPtr doc, const char *xslfilename, client_t *client)
292
{
293
    xmlDocPtr res;
294
    xsltStylesheetPtr cur;
295
    xmlChar *string;
296
    int len, problem = 0;
297
    const char *mediatype = NULL;
298
    const char *charset = NULL;
299

300 301
    xmlSetGenericErrorFunc("", log_parse_failure);
    xsltSetGenericErrorFunc("", log_parse_failure);
302
    xsltSetLoaderFunc(custom_loader);
303

304 305 306
    thread_mutex_lock(&xsltlock);
    cur = xslt_get_stylesheet(xslfilename);

307 308
    if (cur == NULL)
    {
309
        thread_mutex_unlock(&xsltlock);
310
        ICECAST_LOG_ERROR("problem reading stylesheet \"%s\"", xslfilename);
311
        client_send_error_by_id(client, ICECAST_ERROR_XSLT_PARSE);
312
        return;
313
    }
314

315
    res = xsltApplyStylesheet(cur, doc, NULL);
316 317 318 319
    if (res != NULL) {
        if (xsltSaveResultToString(&string, &len, res, cur) < 0)
            problem = 1;
    } else {
320
        problem = 1;
321
    }
322

323 324 325 326
    /* lets find out the content type and character encoding to use */
    if (cur->encoding)
       charset = (char *)cur->encoding;

327 328 329 330 331
    if (cur->mediaType)
        mediatype = (char *)cur->mediaType;
    else
    {
        /* check method for the default, a missing method assumes xml */
332
        if (cur->method && xmlStrcmp (cur->method, XMLSTR("html")) == 0)
333 334
            mediatype = "text/html";
        else
335
            if (cur->method && xmlStrcmp (cur->method, XMLSTR("text")) == 0)
336 337 338 339
                mediatype = "text/plain";
            else
                mediatype = "text/xml";
    }
340
    if (problem == 0)
341
    {
ePirat's avatar
ePirat committed
342 343 344
        ssize_t ret;
        int failed = 0;
        refbuf_t *refbuf;
Philipp Schafft's avatar
Philipp Schafft committed
345
        ssize_t full_len = strlen(mediatype) + (ssize_t)len + (ssize_t)1024;
346 347
        if (full_len < 4096)
            full_len = 4096;
ePirat's avatar
ePirat committed
348
        refbuf = refbuf_new (full_len);
349

350
        if (string == NULL)
351
            string = xmlCharStrdup ("");
352
        ret = util_http_build_header(refbuf->data, full_len, 0, 0, 200, NULL, mediatype, charset, NULL, NULL, client);
353 354
        if (ret == -1) {
            ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
355
            client_send_error_by_id(client, ICECAST_ERROR_GEN_HEADER_GEN_FAILED);
356
        } else {
Philipp Schafft's avatar
Philipp Schafft committed
357
            if ( full_len < (ret + (ssize_t)len + (ssize_t)64) ) {
358
                void *new_data;
Philipp Schafft's avatar
Philipp Schafft committed
359
                full_len = ret + (ssize_t)len + (ssize_t)64;
360 361 362 363 364
                new_data = realloc(refbuf->data, full_len);
                if (new_data) {
                    ICECAST_LOG_DEBUG("Client buffer reallocation succeeded.");
                    refbuf->data = new_data;
                    refbuf->len = full_len;
365
                    ret = util_http_build_header(refbuf->data, full_len, 0, 0, 200, NULL, mediatype, charset, NULL, NULL, client);
366 367
                    if (ret == -1) {
                        ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
368
                        client_send_error_by_id(client, ICECAST_ERROR_GEN_HEADER_GEN_FAILED);
369 370 371 372
                        failed = 1;
                    }
                } else {
                    ICECAST_LOG_ERROR("Client buffer reallocation failed. Dropping client.");
373
                    client_send_error_by_id(client, ICECAST_ERROR_GEN_BUFFER_REALLOC);
374 375 376 377 378 379 380 381 382 383 384 385 386 387
                    failed = 1;
                }
            }

            if (!failed) {
                  snprintf(refbuf->data + ret, full_len - ret, "Content-Length: %d\r\n\r\n%s", len, string);

                client->respcode = 200;
                client_set_queue (client, NULL);
                client->refbuf = refbuf;
                refbuf->len = strlen (refbuf->data);
                fserve_add_client (client, NULL);
            }
        }
388 389
        xmlFree (string);
    }
390 391
    else
    {
392
        ICECAST_LOG_WARN("problem applying stylesheet \"%s\"", xslfilename);
393
        client_send_error_by_id(client, ICECAST_ERROR_XSLT_problem);
394
    }
395
    thread_mutex_unlock (&xsltlock);
396 397 398
    xmlFreeDoc(res);
}