xslt.c 9.93 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 11 12
 *                      Michael Smith <msmith@xiph.org>,
 *                      oddsock <oddsock@xiph.org>,
 *                      Karl Heyes <karl@xiph.org>
 *                      and others (see AUTHORS for details).
 */

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

17 18 19 20 21 22 23
#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>
24
#include <libxml/uri.h>
25 26 27 28
#include <libxslt/xslt.h>
#include <libxslt/xsltInternals.h>
#include <libxslt/transform.h>
#include <libxslt/xsltutils.h>
29
#include <libxslt/documents.h>
30

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

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

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

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

#include "connection.h"

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

58
#define CATMODULE "xslt"
Karl Heyes's avatar
Karl Heyes committed
59

60 61 62
#include "logging.h"

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

69 70 71 72 73 74 75
#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)
76
        return (0);
77

78
    buf = xmlAllocOutputBuffer(NULL);
79 80

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

95 96 97
/* Keep it small... */
#define CACHESIZE 3

98 99
static stylesheet_cache_t cache[CACHESIZE];
static mutex_t xsltlock;
100

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

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

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

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

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

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

    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;
}

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

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

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

174
                    cache[i].last_modified = file.st_mtime;
175
                    cache[i].stylesheet = xsltParseStylesheetFile(XMLSTR(fn));
176 177
                    cache[i].cache_age = time(NULL);
                }
178
                ICECAST_LOG_DEBUG("Using cached sheet %i", i);
179 180 181 182 183 184 185 186 187 188 189 190 191 192
                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);
193
    cache[i].stylesheet = xsltParseStylesheetFile(XMLSTR(fn));
194 195 196
    cache[i].cache_age = time(NULL);
    return cache[i].stylesheet;
}
197

198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
/* Custom xslt loader */
static xmlDocPtr custom_loader(const        xmlChar *URI,
                               xmlDictPtr   dict,
                               int          options,
                               void        *ctxt,
                               xsltLoadType type)
{
    xmlDocPtr ret;
    xmlChar *rel_path, *fn, *final_URI;
    xsltStylesheet *c;
    ice_config_t *config;
    final_URI = xmlStrdup(URI);
    switch (type) {
        /* In case an include is loaded */
        case XSLT_LOAD_STYLESHEET:
            c = (xsltStylesheet *) ctxt;
            /* Check if we actually have context/path */
            if (ctxt == NULL || c->doc->URL == NULL)
                break;
            rel_path = xmlBuildRelativeURI(URI, c->doc->URL);
            if (rel_path != NULL && admin_path != NULL) {
                struct stat file;
                fn = xmlBuildURI(rel_path, admin_path);
                if (fn != NULL && stat((char *)fn, &file) == 0) {
                    final_URI = fn;
                }
            }
        break;
        /* In case a top stylesheet is loaded */
        case XSLT_LOAD_START:
            config = config_get_config();
            admin_path = xmlCharStrdup(config->adminroot_dir);
            config_release_config();
            admin_path = xmlStrcat(admin_path, (xmlChar *)"/");
        break;

        default:
        break;
    }
    /* Get the actual xmlDoc */
    ret = xslt_loader(final_URI, dict, options, ctxt, type);
    xmlFree(final_URI);
    return ret;
}

243
void xslt_transform(xmlDocPtr doc, const char *xslfilename, client_t *client)
244
{
245
    xmlDocPtr res;
246
    xsltStylesheetPtr cur;
247
    xmlChar *string;
248
    int len, problem = 0;
249
    const char *mediatype = NULL;
250
    const char *charset = NULL;
251

252 253
    xmlSetGenericErrorFunc("", log_parse_failure);
    xsltSetGenericErrorFunc("", log_parse_failure);
254
    xsltSetLoaderFunc(custom_loader);
255

256 257 258
    thread_mutex_lock(&xsltlock);
    cur = xslt_get_stylesheet(xslfilename);

259 260
    if (cur == NULL)
    {
261
        thread_mutex_unlock(&xsltlock);
262
        ICECAST_LOG_ERROR("problem reading stylesheet \"%s\"", xslfilename);
263
        client_send_error(client, 404, 0, "Could not parse XSLT file");
264
        return;
265
    }
266

267
    res = xsltApplyStylesheet(cur, doc, NULL);
268

269 270
    if (xsltSaveResultToString (&string, &len, res, cur) < 0)
        problem = 1;
271

272 273 274 275
    /* lets find out the content type and character encoding to use */
    if (cur->encoding)
       charset = (char *)cur->encoding;

276 277 278 279 280
    if (cur->mediaType)
        mediatype = (char *)cur->mediaType;
    else
    {
        /* check method for the default, a missing method assumes xml */
281
        if (cur->method && xmlStrcmp (cur->method, XMLSTR("html")) == 0)
282 283
            mediatype = "text/html";
        else
284
            if (cur->method && xmlStrcmp (cur->method, XMLSTR("text")) == 0)
285 286 287 288
                mediatype = "text/plain";
            else
                mediatype = "text/xml";
    }
289
    if (problem == 0)
290
    {
ePirat's avatar
ePirat committed
291 292 293
        ssize_t ret;
        int failed = 0;
        refbuf_t *refbuf;
Philipp Schafft's avatar
Philipp Schafft committed
294
        ssize_t full_len = strlen(mediatype) + (ssize_t)len + (ssize_t)1024;
295 296
        if (full_len < 4096)
            full_len = 4096;
ePirat's avatar
ePirat committed
297
        refbuf = refbuf_new (full_len);
298

299
        if (string == NULL)
300
            string = xmlCharStrdup ("");
301
        ret = util_http_build_header(refbuf->data, full_len, 0, 0, 200, NULL, mediatype, charset, NULL, NULL, client);
302 303
        if (ret == -1) {
            ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
304
            client_send_error(client, 500, 0, "Header generation failed.");
305
        } else {
Philipp Schafft's avatar
Philipp Schafft committed
306
            if ( full_len < (ret + (ssize_t)len + (ssize_t)64) ) {
307
                void *new_data;
Philipp Schafft's avatar
Philipp Schafft committed
308
                full_len = ret + (ssize_t)len + (ssize_t)64;
309 310 311 312 313
                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;
314
                    ret = util_http_build_header(refbuf->data, full_len, 0, 0, 200, NULL, mediatype, charset, NULL, NULL, client);
315 316
                    if (ret == -1) {
                        ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
317
                        client_send_error(client, 500, 0, "Header generation failed.");
318 319 320 321
                        failed = 1;
                    }
                } else {
                    ICECAST_LOG_ERROR("Client buffer reallocation failed. Dropping client.");
322
                    client_send_error(client, 500, 0, "Buffer reallocation failed.");
323 324 325 326 327 328 329 330 331 332 333 334 335 336
                    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);
            }
        }
337 338
        xmlFree (string);
    }
339 340
    else
    {
341
        ICECAST_LOG_WARN("problem applying stylesheet \"%s\"", xslfilename);
342
        client_send_error(client, 404, 0, "XSLT problem");
343
    }
344
    thread_mutex_unlock (&xsltlock);
345 346 347
    xmlFreeDoc(res);
}