blob: 9b7b4b32d757b1bc2a6cb259410804b8952ce11b [file] [log] [blame]
/*
* Copyright (C) 2009 Gustavo Noronha Silva
* Copyright (C) 2009 Collabora Ltd.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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 "config.h"
#if USE(SOUP)
#include "ResourceResponse.h"
#include "GUniquePtrSoup.h"
#include "HTTPHeaderNames.h"
#include "HTTPParsers.h"
#include "MIMETypeRegistry.h"
#include "SoupVersioning.h"
#include "URLSoup.h"
#include <unicode/uset.h>
#include <wtf/text/CString.h>
#include <wtf/text/WTFString.h>
namespace WebCore {
ResourceResponse::ResourceResponse(SoupMessage* soupMessage, const CString& sniffedContentType)
{
m_url = soupURIToURL(soup_message_get_uri(soupMessage));
switch (soup_message_get_http_version(soupMessage)) {
case SOUP_HTTP_1_0:
m_httpVersion = AtomString("HTTP/1.0", AtomString::ConstructFromLiteral);
break;
case SOUP_HTTP_1_1:
m_httpVersion = AtomString("HTTP/1.1", AtomString::ConstructFromLiteral);
break;
#if SOUP_CHECK_VERSION(2, 99, 3)
case SOUP_HTTP_2_0:
m_httpVersion = AtomString("HTTP/2", AtomString::ConstructFromLiteral);
break;
#endif
}
m_httpStatusCode = soup_message_get_status(soupMessage);
setHTTPStatusText(soup_message_get_reason_phrase(soupMessage));
m_certificate = soup_message_get_tls_peer_certificate(soupMessage);
m_tlsErrors = soup_message_get_tls_peer_certificate_errors(soupMessage);
auto* responseHeaders = soup_message_get_response_headers(soupMessage);
updateFromSoupMessageHeaders(responseHeaders);
String contentType;
const char* officialType = soup_message_headers_get_one(responseHeaders, "Content-Type");
if (!sniffedContentType.isNull() && m_httpStatusCode != SOUP_STATUS_NOT_MODIFIED && sniffedContentType != officialType)
contentType = sniffedContentType.data();
else
contentType = officialType;
setMimeType(extractMIMETypeFromMediaType(contentType));
if (m_mimeType.isEmpty() && m_httpStatusCode != SOUP_STATUS_NOT_MODIFIED)
setMimeType(MIMETypeRegistry::mimeTypeForPath(m_url.path().toString()));
setTextEncodingName(extractCharsetFromMediaType(contentType));
setExpectedContentLength(soup_message_headers_get_content_length(responseHeaders));
}
void ResourceResponse::updateSoupMessageHeaders(SoupMessageHeaders* soupHeaders) const
{
for (const auto& header : httpHeaderFields())
soup_message_headers_append(soupHeaders, header.key.utf8().data(), header.value.utf8().data());
}
void ResourceResponse::updateFromSoupMessageHeaders(SoupMessageHeaders* soupHeaders)
{
SoupMessageHeadersIter headersIter;
const char* headerName;
const char* headerValue;
soup_message_headers_iter_init(&headersIter, soupHeaders);
while (soup_message_headers_iter_next(&headersIter, &headerName, &headerValue))
addHTTPHeaderField(String(headerName), String(headerValue));
}
CertificateInfo ResourceResponse::platformCertificateInfo() const
{
return CertificateInfo(m_certificate.get(), m_tlsErrors);
}
static String sanitizeFilename(const String& filename)
{
if (filename.isEmpty())
return filename;
// Strip leading/trailing whitespaces, path separators and dots
auto result = filename.stripLeadingAndTrailingCharacters([](UChar character) -> bool {
return isSpaceOrNewline(character) || character == '/' || character == '\\' || character == '.';
});
if (result.isEmpty())
return result;
// Replace control, formatting and dangerous characters in filenames.
static USet* illegalCharacterSet = nullptr;
if (!illegalCharacterSet) {
UErrorCode errorCode = U_ZERO_ERROR;
String illegalCharacters = "[[\"~*/:<>?\\\\|][:Cc:][:Cf:]]"_s;
illegalCharacterSet = uset_openPattern(StringView(illegalCharacters).upconvertedCharacters(), illegalCharacters.length(), &errorCode);
ASSERT(U_SUCCESS(errorCode));
}
HashSet<UChar32> illegalCharactersInFilename;
for (unsigned i = 0; i < result.length(); ++i) {
auto character = result[i];
if (uset_contains(illegalCharacterSet, character))
illegalCharactersInFilename.add(character);
}
for (auto character : illegalCharactersInFilename)
result = result.replace(character, '_');
return result;
}
String ResourceResponse::platformSuggestedFilename() const
{
String contentDisposition(httpHeaderField(HTTPHeaderName::ContentDisposition));
if (contentDisposition.isEmpty())
return { };
if (contentDisposition.is8Bit())
contentDisposition = String::fromUTF8WithLatin1Fallback(contentDisposition.characters8(), contentDisposition.length());
GUniquePtr<SoupMessageHeaders> soupHeaders(soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE));
soup_message_headers_append(soupHeaders.get(), "Content-Disposition", contentDisposition.utf8().data());
GRefPtr<GHashTable> params;
soup_message_headers_get_content_disposition(soupHeaders.get(), nullptr, &params.outPtr());
auto filename = params ? String::fromUTF8(static_cast<char*>(g_hash_table_lookup(params.get(), "filename"))) : String();
return sanitizeFilename(filename);
}
}
#endif