blob: 30495ff6e6216b8e0bf6732ac62504c3d7a44fbb [file] [log] [blame]
/*
* This file is part of the XSL implementation.
*
* Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple, Inc. All rights reserved.
* Copyright (C) 2005, 2006 Alexey Proskuryakov <ap@webkit.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "third_party/blink/renderer/core/xml/xslt_processor.h"
#include <libxslt/imports.h>
#include <libxslt/security.h>
#include <libxslt/variables.h>
#include <libxslt/xsltutils.h>
#include "base/numerics/checked_math.h"
#include "third_party/blink/renderer/bindings/core/v8/source_location.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/transform_source.h"
#include "third_party/blink/renderer/core/editing/serializers/serialization.h"
#include "third_party/blink/renderer/core/frame/frame_console.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/xml/parser/xml_document_parser.h"
#include "third_party/blink/renderer/core/xml/xsl_style_sheet.h"
#include "third_party/blink/renderer/core/xml/xslt_extensions.h"
#include "third_party/blink/renderer/core/xml/xslt_unicode_sort.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h"
#include "third_party/blink/renderer/platform/loader/fetch/raw_resource.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_error.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/allocator/partitions.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
#include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
#include "third_party/blink/renderer/platform/wtf/text/string_buffer.h"
#include "third_party/blink/renderer/platform/wtf/text/utf8.h"
namespace blink {
void XSLTProcessor::GenericErrorFunc(void*, const char*, ...) {
// It would be nice to do something with this error message.
}
void XSLTProcessor::ParseErrorFunc(void* user_data, xmlError* error) {
FrameConsole* console = static_cast<FrameConsole*>(user_data);
if (!console)
return;
mojom::ConsoleMessageLevel level;
switch (error->level) {
case XML_ERR_NONE:
level = mojom::ConsoleMessageLevel::kVerbose;
break;
case XML_ERR_WARNING:
level = mojom::ConsoleMessageLevel::kWarning;
break;
case XML_ERR_ERROR:
case XML_ERR_FATAL:
default:
level = mojom::ConsoleMessageLevel::kError;
break;
}
console->AddMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kXml, level, error->message,
std::make_unique<SourceLocation>(error->file, error->line, 0, nullptr)));
}
// FIXME: There seems to be no way to control the ctxt pointer for loading here,
// thus we have globals.
static XSLTProcessor* g_global_processor = nullptr;
static ResourceFetcher* g_global_resource_fetcher = nullptr;
static xmlDocPtr DocLoaderFunc(const xmlChar* uri,
xmlDictPtr,
int options,
void* ctxt,
xsltLoadType type) {
if (!g_global_processor)
return nullptr;
switch (type) {
case XSLT_LOAD_DOCUMENT: {
xsltTransformContextPtr context = (xsltTransformContextPtr)ctxt;
xmlChar* base = xmlNodeGetBase(context->document->doc, context->node);
KURL url(KURL(reinterpret_cast<const char*>(base)),
reinterpret_cast<const char*>(uri));
xmlFree(base);
ResourceLoaderOptions fetch_options(nullptr /* world */);
fetch_options.initiator_info.name = fetch_initiator_type_names::kXml;
FetchParameters params(ResourceRequest(url), fetch_options);
params.MutableResourceRequest().SetMode(
network::mojom::RequestMode::kSameOrigin);
Resource* resource =
RawResource::FetchSynchronously(params, g_global_resource_fetcher);
if (!g_global_processor)
return nullptr;
scoped_refptr<const SharedBuffer> data = resource->ResourceBuffer();
if (!data)
return nullptr;
FrameConsole* console = nullptr;
LocalFrame* frame =
g_global_processor->XslStylesheet()->OwnerDocument()->GetFrame();
if (frame)
console = &frame->Console();
xmlSetStructuredErrorFunc(console, XSLTProcessor::ParseErrorFunc);
xmlSetGenericErrorFunc(console, XSLTProcessor::GenericErrorFunc);
xmlDocPtr doc = nullptr;
// We don't specify an encoding here. Neither Gecko nor WinIE respects
// the encoding specified in the HTTP headers.
xmlParserCtxtPtr ctx = xmlCreatePushParserCtxt(
nullptr, nullptr, nullptr, 0, reinterpret_cast<const char*>(uri));
if (ctx && !xmlCtxtUseOptions(ctx, options)) {
size_t offset = 0;
for (const auto& span : *data) {
bool final_chunk = offset + span.size() == data->size();
// Stop parsing chunks if xmlParseChunk returns an error.
if (xmlParseChunk(ctx, span.data(), static_cast<int>(span.size()),
final_chunk))
break;
offset += span.size();
}
if (ctx->wellFormed)
doc = ctx->myDoc;
}
xmlFreeParserCtxt(ctx);
xmlSetStructuredErrorFunc(nullptr, nullptr);
xmlSetGenericErrorFunc(nullptr, nullptr);
return doc;
}
case XSLT_LOAD_STYLESHEET:
return g_global_processor->XslStylesheet()->LocateStylesheetSubResource(
((xsltStylesheetPtr)ctxt)->doc, uri);
default:
break;
}
return nullptr;
}
static inline void SetXSLTLoadCallBack(xsltDocLoaderFunc func,
XSLTProcessor* processor,
ResourceFetcher* fetcher) {
xsltSetLoaderFunc(func);
g_global_processor = processor;
g_global_resource_fetcher = fetcher;
}
static int WriteToStringBuilder(void* context, const char* buffer, int len) {
StringBuilder& result_output = *static_cast<StringBuilder*>(context);
if (!len)
return 0;
StringBuffer<UChar> string_buffer(len);
UChar* buffer_u_char = string_buffer.Characters();
UChar* buffer_u_char_end = buffer_u_char + len;
const char* string_current = buffer;
WTF::unicode::ConversionResult result = WTF::unicode::ConvertUTF8ToUTF16(
&string_current, buffer + len, &buffer_u_char, buffer_u_char_end);
if (result != WTF::unicode::kConversionOK &&
result != WTF::unicode::kSourceExhausted) {
NOTREACHED();
return -1;
}
int utf16_length =
static_cast<int>(buffer_u_char - string_buffer.Characters());
result_output.Append(string_buffer.Characters(), utf16_length);
return static_cast<int>(string_current - buffer);
}
static bool SaveResultToString(xmlDocPtr result_doc,
xsltStylesheetPtr sheet,
String& result_string) {
xmlOutputBufferPtr output_buf = xmlAllocOutputBuffer(nullptr);
if (!output_buf)
return false;
StringBuilder result_builder;
output_buf->context = &result_builder;
output_buf->writecallback = WriteToStringBuilder;
int retval = xsltSaveResultTo(output_buf, result_doc, sheet);
xmlOutputBufferClose(output_buf);
if (retval < 0)
return false;
// Workaround for <http://bugzilla.gnome.org/show_bug.cgi?id=495668>:
// libxslt appends an extra line feed to the result.
if (result_builder.length() > 0 &&
result_builder[result_builder.length() - 1] == '\n')
result_builder.Resize(result_builder.length() - 1);
result_string = result_builder.ToString();
return true;
}
static char* AllocateParameterArray(const char* data) {
size_t length = strlen(data) + 1;
char* parameter_array = static_cast<char*>(WTF::Partitions::FastMalloc(
length, WTF_HEAP_PROFILER_TYPE_NAME(XSLTProcessor)));
memcpy(parameter_array, data, length);
return parameter_array;
}
static const char** XsltParamArrayFromParameterMap(
XSLTProcessor::ParameterMap& parameters) {
if (parameters.IsEmpty())
return nullptr;
base::CheckedNumeric<size_t> size = parameters.size();
size *= 2;
++size;
size *= sizeof(char*);
const char** parameter_array =
static_cast<const char**>(WTF::Partitions::FastMalloc(
size.ValueOrDie(), WTF_HEAP_PROFILER_TYPE_NAME(XSLTProcessor)));
unsigned index = 0;
for (auto& parameter : parameters) {
parameter_array[index++] =
AllocateParameterArray(parameter.key.Utf8().c_str());
parameter_array[index++] =
AllocateParameterArray(parameter.value.Utf8().c_str());
}
parameter_array[index] = nullptr;
return parameter_array;
}
static void FreeXsltParamArray(const char** params) {
const char** temp = params;
if (!params)
return;
while (*temp) {
WTF::Partitions::FastFree(const_cast<char*>(*(temp++)));
WTF::Partitions::FastFree(const_cast<char*>(*(temp++)));
}
WTF::Partitions::FastFree(params);
}
static xsltStylesheetPtr XsltStylesheetPointer(
Document* document,
Member<XSLStyleSheet>& cached_stylesheet,
Node* stylesheet_root_node) {
if (!cached_stylesheet && stylesheet_root_node) {
// When using importStylesheet, we will use the given document as the
// imported stylesheet's owner.
cached_stylesheet = MakeGarbageCollected<XSLStyleSheet>(
stylesheet_root_node->parentNode()
? &stylesheet_root_node->parentNode()->GetDocument()
: document,
stylesheet_root_node,
stylesheet_root_node->GetDocument().Url().GetString(),
stylesheet_root_node->GetDocument().Url(),
false); // FIXME: Should we use baseURL here?
// According to Mozilla documentation, the node must be a Document node,
// an xsl:stylesheet or xsl:transform element. But we just use text
// content regardless of node type.
cached_stylesheet->ParseString(CreateMarkup(stylesheet_root_node));
}
if (!cached_stylesheet || !cached_stylesheet->GetDocument())
return nullptr;
return cached_stylesheet->CompileStyleSheet();
}
static inline xmlDocPtr XmlDocPtrFromNode(Node* source_node,
bool& should_delete) {
Document* owner_document = &source_node->GetDocument();
bool source_is_document = (source_node == owner_document);
xmlDocPtr source_doc = nullptr;
if (source_is_document && owner_document->GetTransformSource())
source_doc =
(xmlDocPtr)owner_document->GetTransformSource()->PlatformSource();
if (!source_doc) {
source_doc = (xmlDocPtr)XmlDocPtrForString(
owner_document, CreateMarkup(source_node),
source_is_document ? owner_document->Url().GetString() : String());
should_delete = source_doc;
}
return source_doc;
}
static inline String ResultMIMEType(xmlDocPtr result_doc,
xsltStylesheetPtr sheet) {
// There are three types of output we need to be able to deal with:
// HTML (create an HTML document), XML (create an XML document),
// and text (wrap in a <pre> and create an XML document).
const xmlChar* result_type = nullptr;
XSLT_GET_IMPORT_PTR(result_type, sheet, method);
if (!result_type && result_doc->type == XML_HTML_DOCUMENT_NODE)
result_type = (const xmlChar*)"html";
if (xmlStrEqual(result_type, (const xmlChar*)"html"))
return "text/html";
if (xmlStrEqual(result_type, (const xmlChar*)"text"))
return "text/plain";
return "application/xml";
}
bool XSLTProcessor::TransformToString(Node* source_node,
String& mime_type,
String& result_string,
String& result_encoding) {
Document* owner_document = &source_node->GetDocument();
SetXSLTLoadCallBack(DocLoaderFunc, this, owner_document->Fetcher());
xsltStylesheetPtr sheet = XsltStylesheetPointer(document_.Get(), stylesheet_,
stylesheet_root_node_.Get());
if (!sheet) {
SetXSLTLoadCallBack(nullptr, nullptr, nullptr);
stylesheet_ = nullptr;
return false;
}
stylesheet_->ClearDocuments();
xmlChar* orig_method = sheet->method;
if (!orig_method && mime_type == "text/html")
sheet->method = (xmlChar*)"html";
bool success = false;
bool should_free_source_doc = false;
if (xmlDocPtr source_doc =
XmlDocPtrFromNode(source_node, should_free_source_doc)) {
// The XML declaration would prevent parsing the result as a fragment,
// and it's not needed even for documents, as the result of this
// function is always immediately parsed.
sheet->omitXmlDeclaration = true;
xsltTransformContextPtr transform_context =
xsltNewTransformContext(sheet, source_doc);
RegisterXSLTExtensions(transform_context);
xsltSecurityPrefsPtr security_prefs = xsltNewSecurityPrefs();
// Read permissions are checked by docLoaderFunc.
CHECK_EQ(0, xsltSetSecurityPrefs(security_prefs, XSLT_SECPREF_WRITE_FILE,
xsltSecurityForbid));
CHECK_EQ(0,
xsltSetSecurityPrefs(security_prefs, XSLT_SECPREF_CREATE_DIRECTORY,
xsltSecurityForbid));
CHECK_EQ(0, xsltSetSecurityPrefs(security_prefs, XSLT_SECPREF_WRITE_NETWORK,
xsltSecurityForbid));
CHECK_EQ(0, xsltSetCtxtSecurityPrefs(security_prefs, transform_context));
// <http://bugs.webkit.org/show_bug.cgi?id=16077>: XSLT processor
// <xsl:sort> algorithm only compares by code point.
xsltSetCtxtSortFunc(transform_context, XsltUnicodeSortFunction);
// This is a workaround for a bug in libxslt.
// The bug has been fixed in version 1.1.13, so once we ship that this
// can be removed.
if (!transform_context->globalVars)
transform_context->globalVars = xmlHashCreate(20);
const char** params = XsltParamArrayFromParameterMap(parameters_);
xsltQuoteUserParams(transform_context, params);
xmlDocPtr result_doc = xsltApplyStylesheetUser(
sheet, source_doc, nullptr, nullptr, nullptr, transform_context);
xsltFreeTransformContext(transform_context);
xsltFreeSecurityPrefs(security_prefs);
FreeXsltParamArray(params);
if (should_free_source_doc)
xmlFreeDoc(source_doc);
success = SaveResultToString(result_doc, sheet, result_string);
if (success) {
mime_type = ResultMIMEType(result_doc, sheet);
result_encoding = (char*)result_doc->encoding;
}
xmlFreeDoc(result_doc);
}
sheet->method = orig_method;
SetXSLTLoadCallBack(nullptr, nullptr, nullptr);
xsltFreeStylesheet(sheet);
stylesheet_ = nullptr;
return success;
}
} // namespace blink