/* * 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 * * 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 #include #include #include #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(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( mojom::ConsoleMessageSource::kXml, level, error->message, std::make_unique(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(base)), reinterpret_cast(uri)); xmlFree(base); ResourceLoaderOptions fetch_options; 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 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(uri)); if (ctx && !xmlCtxtUseOptions(ctx, options)) { size_t offset = 0; for (const auto& span : *data) { bool final_chunk = offset + span.size() == data->size(); if (!xmlParseChunk(ctx, span.data(), static_cast(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(context); if (!len) return 0; StringBuffer 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(buffer_u_char - string_buffer.Characters()); result_output.Append(string_buffer.Characters(), utf16_length); return static_cast(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 : // 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(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 = parameters.size(); size *= 2; ++size; size *= sizeof(char*); const char** parameter_array = static_cast(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(*(temp++))); WTF::Partitions::FastFree(const_cast(*(temp++))); } WTF::Partitions::FastFree(params); } static xsltStylesheetPtr XsltStylesheetPointer( Document* document, Member& 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( 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
 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));

    // : XSLT processor
    //  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