xslt.c 6.62 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
/* Icecast
 *
 * This program is distributed under the GNU General Public License, version 2.
 * A copy of this license is included with this source.
 *
 * Copyright 2000-2004, Jack Moffitt <jack@xiph.org, 
 *                      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 24 25 26 27 28
#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>
#include <libxslt/xslt.h>
#include <libxslt/xsltInternals.h>
#include <libxslt/transform.h>
#include <libxslt/xsltutils.h>

29 30 31 32
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>

33
#ifdef  HAVE_SYS_TIME_H
34 35 36
#include <sys/time.h>
#endif

37 38 39
#ifdef WIN32
#define snprintf _snprintf
#endif
40

Karl Heyes's avatar
Karl Heyes committed
41 42 43 44
#include "thread/thread.h"
#include "avl/avl.h"
#include "httpp/httpp.h"
#include "net/sock.h"
45 46 47 48 49 50 51

#include "connection.h"

#include "global.h"
#include "refbuf.h"
#include "client.h"
#include "stats.h"
52
#include "fserve.h"
Philipp Schafft's avatar
Philipp Schafft committed
53
#include "util.h"
54

55
#define CATMODULE "xslt"
Karl Heyes's avatar
Karl Heyes committed
56

57 58 59 60 61 62 63 64 65
#include "logging.h"

typedef struct {
    char              *filename;
    time_t             last_modified;
    time_t             cache_age;
    xsltStylesheetPtr  stylesheet;
} stylesheet_cache_t;

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
#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)
	return(0);

	buf = xmlAllocOutputBuffer(NULL);

    if (buf == NULL)
		return(-1);
    xsltSaveResultTo(buf, result, style);
    if (buf->conv != NULL) {
		*doc_txt_len = buf->conv->use;
		*doc_txt_ptr = xmlStrndup(buf->conv->content, *doc_txt_len);
    } else {
		*doc_txt_len = buf->buffer->use;
		*doc_txt_ptr = xmlStrndup(buf->buffer->content, *doc_txt_len);
    }
    (void)xmlOutputBufferClose(buf);
    return 0;
}
#endif

92 93 94
/* Keep it small... */
#define CACHESIZE 3

95 96
static stylesheet_cache_t cache[CACHESIZE];
static mutex_t xsltlock;
97

98
void xslt_initialize(void)
99 100 101
{
    memset(cache, 0, sizeof(stylesheet_cache_t)*CACHESIZE);
    thread_mutex_create(&xsltlock);
102 103
    xmlInitParser();
    LIBXML_TEST_VERSION
104 105
    xmlSubstituteEntitiesDefault(1);
    xmlLoadExtDtdDefaultValue = 1;
106 107
}

108
void xslt_shutdown(void) {
109 110 111 112 113 114 115 116 117
    int i;

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

118
    thread_mutex_destroy (&xsltlock);
119
    xmlCleanupParser();
120 121 122
    xsltCleanupGlobals();
}

123
static int evict_cache_entry(void) {
brendan's avatar
brendan committed
124
    int i, age=0, oldest=0;
125 126 127 128 129 130 131 132 133 134 135 136 137 138

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

139
static xsltStylesheetPtr xslt_get_stylesheet(const char *fn) {
140 141 142 143 144
    int i;
    int empty = -1;
    struct stat file;

    if(stat(fn, &file)) {
145
        ICECAST_LOG_WARN("Error checking for stylesheet file \"%s\": %s", fn, 
146
                strerror(errno));
147 148 149 150 151 152
        return NULL;
    }

    for(i=0; i < CACHESIZE; i++) {
        if(cache[i].filename)
        {
153 154 155
#ifdef _WIN32
            if(!stricmp(fn, cache[i].filename))
#else
156
            if(!strcmp(fn, cache[i].filename))
157
#endif
158 159 160 161 162 163
            {
                if(file.st_mtime > cache[i].last_modified)
                {
                    xsltFreeStylesheet(cache[i].stylesheet);

                    cache[i].last_modified = file.st_mtime;
164
                    cache[i].stylesheet = xsltParseStylesheetFile (XMLSTR(fn));
165 166
                    cache[i].cache_age = time(NULL);
                }
167
                ICECAST_LOG_DEBUG("Using cached sheet %i", i);
168 169 170 171 172 173 174 175 176 177 178 179 180 181
                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);
182
    cache[i].stylesheet = xsltParseStylesheetFile (XMLSTR(fn));
183 184 185
    cache[i].cache_age = time(NULL);
    return cache[i].stylesheet;
}
186

187
void xslt_transform(xmlDocPtr doc, const char *xslfilename, client_t *client)
188
{
189 190
    xmlDocPtr    res;
    xsltStylesheetPtr cur;
191
    xmlChar *string;
192
    int len, problem = 0;
193
    const char *mediatype = NULL;
194
    const char *charset = NULL;
195

196 197 198
    xmlSetGenericErrorFunc ("", log_parse_failure);
    xsltSetGenericErrorFunc ("", log_parse_failure);

199 200 201
    thread_mutex_lock(&xsltlock);
    cur = xslt_get_stylesheet(xslfilename);

202 203
    if (cur == NULL)
    {
204
        thread_mutex_unlock(&xsltlock);
205
        ICECAST_LOG_ERROR("problem reading stylesheet \"%s\"", xslfilename);
206
        client_send_404 (client, "Could not parse XSLT file");
207
        return;
208
    }
209

210
    res = xsltApplyStylesheet(cur, doc, NULL);
211

212 213
    if (xsltSaveResultToString (&string, &len, res, cur) < 0)
        problem = 1;
214

215 216 217 218
    /* lets find out the content type and character encoding to use */
    if (cur->encoding)
       charset = (char *)cur->encoding;

219 220 221 222 223
    if (cur->mediaType)
        mediatype = (char *)cur->mediaType;
    else
    {
        /* check method for the default, a missing method assumes xml */
224
        if (cur->method && xmlStrcmp (cur->method, XMLSTR("html")) == 0)
225 226
            mediatype = "text/html";
        else
227
            if (cur->method && xmlStrcmp (cur->method, XMLSTR("text")) == 0)
228 229 230 231
                mediatype = "text/plain";
            else
                mediatype = "text/xml";
    }
232
    if (problem == 0)
233
    {
234
        /* the 100 is to allow for the hardcoded headers */
235
        unsigned int full_len = strlen (mediatype) + len + 256;
236
        refbuf_t *refbuf = refbuf_new (full_len);
237
	ssize_t ret;
238

239
        if (string == NULL)
240
            string = xmlCharStrdup ("");
241
        ret = util_http_build_header(refbuf->data, full_len, 0, 0, 200, NULL, mediatype, charset, NULL);
242 243 244
	snprintf (refbuf->data + ret, full_len - ret,
                "Content-Length: %d\r\n\r\n%s",
                len, string);
245

246
        client->respcode = 200;
247
        client_set_queue (client, NULL);
248 249
        client->refbuf = refbuf;
        refbuf->len = strlen (refbuf->data);
250 251 252
        fserve_add_client (client, NULL);
        xmlFree (string);
    }
253 254
    else
    {
255
        ICECAST_LOG_WARN("problem applying stylesheet \"%s\"", xslfilename);
256 257
        client_send_404 (client, "XSLT problem");
    }
258
    thread_mutex_unlock (&xsltlock);
259 260 261
    xmlFreeDoc(res);
}