1/*
2 * This file is part of the XSL implementation.
3 *
4 * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple, Inc. All rights reserved.
5 * Copyright (C) 2005, 2006 Alexey Proskuryakov <ap@webkit.org>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB.  If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 */
22
23#include "config.h"
24
25#if ENABLE(XSLT)
26
27#include "XSLTProcessor.h"
28
29#include "CachedResourceLoader.h"
30#include "Document.h"
31#include "Frame.h"
32#include "Page.h"
33#include "PageConsole.h"
34#include "ResourceError.h"
35#include "ResourceRequest.h"
36#include "ResourceResponse.h"
37#include "SecurityOrigin.h"
38#include "TransformSource.h"
39#include "XMLDocumentParser.h"
40#include "XSLStyleSheet.h"
41#include "XSLTExtensions.h"
42#include "XSLTUnicodeSort.h"
43#include "markup.h"
44#include <libxslt/imports.h>
45#include <libxslt/security.h>
46#include <libxslt/variables.h>
47#include <libxslt/xsltutils.h>
48#include <wtf/Assertions.h>
49#include <wtf/Vector.h>
50#include <wtf/text/CString.h>
51#include <wtf/text/StringBuffer.h>
52#include <wtf/unicode/UTF8.h>
53
54#if PLATFORM(MAC)
55#include "SoftLinking.h"
56
57SOFT_LINK_LIBRARY(libxslt);
58SOFT_LINK(libxslt, xsltFreeStylesheet, void, (xsltStylesheetPtr sheet), (sheet))
59SOFT_LINK(libxslt, xsltFreeTransformContext, void, (xsltTransformContextPtr ctxt), (ctxt))
60SOFT_LINK(libxslt, xsltNewTransformContext, xsltTransformContextPtr, (xsltStylesheetPtr style, xmlDocPtr doc), (style, doc))
61SOFT_LINK(libxslt, xsltApplyStylesheetUser, xmlDocPtr, (xsltStylesheetPtr style, xmlDocPtr doc, const char** params, const char* output, FILE* profile, xsltTransformContextPtr userCtxt), (style, doc, params, output, profile, userCtxt))
62SOFT_LINK(libxslt, xsltQuoteUserParams, int, (xsltTransformContextPtr ctxt, const char** params), (ctxt, params))
63SOFT_LINK(libxslt, xsltSetCtxtSortFunc, void, (xsltTransformContextPtr ctxt, xsltSortFunc handler), (ctxt, handler))
64SOFT_LINK(libxslt, xsltSetLoaderFunc, void, (xsltDocLoaderFunc f), (f))
65SOFT_LINK(libxslt, xsltSaveResultTo, int, (xmlOutputBufferPtr buf, xmlDocPtr result, xsltStylesheetPtr style), (buf, result, style))
66SOFT_LINK(libxslt, xsltNextImport, xsltStylesheetPtr, (xsltStylesheetPtr style), (style))
67SOFT_LINK(libxslt, xsltNewSecurityPrefs, xsltSecurityPrefsPtr, (), ())
68SOFT_LINK(libxslt, xsltFreeSecurityPrefs, void, (xsltSecurityPrefsPtr sec), (sec))
69SOFT_LINK(libxslt, xsltSetSecurityPrefs, int, (xsltSecurityPrefsPtr sec, xsltSecurityOption option, xsltSecurityCheck func), (sec, option, func))
70SOFT_LINK(libxslt, xsltSetCtxtSecurityPrefs, int, (xsltSecurityPrefsPtr sec, xsltTransformContextPtr ctxt), (sec, ctxt))
71SOFT_LINK(libxslt, xsltSecurityForbid, int, (xsltSecurityPrefsPtr sec, xsltTransformContextPtr ctxt, const char* value), (sec, ctxt, value))
72
73#endif
74
75namespace WebCore {
76
77void XSLTProcessor::genericErrorFunc(void*, const char*, ...)
78{
79    // It would be nice to do something with this error message.
80}
81
82void XSLTProcessor::parseErrorFunc(void* userData, xmlError* error)
83{
84    PageConsole* console = static_cast<PageConsole*>(userData);
85    if (!console)
86        return;
87
88    MessageLevel level;
89    switch (error->level) {
90    case XML_ERR_NONE:
91        level = DebugMessageLevel;
92        break;
93    case XML_ERR_WARNING:
94        level = WarningMessageLevel;
95        break;
96    case XML_ERR_ERROR:
97    case XML_ERR_FATAL:
98    default:
99        level = ErrorMessageLevel;
100        break;
101    }
102
103    // xmlError->int2 is the column number of the error or 0 if N/A.
104    console->addMessage(XMLMessageSource, level, error->message, error->file, error->line, error->int2);
105}
106
107// FIXME: There seems to be no way to control the ctxt pointer for loading here, thus we have globals.
108static XSLTProcessor* globalProcessor = 0;
109static CachedResourceLoader* globalCachedResourceLoader = 0;
110static xmlDocPtr docLoaderFunc(const xmlChar* uri,
111                               xmlDictPtr,
112                               int options,
113                               void* ctxt,
114                               xsltLoadType type)
115{
116    if (!globalProcessor)
117        return 0;
118
119    switch (type) {
120    case XSLT_LOAD_DOCUMENT: {
121        xsltTransformContextPtr context = (xsltTransformContextPtr)ctxt;
122        xmlChar* base = xmlNodeGetBase(context->document->doc, context->node);
123        KURL url(KURL(ParsedURLString, reinterpret_cast<const char*>(base)), reinterpret_cast<const char*>(uri));
124        xmlFree(base);
125        ResourceError error;
126        ResourceResponse response;
127
128        Vector<char> data;
129
130        bool requestAllowed = globalCachedResourceLoader->frame() && globalCachedResourceLoader->document()->securityOrigin()->canRequest(url);
131        if (requestAllowed) {
132            globalCachedResourceLoader->frame()->loader()->loadResourceSynchronously(url, AllowStoredCredentials, DoNotAskClientForCrossOriginCredentials, error, response, data);
133            requestAllowed = globalCachedResourceLoader->document()->securityOrigin()->canRequest(response.url());
134        }
135        if (!requestAllowed) {
136            data.clear();
137            globalCachedResourceLoader->printAccessDeniedMessage(url);
138        }
139
140        PageConsole* console = 0;
141        Frame* frame = globalProcessor->xslStylesheet()->ownerDocument()->frame();
142        if (frame && frame->page())
143            console = frame->page()->console();
144        xmlSetStructuredErrorFunc(console, XSLTProcessor::parseErrorFunc);
145        xmlSetGenericErrorFunc(console, XSLTProcessor::genericErrorFunc);
146
147        // We don't specify an encoding here. Neither Gecko nor WinIE respects
148        // the encoding specified in the HTTP headers.
149        xmlDocPtr doc = xmlReadMemory(data.data(), data.size(), (const char*)uri, 0, options);
150
151        xmlSetStructuredErrorFunc(0, 0);
152        xmlSetGenericErrorFunc(0, 0);
153
154        return doc;
155    }
156    case XSLT_LOAD_STYLESHEET:
157        return globalProcessor->xslStylesheet()->locateStylesheetSubResource(((xsltStylesheetPtr)ctxt)->doc, uri);
158    default:
159        break;
160    }
161
162    return 0;
163}
164
165static inline void setXSLTLoadCallBack(xsltDocLoaderFunc func, XSLTProcessor* processor, CachedResourceLoader* cachedResourceLoader)
166{
167    xsltSetLoaderFunc(func);
168    globalProcessor = processor;
169    globalCachedResourceLoader = cachedResourceLoader;
170}
171
172static int writeToStringBuilder(void* context, const char* buffer, int len)
173{
174    StringBuilder& resultOutput = *static_cast<StringBuilder*>(context);
175
176    if (!len)
177        return 0;
178
179    StringBuffer<UChar> stringBuffer(len);
180    UChar* bufferUChar = stringBuffer.characters();
181    UChar* bufferUCharEnd = bufferUChar + len;
182
183    const char* stringCurrent = buffer;
184    WTF::Unicode::ConversionResult result = WTF::Unicode::convertUTF8ToUTF16(&stringCurrent, buffer + len, &bufferUChar, bufferUCharEnd);
185    if (result != WTF::Unicode::conversionOK && result != WTF::Unicode::sourceExhausted) {
186        ASSERT_NOT_REACHED();
187        return -1;
188    }
189
190    int utf16Length = bufferUChar - stringBuffer.characters();
191    resultOutput.append(stringBuffer.characters(), utf16Length);
192    return stringCurrent - buffer;
193}
194
195static bool saveResultToString(xmlDocPtr resultDoc, xsltStylesheetPtr sheet, String& resultString)
196{
197    xmlOutputBufferPtr outputBuf = xmlAllocOutputBuffer(0);
198    if (!outputBuf)
199        return false;
200
201    StringBuilder resultBuilder;
202    outputBuf->context = &resultBuilder;
203    outputBuf->writecallback = writeToStringBuilder;
204
205    int retval = xsltSaveResultTo(outputBuf, resultDoc, sheet);
206    xmlOutputBufferClose(outputBuf);
207    if (retval < 0)
208        return false;
209
210    // Workaround for <http://bugzilla.gnome.org/show_bug.cgi?id=495668>: libxslt appends an extra line feed to the result.
211    if (resultBuilder.length() > 0 && resultBuilder[resultBuilder.length() - 1] == '\n')
212        resultBuilder.resize(resultBuilder.length() - 1);
213
214    resultString = resultBuilder.toString();
215
216    return true;
217}
218
219static const char** xsltParamArrayFromParameterMap(XSLTProcessor::ParameterMap& parameters)
220{
221    if (parameters.isEmpty())
222        return 0;
223
224    const char** parameterArray = (const char**)fastMalloc(((parameters.size() * 2) + 1) * sizeof(char*));
225
226    XSLTProcessor::ParameterMap::iterator end = parameters.end();
227    unsigned index = 0;
228    for (XSLTProcessor::ParameterMap::iterator it = parameters.begin(); it != end; ++it) {
229        parameterArray[index++] = fastStrDup(it->key.utf8().data());
230        parameterArray[index++] = fastStrDup(it->value.utf8().data());
231    }
232    parameterArray[index] = 0;
233
234    return parameterArray;
235}
236
237static void freeXsltParamArray(const char** params)
238{
239    const char** temp = params;
240    if (!params)
241        return;
242
243    while (*temp) {
244        fastFree((void*)*(temp++));
245        fastFree((void*)*(temp++));
246    }
247    fastFree(params);
248}
249
250static xsltStylesheetPtr xsltStylesheetPointer(RefPtr<XSLStyleSheet>& cachedStylesheet, Node* stylesheetRootNode)
251{
252    if (!cachedStylesheet && stylesheetRootNode) {
253        cachedStylesheet = XSLStyleSheet::createForXSLTProcessor(stylesheetRootNode->parentNode() ? stylesheetRootNode->parentNode() : stylesheetRootNode,
254            stylesheetRootNode->document()->url().string(),
255            stylesheetRootNode->document()->url()); // FIXME: Should we use baseURL here?
256
257        // According to Mozilla documentation, the node must be a Document node, an xsl:stylesheet or xsl:transform element.
258        // But we just use text content regardless of node type.
259        cachedStylesheet->parseString(createMarkup(stylesheetRootNode));
260    }
261
262    if (!cachedStylesheet || !cachedStylesheet->document())
263        return 0;
264
265    return cachedStylesheet->compileStyleSheet();
266}
267
268static inline xmlDocPtr xmlDocPtrFromNode(Node* sourceNode, bool& shouldDelete)
269{
270    RefPtr<Document> ownerDocument = sourceNode->document();
271    bool sourceIsDocument = (sourceNode == ownerDocument.get());
272
273    xmlDocPtr sourceDoc = 0;
274    if (sourceIsDocument && ownerDocument->transformSource())
275        sourceDoc = (xmlDocPtr)ownerDocument->transformSource()->platformSource();
276    if (!sourceDoc) {
277        sourceDoc = (xmlDocPtr)xmlDocPtrForString(ownerDocument->cachedResourceLoader(), createMarkup(sourceNode),
278            sourceIsDocument ? ownerDocument->url().string() : String());
279        shouldDelete = sourceDoc;
280    }
281    return sourceDoc;
282}
283
284static inline String resultMIMEType(xmlDocPtr resultDoc, xsltStylesheetPtr sheet)
285{
286    // There are three types of output we need to be able to deal with:
287    // HTML (create an HTML document), XML (create an XML document),
288    // and text (wrap in a <pre> and create an XML document).
289
290    const xmlChar* resultType = 0;
291    XSLT_GET_IMPORT_PTR(resultType, sheet, method);
292    if (!resultType && resultDoc->type == XML_HTML_DOCUMENT_NODE)
293        resultType = (const xmlChar*)"html";
294
295    if (xmlStrEqual(resultType, (const xmlChar*)"html"))
296        return "text/html";
297    if (xmlStrEqual(resultType, (const xmlChar*)"text"))
298        return "text/plain";
299
300    return "application/xml";
301}
302
303bool XSLTProcessor::transformToString(Node* sourceNode, String& mimeType, String& resultString, String& resultEncoding)
304{
305    RefPtr<Document> ownerDocument = sourceNode->document();
306
307    setXSLTLoadCallBack(docLoaderFunc, this, ownerDocument->cachedResourceLoader());
308    xsltStylesheetPtr sheet = xsltStylesheetPointer(m_stylesheet, m_stylesheetRootNode.get());
309    if (!sheet) {
310        setXSLTLoadCallBack(0, 0, 0);
311        m_stylesheet = 0;
312        return false;
313    }
314    m_stylesheet->clearDocuments();
315
316    xmlChar* origMethod = sheet->method;
317    if (!origMethod && mimeType == "text/html")
318        sheet->method = (xmlChar*)"html";
319
320    bool success = false;
321    bool shouldFreeSourceDoc = false;
322    if (xmlDocPtr sourceDoc = xmlDocPtrFromNode(sourceNode, shouldFreeSourceDoc)) {
323        // The XML declaration would prevent parsing the result as a fragment, and it's not needed even for documents,
324        // as the result of this function is always immediately parsed.
325        sheet->omitXmlDeclaration = true;
326
327        xsltTransformContextPtr transformContext = xsltNewTransformContext(sheet, sourceDoc);
328        registerXSLTExtensions(transformContext);
329
330        xsltSecurityPrefsPtr securityPrefs = xsltNewSecurityPrefs();
331        // Read permissions are checked by docLoaderFunc.
332        if (0 != xsltSetSecurityPrefs(securityPrefs, XSLT_SECPREF_WRITE_FILE, xsltSecurityForbid))
333            CRASH();
334        if (0 != xsltSetSecurityPrefs(securityPrefs, XSLT_SECPREF_CREATE_DIRECTORY, xsltSecurityForbid))
335            CRASH();
336        if (0 != xsltSetSecurityPrefs(securityPrefs, XSLT_SECPREF_WRITE_NETWORK, xsltSecurityForbid))
337            CRASH();
338        if (0 != xsltSetCtxtSecurityPrefs(securityPrefs, transformContext))
339            CRASH();
340
341        // <http://bugs.webkit.org/show_bug.cgi?id=16077>: XSLT processor <xsl:sort> algorithm only compares by code point.
342        xsltSetCtxtSortFunc(transformContext, xsltUnicodeSortFunction);
343
344        // This is a workaround for a bug in libxslt.
345        // The bug has been fixed in version 1.1.13, so once we ship that this can be removed.
346        if (!transformContext->globalVars)
347           transformContext->globalVars = xmlHashCreate(20);
348
349        const char** params = xsltParamArrayFromParameterMap(m_parameters);
350        xsltQuoteUserParams(transformContext, params);
351        xmlDocPtr resultDoc = xsltApplyStylesheetUser(sheet, sourceDoc, 0, 0, 0, transformContext);
352
353        xsltFreeTransformContext(transformContext);
354        xsltFreeSecurityPrefs(securityPrefs);
355        freeXsltParamArray(params);
356
357        if (shouldFreeSourceDoc)
358            xmlFreeDoc(sourceDoc);
359
360        if ((success = saveResultToString(resultDoc, sheet, resultString))) {
361            mimeType = resultMIMEType(resultDoc, sheet);
362            resultEncoding = (char*)resultDoc->encoding;
363        }
364        xmlFreeDoc(resultDoc);
365    }
366
367    sheet->method = origMethod;
368    setXSLTLoadCallBack(0, 0, 0);
369    xsltFreeStylesheet(sheet);
370    m_stylesheet = 0;
371
372    return success;
373}
374
375} // namespace WebCore
376
377#endif // ENABLE(XSLT)
378