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