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#include "core/xml/XSLTProcessor.h" 25 26#include "core/FetchInitiatorTypeNames.h" 27#include "core/dom/Document.h" 28#include "core/dom/TransformSource.h" 29#include "core/editing/markup.h" 30#include "core/fetch/Resource.h" 31#include "core/fetch/ResourceFetcher.h" 32#include "core/frame/FrameConsole.h" 33#include "core/frame/FrameHost.h" 34#include "core/frame/LocalFrame.h" 35#include "core/inspector/ConsoleMessage.h" 36#include "core/xml/XSLStyleSheet.h" 37#include "core/xml/XSLTExtensions.h" 38#include "core/xml/XSLTUnicodeSort.h" 39#include "core/xml/parser/XMLDocumentParser.h" 40#include "platform/SharedBuffer.h" 41#include "platform/network/ResourceError.h" 42#include "platform/network/ResourceRequest.h" 43#include "platform/network/ResourceResponse.h" 44#include "platform/weborigin/SecurityOrigin.h" 45#include "wtf/Assertions.h" 46#include "wtf/text/CString.h" 47#include "wtf/text/StringBuffer.h" 48#include "wtf/unicode/UTF8.h" 49#include <libxslt/imports.h> 50#include <libxslt/security.h> 51#include <libxslt/variables.h> 52#include <libxslt/xsltutils.h> 53 54namespace blink { 55 56void XSLTProcessor::genericErrorFunc(void*, const char*, ...) 57{ 58 // It would be nice to do something with this error message. 59} 60 61void XSLTProcessor::parseErrorFunc(void* userData, xmlError* error) 62{ 63 FrameConsole* console = static_cast<FrameConsole*>(userData); 64 if (!console) 65 return; 66 67 MessageLevel level; 68 switch (error->level) { 69 case XML_ERR_NONE: 70 level = DebugMessageLevel; 71 break; 72 case XML_ERR_WARNING: 73 level = WarningMessageLevel; 74 break; 75 case XML_ERR_ERROR: 76 case XML_ERR_FATAL: 77 default: 78 level = ErrorMessageLevel; 79 break; 80 } 81 82 console->addMessage(ConsoleMessage::create(XMLMessageSource, level, error->message, error->file, error->line)); 83} 84 85// FIXME: There seems to be no way to control the ctxt pointer for loading here, thus we have globals. 86static XSLTProcessor* globalProcessor = 0; 87static ResourceFetcher* globalResourceFetcher = 0; 88 89static xmlDocPtr docLoaderFunc( 90 const xmlChar* uri, xmlDictPtr, int options, void* ctxt, xsltLoadType type) 91{ 92 if (!globalProcessor) 93 return 0; 94 95 switch (type) { 96 case XSLT_LOAD_DOCUMENT: { 97 xsltTransformContextPtr context = (xsltTransformContextPtr)ctxt; 98 xmlChar* base = xmlNodeGetBase(context->document->doc, context->node); 99 KURL url(KURL(ParsedURLString, reinterpret_cast<const char*>(base)), reinterpret_cast<const char*>(uri)); 100 xmlFree(base); 101 102 ResourceLoaderOptions fetchOptions(ResourceFetcher::defaultResourceOptions()); 103 FetchRequest request(ResourceRequest(url), FetchInitiatorTypeNames::xml, fetchOptions); 104 request.setOriginRestriction(FetchRequest::RestrictToSameOrigin); 105 ResourcePtr<Resource> resource = globalResourceFetcher->fetchSynchronously(request); 106 if (!resource || !globalProcessor) 107 return 0; 108 109 FrameConsole* console = 0; 110 LocalFrame* frame = globalProcessor->xslStylesheet()->ownerDocument()->frame(); 111 if (frame) 112 console = &frame->console(); 113 xmlSetStructuredErrorFunc(console, XSLTProcessor::parseErrorFunc); 114 xmlSetGenericErrorFunc(console, XSLTProcessor::genericErrorFunc); 115 116 // We don't specify an encoding here. Neither Gecko nor WinIE respects 117 // the encoding specified in the HTTP headers. 118 SharedBuffer* data = resource->resourceBuffer(); 119 xmlDocPtr doc = data ? xmlReadMemory(data->data(), data->size(), (const char*)uri, 0, options) : 0; 120 121 xmlSetStructuredErrorFunc(0, 0); 122 xmlSetGenericErrorFunc(0, 0); 123 124 return doc; 125 } 126 case XSLT_LOAD_STYLESHEET: 127 return globalProcessor->xslStylesheet()->locateStylesheetSubResource(((xsltStylesheetPtr)ctxt)->doc, uri); 128 default: 129 break; 130 } 131 132 return 0; 133} 134 135static inline void setXSLTLoadCallBack(xsltDocLoaderFunc func, XSLTProcessor* processor, ResourceFetcher* fetcher) 136{ 137 xsltSetLoaderFunc(func); 138 globalProcessor = processor; 139 globalResourceFetcher = fetcher; 140} 141 142static int writeToStringBuilder(void* context, const char* buffer, int len) 143{ 144 StringBuilder& resultOutput = *static_cast<StringBuilder*>(context); 145 146 if (!len) 147 return 0; 148 149 StringBuffer<UChar> stringBuffer(len); 150 UChar* bufferUChar = stringBuffer.characters(); 151 UChar* bufferUCharEnd = bufferUChar + len; 152 153 const char* stringCurrent = buffer; 154 WTF::Unicode::ConversionResult result = WTF::Unicode::convertUTF8ToUTF16(&stringCurrent, buffer + len, &bufferUChar, bufferUCharEnd); 155 if (result != WTF::Unicode::conversionOK && result != WTF::Unicode::sourceExhausted) { 156 ASSERT_NOT_REACHED(); 157 return -1; 158 } 159 160 int utf16Length = bufferUChar - stringBuffer.characters(); 161 resultOutput.append(stringBuffer.characters(), utf16Length); 162 return stringCurrent - buffer; 163} 164 165static bool saveResultToString(xmlDocPtr resultDoc, xsltStylesheetPtr sheet, String& resultString) 166{ 167 xmlOutputBufferPtr outputBuf = xmlAllocOutputBuffer(0); 168 if (!outputBuf) 169 return false; 170 171 StringBuilder resultBuilder; 172 outputBuf->context = &resultBuilder; 173 outputBuf->writecallback = writeToStringBuilder; 174 175 int retval = xsltSaveResultTo(outputBuf, resultDoc, sheet); 176 xmlOutputBufferClose(outputBuf); 177 if (retval < 0) 178 return false; 179 180 // Workaround for <http://bugzilla.gnome.org/show_bug.cgi?id=495668>: 181 // libxslt appends an extra line feed to the result. 182 if (resultBuilder.length() > 0 && resultBuilder[resultBuilder.length() - 1] == '\n') 183 resultBuilder.resize(resultBuilder.length() - 1); 184 185 resultString = resultBuilder.toString(); 186 187 return true; 188} 189 190static const char** xsltParamArrayFromParameterMap(XSLTProcessor::ParameterMap& parameters) 191{ 192 if (parameters.isEmpty()) 193 return 0; 194 195 const char** parameterArray = static_cast<const char**>(fastMalloc(((parameters.size() * 2) + 1) * sizeof(char*))); 196 197 XSLTProcessor::ParameterMap::iterator end = parameters.end(); 198 unsigned index = 0; 199 for (XSLTProcessor::ParameterMap::iterator it = parameters.begin(); it != end; ++it) { 200 parameterArray[index++] = fastStrDup(it->key.utf8().data()); 201 parameterArray[index++] = fastStrDup(it->value.utf8().data()); 202 } 203 parameterArray[index] = 0; 204 205 return parameterArray; 206} 207 208static void freeXsltParamArray(const char** params) 209{ 210 const char** temp = params; 211 if (!params) 212 return; 213 214 while (*temp) { 215 fastFree(const_cast<char*>(*(temp++))); 216 fastFree(const_cast<char*>(*(temp++))); 217 } 218 fastFree(params); 219} 220 221static xsltStylesheetPtr xsltStylesheetPointer(Document* document, RefPtrWillBeMember<XSLStyleSheet>& cachedStylesheet, Node* stylesheetRootNode) 222{ 223 if (!cachedStylesheet && stylesheetRootNode) { 224 // When using importStylesheet, we will use the given document as the imported stylesheet's owner. 225 cachedStylesheet = XSLStyleSheet::createForXSLTProcessor( 226 stylesheetRootNode->parentNode() ? &stylesheetRootNode->parentNode()->document() : document, 227 stylesheetRootNode, 228 stylesheetRootNode->document().url().string(), 229 stylesheetRootNode->document().url()); // FIXME: Should we use baseURL here? 230 231 // According to Mozilla documentation, the node must be a Document node, 232 // an xsl:stylesheet or xsl:transform element. But we just use text 233 // content regardless of node type. 234 cachedStylesheet->parseString(createMarkup(stylesheetRootNode)); 235 } 236 237 if (!cachedStylesheet || !cachedStylesheet->document()) 238 return 0; 239 240 return cachedStylesheet->compileStyleSheet(); 241} 242 243static inline xmlDocPtr xmlDocPtrFromNode(Node* sourceNode, bool& shouldDelete) 244{ 245 RefPtrWillBeRawPtr<Document> ownerDocument(sourceNode->document()); 246 bool sourceIsDocument = (sourceNode == ownerDocument.get()); 247 248 xmlDocPtr sourceDoc = 0; 249 if (sourceIsDocument && ownerDocument->transformSource()) 250 sourceDoc = (xmlDocPtr)ownerDocument->transformSource()->platformSource(); 251 if (!sourceDoc) { 252 sourceDoc = (xmlDocPtr)xmlDocPtrForString(ownerDocument->fetcher(), createMarkup(sourceNode), 253 sourceIsDocument ? ownerDocument->url().string() : String()); 254 shouldDelete = sourceDoc; 255 } 256 return sourceDoc; 257} 258 259static inline String resultMIMEType(xmlDocPtr resultDoc, xsltStylesheetPtr sheet) 260{ 261 // There are three types of output we need to be able to deal with: 262 // HTML (create an HTML document), XML (create an XML document), 263 // and text (wrap in a <pre> and create an XML document). 264 265 const xmlChar* resultType = 0; 266 XSLT_GET_IMPORT_PTR(resultType, sheet, method); 267 if (!resultType && resultDoc->type == XML_HTML_DOCUMENT_NODE) 268 resultType = (const xmlChar*)"html"; 269 270 if (xmlStrEqual(resultType, (const xmlChar*)"html")) 271 return "text/html"; 272 if (xmlStrEqual(resultType, (const xmlChar*)"text")) 273 return "text/plain"; 274 275 return "application/xml"; 276} 277 278bool XSLTProcessor::transformToString(Node* sourceNode, String& mimeType, String& resultString, String& resultEncoding) 279{ 280 RefPtrWillBeRawPtr<Document> ownerDocument(sourceNode->document()); 281 282 setXSLTLoadCallBack(docLoaderFunc, this, ownerDocument->fetcher()); 283 xsltStylesheetPtr sheet = xsltStylesheetPointer(m_document.get(), m_stylesheet, m_stylesheetRootNode.get()); 284 if (!sheet) { 285 setXSLTLoadCallBack(0, 0, 0); 286 m_stylesheet = nullptr; 287 return false; 288 } 289 m_stylesheet->clearDocuments(); 290 291 xmlChar* origMethod = sheet->method; 292 if (!origMethod && mimeType == "text/html") 293 sheet->method = (xmlChar*)"html"; 294 295 bool success = false; 296 bool shouldFreeSourceDoc = false; 297 if (xmlDocPtr sourceDoc = xmlDocPtrFromNode(sourceNode, shouldFreeSourceDoc)) { 298 // The XML declaration would prevent parsing the result as a fragment, 299 // and it's not needed even for documents, as the result of this 300 // function is always immediately parsed. 301 sheet->omitXmlDeclaration = true; 302 303 xsltTransformContextPtr transformContext = xsltNewTransformContext(sheet, sourceDoc); 304 registerXSLTExtensions(transformContext); 305 306 xsltSecurityPrefsPtr securityPrefs = xsltNewSecurityPrefs(); 307 // Read permissions are checked by docLoaderFunc. 308 if (0 != xsltSetSecurityPrefs(securityPrefs, XSLT_SECPREF_WRITE_FILE, xsltSecurityForbid)) 309 CRASH(); 310 if (0 != xsltSetSecurityPrefs(securityPrefs, XSLT_SECPREF_CREATE_DIRECTORY, xsltSecurityForbid)) 311 CRASH(); 312 if (0 != xsltSetSecurityPrefs(securityPrefs, XSLT_SECPREF_WRITE_NETWORK, xsltSecurityForbid)) 313 CRASH(); 314 if (0 != xsltSetCtxtSecurityPrefs(securityPrefs, transformContext)) 315 CRASH(); 316 317 // <http://bugs.webkit.org/show_bug.cgi?id=16077>: XSLT processor 318 // <xsl:sort> algorithm only compares by code point. 319 xsltSetCtxtSortFunc(transformContext, xsltUnicodeSortFunction); 320 321 // This is a workaround for a bug in libxslt. 322 // The bug has been fixed in version 1.1.13, so once we ship that this 323 // can be removed. 324 if (!transformContext->globalVars) 325 transformContext->globalVars = xmlHashCreate(20); 326 327 const char** params = xsltParamArrayFromParameterMap(m_parameters); 328 xsltQuoteUserParams(transformContext, params); 329 xmlDocPtr resultDoc = xsltApplyStylesheetUser(sheet, sourceDoc, 0, 0, 0, transformContext); 330 331 xsltFreeTransformContext(transformContext); 332 xsltFreeSecurityPrefs(securityPrefs); 333 freeXsltParamArray(params); 334 335 if (shouldFreeSourceDoc) 336 xmlFreeDoc(sourceDoc); 337 338 success = saveResultToString(resultDoc, sheet, resultString); 339 if (success) { 340 mimeType = resultMIMEType(resultDoc, sheet); 341 resultEncoding = (char*)resultDoc->encoding; 342 } 343 xmlFreeDoc(resultDoc); 344 } 345 346 sheet->method = origMethod; 347 setXSLTLoadCallBack(0, 0, 0); 348 xsltFreeStylesheet(sheet); 349 m_stylesheet = nullptr; 350 351 return success; 352} 353 354} // namespace blink 355