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 "third_party/blink/renderer/core/xml/xslt_processor.h"
24 
25 #include <libxslt/imports.h>
26 #include <libxslt/security.h>
27 #include <libxslt/variables.h>
28 #include <libxslt/xsltutils.h>
29 #include "base/numerics/checked_math.h"
30 #include "third_party/blink/renderer/bindings/core/v8/source_location.h"
31 #include "third_party/blink/renderer/core/dom/document.h"
32 #include "third_party/blink/renderer/core/dom/transform_source.h"
33 #include "third_party/blink/renderer/core/editing/serializers/serialization.h"
34 #include "third_party/blink/renderer/core/frame/frame_console.h"
35 #include "third_party/blink/renderer/core/frame/local_frame.h"
36 #include "third_party/blink/renderer/core/inspector/console_message.h"
37 #include "third_party/blink/renderer/core/xml/parser/xml_document_parser.h"
38 #include "third_party/blink/renderer/core/xml/xsl_style_sheet.h"
39 #include "third_party/blink/renderer/core/xml/xslt_extensions.h"
40 #include "third_party/blink/renderer/core/xml/xslt_unicode_sort.h"
41 #include "third_party/blink/renderer/platform/heap/heap.h"
42 #include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h"
43 #include "third_party/blink/renderer/platform/loader/fetch/raw_resource.h"
44 #include "third_party/blink/renderer/platform/loader/fetch/resource.h"
45 #include "third_party/blink/renderer/platform/loader/fetch/resource_error.h"
46 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
47 #include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
48 #include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
49 #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
50 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
51 #include "third_party/blink/renderer/platform/wtf/allocator/partitions.h"
52 #include "third_party/blink/renderer/platform/wtf/assertions.h"
53 #include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
54 #include "third_party/blink/renderer/platform/wtf/text/string_buffer.h"
55 #include "third_party/blink/renderer/platform/wtf/text/utf8.h"
56 
57 namespace blink {
58 
GenericErrorFunc(void *,const char *,...)59 void XSLTProcessor::GenericErrorFunc(void*, const char*, ...) {
60   // It would be nice to do something with this error message.
61 }
62 
ParseErrorFunc(void * user_data,xmlError * error)63 void XSLTProcessor::ParseErrorFunc(void* user_data, xmlError* error) {
64   FrameConsole* console = static_cast<FrameConsole*>(user_data);
65   if (!console)
66     return;
67 
68   mojom::ConsoleMessageLevel level;
69   switch (error->level) {
70     case XML_ERR_NONE:
71       level = mojom::ConsoleMessageLevel::kVerbose;
72       break;
73     case XML_ERR_WARNING:
74       level = mojom::ConsoleMessageLevel::kWarning;
75       break;
76     case XML_ERR_ERROR:
77     case XML_ERR_FATAL:
78     default:
79       level = mojom::ConsoleMessageLevel::kError;
80       break;
81   }
82 
83   console->AddMessage(MakeGarbageCollected<ConsoleMessage>(
84       mojom::ConsoleMessageSource::kXml, level, error->message,
85       std::make_unique<SourceLocation>(error->file, error->line, 0, nullptr)));
86 }
87 
88 // FIXME: There seems to be no way to control the ctxt pointer for loading here,
89 // thus we have globals.
90 static XSLTProcessor* g_global_processor = nullptr;
91 static ResourceFetcher* g_global_resource_fetcher = nullptr;
92 
DocLoaderFunc(const xmlChar * uri,xmlDictPtr,int options,void * ctxt,xsltLoadType type)93 static xmlDocPtr DocLoaderFunc(const xmlChar* uri,
94                                xmlDictPtr,
95                                int options,
96                                void* ctxt,
97                                xsltLoadType type) {
98   if (!g_global_processor)
99     return nullptr;
100 
101   switch (type) {
102     case XSLT_LOAD_DOCUMENT: {
103       xsltTransformContextPtr context = (xsltTransformContextPtr)ctxt;
104       xmlChar* base = xmlNodeGetBase(context->document->doc, context->node);
105       KURL url(KURL(reinterpret_cast<const char*>(base)),
106                reinterpret_cast<const char*>(uri));
107       xmlFree(base);
108 
109       ResourceLoaderOptions fetch_options;
110       fetch_options.initiator_info.name = fetch_initiator_type_names::kXml;
111       FetchParameters params(ResourceRequest(url), fetch_options);
112       params.MutableResourceRequest().SetMode(
113           network::mojom::RequestMode::kSameOrigin);
114       Resource* resource =
115           RawResource::FetchSynchronously(params, g_global_resource_fetcher);
116       if (!g_global_processor)
117         return nullptr;
118       scoped_refptr<const SharedBuffer> data = resource->ResourceBuffer();
119       if (!data)
120         return nullptr;
121 
122       FrameConsole* console = nullptr;
123       LocalFrame* frame =
124           g_global_processor->XslStylesheet()->OwnerDocument()->GetFrame();
125       if (frame)
126         console = &frame->Console();
127       xmlSetStructuredErrorFunc(console, XSLTProcessor::ParseErrorFunc);
128       xmlSetGenericErrorFunc(console, XSLTProcessor::GenericErrorFunc);
129 
130       xmlDocPtr doc = nullptr;
131 
132       // We don't specify an encoding here. Neither Gecko nor WinIE respects
133       // the encoding specified in the HTTP headers.
134       xmlParserCtxtPtr ctx = xmlCreatePushParserCtxt(
135           nullptr, nullptr, nullptr, 0, reinterpret_cast<const char*>(uri));
136       if (ctx && !xmlCtxtUseOptions(ctx, options)) {
137         size_t offset = 0;
138         for (const auto& span : *data) {
139           bool final_chunk = offset + span.size() == data->size();
140           if (!xmlParseChunk(ctx, span.data(), static_cast<int>(span.size()),
141                              final_chunk))
142             break;
143           offset += span.size();
144         }
145 
146         if (ctx->wellFormed)
147           doc = ctx->myDoc;
148       }
149 
150       xmlFreeParserCtxt(ctx);
151       xmlSetStructuredErrorFunc(nullptr, nullptr);
152       xmlSetGenericErrorFunc(nullptr, nullptr);
153 
154       return doc;
155     }
156     case XSLT_LOAD_STYLESHEET:
157       return g_global_processor->XslStylesheet()->LocateStylesheetSubResource(
158           ((xsltStylesheetPtr)ctxt)->doc, uri);
159     default:
160       break;
161   }
162 
163   return nullptr;
164 }
165 
SetXSLTLoadCallBack(xsltDocLoaderFunc func,XSLTProcessor * processor,ResourceFetcher * fetcher)166 static inline void SetXSLTLoadCallBack(xsltDocLoaderFunc func,
167                                        XSLTProcessor* processor,
168                                        ResourceFetcher* fetcher) {
169   xsltSetLoaderFunc(func);
170   g_global_processor = processor;
171   g_global_resource_fetcher = fetcher;
172 }
173 
WriteToStringBuilder(void * context,const char * buffer,int len)174 static int WriteToStringBuilder(void* context, const char* buffer, int len) {
175   StringBuilder& result_output = *static_cast<StringBuilder*>(context);
176 
177   if (!len)
178     return 0;
179 
180   StringBuffer<UChar> string_buffer(len);
181   UChar* buffer_u_char = string_buffer.Characters();
182   UChar* buffer_u_char_end = buffer_u_char + len;
183 
184   const char* string_current = buffer;
185   WTF::unicode::ConversionResult result = WTF::unicode::ConvertUTF8ToUTF16(
186       &string_current, buffer + len, &buffer_u_char, buffer_u_char_end);
187   if (result != WTF::unicode::kConversionOK &&
188       result != WTF::unicode::kSourceExhausted) {
189     NOTREACHED();
190     return -1;
191   }
192 
193   int utf16_length =
194       static_cast<int>(buffer_u_char - string_buffer.Characters());
195   result_output.Append(string_buffer.Characters(), utf16_length);
196   return static_cast<int>(string_current - buffer);
197 }
198 
SaveResultToString(xmlDocPtr result_doc,xsltStylesheetPtr sheet,String & result_string)199 static bool SaveResultToString(xmlDocPtr result_doc,
200                                xsltStylesheetPtr sheet,
201                                String& result_string) {
202   xmlOutputBufferPtr output_buf = xmlAllocOutputBuffer(nullptr);
203   if (!output_buf)
204     return false;
205 
206   StringBuilder result_builder;
207   output_buf->context = &result_builder;
208   output_buf->writecallback = WriteToStringBuilder;
209 
210   int retval = xsltSaveResultTo(output_buf, result_doc, sheet);
211   xmlOutputBufferClose(output_buf);
212   if (retval < 0)
213     return false;
214 
215   // Workaround for <http://bugzilla.gnome.org/show_bug.cgi?id=495668>:
216   // libxslt appends an extra line feed to the result.
217   if (result_builder.length() > 0 &&
218       result_builder[result_builder.length() - 1] == '\n')
219     result_builder.Resize(result_builder.length() - 1);
220 
221   result_string = result_builder.ToString();
222 
223   return true;
224 }
225 
AllocateParameterArray(const char * data)226 static char* AllocateParameterArray(const char* data) {
227   size_t length = strlen(data) + 1;
228   char* parameter_array = static_cast<char*>(WTF::Partitions::FastMalloc(
229       length, WTF_HEAP_PROFILER_TYPE_NAME(XSLTProcessor)));
230   memcpy(parameter_array, data, length);
231   return parameter_array;
232 }
233 
XsltParamArrayFromParameterMap(XSLTProcessor::ParameterMap & parameters)234 static const char** XsltParamArrayFromParameterMap(
235     XSLTProcessor::ParameterMap& parameters) {
236   if (parameters.IsEmpty())
237     return nullptr;
238 
239   base::CheckedNumeric<size_t> size = parameters.size();
240   size *= 2;
241   ++size;
242   size *= sizeof(char*);
243   const char** parameter_array =
244       static_cast<const char**>(WTF::Partitions::FastMalloc(
245           size.ValueOrDie(), WTF_HEAP_PROFILER_TYPE_NAME(XSLTProcessor)));
246 
247   unsigned index = 0;
248   for (auto& parameter : parameters) {
249     parameter_array[index++] =
250         AllocateParameterArray(parameter.key.Utf8().c_str());
251     parameter_array[index++] =
252         AllocateParameterArray(parameter.value.Utf8().c_str());
253   }
254   parameter_array[index] = nullptr;
255 
256   return parameter_array;
257 }
258 
FreeXsltParamArray(const char ** params)259 static void FreeXsltParamArray(const char** params) {
260   const char** temp = params;
261   if (!params)
262     return;
263 
264   while (*temp) {
265     WTF::Partitions::FastFree(const_cast<char*>(*(temp++)));
266     WTF::Partitions::FastFree(const_cast<char*>(*(temp++)));
267   }
268   WTF::Partitions::FastFree(params);
269 }
270 
XsltStylesheetPointer(Document * document,Member<XSLStyleSheet> & cached_stylesheet,Node * stylesheet_root_node)271 static xsltStylesheetPtr XsltStylesheetPointer(
272     Document* document,
273     Member<XSLStyleSheet>& cached_stylesheet,
274     Node* stylesheet_root_node) {
275   if (!cached_stylesheet && stylesheet_root_node) {
276     // When using importStylesheet, we will use the given document as the
277     // imported stylesheet's owner.
278     cached_stylesheet = MakeGarbageCollected<XSLStyleSheet>(
279         stylesheet_root_node->parentNode()
280             ? &stylesheet_root_node->parentNode()->GetDocument()
281             : document,
282         stylesheet_root_node,
283         stylesheet_root_node->GetDocument().Url().GetString(),
284         stylesheet_root_node->GetDocument().Url(),
285         false);  // FIXME: Should we use baseURL here?
286 
287     // According to Mozilla documentation, the node must be a Document node,
288     // an xsl:stylesheet or xsl:transform element. But we just use text
289     // content regardless of node type.
290     cached_stylesheet->ParseString(CreateMarkup(stylesheet_root_node));
291   }
292 
293   if (!cached_stylesheet || !cached_stylesheet->GetDocument())
294     return nullptr;
295 
296   return cached_stylesheet->CompileStyleSheet();
297 }
298 
XmlDocPtrFromNode(Node * source_node,bool & should_delete)299 static inline xmlDocPtr XmlDocPtrFromNode(Node* source_node,
300                                           bool& should_delete) {
301   Document* owner_document = &source_node->GetDocument();
302   bool source_is_document = (source_node == owner_document);
303 
304   xmlDocPtr source_doc = nullptr;
305   if (source_is_document && owner_document->GetTransformSource())
306     source_doc =
307         (xmlDocPtr)owner_document->GetTransformSource()->PlatformSource();
308   if (!source_doc) {
309     source_doc = (xmlDocPtr)XmlDocPtrForString(
310         owner_document, CreateMarkup(source_node),
311         source_is_document ? owner_document->Url().GetString() : String());
312     should_delete = source_doc;
313   }
314   return source_doc;
315 }
316 
ResultMIMEType(xmlDocPtr result_doc,xsltStylesheetPtr sheet)317 static inline String ResultMIMEType(xmlDocPtr result_doc,
318                                     xsltStylesheetPtr sheet) {
319   // There are three types of output we need to be able to deal with:
320   // HTML (create an HTML document), XML (create an XML document),
321   // and text (wrap in a <pre> and create an XML document).
322 
323   const xmlChar* result_type = nullptr;
324   XSLT_GET_IMPORT_PTR(result_type, sheet, method);
325   if (!result_type && result_doc->type == XML_HTML_DOCUMENT_NODE)
326     result_type = (const xmlChar*)"html";
327 
328   if (xmlStrEqual(result_type, (const xmlChar*)"html"))
329     return "text/html";
330   if (xmlStrEqual(result_type, (const xmlChar*)"text"))
331     return "text/plain";
332 
333   return "application/xml";
334 }
335 
TransformToString(Node * source_node,String & mime_type,String & result_string,String & result_encoding)336 bool XSLTProcessor::TransformToString(Node* source_node,
337                                       String& mime_type,
338                                       String& result_string,
339                                       String& result_encoding) {
340   Document* owner_document = &source_node->GetDocument();
341 
342   SetXSLTLoadCallBack(DocLoaderFunc, this, owner_document->Fetcher());
343   xsltStylesheetPtr sheet = XsltStylesheetPointer(document_.Get(), stylesheet_,
344                                                   stylesheet_root_node_.Get());
345   if (!sheet) {
346     SetXSLTLoadCallBack(nullptr, nullptr, nullptr);
347     stylesheet_ = nullptr;
348     return false;
349   }
350   stylesheet_->ClearDocuments();
351 
352   xmlChar* orig_method = sheet->method;
353   if (!orig_method && mime_type == "text/html")
354     sheet->method = (xmlChar*)"html";
355 
356   bool success = false;
357   bool should_free_source_doc = false;
358   if (xmlDocPtr source_doc =
359           XmlDocPtrFromNode(source_node, should_free_source_doc)) {
360     // The XML declaration would prevent parsing the result as a fragment,
361     // and it's not needed even for documents, as the result of this
362     // function is always immediately parsed.
363     sheet->omitXmlDeclaration = true;
364 
365     xsltTransformContextPtr transform_context =
366         xsltNewTransformContext(sheet, source_doc);
367     RegisterXSLTExtensions(transform_context);
368 
369     xsltSecurityPrefsPtr security_prefs = xsltNewSecurityPrefs();
370     // Read permissions are checked by docLoaderFunc.
371     CHECK_EQ(0, xsltSetSecurityPrefs(security_prefs, XSLT_SECPREF_WRITE_FILE,
372                                      xsltSecurityForbid));
373     CHECK_EQ(0,
374              xsltSetSecurityPrefs(security_prefs, XSLT_SECPREF_CREATE_DIRECTORY,
375                                   xsltSecurityForbid));
376     CHECK_EQ(0, xsltSetSecurityPrefs(security_prefs, XSLT_SECPREF_WRITE_NETWORK,
377                                      xsltSecurityForbid));
378     CHECK_EQ(0, xsltSetCtxtSecurityPrefs(security_prefs, transform_context));
379 
380     // <http://bugs.webkit.org/show_bug.cgi?id=16077>: XSLT processor
381     // <xsl:sort> algorithm only compares by code point.
382     xsltSetCtxtSortFunc(transform_context, XsltUnicodeSortFunction);
383 
384     // This is a workaround for a bug in libxslt.
385     // The bug has been fixed in version 1.1.13, so once we ship that this
386     // can be removed.
387     if (!transform_context->globalVars)
388       transform_context->globalVars = xmlHashCreate(20);
389 
390     const char** params = XsltParamArrayFromParameterMap(parameters_);
391     xsltQuoteUserParams(transform_context, params);
392     xmlDocPtr result_doc = xsltApplyStylesheetUser(
393         sheet, source_doc, nullptr, nullptr, nullptr, transform_context);
394 
395     xsltFreeTransformContext(transform_context);
396     xsltFreeSecurityPrefs(security_prefs);
397     FreeXsltParamArray(params);
398 
399     if (should_free_source_doc)
400       xmlFreeDoc(source_doc);
401 
402     success = SaveResultToString(result_doc, sheet, result_string);
403     if (success) {
404       mime_type = ResultMIMEType(result_doc, sheet);
405       result_encoding = (char*)result_doc->encoding;
406     }
407     xmlFreeDoc(result_doc);
408   }
409 
410   sheet->method = orig_method;
411   SetXSLTLoadCallBack(nullptr, nullptr, nullptr);
412   xsltFreeStylesheet(sheet);
413   stylesheet_ = nullptr;
414 
415   return success;
416 }
417 
418 }  // namespace blink
419