1/*
2 * Copyright (C) 2003, 2006 Apple Inc. All rights reserved.
3 * Copyright (C) 2009, 2012 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26#include "config.h"
27#include "ResourceRequestBase.h"
28
29#include "HTTPHeaderNames.h"
30#include "PublicSuffix.h"
31#include "ResourceRequest.h"
32#include "ResourceResponse.h"
33#include "SecurityPolicy.h"
34#include <wtf/PointerComparison.h>
35
36namespace WebCore {
37
38#if PLATFORM(IOS_FAMILY) || USE(CFURLCONNECTION)
39double ResourceRequestBase::s_defaultTimeoutInterval = INT_MAX;
40#else
41// Will use NSURLRequest default timeout unless set to a non-zero value with setDefaultTimeoutInterval().
42// For libsoup the timeout enabled with integer milliseconds. We set 0 as the default value to avoid integer overflow.
43double ResourceRequestBase::s_defaultTimeoutInterval = 0;
44#endif
45
46inline const ResourceRequest& ResourceRequestBase::asResourceRequest() const
47{
48 return *static_cast<const ResourceRequest*>(this);
49}
50
51ResourceRequest ResourceRequestBase::isolatedCopy() const
52{
53 ResourceRequest request;
54 request.setAsIsolatedCopy(asResourceRequest());
55 return request;
56}
57
58void ResourceRequestBase::setAsIsolatedCopy(const ResourceRequest& other)
59{
60 setURL(other.url().isolatedCopy());
61 setCachePolicy(other.cachePolicy());
62 setTimeoutInterval(other.timeoutInterval());
63 setFirstPartyForCookies(other.firstPartyForCookies().isolatedCopy());
64 setHTTPMethod(other.httpMethod().isolatedCopy());
65 setPriority(other.priority());
66 setRequester(other.requester());
67 setInitiatorIdentifier(other.initiatorIdentifier().isolatedCopy());
68 setCachePartition(other.cachePartition().isolatedCopy());
69
70 if (auto inspectorInitiatorNodeIdentifier = other.inspectorInitiatorNodeIdentifier())
71 setInspectorInitiatorNodeIdentifier(*inspectorInitiatorNodeIdentifier);
72
73 if (!other.isSameSiteUnspecified())
74 setIsSameSite(other.isSameSite());
75 setIsTopSite(other.isTopSite());
76
77 updateResourceRequest();
78 m_httpHeaderFields = other.httpHeaderFields().isolatedCopy();
79
80 size_t encodingCount = other.m_responseContentDispositionEncodingFallbackArray.size();
81 if (encodingCount > 0) {
82 String encoding1 = other.m_responseContentDispositionEncodingFallbackArray[0].isolatedCopy();
83 String encoding2;
84 String encoding3;
85 if (encodingCount > 1) {
86 encoding2 = other.m_responseContentDispositionEncodingFallbackArray[1].isolatedCopy();
87 if (encodingCount > 2)
88 encoding3 = other.m_responseContentDispositionEncodingFallbackArray[2].isolatedCopy();
89 }
90 ASSERT(encodingCount <= 3);
91 setResponseContentDispositionEncodingFallbackArray(encoding1, encoding2, encoding3);
92 }
93 if (other.m_httpBody)
94 setHTTPBody(other.m_httpBody->isolatedCopy());
95 setAllowCookies(other.m_allowCookies);
96}
97
98bool ResourceRequestBase::isEmpty() const
99{
100 updateResourceRequest();
101
102 return m_url.isEmpty();
103}
104
105bool ResourceRequestBase::isNull() const
106{
107 updateResourceRequest();
108
109 return m_url.isNull();
110}
111
112const URL& ResourceRequestBase::url() const
113{
114 updateResourceRequest();
115
116 return m_url;
117}
118
119void ResourceRequestBase::setURL(const URL& url)
120{
121 updateResourceRequest();
122
123 m_url = url;
124
125 m_platformRequestUpdated = false;
126}
127
128static bool shouldUseGet(const ResourceRequestBase& request, const ResourceResponse& redirectResponse)
129{
130 if (redirectResponse.httpStatusCode() == 301 || redirectResponse.httpStatusCode() == 302)
131 return equalLettersIgnoringASCIICase(request.httpMethod(), "post");
132 return redirectResponse.httpStatusCode() == 303;
133}
134
135ResourceRequest ResourceRequestBase::redirectedRequest(const ResourceResponse& redirectResponse, bool shouldClearReferrerOnHTTPSToHTTPRedirect) const
136{
137 ASSERT(redirectResponse.isRedirection());
138 // This method is based on https://fetch.spec.whatwg.org/#http-redirect-fetch.
139 // It also implements additional processing like done by CFNetwork layer.
140
141 auto request = asResourceRequest();
142 auto location = redirectResponse.httpHeaderField(HTTPHeaderName::Location);
143
144 request.setURL(location.isEmpty() ? URL { } : URL { redirectResponse.url(), location });
145
146 if (shouldUseGet(*this, redirectResponse)) {
147 request.setHTTPMethod("GET"_s);
148 request.setHTTPBody(nullptr);
149 request.clearHTTPContentType();
150 request.m_httpHeaderFields.remove(HTTPHeaderName::ContentLength);
151 }
152
153 if (shouldClearReferrerOnHTTPSToHTTPRedirect && !request.url().protocolIs("https") && WTF::protocolIs(request.httpReferrer(), "https"))
154 request.clearHTTPReferrer();
155
156 if (!protocolHostAndPortAreEqual(request.url(), redirectResponse.url()))
157 request.clearHTTPOrigin();
158 request.clearHTTPAuthorization();
159 request.m_httpHeaderFields.remove(HTTPHeaderName::ProxyAuthorization);
160
161 return request;
162}
163
164void ResourceRequestBase::removeCredentials()
165{
166 updateResourceRequest();
167
168 if (m_url.user().isEmpty() && m_url.pass().isEmpty())
169 return;
170
171 m_url.setUser(String());
172 m_url.setPass(String());
173
174 m_platformRequestUpdated = false;
175}
176
177ResourceRequestCachePolicy ResourceRequestBase::cachePolicy() const
178{
179 updateResourceRequest();
180
181 return m_cachePolicy;
182}
183
184void ResourceRequestBase::setCachePolicy(ResourceRequestCachePolicy cachePolicy)
185{
186 updateResourceRequest();
187
188 if (m_cachePolicy == cachePolicy)
189 return;
190
191 m_cachePolicy = cachePolicy;
192
193 m_platformRequestUpdated = false;
194}
195
196double ResourceRequestBase::timeoutInterval() const
197{
198 updateResourceRequest();
199
200 return m_timeoutInterval;
201}
202
203void ResourceRequestBase::setTimeoutInterval(double timeoutInterval)
204{
205 updateResourceRequest();
206
207 if (m_timeoutInterval == timeoutInterval)
208 return;
209
210 m_timeoutInterval = timeoutInterval;
211
212 m_platformRequestUpdated = false;
213}
214
215const URL& ResourceRequestBase::firstPartyForCookies() const
216{
217 updateResourceRequest();
218
219 return m_firstPartyForCookies;
220}
221
222void ResourceRequestBase::setFirstPartyForCookies(const URL& firstPartyForCookies)
223{
224 updateResourceRequest();
225
226 if (m_firstPartyForCookies == firstPartyForCookies)
227 return;
228
229 m_firstPartyForCookies = firstPartyForCookies;
230
231 m_platformRequestUpdated = false;
232}
233
234bool ResourceRequestBase::isSameSite() const
235{
236 updateResourceRequest();
237
238 return m_sameSiteDisposition == SameSiteDisposition::SameSite;
239}
240
241void ResourceRequestBase::setIsSameSite(bool isSameSite)
242{
243 updateResourceRequest();
244
245 SameSiteDisposition newDisposition = isSameSite ? SameSiteDisposition::SameSite : SameSiteDisposition::CrossSite;
246 if (m_sameSiteDisposition == newDisposition)
247 return;
248
249 m_sameSiteDisposition = newDisposition;
250
251 m_platformRequestUpdated = false;
252}
253
254bool ResourceRequestBase::isTopSite() const
255{
256 updateResourceRequest();
257
258 return m_isTopSite;
259}
260
261void ResourceRequestBase::setIsTopSite(bool isTopSite)
262{
263 updateResourceRequest();
264
265 if (m_isTopSite == isTopSite)
266 return;
267
268 m_isTopSite = isTopSite;
269
270 m_platformRequestUpdated = false;
271}
272
273const String& ResourceRequestBase::httpMethod() const
274{
275 updateResourceRequest();
276
277 return m_httpMethod;
278}
279
280void ResourceRequestBase::setHTTPMethod(const String& httpMethod)
281{
282 updateResourceRequest();
283
284 if (m_httpMethod == httpMethod)
285 return;
286
287 m_httpMethod = httpMethod;
288
289 m_platformRequestUpdated = false;
290}
291
292const HTTPHeaderMap& ResourceRequestBase::httpHeaderFields() const
293{
294 updateResourceRequest();
295
296 return m_httpHeaderFields;
297}
298
299String ResourceRequestBase::httpHeaderField(const String& name) const
300{
301 updateResourceRequest();
302
303 return m_httpHeaderFields.get(name);
304}
305
306String ResourceRequestBase::httpHeaderField(HTTPHeaderName name) const
307{
308 updateResourceRequest();
309
310 return m_httpHeaderFields.get(name);
311}
312
313void ResourceRequestBase::setHTTPHeaderField(const String& name, const String& value)
314{
315 updateResourceRequest();
316
317 m_httpHeaderFields.set(name, value);
318
319 m_platformRequestUpdated = false;
320}
321
322void ResourceRequestBase::setHTTPHeaderField(HTTPHeaderName name, const String& value)
323{
324 updateResourceRequest();
325
326 m_httpHeaderFields.set(name, value);
327
328 m_platformRequestUpdated = false;
329}
330
331void ResourceRequestBase::clearHTTPAuthorization()
332{
333 updateResourceRequest();
334
335 if (!m_httpHeaderFields.remove(HTTPHeaderName::Authorization))
336 return;
337
338 m_platformRequestUpdated = false;
339}
340
341String ResourceRequestBase::httpContentType() const
342{
343 return httpHeaderField(HTTPHeaderName::ContentType);
344}
345
346void ResourceRequestBase::setHTTPContentType(const String& httpContentType)
347{
348 setHTTPHeaderField(HTTPHeaderName::ContentType, httpContentType);
349}
350
351void ResourceRequestBase::clearHTTPContentType()
352{
353 updateResourceRequest();
354
355 m_httpHeaderFields.remove(HTTPHeaderName::ContentType);
356
357 m_platformRequestUpdated = false;
358}
359
360String ResourceRequestBase::httpReferrer() const
361{
362 return httpHeaderField(HTTPHeaderName::Referer);
363}
364
365bool ResourceRequestBase::hasHTTPReferrer() const
366{
367 return m_httpHeaderFields.contains(HTTPHeaderName::Referer);
368}
369
370void ResourceRequestBase::setHTTPReferrer(const String& httpReferrer)
371{
372 setHTTPHeaderField(HTTPHeaderName::Referer, httpReferrer);
373}
374
375void ResourceRequestBase::setExistingHTTPReferrerToOriginString()
376{
377 if (!hasHTTPReferrer())
378 return;
379
380 setHTTPHeaderField(HTTPHeaderName::Referer, SecurityPolicy::referrerToOriginString(httpReferrer()));
381}
382
383void ResourceRequestBase::clearHTTPReferrer()
384{
385 updateResourceRequest();
386
387 m_httpHeaderFields.remove(HTTPHeaderName::Referer);
388
389 m_platformRequestUpdated = false;
390}
391
392String ResourceRequestBase::httpOrigin() const
393{
394 return httpHeaderField(HTTPHeaderName::Origin);
395}
396
397void ResourceRequestBase::setHTTPOrigin(const String& httpOrigin)
398{
399 setHTTPHeaderField(HTTPHeaderName::Origin, httpOrigin);
400}
401
402bool ResourceRequestBase::hasHTTPOrigin() const
403{
404 return m_httpHeaderFields.contains(HTTPHeaderName::Origin);
405}
406
407void ResourceRequestBase::clearHTTPOrigin()
408{
409 updateResourceRequest();
410
411 m_httpHeaderFields.remove(HTTPHeaderName::Origin);
412
413 m_platformRequestUpdated = false;
414}
415
416bool ResourceRequestBase::hasHTTPHeader(HTTPHeaderName name) const
417{
418 return m_httpHeaderFields.contains(name);
419}
420
421String ResourceRequestBase::httpUserAgent() const
422{
423 return httpHeaderField(HTTPHeaderName::UserAgent);
424}
425
426void ResourceRequestBase::setHTTPUserAgent(const String& httpUserAgent)
427{
428 setHTTPHeaderField(HTTPHeaderName::UserAgent, httpUserAgent);
429}
430
431void ResourceRequestBase::clearHTTPUserAgent()
432{
433 updateResourceRequest();
434
435 m_httpHeaderFields.remove(HTTPHeaderName::UserAgent);
436
437 m_platformRequestUpdated = false;
438}
439
440String ResourceRequestBase::httpAccept() const
441{
442 return httpHeaderField(HTTPHeaderName::Accept);
443}
444
445void ResourceRequestBase::setHTTPAccept(const String& httpAccept)
446{
447 setHTTPHeaderField(HTTPHeaderName::Accept, httpAccept);
448}
449
450void ResourceRequestBase::clearHTTPAccept()
451{
452 updateResourceRequest();
453
454 m_httpHeaderFields.remove(HTTPHeaderName::Accept);
455
456 m_platformRequestUpdated = false;
457}
458
459void ResourceRequestBase::clearHTTPAcceptEncoding()
460{
461 updateResourceRequest();
462
463 m_httpHeaderFields.remove(HTTPHeaderName::AcceptEncoding);
464
465 m_platformRequestUpdated = false;
466}
467
468void ResourceRequestBase::setResponseContentDispositionEncodingFallbackArray(const String& encoding1, const String& encoding2, const String& encoding3)
469{
470 updateResourceRequest();
471
472 m_responseContentDispositionEncodingFallbackArray.clear();
473 m_responseContentDispositionEncodingFallbackArray.reserveInitialCapacity(!encoding1.isNull() + !encoding2.isNull() + !encoding3.isNull());
474 if (!encoding1.isNull())
475 m_responseContentDispositionEncodingFallbackArray.uncheckedAppend(encoding1);
476 if (!encoding2.isNull())
477 m_responseContentDispositionEncodingFallbackArray.uncheckedAppend(encoding2);
478 if (!encoding3.isNull())
479 m_responseContentDispositionEncodingFallbackArray.uncheckedAppend(encoding3);
480
481 m_platformRequestUpdated = false;
482}
483
484FormData* ResourceRequestBase::httpBody() const
485{
486 updateResourceRequest(HTTPBodyUpdatePolicy::UpdateHTTPBody);
487
488 return m_httpBody.get();
489}
490
491bool ResourceRequestBase::hasUpload() const
492{
493 if (auto* body = httpBody()) {
494 for (auto& element : body->elements()) {
495 if (WTF::holds_alternative<WebCore::FormDataElement::EncodedFileData>(element.data) || WTF::holds_alternative<WebCore::FormDataElement::EncodedBlobData>(element.data))
496 return true;
497 }
498 }
499
500 return false;
501}
502
503void ResourceRequestBase::setHTTPBody(RefPtr<FormData>&& httpBody)
504{
505 updateResourceRequest();
506
507 m_httpBody = WTFMove(httpBody);
508
509 m_resourceRequestBodyUpdated = true;
510
511 m_platformRequestBodyUpdated = false;
512}
513
514bool ResourceRequestBase::allowCookies() const
515{
516 updateResourceRequest();
517
518 return m_allowCookies;
519}
520
521void ResourceRequestBase::setAllowCookies(bool allowCookies)
522{
523 updateResourceRequest();
524
525 if (m_allowCookies == allowCookies)
526 return;
527
528 m_allowCookies = allowCookies;
529
530 m_platformRequestUpdated = false;
531}
532
533ResourceLoadPriority ResourceRequestBase::priority() const
534{
535 updateResourceRequest();
536
537 return m_priority;
538}
539
540void ResourceRequestBase::setPriority(ResourceLoadPriority priority)
541{
542 updateResourceRequest();
543
544 if (m_priority == priority)
545 return;
546
547 m_priority = priority;
548
549 m_platformRequestUpdated = false;
550}
551
552void ResourceRequestBase::addHTTPHeaderFieldIfNotPresent(HTTPHeaderName name, const String& value)
553{
554 updateResourceRequest();
555
556 if (!m_httpHeaderFields.addIfNotPresent(name, value))
557 return;
558
559 m_platformRequestUpdated = false;
560}
561
562void ResourceRequestBase::addHTTPHeaderField(HTTPHeaderName name, const String& value)
563{
564 updateResourceRequest();
565
566 m_httpHeaderFields.add(name, value);
567
568 m_platformRequestUpdated = false;
569}
570
571void ResourceRequestBase::addHTTPHeaderField(const String& name, const String& value)
572{
573 updateResourceRequest();
574
575 m_httpHeaderFields.add(name, value);
576
577 m_platformRequestUpdated = false;
578}
579
580bool ResourceRequestBase::hasHTTPHeaderField(HTTPHeaderName headerName) const
581{
582 return m_httpHeaderFields.contains(headerName);
583}
584
585void ResourceRequestBase::setHTTPHeaderFields(HTTPHeaderMap headerFields)
586{
587 updateResourceRequest();
588
589 m_httpHeaderFields = WTFMove(headerFields);
590
591 m_platformRequestUpdated = false;
592}
593
594#if USE(SYSTEM_PREVIEW)
595bool ResourceRequestBase::isSystemPreview() const
596{
597 return m_isSystemPreview;
598}
599
600void ResourceRequestBase::setSystemPreview(bool s)
601{
602 m_isSystemPreview = s;
603}
604
605const IntRect& ResourceRequestBase::systemPreviewRect() const
606{
607 return m_systemPreviewRect;
608}
609
610void ResourceRequestBase::setSystemPreviewRect(const IntRect& rect)
611{
612 m_systemPreviewRect = rect;
613}
614#endif
615
616bool equalIgnoringHeaderFields(const ResourceRequestBase& a, const ResourceRequestBase& b)
617{
618 if (a.url() != b.url())
619 return false;
620
621 if (a.cachePolicy() != b.cachePolicy())
622 return false;
623
624 if (a.timeoutInterval() != b.timeoutInterval())
625 return false;
626
627 if (a.firstPartyForCookies() != b.firstPartyForCookies())
628 return false;
629
630 if (a.isSameSite() != b.isSameSite())
631 return false;
632
633 if (a.isTopSite() != b.isTopSite())
634 return false;
635
636 if (a.httpMethod() != b.httpMethod())
637 return false;
638
639 if (a.allowCookies() != b.allowCookies())
640 return false;
641
642 if (a.priority() != b.priority())
643 return false;
644
645 if (a.requester() != b.requester())
646 return false;
647
648 return arePointingToEqualData(a.httpBody(), b.httpBody());
649}
650
651bool ResourceRequestBase::equal(const ResourceRequest& a, const ResourceRequest& b)
652{
653 if (!equalIgnoringHeaderFields(a, b))
654 return false;
655
656 if (a.httpHeaderFields() != b.httpHeaderFields())
657 return false;
658
659 return ResourceRequest::platformCompare(a, b);
660}
661
662static const HTTPHeaderName conditionalHeaderNames[] = {
663 HTTPHeaderName::IfMatch,
664 HTTPHeaderName::IfModifiedSince,
665 HTTPHeaderName::IfNoneMatch,
666 HTTPHeaderName::IfRange,
667 HTTPHeaderName::IfUnmodifiedSince
668};
669
670bool ResourceRequestBase::isConditional() const
671{
672 updateResourceRequest();
673
674 for (auto headerName : conditionalHeaderNames) {
675 if (m_httpHeaderFields.contains(headerName))
676 return true;
677 }
678
679 return false;
680}
681
682void ResourceRequestBase::makeUnconditional()
683{
684 updateResourceRequest();
685
686 for (auto headerName : conditionalHeaderNames)
687 m_httpHeaderFields.remove(headerName);
688}
689
690double ResourceRequestBase::defaultTimeoutInterval()
691{
692 return s_defaultTimeoutInterval;
693}
694
695void ResourceRequestBase::setDefaultTimeoutInterval(double timeoutInterval)
696{
697 s_defaultTimeoutInterval = timeoutInterval;
698}
699
700void ResourceRequestBase::updatePlatformRequest(HTTPBodyUpdatePolicy bodyPolicy) const
701{
702 if (!m_platformRequestUpdated) {
703 ASSERT(m_resourceRequestUpdated);
704 const_cast<ResourceRequest&>(asResourceRequest()).doUpdatePlatformRequest();
705 m_platformRequestUpdated = true;
706 }
707
708 if (!m_platformRequestBodyUpdated && bodyPolicy == HTTPBodyUpdatePolicy::UpdateHTTPBody) {
709 ASSERT(m_resourceRequestBodyUpdated);
710 const_cast<ResourceRequest&>(asResourceRequest()).doUpdatePlatformHTTPBody();
711 m_platformRequestBodyUpdated = true;
712 }
713}
714
715void ResourceRequestBase::updateResourceRequest(HTTPBodyUpdatePolicy bodyPolicy) const
716{
717 if (!m_resourceRequestUpdated) {
718 ASSERT(m_platformRequestUpdated);
719 const_cast<ResourceRequest&>(asResourceRequest()).doUpdateResourceRequest();
720 m_resourceRequestUpdated = true;
721 }
722
723 if (!m_resourceRequestBodyUpdated && bodyPolicy == HTTPBodyUpdatePolicy::UpdateHTTPBody) {
724 ASSERT(m_platformRequestBodyUpdated);
725 const_cast<ResourceRequest&>(asResourceRequest()).doUpdateResourceHTTPBody();
726 m_resourceRequestBodyUpdated = true;
727 }
728}
729
730#if !PLATFORM(COCOA) && !USE(CFURLCONNECTION) && !USE(SOUP)
731unsigned initializeMaximumHTTPConnectionCountPerHost()
732{
733 // This is used by the loader to control the number of issued parallel load requests.
734 // Four seems to be a common default in HTTP frameworks.
735 return 4;
736}
737#endif
738
739void ResourceRequestBase::setCachePartition(const String& cachePartition)
740{
741#if ENABLE(CACHE_PARTITIONING)
742 ASSERT(!cachePartition.isNull());
743 ASSERT(cachePartition == partitionName(cachePartition));
744 m_cachePartition = cachePartition;
745#else
746 UNUSED_PARAM(cachePartition);
747#endif
748}
749
750String ResourceRequestBase::partitionName(const String& domain)
751{
752#if ENABLE(PUBLIC_SUFFIX_LIST)
753 if (domain.isNull())
754 return emptyString();
755 String highLevel = topPrivatelyControlledDomain(domain);
756 if (highLevel.isNull())
757 return emptyString();
758 return highLevel;
759#else
760 UNUSED_PARAM(domain);
761#if ENABLE(CACHE_PARTITIONING)
762#error Cache partitioning requires PUBLIC_SUFFIX_LIST
763#endif
764 return emptyString();
765#endif
766}
767
768}
769