/* * Copyright (C) 2016 Canon Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted, provided that the following conditions * are required to be met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Canon Inc. nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "FetchHeaders.h" #include "HTTPParsers.h" namespace WebCore { // https://fetch.spec.whatwg.org/#concept-headers-remove-privileged-no-cors-request-headers static void removePrivilegedNoCORSRequestHeaders(HTTPHeaderMap& headers) { headers.remove(HTTPHeaderName::Range); } static ExceptionOr canWriteHeader(const String& name, const String& value, const String& combinedValue, FetchHeaders::Guard guard) { if (!isValidHTTPToken(name)) return Exception { TypeError, makeString("Invalid header name: '", name, "'") }; ASSERT(value.isEmpty() || (!isHTTPSpace(value[0]) && !isHTTPSpace(value[value.length() - 1]))); if (!isValidHTTPHeaderValue((value))) return Exception { TypeError, makeString("Header '", name, "' has invalid value: '", value, "'") }; if (guard == FetchHeaders::Guard::Immutable) return Exception { TypeError, "Headers object's guard is 'immutable'"_s }; if (guard == FetchHeaders::Guard::Request && isForbiddenHeaderName(name)) return false; if (guard == FetchHeaders::Guard::RequestNoCors && !combinedValue.isEmpty() && !isSimpleHeader(name, combinedValue)) return false; if (guard == FetchHeaders::Guard::Response && isForbiddenResponseHeaderName(name)) return false; return true; } static ExceptionOr appendToHeaderMap(const String& name, const String& value, HTTPHeaderMap& headers, FetchHeaders::Guard guard) { String normalizedValue = stripLeadingAndTrailingHTTPSpaces(value); String combinedValue = normalizedValue; if (headers.contains(name)) combinedValue = makeString(headers.get(name), ", ", normalizedValue); auto canWriteResult = canWriteHeader(name, normalizedValue, combinedValue, guard); if (canWriteResult.hasException()) return canWriteResult.releaseException(); if (!canWriteResult.releaseReturnValue()) return { }; headers.set(name, combinedValue); if (guard == FetchHeaders::Guard::RequestNoCors) removePrivilegedNoCORSRequestHeaders(headers); return { }; } static ExceptionOr appendToHeaderMap(const HTTPHeaderMap::HTTPHeaderMapConstIterator::KeyValue& header, HTTPHeaderMap& headers, FetchHeaders::Guard guard) { String normalizedValue = stripLeadingAndTrailingHTTPSpaces(header.value); auto canWriteResult = canWriteHeader(header.key, normalizedValue, header.value, guard); if (canWriteResult.hasException()) return canWriteResult.releaseException(); if (!canWriteResult.releaseReturnValue()) return { }; if (header.keyAsHTTPHeaderName) headers.add(header.keyAsHTTPHeaderName.value(), header.value); else headers.add(header.key, header.value); if (guard == FetchHeaders::Guard::RequestNoCors) removePrivilegedNoCORSRequestHeaders(headers); return { }; } // https://fetch.spec.whatwg.org/#concept-headers-fill static ExceptionOr fillHeaderMap(HTTPHeaderMap& headers, const FetchHeaders::Init& headersInit, FetchHeaders::Guard guard) { if (std::holds_alternative>>(headersInit)) { auto& sequence = std::get>>(headersInit); for (auto& header : sequence) { if (header.size() != 2) return Exception { TypeError, "Header sub-sequence must contain exactly two items"_s }; auto result = appendToHeaderMap(header[0], header[1], headers, guard); if (result.hasException()) return result.releaseException(); } } else { auto& record = std::get>>(headersInit); for (auto& header : record) { auto result = appendToHeaderMap(header.key, header.value, headers, guard); if (result.hasException()) return result.releaseException(); } } return { }; } ExceptionOr> FetchHeaders::create(std::optional&& headersInit) { HTTPHeaderMap headers; if (headersInit) { auto result = fillHeaderMap(headers, *headersInit, Guard::None); if (result.hasException()) return result.releaseException(); } return adoptRef(*new FetchHeaders { Guard::None, WTFMove(headers) }); } ExceptionOr FetchHeaders::fill(const Init& headerInit) { return fillHeaderMap(m_headers, headerInit, m_guard); } ExceptionOr FetchHeaders::fill(const FetchHeaders& otherHeaders) { for (auto& header : otherHeaders.m_headers) { auto result = appendToHeaderMap(header, m_headers, m_guard); if (result.hasException()) return result.releaseException(); } return { }; } ExceptionOr FetchHeaders::append(const String& name, const String& value) { return appendToHeaderMap(name, value, m_headers, m_guard); } // https://fetch.spec.whatwg.org/#dom-headers-delete ExceptionOr FetchHeaders::remove(const String& name) { if (!isValidHTTPToken(name)) return Exception { TypeError, makeString("Invalid header name: '", name, "'") }; if (m_guard == FetchHeaders::Guard::Immutable) return Exception { TypeError, "Headers object's guard is 'immutable'"_s }; if (m_guard == FetchHeaders::Guard::Request && isForbiddenHeaderName(name)) return { }; if (m_guard == FetchHeaders::Guard::RequestNoCors && !isNoCORSSafelistedRequestHeaderName(name) && !isPriviledgedNoCORSRequestHeaderName(name)) return { }; if (m_guard == FetchHeaders::Guard::Response && isForbiddenResponseHeaderName(name)) return { }; m_headers.remove(name); if (m_guard == FetchHeaders::Guard::RequestNoCors) removePrivilegedNoCORSRequestHeaders(m_headers); return { }; } ExceptionOr FetchHeaders::get(const String& name) const { if (!isValidHTTPToken(name)) return Exception { TypeError, makeString("Invalid header name: '", name, "'") }; return m_headers.get(name); } ExceptionOr FetchHeaders::has(const String& name) const { if (!isValidHTTPToken(name)) return Exception { TypeError, makeString("Invalid header name: '", name, "'") }; return m_headers.contains(name); } ExceptionOr FetchHeaders::set(const String& name, const String& value) { String normalizedValue = stripLeadingAndTrailingHTTPSpaces(value); auto canWriteResult = canWriteHeader(name, normalizedValue, normalizedValue, m_guard); if (canWriteResult.hasException()) return canWriteResult.releaseException(); if (!canWriteResult.releaseReturnValue()) return { }; m_headers.set(name, normalizedValue); if (m_guard == FetchHeaders::Guard::RequestNoCors) removePrivilegedNoCORSRequestHeaders(m_headers); return { }; } void FetchHeaders::filterAndFill(const HTTPHeaderMap& headers, Guard guard) { for (auto& header : headers) { String normalizedValue = stripLeadingAndTrailingHTTPSpaces(header.value); auto canWriteResult = canWriteHeader(header.key, normalizedValue, header.value, guard); if (canWriteResult.hasException()) continue; if (!canWriteResult.releaseReturnValue()) continue; if (header.keyAsHTTPHeaderName) m_headers.add(header.keyAsHTTPHeaderName.value(), header.value); else m_headers.add(header.key, header.value); } } std::optional> FetchHeaders::Iterator::next() { while (m_currentIndex < m_keys.size()) { auto key = m_keys[m_currentIndex++]; auto value = m_headers->m_headers.get(key); if (!value.isNull()) return KeyValuePair { WTFMove(key), WTFMove(value) }; } return std::nullopt; } FetchHeaders::Iterator::Iterator(FetchHeaders& headers) : m_headers(headers) { m_keys.reserveInitialCapacity(headers.m_headers.size()); for (auto& header : headers.m_headers) m_keys.uncheckedAppend(header.key.convertToASCIILowercase()); std::sort(m_keys.begin(), m_keys.end(), WTF::codePointCompareLessThan); } } // namespace WebCore