diff options
Diffstat (limited to 'src/network')
57 files changed, 4291 insertions, 644 deletions
diff --git a/src/network/access/access.pri b/src/network/access/access.pri index 38e9c25269..746ddc6916 100644 --- a/src/network/access/access.pri +++ b/src/network/access/access.pri @@ -37,7 +37,8 @@ HEADERS += \ access/qnetworkdiskcache.h \ access/qhttpthreaddelegate_p.h \ access/qhttpmultipart.h \ - access/qhttpmultipart_p.h + access/qhttpmultipart_p.h \ + access/qnetworkfile_p.h SOURCES += \ access/qftp.cpp \ @@ -68,8 +69,10 @@ SOURCES += \ access/qabstractnetworkcache.cpp \ access/qnetworkdiskcache.cpp \ access/qhttpthreaddelegate.cpp \ - access/qhttpmultipart.cpp + access/qhttpmultipart.cpp \ + access/qnetworkfile.cpp mac: LIBS_PRIVATE += -framework Security include($$PWD/../../3rdparty/zlib_dependency.pri) +include($$PWD/http2/http2.pri) diff --git a/src/network/access/http2/bitstreams.cpp b/src/network/access/http2/bitstreams.cpp new file mode 100644 index 0000000000..d22c7cd4ec --- /dev/null +++ b/src/network/access/http2/bitstreams.cpp @@ -0,0 +1,336 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "bitstreams_p.h" +#include "huffman_p.h" + +#include <QtCore/qbytearray.h> + +#include <limits> + +QT_BEGIN_NAMESPACE + +static_assert(std::numeric_limits<uchar>::digits == 8, "octets expected"); + +namespace HPack +{ + +BitOStream::BitOStream(std::vector<uchar> &b) + : buffer(b), + // All data 'packed' before: + bitsSet(8 * quint64(b.size())) +{ +} + +void BitOStream::writeBits(uchar bits, quint8 bitLength) +{ + Q_ASSERT(bitLength <= 8); + + quint8 count = bitsSet % 8; // bits used in buffer.back(), but 0 means 8 + bits <<= 8 - bitLength; // at top of byte, lower bits clear + if (count) { // we have a part-used byte; fill it some more: + buffer.back() |= bits >> count; + count = 8 - count; + } // count bits have been consumed (and 0 now means 0) + if (bitLength > count) + buffer.push_back(bits << count); + + bitsSet += bitLength; +} + +void BitOStream::write(quint32 src) +{ + const quint8 prefixLen = 8 - bitsSet % 8; + const quint32 fullPrefix = (1 << prefixLen) - 1; + + // https://http2.github.io/http2-spec/compression.html#low-level.representation, + // 5.1 + if (src < fullPrefix) { + writeBits(uchar(src), prefixLen); + } else { + writeBits(uchar(fullPrefix), prefixLen); + // We're on the byte boundary now, + // so we can just 'push_back'. + Q_ASSERT(!(bitsSet % 8)); + src -= fullPrefix; + while (src >= 128) { + buffer.push_back(uchar(src % 128 + 128)); + src /= 128; + bitsSet += 8; + } + buffer.push_back(src); + bitsSet += 8; + } +} + +void BitOStream::write(const QByteArray &src, bool compressed) +{ + quint32 byteLen = src.size(); + if (compressed && byteLen) { + const auto bitLen = huffman_encoded_bit_length(src); + Q_ASSERT(bitLen && std::numeric_limits<quint32>::max() >= (bitLen + 7) / 8); + byteLen = (bitLen + 7) / 8; + writeBits(uchar(1), 1); // bit set - compressed + } else { + writeBits(uchar(0), 1); // no compression. + } + + write(byteLen); + + if (compressed) { + huffman_encode_string(src, *this); + } else { + bitsSet += quint64(src.size()) * 8; + buffer.insert(buffer.end(), src.begin(), src.end()); + } +} + +quint64 BitOStream::bitLength() const +{ + return bitsSet; +} + +quint64 BitOStream::byteLength() const +{ + return buffer.size(); +} + +const uchar *BitOStream::begin() const +{ + return &buffer[0]; +} + +const uchar *BitOStream::end() const +{ + return &buffer[0] + buffer.size(); +} + +void BitOStream::clear() +{ + buffer.clear(); + bitsSet = 0; +} + +BitIStream::BitIStream() + : first(), + last(), + offset(), + streamError(Error::NoError) +{ +} + +BitIStream::BitIStream(const uchar *begin, const uchar *end) + : first(begin), + last(end), + offset(), + streamError(Error::NoError) +{ +} + +quint64 BitIStream::bitLength() const +{ + return quint64(last - first) * 8; +} + +bool BitIStream::hasMoreBits() const +{ + return offset < bitLength(); +} + +bool BitIStream::skipBits(quint64 nBits) +{ + if (nBits > bitLength() || bitLength() - nBits < offset) + return false; + + offset += nBits; + return true; +} + +bool BitIStream::rewindOffset(quint64 nBits) +{ + if (nBits > offset) + return false; + + offset -= nBits; + return true; +} + +bool BitIStream::read(quint32 *dstPtr) +{ + Q_ASSERT(dstPtr); + quint32 &dst = *dstPtr; + + // 5.1 Integer Representation + // + // Integers are used to represent name indexes, header field indexes, or string lengths. + // An integer representation can start anywhere within an octet. + // To allow for optimized processing, an integer representation always finishes at the end of an octet. + // An integer is represented in two parts: a prefix that fills the current octet and an optional + // list of octets that are used if the integer value does not fit within the prefix. + // The number of bits of the prefix (called N) is a parameter of the integer representation. + // If the integer value is small enough, i.e., strictly less than 2N-1, it is compressed within the N-bit prefix. + // ... + // The prefix size, N, is always between 1 and 8 bits. An integer + // starting at an octet boundary will have an 8-bit prefix. + + // Technically, such integers can be of any size, but as we do not have arbitrary-long integers, + // everything that does not fit into 'dst' we consider as an error (after all, try to allocate a string + // of such size and ... hehehe - send it as a part of a header! + + // This function updates the offset _only_ if the read was successful. + if (offset >= bitLength()) { + setError(Error::NotEnoughData); + return false; + } + + setError(Error::NoError); + + const quint32 prefixLen = 8 - offset % 8; + const quint32 fullPrefix = (1 << prefixLen) - 1; + + const uchar prefix = uchar(first[offset / 8] & fullPrefix); + if (prefix < fullPrefix) { + // The number fitted into the prefix bits. + dst = prefix; + offset += prefixLen; + return true; + } + + quint32 newOffset = offset + prefixLen; + // We have a list of bytes representing an integer ... + quint64 val = prefix; + quint32 octetPower = 0; + + while (true) { + if (newOffset >= bitLength()) { + setError(Error::NotEnoughData); + return false; + } + + const uchar octet = first[newOffset / 8]; + + if (octetPower == 28 && octet > 15) { + qCritical("integer is too big"); + setError(Error::InvalidInteger); + return false; + } + + val += quint32(octet & 0x7f) << octetPower; + newOffset += 8; + + if (!(octet & 0x80)) { + // The most significant bit of each octet is used + // as a continuation flag: its value is set to 1 + // except for the last octet in the list. + break; + } + + octetPower += 7; + } + + dst = val; + offset = newOffset; + Q_ASSERT(!(offset % 8)); + + return true; +} + +bool BitIStream::read(QByteArray *dstPtr) +{ + Q_ASSERT(dstPtr); + QByteArray &dst = *dstPtr; + //5.2 String Literal Representation + // + // Header field names and header field values can be represented as string literals. + // A string literal is compressed as a sequence of octets, either by directly encoding + // the string literal's octets or by using a Huffman code. + + // We update the offset _only_ if the read was successful. + + const quint64 oldOffset = offset; + uchar compressed = 0; + if (peekBits(offset, 1, &compressed) != 1 || !skipBits(1)) { + setError(Error::NotEnoughData); + return false; + } + + setError(Error::NoError); + + quint32 len = 0; + if (read(&len)) { + Q_ASSERT(!(offset % 8)); + if (len <= (bitLength() - offset) / 8) { // We have enough data to read a string ... + if (!compressed) { + // Now good news, integer always ends on a byte boundary. + // We can read 'len' bytes without any bit magic. + const char *src = reinterpret_cast<const char *>(first + offset / 8); + dst = QByteArray(src, len); + offset += quint64(len) * 8; + return true; + } + + BitIStream slice(first + offset / 8, first + offset / 8 + len); + if (huffman_decode_string(slice, &dst)) { + offset += quint64(len) * 8; + return true; + } + + setError(Error::CompressionError); + } else { + setError(Error::NotEnoughData); + } + } // else the exact reason was set by read(quint32). + + offset = oldOffset; + return false; +} + +BitIStream::Error BitIStream::error() const +{ + return streamError; +} + +void BitIStream::setError(Error newState) +{ + streamError = newState; +} + +} // namespace HPack + +QT_END_NAMESPACE diff --git a/src/network/access/http2/bitstreams_p.h b/src/network/access/http2/bitstreams_p.h new file mode 100644 index 0000000000..9eba319dc2 --- /dev/null +++ b/src/network/access/http2/bitstreams_p.h @@ -0,0 +1,185 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef BITSTREAMS_P_H +#define BITSTREAMS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qglobal.h> +#include <QtCore/qdebug.h> + +#include <type_traits> +#include <algorithm> +#include <vector> + +QT_BEGIN_NAMESPACE + +class QByteArray; + +namespace HPack +{ + +// BitOStream works with an external buffer, +// for example, HEADERS frame. +class Q_AUTOTEST_EXPORT BitOStream +{ +public: + BitOStream(std::vector<uchar> &buffer); + + // Write 'bitLength' bits from the least significant + // bits in 'bits' to bitstream: + void writeBits(uchar bits, quint8 bitLength); + // HPACK data format, we support: + // * 32-bit integers + // * strings + void write(quint32 src); + void write(const QByteArray &src, bool compressed); + + quint64 bitLength() const; + quint64 byteLength() const; + const uchar *begin() const; + const uchar *end() const; + + void clear(); + +private: + Q_DISABLE_COPY(BitOStream); + + std::vector<uchar> &buffer; + quint64 bitsSet; +}; + +class Q_AUTOTEST_EXPORT BitIStream +{ +public: + // Error is set by 'read' functions. + // 'peek' does not set the error, + // since it just peeks some bits + // without the notion of wrong/right. + // 'read' functions only change 'streamOffset' + // on success. + enum class Error + { + NoError, + NotEnoughData, + CompressionError, + InvalidInteger + }; + + BitIStream(); + BitIStream(const uchar *f, const uchar *l); + + quint64 bitLength() const; + bool hasMoreBits() const; + + // peekBits tries to read 'length' bits from the bitstream into + // 'dst' ('length' must be <= sizeof(dst) * 8), packing them + // starting from the most significant bit of the most significant + // byte. It's a template so that we can use it with different + // integer types. Returns the number of bits actually read. + // Does not change stream's offset. + + template<class T> + quint64 peekBits(quint64 from, quint64 length, T *dstPtr) const + { + static_assert(std::is_unsigned<T>::value, "peekBits: unsigned integer type expected"); + + Q_ASSERT(dstPtr); + Q_ASSERT(length <= sizeof(T) * 8); + + if (from >= bitLength() || !length) + return 0; + + T &dst = *dstPtr; + dst = T(); + length = std::min(length, bitLength() - from); + + const uchar *srcByte = first + from / 8; + auto bitsToRead = length + from % 8; + + while (bitsToRead > 8) { + dst = (dst << 8) | *srcByte; + bitsToRead -= 8; + ++srcByte; + } + + dst <<= bitsToRead; + dst |= *srcByte >> (8 - bitsToRead); + dst <<= sizeof(T) * 8 - length; + + return length; + } + + quint64 streamOffset() const + { + return offset; + } + + bool skipBits(quint64 nBits); + bool rewindOffset(quint64 nBits); + + bool read(quint32 *dstPtr); + bool read(QByteArray *dstPtr); + + Error error() const; + +private: + void setError(Error newState); + + const uchar *first; + const uchar *last; + quint64 offset; + Error streamError; +}; + +} // namespace HPack + +QT_END_NAMESPACE + +#endif diff --git a/src/network/access/http2/hpack.cpp b/src/network/access/http2/hpack.cpp new file mode 100644 index 0000000000..95e6f9051b --- /dev/null +++ b/src/network/access/http2/hpack.cpp @@ -0,0 +1,551 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "bitstreams_p.h" +#include "hpack_p.h" + +#include <QtCore/qbytearray.h> +#include <QtCore/qdebug.h> + +#include <limits> + +QT_BEGIN_NAMESPACE + +namespace HPack +{ + +HeaderSize header_size(const HttpHeader &header) +{ + HeaderSize size(true, 0); + for (const HeaderField &field : header) { + HeaderSize delta = entry_size(field); + if (!delta.first) + return HeaderSize(); + if (std::numeric_limits<quint32>::max() - size.second < delta.second) + return HeaderSize(); + size.second += delta.second; + } + + return size; +} + +struct BitPattern +{ + BitPattern() + : value(), + bitLength() + { + } + + BitPattern(uchar v, uchar len) + : value(v), + bitLength(len) + { + } + + uchar value; + uchar bitLength; +}; + +bool operator == (const BitPattern &lhs, const BitPattern &rhs) +{ + return lhs.bitLength == rhs.bitLength && lhs.value == rhs.value; +} + +namespace +{ + +using StreamError = BitIStream::Error; + +// There are several bit patterns to distinguish header fields: +// 1 - indexed +// 01 - literal with incremented indexing +// 0000 - literal without indexing +// 0001 - literal, never indexing +// 001 - dynamic table size update. + +// It's always 1 or 0 actually, but the number of bits to extract +// from the input stream - differs. +const BitPattern Indexed(1, 1); +const BitPattern LiteralIncrementalIndexing(1, 2); +const BitPattern LiteralNoIndexing(0, 4); +const BitPattern LiteralNeverIndexing(1, 4); +const BitPattern SizeUpdate(1, 3); + +bool is_literal_field(const BitPattern &pattern) +{ + return pattern == LiteralIncrementalIndexing + || pattern == LiteralNoIndexing + || pattern == LiteralNeverIndexing; +} + +void write_bit_pattern(const BitPattern &pattern, BitOStream &outputStream) +{ + outputStream.writeBits(pattern.value, pattern.bitLength); +} + +bool read_bit_pattern(const BitPattern &pattern, BitIStream &inputStream) +{ + uchar chunk = 0; + + const quint32 bitsRead = inputStream.peekBits(inputStream.streamOffset(), + pattern.bitLength, &chunk); + if (bitsRead != pattern.bitLength) + return false; + + // Since peekBits packs in the most significant bits, shift it! + chunk >>= (8 - bitsRead); + if (chunk != pattern.value) + return false; + + inputStream.skipBits(pattern.bitLength); + + return true; +} + +bool is_request_pseudo_header(const QByteArray &name) +{ + return name == ":method" || name == ":scheme" || + name == ":authority" || name == ":path"; +} + +} // unnamed namespace + +Encoder::Encoder(quint32 size, bool compress) + : lookupTable(size, true /*encoder needs search index*/), + compressStrings(compress) +{ +} + +quint32 Encoder::dynamicTableSize() const +{ + return lookupTable.dynamicDataSize(); +} + +bool Encoder::encodeRequest(BitOStream &outputStream, const HttpHeader &header) +{ + if (!header.size()) { + qDebug("empty header"); + return false; + } + + if (!encodeRequestPseudoHeaders(outputStream, header)) + return false; + + for (const auto &field : header) { + if (is_request_pseudo_header(field.name)) + continue; + + if (!encodeHeaderField(outputStream, field)) + return false; + } + + return true; +} + +bool Encoder::encodeResponse(BitOStream &outputStream, const HttpHeader &header) +{ + if (!header.size()) { + qDebug("empty header"); + return false; + } + + if (!encodeResponsePseudoHeaders(outputStream, header)) + return false; + + for (const auto &field : header) { + if (field.name == ":status") + continue; + + if (!encodeHeaderField(outputStream, field)) + return false; + } + + return true; +} + +bool Encoder::encodeSizeUpdate(BitOStream &outputStream, quint32 newSize) +{ + if (!lookupTable.updateDynamicTableSize(newSize)) { + qDebug("failed to update own table size"); + return false; + } + + write_bit_pattern(SizeUpdate, outputStream); + outputStream.write(newSize); + + return true; +} + +void Encoder::setMaxDynamicTableSize(quint32 size) +{ + // Up to a caller (HTTP2 protocol handler) + // to validate this size first. + lookupTable.setMaxDynamicTableSize(size); +} + +bool Encoder::encodeRequestPseudoHeaders(BitOStream &outputStream, + const HttpHeader &header) +{ + // The following pseudo-header fields are defined for HTTP/2 requests: + // - The :method pseudo-header field includes the HTTP method + // - The :scheme pseudo-header field includes the scheme portion of the target URI + // - The :authority pseudo-header field includes the authority portion of the target URI + // - The :path pseudo-header field includes the path and query parts of the target URI + + // All HTTP/2 requests MUST include exactly one valid value for the :method, + // :scheme, and :path pseudo-header fields, unless it is a CONNECT request + // (Section 8.3). An HTTP request that omits mandatory pseudo-header fields + // is malformed (Section 8.1.2.6). + + using size_type = decltype(header.size()); + + bool methodFound = false; + const char *headerName[] = {":authority", ":scheme", ":path"}; + const size_type nHeaders = sizeof headerName / sizeof headerName[0]; + bool headerFound[nHeaders] = {}; + + for (const auto &field : header) { + if (field.name == ":status") { + qCritical("invalid pseudo-header (:status) in a request"); + return false; + } + + if (field.name == ":method") { + if (methodFound) { + qCritical("only one :method pseudo-header is allowed"); + return false; + } + + if (!encodeMethod(outputStream, field)) + return false; + methodFound = true; + } else if (field.name == "cookie") { + // "crumbs" ... + } else { + for (size_type j = 0; j < nHeaders; ++j) { + if (field.name == headerName[j]) { + if (headerFound[j]) { + qCritical() << "only one" << headerName[j] << "pseudo-header is allowed"; + return false; + } + if (!encodeHeaderField(outputStream, field)) + return false; + headerFound[j] = true; + break; + } + } + } + } + + if (!methodFound) { + qCritical("mandatory :method pseudo-header not found"); + return false; + } + + // 1: don't demand headerFound[0], as :authority isn't mandatory. + for (size_type i = 1; i < nHeaders; ++i) { + if (!headerFound[i]) { + qCritical() << "mandatory" << headerName[i] + << "pseudo-header not found"; + return false; + } + } + + return true; +} + +bool Encoder::encodeHeaderField(BitOStream &outputStream, const HeaderField &field) +{ + // TODO: at the moment we never use LiteralNo/Never Indexing ... + + // Here we try: + // 1. indexed + // 2. literal indexed with indexed name/literal value + // 3. literal indexed with literal name/literal value + if (const auto index = lookupTable.indexOf(field.name, field.value)) + return encodeIndexedField(outputStream, index); + + if (const auto index = lookupTable.indexOf(field.name)) { + return encodeLiteralField(outputStream, LiteralIncrementalIndexing, + index, field.value, compressStrings); + } + + return encodeLiteralField(outputStream, LiteralIncrementalIndexing, + field.name, field.value, compressStrings); +} + +bool Encoder::encodeMethod(BitOStream &outputStream, const HeaderField &field) +{ + Q_ASSERT(field.name == ":method"); + quint32 index = lookupTable.indexOf(field.name, field.value); + if (index) + return encodeIndexedField(outputStream, index); + + index = lookupTable.indexOf(field.name); + Q_ASSERT(index); // ":method" is always in the static table ... + return encodeLiteralField(outputStream, LiteralIncrementalIndexing, + index, field.value, compressStrings); +} + +bool Encoder::encodeResponsePseudoHeaders(BitOStream &outputStream, const HttpHeader &header) +{ + bool statusFound = false; + for (const auto &field : header) { + if (is_request_pseudo_header(field.name)) { + qCritical() << "invalid pseudo-header" << field.name << "in http response"; + return false; + } + + if (field.name == ":status") { + if (statusFound) { + qDebug("only one :status pseudo-header is allowed"); + return false; + } + if (!encodeHeaderField(outputStream, field)) + return false; + statusFound = true; + } else if (field.name == "cookie") { + // "crumbs".. + } + } + + if (!statusFound) + qCritical("mandatory :status pseudo-header not found"); + + return statusFound; +} + +bool Encoder::encodeIndexedField(BitOStream &outputStream, quint32 index) const +{ + Q_ASSERT(lookupTable.indexIsValid(index)); + + write_bit_pattern(Indexed, outputStream); + outputStream.write(index); + + return true; +} + +bool Encoder::encodeLiteralField(BitOStream &outputStream, const BitPattern &fieldType, + const QByteArray &name, const QByteArray &value, + bool withCompression) +{ + Q_ASSERT(is_literal_field(fieldType)); + // According to HPACK, the bit pattern is + // 01 | 000000 (integer 0 that fits into 6-bit prefix), + // since integers always end on byte boundary, + // this also implies that we always start at bit offset == 0. + if (outputStream.bitLength() % 8) { + qCritical("invalid bit offset"); + return false; + } + + if (fieldType == LiteralIncrementalIndexing) { + if (!lookupTable.prependField(name, value)) + qDebug("failed to prepend a new field"); + } + + write_bit_pattern(fieldType, outputStream); + + outputStream.write(0); + outputStream.write(name, withCompression); + outputStream.write(value, withCompression); + + return true; +} + +bool Encoder::encodeLiteralField(BitOStream &outputStream, const BitPattern &fieldType, + quint32 nameIndex, const QByteArray &value, + bool withCompression) +{ + Q_ASSERT(is_literal_field(fieldType)); + + QByteArray name; + const bool found = lookupTable.fieldName(nameIndex, &name); + Q_UNUSED(found) Q_ASSERT(found); + + if (fieldType == LiteralIncrementalIndexing) { + if (!lookupTable.prependField(name, value)) + qDebug("failed to prepend a new field"); + } + + write_bit_pattern(fieldType, outputStream); + outputStream.write(nameIndex); + outputStream.write(value, withCompression); + + return true; +} + +Decoder::Decoder(quint32 size) + : lookupTable{size, false /* we do not need search index ... */} +{ +} + +bool Decoder::decodeHeaderFields(BitIStream &inputStream) +{ + header.clear(); + while (true) { + if (read_bit_pattern(Indexed, inputStream)) { + if (!decodeIndexedField(inputStream)) + return false; + } else if (read_bit_pattern(LiteralIncrementalIndexing, inputStream)) { + if (!decodeLiteralField(LiteralIncrementalIndexing, inputStream)) + return false; + } else if (read_bit_pattern(LiteralNoIndexing, inputStream)) { + if (!decodeLiteralField(LiteralNoIndexing, inputStream)) + return false; + } else if (read_bit_pattern(LiteralNeverIndexing, inputStream)) { + if (!decodeLiteralField(LiteralNeverIndexing, inputStream)) + return false; + } else if (read_bit_pattern(SizeUpdate, inputStream)) { + if (!decodeSizeUpdate(inputStream)) + return false; + } else { + return inputStream.bitLength() == inputStream.streamOffset(); + } + } + + return false; +} + +quint32 Decoder::dynamicTableSize() const +{ + return lookupTable.dynamicDataSize(); +} + +void Decoder::setMaxDynamicTableSize(quint32 size) +{ + // Up to a caller (HTTP2 protocol handler) + // to validate this size first. + lookupTable.setMaxDynamicTableSize(size); +} + +bool Decoder::decodeIndexedField(BitIStream &inputStream) +{ + quint32 index = 0; + if (inputStream.read(&index)) { + if (!index) { + // "The index value of 0 is not used. + // It MUST be treated as a decoding + // error if found in an indexed header + // field representation." + return false; + } + + QByteArray name, value; + if (lookupTable.field(index, &name, &value)) + return processDecodedField(Indexed, name, value); + } else { + handleStreamError(inputStream); + } + + return false; +} + +bool Decoder::decodeSizeUpdate(BitIStream &inputStream) +{ + // For now, just read and skip bits. + quint32 maxSize = 0; + if (inputStream.read(&maxSize)) { + if (!lookupTable.updateDynamicTableSize(maxSize)) + return false; + + return true; + } + + handleStreamError(inputStream); + return false; +} + +bool Decoder::decodeLiteralField(const BitPattern &fieldType, BitIStream &inputStream) +{ + // https://http2.github.io/http2-spec/compression.html + // 6.2.1, 6.2.2, 6.2.3 + // Format for all 'literal' is similar, + // the difference - is how we update/not our lookup table. + quint32 index = 0; + if (inputStream.read(&index)) { + QByteArray name; + if (!index) { + // Read a string. + if (!inputStream.read(&name)) { + handleStreamError(inputStream); + return false; + } + } else { + if (!lookupTable.fieldName(index, &name)) + return false; + } + + QByteArray value; + if (inputStream.read(&value)) + return processDecodedField(fieldType, name, value); + } + + handleStreamError(inputStream); + + return false; +} + +bool Decoder::processDecodedField(const BitPattern &fieldType, + const QByteArray &name, + const QByteArray &value) +{ + if (fieldType == LiteralIncrementalIndexing) { + if (!lookupTable.prependField(name, value)) + return false; + } + + header.push_back(HeaderField(name, value)); + return true; +} + +void Decoder::handleStreamError(BitIStream &inputStream) +{ + const auto errorCode(inputStream.error()); + if (errorCode == StreamError::NoError) + return; + + // For now error handling not needed here, + // HTTP2 layer will end with session error/COMPRESSION_ERROR. +} + +} + +QT_END_NAMESPACE diff --git a/src/network/access/http2/hpack_p.h b/src/network/access/http2/hpack_p.h new file mode 100644 index 0000000000..6a1d30d87b --- /dev/null +++ b/src/network/access/http2/hpack_p.h @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef HPACK_P_H +#define HPACK_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "hpacktable_p.h" + +#include <QtCore/qglobal.h> + +#include <vector> + +QT_BEGIN_NAMESPACE + +class QByteArray; + +namespace HPack +{ + +using HttpHeader = std::vector<HeaderField>; +HeaderSize header_size(const HttpHeader &header); + +class Q_AUTOTEST_EXPORT Encoder +{ +public: + Encoder(quint32 maxTableSize, bool compressStrings); + + quint32 dynamicTableSize() const; + + bool encodeRequest(class BitOStream &outputStream, + const HttpHeader &header); + bool encodeResponse(BitOStream &outputStream, + const HttpHeader &header); + + bool encodeSizeUpdate(BitOStream &outputStream, + quint32 newSize); + + void setMaxDynamicTableSize(quint32 size); + +private: + bool encodeRequestPseudoHeaders(BitOStream &outputStream, + const HttpHeader &header); + bool encodeHeaderField(BitOStream &outputStream, + const HeaderField &field); + bool encodeMethod(BitOStream &outputStream, + const HeaderField &field); + + bool encodeResponsePseudoHeaders(BitOStream &outputStream, + const HttpHeader &header); + + bool encodeIndexedField(BitOStream &outputStream, quint32 index) const; + + + bool encodeLiteralField(BitOStream &outputStream, + const struct BitPattern &fieldType, + quint32 nameIndex, + const QByteArray &value, + bool withCompression); + + bool encodeLiteralField(BitOStream &outputStream, + const BitPattern &fieldType, + const QByteArray &name, + const QByteArray &value, + bool withCompression); + + FieldLookupTable lookupTable; + bool compressStrings; +}; + +class Q_AUTOTEST_EXPORT Decoder +{ +public: + Decoder(quint32 maxTableSize); + + bool decodeHeaderFields(class BitIStream &inputStream); + + const HttpHeader &decodedHeader() const + { + return header; + } + + quint32 dynamicTableSize() const; + + void setMaxDynamicTableSize(quint32 size); + +private: + + bool decodeIndexedField(BitIStream &inputStream); + bool decodeSizeUpdate(BitIStream &inputStream); + bool decodeLiteralField(const BitPattern &fieldType, + BitIStream &inputStream); + + bool processDecodedField(const BitPattern &fieldType, + const QByteArray &name, + const QByteArray &value); + + void handleStreamError(BitIStream &inputStream); + + HttpHeader header; + FieldLookupTable lookupTable; +}; + +} + +QT_END_NAMESPACE + +#endif + diff --git a/src/network/access/http2/hpacktable.cpp b/src/network/access/http2/hpacktable.cpp new file mode 100644 index 0000000000..db9574e2bc --- /dev/null +++ b/src/network/access/http2/hpacktable.cpp @@ -0,0 +1,533 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "hpacktable_p.h" + +#include <QtCore/qdebug.h> + +#include <algorithm> +#include <cstring> +#include <limits> + + +QT_BEGIN_NAMESPACE + +namespace HPack +{ + +HeaderSize entry_size(const QByteArray &name, const QByteArray &value) +{ + // 32 comes from HPACK: + // "4.1 Calculating Table Size + // Note: The additional 32 octets account for an estimated overhead associated + // with an entry. For example, an entry structure using two 64-bit pointers + // to reference the name and the value of the entry and two 64-bit integers + // for counting the number of references to the name and value would have + // 32 octets of overhead." + + const unsigned sum = unsigned(name.size()) + value.size(); + if (std::numeric_limits<unsigned>::max() - 32 < sum) + return HeaderSize(); + if (sum + 32 > std::numeric_limits<quint32>::max()) + return HeaderSize(); + return HeaderSize(true, quint32(sum + 32)); +} + +namespace +{ + +int compare(const QByteArray &lhs, const QByteArray &rhs) +{ + if (const int minLen = std::min(lhs.size(), rhs.size())) { + // We use memcmp, since strings in headers are allowed + // to contain '\0'. + const int cmp = std::memcmp(lhs.constData(), rhs.constData(), minLen); + if (cmp) + return cmp; + } + + return lhs.size() - rhs.size(); +} + +} // unnamed namespace + +FieldLookupTable::SearchEntry::SearchEntry() + : field(), + chunk(), + offset(), + table() +{ +} + +FieldLookupTable::SearchEntry::SearchEntry(const HeaderField *f, + const Chunk *c, + quint32 o, + const FieldLookupTable *t) + : field(f), + chunk(c), + offset(o), + table(t) +{ + Q_ASSERT(field); +} + +bool FieldLookupTable::SearchEntry::operator < (const SearchEntry &rhs)const +{ + Q_ASSERT(field); + Q_ASSERT(rhs.field); + + int cmp = compare(field->name, rhs.field->name); + if (cmp) + return cmp < 0; + + cmp = compare(field->value, rhs.field->value); + if (cmp) + return cmp < 0; + + if (!chunk) // 'this' is not in the searchIndex. + return rhs.chunk; + + if (!rhs.chunk) // not in the searchIndex. + return false; + + Q_ASSERT(table); + Q_ASSERT(rhs.table == table); + + const quint32 leftChunkIndex = table->indexOfChunk(chunk); + const quint32 rightChunkIndex = rhs.table->indexOfChunk(rhs.chunk); + + // Later added - smaller is chunk index (since we push_front). + if (leftChunkIndex != rightChunkIndex) + return leftChunkIndex > rightChunkIndex; + + // Later added - smaller is offset. + return offset > rhs.offset; +} + +// This data is from HPACK's specs and it's quite +// conveniently sorted == works with binary search as it is. +// Later this can probably change and instead of simple +// vector we'll just reuse FieldLookupTable. +// TODO: it makes sense to generate this table while ... +// configuring/building Qt (some script downloading/parsing/generating +// would be quite handy). +const std::vector<HeaderField> &staticTable() +{ + static std::vector<HeaderField> table = { + {":authority", ""}, + {":method", "GET"}, + {":method", "POST"}, + {":path", "/"}, + {":path", "/index.html"}, + {":scheme", "http"}, + {":scheme", "https"}, + {":status", "200"}, + {":status", "204"}, + {":status", "206"}, + {":status", "304"}, + {":status", "400"}, + {":status", "404"}, + {":status", "500"}, + {"accept-charset", ""}, + {"accept-encoding", "gzip, deflate"}, + {"accept-language", ""}, + {"accept-ranges", ""}, + {"accept", ""}, + {"access-control-allow-origin", ""}, + {"age", ""}, + {"allow", ""}, + {"authorization", ""}, + {"cache-control", ""}, + {"content-disposition", ""}, + {"content-encoding", ""}, + {"content-language", ""}, + {"content-length", ""}, + {"content-location", ""}, + {"content-range", ""}, + {"content-type", ""}, + {"cookie", ""}, + {"date", ""}, + {"etag", ""}, + {"expect", ""}, + {"expires", ""}, + {"from", ""}, + {"host", ""}, + {"if-match", ""}, + {"if-modified-since", ""}, + {"if-none-match", ""}, + {"if-range", ""}, + {"if-unmodified-since", ""}, + {"last-modified", ""}, + {"link", ""}, + {"location", ""}, + {"max-forwards", ""}, + {"proxy-authenticate", ""}, + {"proxy-authorization", ""}, + {"range", ""}, + {"referer", ""}, + {"refresh", ""}, + {"retry-after", ""}, + {"server", ""}, + {"set-cookie", ""}, + {"strict-transport-security", ""}, + {"transfer-encoding", ""}, + {"user-agent", ""}, + {"vary", ""}, + {"via", ""}, + {"www-authenticate", ""} + }; + + return table; +} + +FieldLookupTable::FieldLookupTable(quint32 maxSize, bool use) + : maxTableSize(maxSize), + tableCapacity(maxSize), + useIndex(use), + nDynamic(), + begin(), + end(), + dataSize() +{ +} + + +bool FieldLookupTable::prependField(const QByteArray &name, const QByteArray &value) +{ + const auto entrySize = entry_size(name, value); + if (!entrySize.first) + return false; + + if (entrySize.second > tableCapacity) { + clearDynamicTable(); + return true; + } + + while (nDynamic && tableCapacity - dataSize < entrySize.second) + evictEntry(); + + if (!begin) { + // Either no more space or empty table ... + chunks.push_front(ChunkPtr(new Chunk(ChunkSize))); + end += ChunkSize; + begin = ChunkSize; + } + + --begin; + + dataSize += entrySize.second; + ++nDynamic; + + auto &newField = front(); + newField.name = name; + newField.value = value; + + if (useIndex) { + const auto result = searchIndex.insert(frontKey()); + Q_UNUSED(result) Q_ASSERT(result.second); + } + + return true; +} + +void FieldLookupTable::evictEntry() +{ + if (!nDynamic) + return; + + Q_ASSERT(end != begin); + + if (useIndex) { + const auto res = searchIndex.erase(backKey()); + Q_UNUSED(res) Q_ASSERT(res == 1); + } + + const HeaderField &field = back(); + const auto entrySize = entry_size(field); + Q_ASSERT(entrySize.first); + Q_ASSERT(dataSize >= entrySize.second); + dataSize -= entrySize.second; + + --nDynamic; + --end; + + if (end == begin) { + Q_ASSERT(chunks.size() == 1); + end = ChunkSize; + begin = end; + } else if (!(end % ChunkSize)) { + chunks.pop_back(); + } +} + +quint32 FieldLookupTable::numberOfEntries() const +{ + return quint32(staticTable().size()) + nDynamic; +} + +quint32 FieldLookupTable::numberOfStaticEntries() const +{ + return quint32(staticTable().size()); +} + +quint32 FieldLookupTable::numberOfDynamicEntries() const +{ + return nDynamic; +} + +quint32 FieldLookupTable::dynamicDataSize() const +{ + return dataSize; +} + +void FieldLookupTable::clearDynamicTable() +{ + searchIndex.clear(); + chunks.clear(); + begin = 0; + end = 0; + nDynamic = 0; + dataSize = 0; +} + +bool FieldLookupTable::indexIsValid(quint32 index) const +{ + return index && index <= staticTable().size() + nDynamic; +} + +quint32 FieldLookupTable::indexOf(const QByteArray &name, const QByteArray &value)const +{ + // Start from the static part first: + const auto &table = staticTable(); + const HeaderField field(name, value); + const auto staticPos = std::lower_bound(table.begin(), table.end(), field, + [](const HeaderField &lhs, const HeaderField &rhs) { + int cmp = compare(lhs.name, rhs.name); + if (cmp) + return cmp < 0; + return compare(lhs.value, rhs.value) < 0; + }); + if (staticPos != table.end()) { + if (staticPos->name == name && staticPos->value == value) + return staticPos - table.begin() + 1; + } + + // Now we have to lookup in our dynamic part ... + if (!useIndex) { + qCritical("lookup in dynamic table requires search index enabled"); + return 0; + } + + const SearchEntry key(&field, nullptr, 0, this); + const auto pos = searchIndex.lower_bound(key); + if (pos != searchIndex.end()) { + const HeaderField &found = *pos->field; + if (found.name == name && found.value == value) + return keyToIndex(*pos); + } + + return 0; +} + +quint32 FieldLookupTable::indexOf(const QByteArray &name) const +{ + // Start from the static part first: + const auto &table = staticTable(); + const HeaderField field(name, QByteArray()); + const auto staticPos = std::lower_bound(table.begin(), table.end(), field, + [](const HeaderField &lhs, const HeaderField &rhs) { + return compare(lhs.name, rhs.name) < 0; + }); + if (staticPos != table.end()) { + if (staticPos->name == name) + return staticPos - table.begin() + 1; + } + + // Now we have to lookup in our dynamic part ... + if (!useIndex) { + qCritical("lookup in dynamic table requires search index enabled"); + return 0; + } + + const SearchEntry key(&field, nullptr, 0, this); + const auto pos = searchIndex.lower_bound(key); + if (pos != searchIndex.end()) { + const HeaderField &found = *pos->field; + if (found.name == name) + return keyToIndex(*pos); + } + + return 0; +} + +bool FieldLookupTable::field(quint32 index, QByteArray *name, QByteArray *value) const +{ + Q_ASSERT(name); + Q_ASSERT(value); + + if (!indexIsValid(index)) + return false; + + const auto &table = staticTable(); + if (index - 1 < table.size()) { + *name = table[index - 1].name; + *value = table[index - 1].value; + return true; + } + + index = index - 1 - quint32(table.size()) + begin; + const auto chunkIndex = index / ChunkSize; + Q_ASSERT(chunkIndex < chunks.size()); + const auto offset = index % ChunkSize; + const HeaderField &found = (*chunks[chunkIndex])[offset]; + *name = found.name; + *value = found.value; + + return true; +} + +bool FieldLookupTable::fieldName(quint32 index, QByteArray *dst) const +{ + Q_ASSERT(dst); + return field(index, dst, &dummyDst); +} + +bool FieldLookupTable::fieldValue(quint32 index, QByteArray *dst) const +{ + Q_ASSERT(dst); + return field(index, &dummyDst, dst); +} + +const HeaderField &FieldLookupTable::front() const +{ + Q_ASSERT(nDynamic && begin != end && chunks.size()); + return (*chunks[0])[begin]; +} + +HeaderField &FieldLookupTable::front() +{ + Q_ASSERT(nDynamic && begin != end && chunks.size()); + return (*chunks[0])[begin]; +} + +const HeaderField &FieldLookupTable::back() const +{ + Q_ASSERT(nDynamic && end && end != begin); + + const quint32 absIndex = end - 1; + const quint32 chunkIndex = absIndex / ChunkSize; + Q_ASSERT(chunkIndex < chunks.size()); + const quint32 offset = absIndex % ChunkSize; + return (*chunks[chunkIndex])[offset]; +} + +quint32 FieldLookupTable::indexOfChunk(const Chunk *chunk) const +{ + Q_ASSERT(chunk); + + for (size_type i = 0; i < chunks.size(); ++i) { + if (chunks[i].get() == chunk) + return quint32(i); + } + + Q_UNREACHABLE(); + return 0; +} + +quint32 FieldLookupTable::keyToIndex(const SearchEntry &key) const +{ + Q_ASSERT(key.chunk); + + const auto chunkIndex = indexOfChunk(key.chunk); + const auto offset = key.offset; + Q_ASSERT(offset < ChunkSize); + Q_ASSERT(chunkIndex || offset >= begin); + + return quint32(offset + chunkIndex * ChunkSize - begin + 1 + staticTable().size()); +} + +FieldLookupTable::SearchEntry FieldLookupTable::frontKey() const +{ + Q_ASSERT(chunks.size() && end != begin); + return SearchEntry(&front(), chunks.front().get(), begin, this); +} + +FieldLookupTable::SearchEntry FieldLookupTable::backKey() const +{ + Q_ASSERT(chunks.size() && end != begin); + + const HeaderField &field = back(); + const quint32 absIndex = end - 1; + const auto offset = absIndex % ChunkSize; + const auto chunk = chunks[absIndex / ChunkSize].get(); + + return SearchEntry(&field, chunk, offset, this); +} + +bool FieldLookupTable::updateDynamicTableSize(quint32 size) +{ + if (!size) { + clearDynamicTable(); + return true; + } + + if (size > maxTableSize) + return false; + + tableCapacity = size; + while (nDynamic && dataSize > tableCapacity) + evictEntry(); + + return true; +} + +void FieldLookupTable::setMaxDynamicTableSize(quint32 size) +{ + // This is for an external user, for example, HTTP2 protocol + // layer that can receive SETTINGS frame from its peer. + // No validity checks here, up to this external user. + // We update max size and capacity (this can also result in + // items evicted or even dynamic table completely cleared). + maxTableSize = size; + updateDynamicTableSize(size); +} + +} + +QT_END_NAMESPACE diff --git a/src/network/access/http2/hpacktable_p.h b/src/network/access/http2/hpacktable_p.h new file mode 100644 index 0000000000..aaea89b986 --- /dev/null +++ b/src/network/access/http2/hpacktable_p.h @@ -0,0 +1,237 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef HPACKTABLE_P_H +#define HPACKTABLE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qbytearray.h> +#include <QtCore/qglobal.h> +#include <QtCore/qpair.h> + +#include <vector> +#include <memory> +#include <deque> +#include <set> + +QT_BEGIN_NAMESPACE + +namespace HPack +{ + +struct Q_AUTOTEST_EXPORT HeaderField +{ + HeaderField() + { + } + + HeaderField(const QByteArray &n, const QByteArray &v) + : name(n), + value(v) + { + } + + bool operator == (const HeaderField &rhs) const + { + return name == rhs.name && value == rhs.value; + } + + QByteArray name; + QByteArray value; +}; + +using HeaderSize = QPair<bool, quint32>; + +HeaderSize entry_size(const QByteArray &name, const QByteArray &value); + +inline HeaderSize entry_size(const HeaderField &entry) +{ + return entry_size(entry.name, entry.value); +} + +/* + Lookup table consists of two parts (HPACK, 2.3): + the immutable static table (pre-defined by HPACK's specs) + and dynamic table which is updated while + compressing/decompressing headers. + + Table must provide/implement: + 1. Fast random access - we read fields' indices from + HPACK's bit stream. + 2. FIFO for dynamic part - to push new items to the front + and evict them from the back (HPACK, 2.3.2). + 3. Fast lookup - encoder receives pairs of strings + (name|value) and it has to find an index for a pair + as the whole or for a name at least (if it's already + in either static or dynamic table). + + Static table is an immutable vector. + + Dynamic part is implemented in a way similar to std::deque - + it's a vector of pointers to chunks. Each chunk is a vector of + (name|value) pairs. Once allocated with a fixed size, chunk + never re-allocates its data, so entries' addresses do not change. + We add new chunks prepending them to the front of a vector, + in each chunk we fill (name|value) pairs starting from the back + of the chunk (this simplifies item eviction/FIFO). + Given a 'linear' index we can find a chunk number and + offset in this chunk - random access. + + Lookup in a static part is straightforward: + it's an (immutable) vector, data is sorted, + contains no duplicates, we use binary search comparing string values. + + To provide a lookup in dynamic table faster than a linear search, + we have an std::set of 'SearchEntries', where each entry contains: + - a pointer to a (name|value) pair (to compare + name|value strings). + - a pointer to a chunk containing this pair and + - an offset within this chunk - to calculate a + 'linear' index. + + Entries in a table can be duplicated (HPACK, 2.3.2), + if we evict an entry, we must update our index removing + the exactly right key, thus keys in this set are sorted + by name|value pairs first, and then by chunk index/offset + (so that NewSearchEntryKey < OldSearchEntry even if strings + are equal). +*/ + +class Q_AUTOTEST_EXPORT FieldLookupTable +{ +public: + enum + { + ChunkSize = 16, + DefaultSize = 4096 // Recommended by HTTP2. + }; + + FieldLookupTable(quint32 maxTableSize, bool useIndex); + + bool prependField(const QByteArray &name, const QByteArray &value); + void evictEntry(); + + quint32 numberOfEntries() const; + quint32 numberOfStaticEntries() const; + quint32 numberOfDynamicEntries() const; + quint32 dynamicDataSize() const; + void clearDynamicTable(); + + bool indexIsValid(quint32 index) const; + quint32 indexOf(const QByteArray &name, const QByteArray &value) const; + quint32 indexOf(const QByteArray &name) const; + bool field(quint32 index, QByteArray *name, QByteArray *value) const; + bool fieldName(quint32 index, QByteArray *dst) const; + bool fieldValue(quint32 index, QByteArray *dst) const; + + bool updateDynamicTableSize(quint32 size); + void setMaxDynamicTableSize(quint32 size); + +private: + // Table's maximum size is controlled + // by SETTINGS_HEADER_TABLE_SIZE (HTTP/2, 6.5.2). + quint32 maxTableSize; + // The tableCapacity is how many bytes the table + // can currently hold. It cannot exceed maxTableSize. + // It can be modified by a special message in + // the HPACK bit stream (HPACK, 6.3). + quint32 tableCapacity; + + using Chunk = std::vector<HeaderField>; + using ChunkPtr = std::unique_ptr<Chunk>; + std::deque<ChunkPtr> chunks; + using size_type = std::deque<ChunkPtr>::size_type; + + struct SearchEntry; + friend struct SearchEntry; + + struct SearchEntry + { + SearchEntry(); + SearchEntry(const HeaderField *f, const Chunk *c, + quint32 o, const FieldLookupTable *t); + + const HeaderField *field; + const Chunk *chunk; + const quint32 offset; + const FieldLookupTable *table; + + bool operator < (const SearchEntry &rhs) const; + }; + + bool useIndex; + std::set<SearchEntry> searchIndex; + + SearchEntry frontKey() const; + SearchEntry backKey() const; + + bool fieldAt(quint32 index, HeaderField *field) const; + + const HeaderField &front() const; + HeaderField &front(); + const HeaderField &back() const; + + quint32 nDynamic; + quint32 begin; + quint32 end; + quint32 dataSize; + + quint32 indexOfChunk(const Chunk *chunk) const; + quint32 keyToIndex(const SearchEntry &key) const; + + mutable QByteArray dummyDst; + + Q_DISABLE_COPY(FieldLookupTable); +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/network/access/http2/http2.pri b/src/network/access/http2/http2.pri new file mode 100644 index 0000000000..2157e35e2f --- /dev/null +++ b/src/network/access/http2/http2.pri @@ -0,0 +1,11 @@ +HEADERS += \ + access/http2/bitstreams_p.h \ + access/http2/huffman_p.h \ + access/http2/hpack_p.h \ + access/http2/hpacktable_p.h + +SOURCES += \ + access/http2/bitstreams.cpp \ + access/http2/huffman.cpp \ + access/http2/hpack.cpp \ + access/http2/hpacktable.cpp diff --git a/src/network/access/http2/huffman.cpp b/src/network/access/http2/huffman.cpp new file mode 100644 index 0000000000..0c1aa54dd6 --- /dev/null +++ b/src/network/access/http2/huffman.cpp @@ -0,0 +1,573 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "bitstreams_p.h" +#include "huffman_p.h" + +#include <QtCore/qbytearray.h> + +#include <algorithm> +#include <limits> + +QT_BEGIN_NAMESPACE + +namespace HPack +{ + +/* + The static Huffman code used here was extracted from: + https://http2.github.io/http2-spec/compression.html#huffman.code + + This code was generated from statistics obtained on a large + sample of HTTP headers. It is a canonical Huffman code + with some tweaking to ensure that no symbol has a unique + code length. All codes were left-aligned - for implementation + convenience. + + Using binary trees to implement decoding would be prohibitively + expensive (both memory and time-wise). Instead we use a table-based + approach and any given code itself works as an index into such table(s). + We have 256 possible byte values and code lengths in + a range [5, 26]. This would require a huge table (and most of entries + would be 'wasted', since we only have to encode 256 elements). + Instead we use multi-level tables. The first level table + is using 9-bit length index; some entries in this table are 'terminal', + some reference the next level table(s). + + For example, bytes with values 48 and 49 (ASCII codes for '0' and '1') + both have code length 5, Huffman codes are: 00000 and 00001. They + both are placed in the 'root' table, + the 'root' table has index length == 9: + [00000 | 4 remaining bits] + ... + [00001 | 4 remaining bits] + + All entires with indices between these two will 'point' to value 48 + with bitLength == 5 so that bit stream (for example) 000001010 will be + decoded as: 48 + "put 1010 back into bitstream". + + A good description can be found here: + http://commandlinefanatic.com/cgi-bin/showarticle.cgi?article=art007 + or just google "Efficient Huffman Decoding". + Also see comments below about 'filling holes'. +*/ + +namespace +{ + +const CodeEntry staticHuffmanCodeTable[] +{ + { 0, 0xffc00000ul, 13}, // 11111111|11000 + { 1, 0xffffb000ul, 23}, // 11111111|11111111|1011000 + { 2, 0xfffffe20ul, 28}, // 11111111|11111111|11111110|0010 + { 3, 0xfffffe30ul, 28}, // 11111111|11111111|11111110|0011 + { 4, 0xfffffe40ul, 28}, // 11111111|11111111|11111110|0100 + { 5, 0xfffffe50ul, 28}, // 11111111|11111111|11111110|0101 + { 6, 0xfffffe60ul, 28}, // 11111111|11111111|11111110|0110 + { 7, 0xfffffe70ul, 28}, // 11111111|11111111|11111110|0111 + { 8, 0xfffffe80ul, 28}, // 11111111|11111111|11111110|1000 + { 9, 0xffffea00ul, 24}, // 11111111|11111111|11101010 + { 10, 0xfffffff0ul, 30}, // 11111111|11111111|11111111|111100 + { 11, 0xfffffe90ul, 28}, // 11111111|11111111|11111110|1001 + { 12, 0xfffffea0ul, 28}, // 11111111|11111111|11111110|1010 + { 13, 0xfffffff4ul, 30}, // 11111111|11111111|11111111|111101 + { 14, 0xfffffeb0ul, 28}, // 11111111|11111111|11111110|1011 + { 15, 0xfffffec0ul, 28}, // 11111111|11111111|11111110|1100 + { 16, 0xfffffed0ul, 28}, // 11111111|11111111|11111110|1101 + { 17, 0xfffffee0ul, 28}, // 11111111|11111111|11111110|1110 + { 18, 0xfffffef0ul, 28}, // 11111111|11111111|11111110|1111 + { 19, 0xffffff00ul, 28}, // 11111111|11111111|11111111|0000 + { 20, 0xffffff10ul, 28}, // 11111111|11111111|11111111|0001 + { 21, 0xffffff20ul, 28}, // 11111111|11111111|11111111|0010 + { 22, 0xfffffff8ul, 30}, // 11111111|11111111|11111111|111110 + { 23, 0xffffff30ul, 28}, // 11111111|11111111|11111111|0011 + { 24, 0xffffff40ul, 28}, // 11111111|11111111|11111111|0100 + { 25, 0xffffff50ul, 28}, // 11111111|11111111|11111111|0101 + { 26, 0xffffff60ul, 28}, // 11111111|11111111|11111111|0110 + { 27, 0xffffff70ul, 28}, // 11111111|11111111|11111111|0111 + { 28, 0xffffff80ul, 28}, // 11111111|11111111|11111111|1000 + { 29, 0xffffff90ul, 28}, // 11111111|11111111|11111111|1001 + { 30, 0xffffffa0ul, 28}, // 11111111|11111111|11111111|1010 + { 31, 0xffffffb0ul, 28}, // 11111111|11111111|11111111|1011 + { 32, 0x50000000ul, 6}, // ' ' 010100 + { 33, 0xfe000000ul, 10}, // '!' 11111110|00 + { 34, 0xfe400000ul, 10}, // '"' 11111110|01 + { 35, 0xffa00000ul, 12}, // '#' 11111111|1010 + { 36, 0xffc80000ul, 13}, // '$' 11111111|11001 + { 37, 0x54000000ul, 6}, // '%' 010101 + { 38, 0xf8000000ul, 8}, // '&' 11111000 + { 39, 0xff400000ul, 11}, // ''' 11111111|010 + { 40, 0xfe800000ul, 10}, // '(' 11111110|10 + { 41, 0xfec00000ul, 10}, // ')' 11111110|11 + { 42, 0xf9000000ul, 8}, // '*' 11111001 + { 43, 0xff600000ul, 11}, // '+' 11111111|011 + { 44, 0xfa000000ul, 8}, // ',' 11111010 + { 45, 0x58000000ul, 6}, // '-' 010110 + { 46, 0x5c000000ul, 6}, // '.' 010111 + { 47, 0x60000000ul, 6}, // '/' 011000 + { 48, 0x00000000ul, 5}, // '0' 00000 + { 49, 0x08000000ul, 5}, // '1' 00001 + { 50, 0x10000000ul, 5}, // '2' 00010 + { 51, 0x64000000ul, 6}, // '3' 011001 + { 52, 0x68000000ul, 6}, // '4' 011010 + { 53, 0x6c000000ul, 6}, // '5' 011011 + { 54, 0x70000000ul, 6}, // '6' 011100 + { 55, 0x74000000ul, 6}, // '7' 011101 + { 56, 0x78000000ul, 6}, // '8' 011110 + { 57, 0x7c000000ul, 6}, // '9' 011111 + { 58, 0xb8000000ul, 7}, // ':' 1011100 + { 59, 0xfb000000ul, 8}, // ';' 11111011 + { 60, 0xfff80000ul, 15}, // '<' 11111111|1111100 + { 61, 0x80000000ul, 6}, // '=' 100000 + { 62, 0xffb00000ul, 12}, // '>' 11111111|1011 + { 63, 0xff000000ul, 10}, // '?' 11111111|00 + { 64, 0xffd00000ul, 13}, // '@' 11111111|11010 + { 65, 0x84000000ul, 6}, // 'A' 100001 + { 66, 0xba000000ul, 7}, // 'B' 1011101 + { 67, 0xbc000000ul, 7}, // 'C' 1011110 + { 68, 0xbe000000ul, 7}, // 'D' 1011111 + { 69, 0xc0000000ul, 7}, // 'E' 1100000 + { 70, 0xc2000000ul, 7}, // 'F' 1100001 + { 71, 0xc4000000ul, 7}, // 'G' 1100010 + { 72, 0xc6000000ul, 7}, // 'H' 1100011 + { 73, 0xc8000000ul, 7}, // 'I' 1100100 + { 74, 0xca000000ul, 7}, // 'J' 1100101 + { 75, 0xcc000000ul, 7}, // 'K' 1100110 + { 76, 0xce000000ul, 7}, // 'L' 1100111 + { 77, 0xd0000000ul, 7}, // 'M' 1101000 + { 78, 0xd2000000ul, 7}, // 'N' 1101001 + { 79, 0xd4000000ul, 7}, // 'O' 1101010 + { 80, 0xd6000000ul, 7}, // 'P' 1101011 + { 81, 0xd8000000ul, 7}, // 'Q' 1101100 + { 82, 0xda000000ul, 7}, // 'R' 1101101 + { 83, 0xdc000000ul, 7}, // 'S' 1101110 + { 84, 0xde000000ul, 7}, // 'T' 1101111 + { 85, 0xe0000000ul, 7}, // 'U' 1110000 + { 86, 0xe2000000ul, 7}, // 'V' 1110001 + { 87, 0xe4000000ul, 7}, // 'W' 1110010 + { 88, 0xfc000000ul, 8}, // 'X' 11111100 + { 89, 0xe6000000ul, 7}, // 'Y' 1110011 + { 90, 0xfd000000ul, 8}, // 'Z' 11111101 + { 91, 0xffd80000ul, 13}, // '[' 11111111|11011 + { 92, 0xfffe0000ul, 19}, // '\' 11111111|11111110|000 + { 93, 0xffe00000ul, 13}, // ']' 11111111|11100 + { 94, 0xfff00000ul, 14}, // '^' 11111111|111100 + { 95, 0x88000000ul, 6}, // '_' 100010 + { 96, 0xfffa0000ul, 15}, // '`' 11111111|1111101 + { 97, 0x18000000ul, 5}, // 'a' 00011 + { 98, 0x8c000000ul, 6}, // 'b' 100011 + { 99, 0x20000000ul, 5}, // 'c' 00100 + {100, 0x90000000ul, 6}, // 'd' 100100 + {101, 0x28000000ul, 5}, // 'e' 00101 + {102, 0x94000000ul, 6}, // 'f' 100101 + {103, 0x98000000ul, 6}, // 'g' 100110 + {104, 0x9c000000ul, 6}, // 'h' 100111 + {105, 0x30000000ul, 5}, // 'i' 00110 + {106, 0xe8000000ul, 7}, // 'j' 1110100 + {107, 0xea000000ul, 7}, // 'k' 1110101 + {108, 0xa0000000ul, 6}, // 'l' 101000 + {109, 0xa4000000ul, 6}, // 'm' 101001 + {110, 0xa8000000ul, 6}, // 'n' 101010 + {111, 0x38000000ul, 5}, // 'o' 00111 + {112, 0xac000000ul, 6}, // 'p' 101011 + {113, 0xec000000ul, 7}, // 'q' 1110110 + {114, 0xb0000000ul, 6}, // 'r' 101100 + {115, 0x40000000ul, 5}, // 's' 01000 + {116, 0x48000000ul, 5}, // 't' 01001 + {117, 0xb4000000ul, 6}, // 'u' 101101 + {118, 0xee000000ul, 7}, // 'v' 1110111 + {119, 0xf0000000ul, 7}, // 'w' 1111000 + {120, 0xf2000000ul, 7}, // 'x' 1111001 + {121, 0xf4000000ul, 7}, // 'y' 1111010 + {122, 0xf6000000ul, 7}, // 'z' 1111011 + {123, 0xfffc0000ul, 15}, // '{' 11111111|1111110 + {124, 0xff800000ul, 11}, // '|' 11111111|100 + {125, 0xfff40000ul, 14}, // '}' 11111111|111101 + {126, 0xffe80000ul, 13}, // '~' 11111111|11101 + {127, 0xffffffc0ul, 28}, // 11111111|11111111|11111111|1100 + {128, 0xfffe6000ul, 20}, // 11111111|11111110|0110 + {129, 0xffff4800ul, 22}, // 11111111|11111111|010010 + {130, 0xfffe7000ul, 20}, // 11111111|11111110|0111 + {131, 0xfffe8000ul, 20}, // 11111111|11111110|1000 + {132, 0xffff4c00ul, 22}, // 11111111|11111111|010011 + {133, 0xffff5000ul, 22}, // 11111111|11111111|010100 + {134, 0xffff5400ul, 22}, // 11111111|11111111|010101 + {135, 0xffffb200ul, 23}, // 11111111|11111111|1011001 + {136, 0xffff5800ul, 22}, // 11111111|11111111|010110 + {137, 0xffffb400ul, 23}, // 11111111|11111111|1011010 + {138, 0xffffb600ul, 23}, // 11111111|11111111|1011011 + {139, 0xffffb800ul, 23}, // 11111111|11111111|1011100 + {140, 0xffffba00ul, 23}, // 11111111|11111111|1011101 + {141, 0xffffbc00ul, 23}, // 11111111|11111111|1011110 + {142, 0xffffeb00ul, 24}, // 11111111|11111111|11101011 + {143, 0xffffbe00ul, 23}, // 11111111|11111111|1011111 + {144, 0xffffec00ul, 24}, // 11111111|11111111|11101100 + {145, 0xffffed00ul, 24}, // 11111111|11111111|11101101 + {146, 0xffff5c00ul, 22}, // 11111111|11111111|010111 + {147, 0xffffc000ul, 23}, // 11111111|11111111|1100000 + {148, 0xffffee00ul, 24}, // 11111111|11111111|11101110 + {149, 0xffffc200ul, 23}, // 11111111|11111111|1100001 + {150, 0xffffc400ul, 23}, // 11111111|11111111|1100010 + {151, 0xffffc600ul, 23}, // 11111111|11111111|1100011 + {152, 0xffffc800ul, 23}, // 11111111|11111111|1100100 + {153, 0xfffee000ul, 21}, // 11111111|11111110|11100 + {154, 0xffff6000ul, 22}, // 11111111|11111111|011000 + {155, 0xffffca00ul, 23}, // 11111111|11111111|1100101 + {156, 0xffff6400ul, 22}, // 11111111|11111111|011001 + {157, 0xffffcc00ul, 23}, // 11111111|11111111|1100110 + {158, 0xffffce00ul, 23}, // 11111111|11111111|1100111 + {159, 0xffffef00ul, 24}, // 11111111|11111111|11101111 + {160, 0xffff6800ul, 22}, // 11111111|11111111|011010 + {161, 0xfffee800ul, 21}, // 11111111|11111110|11101 + {162, 0xfffe9000ul, 20}, // 11111111|11111110|1001 + {163, 0xffff6c00ul, 22}, // 11111111|11111111|011011 + {164, 0xffff7000ul, 22}, // 11111111|11111111|011100 + {165, 0xffffd000ul, 23}, // 11111111|11111111|1101000 + {166, 0xffffd200ul, 23}, // 11111111|11111111|1101001 + {167, 0xfffef000ul, 21}, // 11111111|11111110|11110 + {168, 0xffffd400ul, 23}, // 11111111|11111111|1101010 + {169, 0xffff7400ul, 22}, // 11111111|11111111|011101 + {170, 0xffff7800ul, 22}, // 11111111|11111111|011110 + {171, 0xfffff000ul, 24}, // 11111111|11111111|11110000 + {172, 0xfffef800ul, 21}, // 11111111|11111110|11111 + {173, 0xffff7c00ul, 22}, // 11111111|11111111|011111 + {174, 0xffffd600ul, 23}, // 11111111|11111111|1101011 + {175, 0xffffd800ul, 23}, // 11111111|11111111|1101100 + {176, 0xffff0000ul, 21}, // 11111111|11111111|00000 + {177, 0xffff0800ul, 21}, // 11111111|11111111|00001 + {178, 0xffff8000ul, 22}, // 11111111|11111111|100000 + {179, 0xffff1000ul, 21}, // 11111111|11111111|00010 + {180, 0xffffda00ul, 23}, // 11111111|11111111|1101101 + {181, 0xffff8400ul, 22}, // 11111111|11111111|100001 + {182, 0xffffdc00ul, 23}, // 11111111|11111111|1101110 + {183, 0xffffde00ul, 23}, // 11111111|11111111|1101111 + {184, 0xfffea000ul, 20}, // 11111111|11111110|1010 + {185, 0xffff8800ul, 22}, // 11111111|11111111|100010 + {186, 0xffff8c00ul, 22}, // 11111111|11111111|100011 + {187, 0xffff9000ul, 22}, // 11111111|11111111|100100 + {188, 0xffffe000ul, 23}, // 11111111|11111111|1110000 + {189, 0xffff9400ul, 22}, // 11111111|11111111|100101 + {190, 0xffff9800ul, 22}, // 11111111|11111111|100110 + {191, 0xffffe200ul, 23}, // 11111111|11111111|1110001 + {192, 0xfffff800ul, 26}, // 11111111|11111111|11111000|00 + {193, 0xfffff840ul, 26}, // 11111111|11111111|11111000|01 + {194, 0xfffeb000ul, 20}, // 11111111|11111110|1011 + {195, 0xfffe2000ul, 19}, // 11111111|11111110|001 + {196, 0xffff9c00ul, 22}, // 11111111|11111111|100111 + {197, 0xffffe400ul, 23}, // 11111111|11111111|1110010 + {198, 0xffffa000ul, 22}, // 11111111|11111111|101000 + {199, 0xfffff600ul, 25}, // 11111111|11111111|11110110|0 + {200, 0xfffff880ul, 26}, // 11111111|11111111|11111000|10 + {201, 0xfffff8c0ul, 26}, // 11111111|11111111|11111000|11 + {202, 0xfffff900ul, 26}, // 11111111|11111111|11111001|00 + {203, 0xfffffbc0ul, 27}, // 11111111|11111111|11111011|110 + {204, 0xfffffbe0ul, 27}, // 11111111|11111111|11111011|111 + {205, 0xfffff940ul, 26}, // 11111111|11111111|11111001|01 + {206, 0xfffff100ul, 24}, // 11111111|11111111|11110001 + {207, 0xfffff680ul, 25}, // 11111111|11111111|11110110|1 + {208, 0xfffe4000ul, 19}, // 11111111|11111110|010 + {209, 0xffff1800ul, 21}, // 11111111|11111111|00011 + {210, 0xfffff980ul, 26}, // 11111111|11111111|11111001|10 + {211, 0xfffffc00ul, 27}, // 11111111|11111111|11111100|000 + {212, 0xfffffc20ul, 27}, // 11111111|11111111|11111100|001 + {213, 0xfffff9c0ul, 26}, // 11111111|11111111|11111001|11 + {214, 0xfffffc40ul, 27}, // 11111111|11111111|11111100|010 + {215, 0xfffff200ul, 24}, // 11111111|11111111|11110010 + {216, 0xffff2000ul, 21}, // 11111111|11111111|00100 + {217, 0xffff2800ul, 21}, // 11111111|11111111|00101 + {218, 0xfffffa00ul, 26}, // 11111111|11111111|11111010|00 + {219, 0xfffffa40ul, 26}, // 11111111|11111111|11111010|01 + {220, 0xffffffd0ul, 28}, // 11111111|11111111|11111111|1101 + {221, 0xfffffc60ul, 27}, // 11111111|11111111|11111100|011 + {222, 0xfffffc80ul, 27}, // 11111111|11111111|11111100|100 + {223, 0xfffffca0ul, 27}, // 11111111|11111111|11111100|101 + {224, 0xfffec000ul, 20}, // 11111111|11111110|1100 + {225, 0xfffff300ul, 24}, // 11111111|11111111|11110011 + {226, 0xfffed000ul, 20}, // 11111111|11111110|1101 + {227, 0xffff3000ul, 21}, // 11111111|11111111|00110 + {228, 0xffffa400ul, 22}, // 11111111|11111111|101001 + {229, 0xffff3800ul, 21}, // 11111111|11111111|00111 + {230, 0xffff4000ul, 21}, // 11111111|11111111|01000 + {231, 0xffffe600ul, 23}, // 11111111|11111111|1110011 + {232, 0xffffa800ul, 22}, // 11111111|11111111|101010 + {233, 0xffffac00ul, 22}, // 11111111|11111111|101011 + {234, 0xfffff700ul, 25}, // 11111111|11111111|11110111|0 + {235, 0xfffff780ul, 25}, // 11111111|11111111|11110111|1 + {236, 0xfffff400ul, 24}, // 11111111|11111111|11110100 + {237, 0xfffff500ul, 24}, // 11111111|11111111|11110101 + {238, 0xfffffa80ul, 26}, // 11111111|11111111|11111010|10 + {239, 0xffffe800ul, 23}, // 11111111|11111111|1110100 + {240, 0xfffffac0ul, 26}, // 11111111|11111111|11111010|11 + {241, 0xfffffcc0ul, 27}, // 11111111|11111111|11111100|110 + {242, 0xfffffb00ul, 26}, // 11111111|11111111|11111011|00 + {243, 0xfffffb40ul, 26}, // 11111111|11111111|11111011|01 + {244, 0xfffffce0ul, 27}, // 11111111|11111111|11111100|111 + {245, 0xfffffd00ul, 27}, // 11111111|11111111|11111101|000 + {246, 0xfffffd20ul, 27}, // 11111111|11111111|11111101|001 + {247, 0xfffffd40ul, 27}, // 11111111|11111111|11111101|010 + {248, 0xfffffd60ul, 27}, // 11111111|11111111|11111101|011 + {249, 0xffffffe0ul, 28}, // 11111111|11111111|11111111|1110 + {250, 0xfffffd80ul, 27}, // 11111111|11111111|11111101|100 + {251, 0xfffffda0ul, 27}, // 11111111|11111111|11111101|101 + {252, 0xfffffdc0ul, 27}, // 11111111|11111111|11111101|110 + {253, 0xfffffde0ul, 27}, // 11111111|11111111|11111101|111 + {254, 0xfffffe00ul, 27}, // 11111111|11111111|11111110|000 + {255, 0xfffffb80ul, 26}, // 11111111|11111111|11111011|10 + {256, 0xfffffffcul, 30} // EOS 11111111|11111111|11111111|111111 +}; + +void write_huffman_code(BitOStream &outputStream, const CodeEntry &code) +{ + // Append octet by octet. + auto bitLength = code.bitLength; + const auto hc = code.huffmanCode >> (32 - bitLength); + + if (bitLength > 24) { + outputStream.writeBits(uchar(hc >> 24), bitLength - 24); + bitLength = 24; + } + + if (bitLength > 16) { + outputStream.writeBits(uchar(hc >> 16), bitLength - 16); + bitLength = 16; + } + + if (bitLength > 8) { + outputStream.writeBits(uchar(hc >> 8), bitLength - 8); + bitLength = 8; + } + + outputStream.writeBits(uchar(hc), bitLength); +} + +} + +// That's from HPACK's specs - we deal with octets. +static_assert(std::numeric_limits<uchar>::digits == 8, "octets expected"); + +quint64 huffman_encoded_bit_length(const QByteArray &inputData) +{ + quint64 bitLength = 0; + for (int i = 0, e = inputData.size(); i < e; ++i) + bitLength += staticHuffmanCodeTable[int(inputData[i])].bitLength; + + return bitLength; +} + +void huffman_encode_string(const QByteArray &inputData, BitOStream &outputStream) +{ + for (int i = 0, e = inputData.size(); i < e; ++i) + write_huffman_code(outputStream, staticHuffmanCodeTable[int(inputData[i])]); + + // Pad bits ... + if (outputStream.bitLength() % 8) + outputStream.writeBits(0xff, 8 - outputStream.bitLength() % 8); +} + +bool padding_is_valid(quint32 chunk, quint32 nBits) +{ + Q_ASSERT(nBits); + + // HPACK, 5.2: "A padding strictly longer than 7 bits MUST be + // treated as a decoding error." + if (nBits > 7) + return false; + // HPACK, 5.2: + // "A padding not corresponding to the most significant bits + // of the code for the EOS symbol MUST be treated as a decoding error." + return (chunk >> (32 - nBits)) == quint32((1 << nBits) - 1); +} + +HuffmanDecoder::HuffmanDecoder() + : minCodeLength() +{ + const auto nCodes = sizeof staticHuffmanCodeTable / sizeof staticHuffmanCodeTable[0]; + + std::vector<CodeEntry> symbols(staticHuffmanCodeTable, staticHuffmanCodeTable + nCodes); + // Now we sort: by bit length first (in the descending order) and by the symbol + // value (descending). Descending order: to make sure we do not create prefix tables with + // short 'indexLength' first and having longer codes that do not fit into such tables later. + std::sort(symbols.begin(), symbols.end(), [](const CodeEntry &code1, const CodeEntry &code2) { + if (code1.bitLength == code2.bitLength) + return code1.byteValue > code2.byteValue; + return code1.bitLength > code2.bitLength; + }); + + minCodeLength = symbols.back().bitLength; // The shortest one, currently it's 5. + + // TODO: add a verification - Huffman codes + // within a given bit length range also + // should be in descending order. + + // Initialize 'prefixTables' and 'tableData'. + addTable(0, quint32(BitConstants::rootPrefix)); // 'root' table. + + for (const auto &s : symbols) { + quint32 tableIndex = 0; + while (true) { + Q_ASSERT(tableIndex < prefixTables.size()); + // Note, by value - since prefixTables will be updated in between. + const auto table = prefixTables[tableIndex]; + // We skip prefixed bits (if any) and use indexed bits only: + const auto entryIndex = s.huffmanCode << table.prefixLength >> (32 - table.indexLength); + // Again, by value. + PrefixTableEntry entry = tableEntry(table, entryIndex); + // How many bits were coded by previous tables and this table: + const auto codedLength = table.prefixLength + table.indexLength; + if (codedLength < s.bitLength) { + // We have to add a new prefix table ... (if it's not done yet). + if (!entry.bitLength) { + entry.nextTable = addTable(codedLength, std::min<quint32>(quint32(BitConstants::childPrefix), + s.bitLength - codedLength)); + entry.bitLength = s.bitLength; + entry.byteValue = s.byteValue; + setTableEntry(table, entryIndex, entry); + } + tableIndex = entry.nextTable; + } else { + // We found the slot for our code (terminal entry): + entry.byteValue = s.byteValue; + entry.bitLength = s.bitLength; + // Refer to our own table as 'nextTable': + entry.nextTable = tableIndex; + setTableEntry(table, entryIndex, entry); + break; + } + } + } + + // Now, we have a table(s) and have to fill 'holes' to + // 'fix' terminal entries. + for (const auto &table : prefixTables) { + const quint32 codedLength = table.prefixLength + table.indexLength; + for (quint32 j = 0; j < table.size();) { + const PrefixTableEntry &entry = tableEntry(table, j); + if (entry.bitLength && entry.bitLength < codedLength) { + const quint32 range = 1 << (codedLength - entry.bitLength); + for (quint32 k = 1; k < range; ++k) + setTableEntry(table, j + k, entry); + j += range; + } else { + ++j; + } + } + } +} + +bool HuffmanDecoder::decodeStream(BitIStream &inputStream, QByteArray &outputBuffer) +{ + while (true) { + quint32 chunk = 0; + const quint32 readBits = inputStream.peekBits(inputStream.streamOffset(), 32, &chunk); + if (!readBits) + return !inputStream.hasMoreBits(); + + if (readBits < minCodeLength) { + inputStream.skipBits(readBits); + return padding_is_valid(chunk, readBits); + } + + quint32 tableIndex = 0; + const PrefixTable *table = &prefixTables[tableIndex]; + quint32 entryIndex = chunk >> (32 - table->indexLength); + PrefixTableEntry entry = tableEntry(*table, entryIndex); + + while (true) { + if (entry.nextTable == tableIndex) + break; + + tableIndex = entry.nextTable; + table = &prefixTables[tableIndex]; + entryIndex = chunk << table->prefixLength >> (32 - table->indexLength); + entry = tableEntry(*table, entryIndex); + } + + if (entry.bitLength > readBits) { + inputStream.skipBits(readBits); + return padding_is_valid(chunk, readBits); + } + + if (!entry.bitLength || entry.byteValue == 256) { + //EOS (256) == compression error (HPACK). + inputStream.skipBits(readBits); + return false; + } + + outputBuffer.append(entry.byteValue); + inputStream.skipBits(entry.bitLength); + } + + return false; +} + +quint32 HuffmanDecoder::addTable(quint32 prefix, quint32 index) +{ + PrefixTable newTable{prefix, index}; + newTable.offset = quint32(tableData.size()); + prefixTables.push_back(newTable); + // Add entries for this table: + tableData.resize(tableData.size() + newTable.size()); + + return quint32(prefixTables.size() - 1); +} + +PrefixTableEntry HuffmanDecoder::tableEntry(const PrefixTable &table, quint32 index) +{ + Q_ASSERT(index < table.size()); + return tableData[table.offset + index]; +} + +void HuffmanDecoder::setTableEntry(const PrefixTable &table, quint32 index, + const PrefixTableEntry &entry) +{ + Q_ASSERT(index < table.size()); + tableData[table.offset + index] = entry; +} + +bool huffman_decode_string(BitIStream &inputStream, QByteArray *outputBuffer) +{ + Q_ASSERT(outputBuffer); + + static HuffmanDecoder decoder; + return decoder.decodeStream(inputStream, *outputBuffer); +} + +} + +QT_END_NAMESPACE diff --git a/src/network/access/http2/huffman_p.h b/src/network/access/http2/huffman_p.h new file mode 100644 index 0000000000..7195661664 --- /dev/null +++ b/src/network/access/http2/huffman_p.h @@ -0,0 +1,182 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef HUFFMAN_P_H +#define HUFFMAN_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +class QByteArray; + +namespace HPack +{ + +struct CodeEntry +{ + CodeEntry() : byteValue(), + huffmanCode(), + bitLength() + { + } + + CodeEntry(quint32 val, quint32 code, quint32 len) + : byteValue(val), + huffmanCode(code), + bitLength(len) + { + } + + quint32 byteValue; + quint32 huffmanCode; + quint32 bitLength; +}; + +class BitOStream; + +quint64 huffman_encoded_bit_length(const QByteArray &inputData); +void huffman_encode_string(const QByteArray &inputData, BitOStream &outputStream); + +// PrefixTable: +// Huffman codes with a small bit length +// fit into a table (these are 'terminal' symbols), +// codes with longer codes require additional +// tables, so several symbols will have the same index +// in a table - pointing into the next table. +// Every table has an 'indexLength' - that's +// how many bits can fit in table's indices + +// 'prefixLength' - how many bits were addressed +// by its 'parent' table(s). +// All PrefixTables are kept in 'prefixTables' array. +// PrefixTable itself does not have any entries, +// it just holds table's prefix/index + 'offset' - +// there table's data starts in an array of all +// possible entries ('tableData'). + +struct PrefixTable +{ + PrefixTable() + : prefixLength(), + indexLength(), + offset() + { + } + + PrefixTable(quint32 prefix, quint32 index) + : prefixLength(prefix), + indexLength(index), + offset() + { + } + + quint32 size()const + { + // Number of entries table contains: + return 1 << indexLength; + } + + quint32 prefixLength; + quint32 indexLength; + quint32 offset; +}; + +// Table entry is either a terminal entry (thus probably the code found) +// or points into another table ('nextTable' - index into +// 'prefixTables' array). If it's a terminal, 'nextTable' index +// refers to the same table. + +struct PrefixTableEntry +{ + PrefixTableEntry() + : bitLength(), + nextTable(), + byteValue() + { + } + + quint32 bitLength; + quint32 nextTable; + quint32 byteValue; +}; + +class BitIStream; + +class HuffmanDecoder +{ +public: + enum class BitConstants + { + rootPrefix = 9, + childPrefix = 6 + }; + + HuffmanDecoder(); + + bool decodeStream(BitIStream &inputStream, QByteArray &outputBuffer); + +private: + quint32 addTable(quint32 prefixLength, quint32 indexLength); + PrefixTableEntry tableEntry(const PrefixTable &table, quint32 index); + void setTableEntry(const PrefixTable &table, quint32 index, const PrefixTableEntry &entry); + + std::vector<PrefixTable> prefixTables; + std::vector<PrefixTableEntry> tableData; + quint32 minCodeLength; +}; + +bool huffman_decode_string(BitIStream &inputStream, QByteArray *outputBuffer); + +} // namespace HPack + +QT_END_NAMESPACE + +#endif + diff --git a/src/network/access/qhttpnetworkreply.cpp b/src/network/access/qhttpnetworkreply.cpp index 3601c36bc2..24ada3a81f 100644 --- a/src/network/access/qhttpnetworkreply.cpp +++ b/src/network/access/qhttpnetworkreply.cpp @@ -484,8 +484,7 @@ qint64 QHttpNetworkReplyPrivate::readStatus(QAbstractSocket *socket) } // is this a valid reply? - if (fragment.length() >= 5 && !fragment.startsWith("HTTP/")) - { + if (fragment.length() == 5 && !fragment.startsWith("HTTP/")) { fragment.clear(); return -1; } @@ -739,6 +738,8 @@ qint64 QHttpNetworkReplyPrivate::readBody(QAbstractSocket *socket, QByteDataBuff #ifndef QT_NO_COMPRESS int QHttpNetworkReplyPrivate::initializeInflateStream() { + Q_ASSERT(inflateStrm); + inflateStrm->zalloc = Z_NULL; inflateStrm->zfree = Z_NULL; inflateStrm->opaque = Z_NULL; diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp index 587ab27a0f..9fee172283 100644 --- a/src/network/access/qnetworkaccessmanager.cpp +++ b/src/network/access/qnetworkaccessmanager.cpp @@ -1516,27 +1516,35 @@ void QNetworkAccessManagerPrivate::clearCache(QNetworkAccessManager *manager) manager->d_func()->objectCache.clear(); manager->d_func()->authenticationManager->clearCache(); - if (manager->d_func()->httpThread) { - manager->d_func()->httpThread->quit(); - manager->d_func()->httpThread->wait(5000); - if (manager->d_func()->httpThread->isFinished()) - delete manager->d_func()->httpThread; - else - QObject::connect(manager->d_func()->httpThread, SIGNAL(finished()), manager->d_func()->httpThread, SLOT(deleteLater())); - manager->d_func()->httpThread = 0; - } + manager->d_func()->destroyThread(); } QNetworkAccessManagerPrivate::~QNetworkAccessManagerPrivate() { - if (httpThread) { - httpThread->quit(); - httpThread->wait(5000); - if (httpThread->isFinished()) - delete httpThread; + destroyThread(); +} + +QThread * QNetworkAccessManagerPrivate::createThread() +{ + if (!thread) { + thread = new QThread; + thread->setObjectName(QStringLiteral("QNetworkAccessManager thread")); + thread->start(); + } + Q_ASSERT(thread); + return thread; +} + +void QNetworkAccessManagerPrivate::destroyThread() +{ + if (thread) { + thread->quit(); + thread->wait(5000); + if (thread->isFinished()) + delete thread; else - QObject::connect(httpThread, SIGNAL(finished()), httpThread, SLOT(deleteLater())); - httpThread = 0; + QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + thread = 0; } } diff --git a/src/network/access/qnetworkaccessmanager.h b/src/network/access/qnetworkaccessmanager.h index cba5cc09d6..6df0b63ba7 100644 --- a/src/network/access/qnetworkaccessmanager.h +++ b/src/network/access/qnetworkaccessmanager.h @@ -173,6 +173,7 @@ private: friend class QNetworkReplyImplPrivate; friend class QNetworkReplyHttpImpl; friend class QNetworkReplyHttpImplPrivate; + friend class QNetworkReplyFileImpl; Q_DECLARE_PRIVATE(QNetworkAccessManager) Q_PRIVATE_SLOT(d_func(), void _q_replyFinished()) diff --git a/src/network/access/qnetworkaccessmanager_p.h b/src/network/access/qnetworkaccessmanager_p.h index 44cf253368..5f8148d6e4 100644 --- a/src/network/access/qnetworkaccessmanager_p.h +++ b/src/network/access/qnetworkaccessmanager_p.h @@ -74,7 +74,7 @@ class QNetworkAccessManagerPrivate: public QObjectPrivate public: QNetworkAccessManagerPrivate() : networkCache(0), cookieJar(0), - httpThread(0), + thread(0), #ifndef QT_NO_NETWORKPROXY proxyFactory(0), #endif @@ -107,6 +107,9 @@ public: } ~QNetworkAccessManagerPrivate(); + QThread * createThread(); + void destroyThread(); + void _q_replyFinished(); void _q_replyEncrypted(); void _q_replySslErrors(const QList<QSslError> &errors); @@ -163,7 +166,7 @@ public: QNetworkCookieJar *cookieJar; - QThread *httpThread; + QThread *thread; #ifndef QT_NO_NETWORKPROXY diff --git a/src/network/access/qnetworkdiskcache.cpp b/src/network/access/qnetworkdiskcache.cpp index 077826ccf6..ce3b773c64 100644 --- a/src/network/access/qnetworkdiskcache.cpp +++ b/src/network/access/qnetworkdiskcache.cpp @@ -422,7 +422,7 @@ QIODevice *QNetworkDiskCache::data(const QUrl &url) // ### verify that QFile uses the fd size and not the file name qint64 size = file->size() - file->pos(); const uchar *p = 0; -#if !defined(Q_OS_WINCE) && !defined(Q_OS_INTEGRITY) +#if !defined(Q_OS_INTEGRITY) p = file->map(file->pos(), size); #endif if (p) { diff --git a/src/network/kernel/qnetworkfunctions_wince.h b/src/network/access/qnetworkfile.cpp index 0e464a47f3..374dd26e2e 100644 --- a/src/network/kernel/qnetworkfunctions_wince.h +++ b/src/network/access/qnetworkfile.cpp @@ -37,60 +37,56 @@ ** ****************************************************************************/ -#ifndef QNETWORKFUNCTIONS_WINCE_H -#define QNETWORKFUNCTIONS_WINCE_H +#include "qnetworkfile_p.h" -#if 0 -#pragma qt_sync_stop_processing -#endif +#include <QtCore/QDebug> +#include <QNetworkReply> +#include <QtCore/QDateTime> +#include <QtCore/QFileInfo> +#include <QtCore/QMetaObject> +#include <QtCore/QCoreApplication> -#ifdef Q_OS_WINCE +QT_BEGIN_NAMESPACE -#include <qt_windows.h> +QNetworkFile::QNetworkFile() + : QFile() +{ +} -QT_BEGIN_NAMESPACE +QNetworkFile::QNetworkFile(const QString &name) + : QFile(name) +{ +} -DECLARE_HANDLE(SC_HANDLE); -typedef enum _SC_ENUM_TYPE { - SC_ENUM_PROCESS_INFO = 0 -} SC_ENUM_TYPE; -#define SC_MANAGER_CONNECT 0x0001 -#define SC_MANAGER_CREATE_SERVICE 0x0002 -#define SC_MANAGER_ENUMERATE_SERVICE 0x0004 -#define SERVICE_ACTIVE 0x00000001 -#define SERVICE_INACTIVE 0x00000002 -#define SERVICE_STATE_ALL (SERVICE_ACTIVE | \ - SERVICE_INACTIVE) -typedef struct _SERVICE_STATUS_PROCESS { - DWORD dwServiceType; - DWORD dwCurrentState; - DWORD dwControlsAccepted; - DWORD dwWin32ExitCode; - DWORD dwServiceSpecificExitCode; - DWORD dwCheckPoint; - DWORD dwWaitHint; - DWORD dwProcessId; - DWORD dwServiceFlags; -} SERVICE_STATUS_PROCESS, *LPSERVICE_STATUS_PROCESS; -typedef struct _ENUM_SERVICE_STATUS_PROCESSA { - LPSTR lpServiceName; - LPSTR lpDisplayName; - SERVICE_STATUS_PROCESS ServiceStatusProcess; -} ENUM_SERVICE_STATUS_PROCESSA, *LPENUM_SERVICE_STATUS_PROCESSA; -typedef struct _ENUM_SERVICE_STATUS_PROCESSW { - LPWSTR lpServiceName; - LPWSTR lpDisplayName; - SERVICE_STATUS_PROCESS ServiceStatusProcess; -} ENUM_SERVICE_STATUS_PROCESSW, *LPENUM_SERVICE_STATUS_PROCESSW; -#ifdef UNICODE -typedef ENUM_SERVICE_STATUS_PROCESSW ENUM_SERVICE_STATUS_PROCESS; -typedef LPENUM_SERVICE_STATUS_PROCESSW LPENUM_SERVICE_STATUS_PROCESS; -#else -typedef ENUM_SERVICE_STATUS_PROCESSA ENUM_SERVICE_STATUS_PROCESS; -typedef LPENUM_SERVICE_STATUS_PROCESSA LPENUM_SERVICE_STATUS_PROCESS; -#endif // UNICODE +void QNetworkFile::open() +{ + bool opened = false; + QFileInfo fi(fileName()); + if (fi.isDir()) { + QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", + "Cannot open %1: Path is a directory").arg(fileName()); + error(QNetworkReply::ContentOperationNotPermittedError, msg); + } else { + headerRead(QNetworkRequest::LastModifiedHeader, QVariant::fromValue(fi.lastModified())); + headerRead(QNetworkRequest::ContentLengthHeader, QVariant::fromValue(fi.size())); + opened = QFile::open(QIODevice::ReadOnly | QIODevice::Unbuffered); + if (!opened) { + QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", + "Error opening %1: %2").arg(fileName(), errorString()); + if (exists()) + error(QNetworkReply::ContentAccessDenied, msg); + else + error(QNetworkReply::ContentNotFoundError, msg); + } + } + finished(opened); +} -QT_END_NAMESPACE +void QNetworkFile::close() +{ + // This override is needed because 'using' keyword cannot be used for slots. And the base + // function is not an invokable/slot function. + QFile::close(); +} -#endif // Q_OS_WINCE -#endif // QNETWORKFUNCTIONS_WINCE_H +QT_END_NAMESPACE diff --git a/src/network/access/qnetworkfile_p.h b/src/network/access/qnetworkfile_p.h new file mode 100644 index 0000000000..7794c0f18a --- /dev/null +++ b/src/network/access/qnetworkfile_p.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKFILE_H +#define QNETWORKFILE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the Network Access API. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QFile> +#include <qnetworkreply.h> + +QT_BEGIN_NAMESPACE + +class QNetworkFile : public QFile +{ + Q_OBJECT +public: + QNetworkFile(); + QNetworkFile(const QString &name); + using QFile::open; + +public Q_SLOTS: + void open(); + void close() Q_DECL_OVERRIDE; + +Q_SIGNALS: + void finished(bool ok); + void headerRead(QNetworkRequest::KnownHeaders header, const QVariant &value); + void error(QNetworkReply::NetworkError error, const QString &message); +}; + +QT_END_NAMESPACE + +#endif // QNETWORKFILE_H diff --git a/src/network/access/qnetworkreplyfileimpl.cpp b/src/network/access/qnetworkreplyfileimpl.cpp index 36bc4b41df..ef319ebf0d 100644 --- a/src/network/access/qnetworkreplyfileimpl.cpp +++ b/src/network/access/qnetworkreplyfileimpl.cpp @@ -40,31 +40,45 @@ #include "qnetworkreplyfileimpl_p.h" #include "QtCore/qdatetime.h" +#include "qnetworkaccessmanager_p.h" #include <QtCore/QCoreApplication> #include <QtCore/QFileInfo> +#include <QtCore/QThread> +#include "qnetworkfile_p.h" +#include "qnetworkrequest.h" QT_BEGIN_NAMESPACE QNetworkReplyFileImplPrivate::QNetworkReplyFileImplPrivate() - : QNetworkReplyPrivate(), realFileSize(0) + : QNetworkReplyPrivate(), managerPrivate(0), realFile(0) { + qRegisterMetaType<QNetworkRequest::KnownHeaders>(); + qRegisterMetaType<QNetworkReply::NetworkError>(); } QNetworkReplyFileImpl::~QNetworkReplyFileImpl() { + QNetworkReplyFileImplPrivate *d = (QNetworkReplyFileImplPrivate*) d_func(); + if (d->realFile) { + if (d->realFile->thread() == QThread::currentThread()) + delete d->realFile; + else + QMetaObject::invokeMethod(d->realFile, "deleteLater", Qt::QueuedConnection); + } } -QNetworkReplyFileImpl::QNetworkReplyFileImpl(QObject *parent, const QNetworkRequest &req, const QNetworkAccessManager::Operation op) - : QNetworkReply(*new QNetworkReplyFileImplPrivate(), parent) +QNetworkReplyFileImpl::QNetworkReplyFileImpl(QNetworkAccessManager *manager, const QNetworkRequest &req, const QNetworkAccessManager::Operation op) + : QNetworkReply(*new QNetworkReplyFileImplPrivate(), manager) { setRequest(req); setUrl(req.url()); setOperation(op); - setFinished(true); QNetworkReply::open(QIODevice::ReadOnly); QNetworkReplyFileImplPrivate *d = (QNetworkReplyFileImplPrivate*) d_func(); + d->managerPrivate = manager->d_func(); + QUrl url = req.url(); if (url.host() == QLatin1String("localhost")) url.setHost(QString()); @@ -77,7 +91,7 @@ QNetworkReplyFileImpl::QNetworkReplyFileImpl(QObject *parent, const QNetworkRequ setError(QNetworkReply::ProtocolInvalidOperationError, msg); QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ProtocolInvalidOperationError)); - QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); + fileOpenFinished(false); return; } #endif @@ -85,7 +99,6 @@ QNetworkReplyFileImpl::QNetworkReplyFileImpl(QObject *parent, const QNetworkRequ url.setPath(QLatin1String("/")); setUrl(url); - QString fileName = url.toLocalFile(); if (fileName.isEmpty()) { const QString scheme = url.scheme(); @@ -101,68 +114,85 @@ QNetworkReplyFileImpl::QNetworkReplyFileImpl(QObject *parent, const QNetworkRequ } } - QFileInfo fi(fileName); - if (fi.isDir()) { - QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Cannot open %1: Path is a directory").arg(url.toString()); - setError(QNetworkReply::ContentOperationNotPermittedError, msg); - QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, - Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentOperationNotPermittedError)); - QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); - return; - } - - d->realFile.setFileName(fileName); - bool opened = d->realFile.open(QIODevice::ReadOnly | QIODevice::Unbuffered); - - // could we open the file? - if (!opened) { - QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Error opening %1: %2") - .arg(d->realFile.fileName(), d->realFile.errorString()); - - if (d->realFile.exists()) { - setError(QNetworkReply::ContentAccessDenied, msg); - QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, - Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentAccessDenied)); - } else { - setError(QNetworkReply::ContentNotFoundError, msg); + if (req.attribute(QNetworkRequest::BackgroundRequestAttribute).toBool()) { // Asynchronous open + auto realFile = new QNetworkFile(fileName); + connect(realFile, &QNetworkFile::headerRead, this, &QNetworkReplyFileImpl::setHeader, + Qt::QueuedConnection); + connect(realFile, &QNetworkFile::error, this, &QNetworkReplyFileImpl::setError, + Qt::QueuedConnection); + connect(realFile, SIGNAL(finished(bool)), SLOT(fileOpenFinished(bool)), + Qt::QueuedConnection); + + realFile->moveToThread(d->managerPrivate->createThread()); + QMetaObject::invokeMethod(realFile, "open", Qt::QueuedConnection); + + d->realFile = realFile; + } else { // Synch open + setFinished(true); + + QFileInfo fi(fileName); + if (fi.isDir()) { + QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Cannot open %1: Path is a directory").arg(url.toString()); + setError(QNetworkReply::ContentOperationNotPermittedError, msg); QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, - Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentNotFoundError)); + Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentOperationNotPermittedError)); + QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); + return; + } + d->realFile = new QFile(fileName, this); + bool opened = d->realFile->open(QIODevice::ReadOnly | QIODevice::Unbuffered); + + // could we open the file? + if (!opened) { + QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Error opening %1: %2") + .arg(d->realFile->fileName(), d->realFile->errorString()); + + if (fi.exists()) { + setError(QNetworkReply::ContentAccessDenied, msg); + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentAccessDenied)); + } else { + setError(QNetworkReply::ContentNotFoundError, msg); + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentNotFoundError)); + } + QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); + return; } + setHeader(QNetworkRequest::LastModifiedHeader, fi.lastModified()); + setHeader(QNetworkRequest::ContentLengthHeader, fi.size()); + + QMetaObject::invokeMethod(this, "metaDataChanged", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, "downloadProgress", Qt::QueuedConnection, + Q_ARG(qint64, fi.size()), Q_ARG(qint64, fi.size())); + QMetaObject::invokeMethod(this, "readyRead", Qt::QueuedConnection); QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); - return; } - - setHeader(QNetworkRequest::LastModifiedHeader, fi.lastModified()); - d->realFileSize = fi.size(); - setHeader(QNetworkRequest::ContentLengthHeader, d->realFileSize); - - QMetaObject::invokeMethod(this, "metaDataChanged", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "downloadProgress", Qt::QueuedConnection, - Q_ARG(qint64, d->realFileSize), Q_ARG(qint64, d->realFileSize)); - QMetaObject::invokeMethod(this, "readyRead", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); } void QNetworkReplyFileImpl::close() { Q_D(QNetworkReplyFileImpl); QNetworkReply::close(); - d->realFile.close(); + if (d->realFile) { + if (d->realFile->thread() == thread()) + d->realFile->close(); + else + QMetaObject::invokeMethod(d->realFile, "close", Qt::QueuedConnection); + } } void QNetworkReplyFileImpl::abort() { - Q_D(QNetworkReplyFileImpl); - QNetworkReply::close(); - d->realFile.close(); + close(); } qint64 QNetworkReplyFileImpl::bytesAvailable() const { Q_D(const QNetworkReplyFileImpl); - if (!d->realFile.isOpen()) + if (!d->isFinished || !d->realFile || !d->realFile->isOpen()) return QNetworkReply::bytesAvailable(); - return QNetworkReply::bytesAvailable() + d->realFile.bytesAvailable(); + return QNetworkReply::bytesAvailable() + d->realFile->bytesAvailable(); } bool QNetworkReplyFileImpl::isSequential () const @@ -172,8 +202,9 @@ bool QNetworkReplyFileImpl::isSequential () const qint64 QNetworkReplyFileImpl::size() const { - Q_D(const QNetworkReplyFileImpl); - return d->realFileSize; + bool ok; + int size = header(QNetworkRequest::ContentLengthHeader).toInt(&ok); + return ok ? size : 0; } /*! @@ -182,11 +213,11 @@ qint64 QNetworkReplyFileImpl::size() const qint64 QNetworkReplyFileImpl::readData(char *data, qint64 maxlen) { Q_D(QNetworkReplyFileImpl); - if (!d->realFile.isOpen()) + if (!d->isFinished || !d->realFile || !d->realFile->isOpen()) return -1; - qint64 ret = d->realFile.read(data, maxlen); - if (bytesAvailable() == 0 && d->realFile.isOpen()) - d->realFile.close(); + qint64 ret = d->realFile->read(data, maxlen); + if (bytesAvailable() == 0) + d->realFile->close(); if (ret == 0 && bytesAvailable() == 0) return -1; else { @@ -196,6 +227,17 @@ qint64 QNetworkReplyFileImpl::readData(char *data, qint64 maxlen) } } +void QNetworkReplyFileImpl::fileOpenFinished(bool isOpen) +{ + setFinished(true); + if (isOpen) { + const auto fileSize = size(); + Q_EMIT metaDataChanged(); + Q_EMIT downloadProgress(fileSize, fileSize); + Q_EMIT readyRead(); + } + Q_EMIT finished(); +} QT_END_NAMESPACE diff --git a/src/network/access/qnetworkreplyfileimpl_p.h b/src/network/access/qnetworkreplyfileimpl_p.h index bac00881a8..1f1be40bc8 100644 --- a/src/network/access/qnetworkreplyfileimpl_p.h +++ b/src/network/access/qnetworkreplyfileimpl_p.h @@ -59,13 +59,12 @@ QT_BEGIN_NAMESPACE - class QNetworkReplyFileImplPrivate; class QNetworkReplyFileImpl: public QNetworkReply { Q_OBJECT public: - QNetworkReplyFileImpl(QObject *parent, const QNetworkRequest &req, const QNetworkAccessManager::Operation op); + QNetworkReplyFileImpl(QNetworkAccessManager *manager, const QNetworkRequest &req, const QNetworkAccessManager::Operation op); ~QNetworkReplyFileImpl(); virtual void abort() Q_DECL_OVERRIDE; @@ -77,6 +76,9 @@ public: virtual qint64 readData(char *data, qint64 maxlen) Q_DECL_OVERRIDE; +private Q_SLOTS: + void fileOpenFinished(bool isOpen); + Q_DECLARE_PRIVATE(QNetworkReplyFileImpl) }; @@ -85,12 +87,14 @@ class QNetworkReplyFileImplPrivate: public QNetworkReplyPrivate public: QNetworkReplyFileImplPrivate(); - QFile realFile; - qint64 realFileSize; + QNetworkAccessManagerPrivate *managerPrivate; + QPointer<QFile> realFile; Q_DECLARE_PUBLIC(QNetworkReplyFileImpl) }; QT_END_NAMESPACE +Q_DECLARE_METATYPE(QNetworkRequest::KnownHeaders) + #endif // QNETWORKREPLYFILEIMPL_H diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp index 6bf3c56db2..30f4bb26ce 100644 --- a/src/network/access/qnetworkreplyhttpimpl.cpp +++ b/src/network/access/qnetworkreplyhttpimpl.cpp @@ -601,17 +601,10 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq thread->setObjectName(QStringLiteral("Qt HTTP synchronous thread")); QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); thread->start(); - } else if (!managerPrivate->httpThread) { + } else { // We use the manager-global thread. // At some point we could switch to having multiple threads if it makes sense. - managerPrivate->httpThread = new QThread(); - managerPrivate->httpThread->setObjectName(QStringLiteral("Qt HTTP thread")); - managerPrivate->httpThread->start(); - - thread = managerPrivate->httpThread; - } else { - // Asynchronous request, thread already exists - thread = managerPrivate->httpThread; + thread = managerPrivate->createThread(); } QUrl url = newHttpRequest.url(); @@ -1123,8 +1116,8 @@ void QNetworkReplyHttpImplPrivate::onRedirected(const QUrl &redirectUrl, int htt cookedHeaders.clear(); - if (managerPrivate->httpThread) - managerPrivate->httpThread->disconnect(); + if (managerPrivate->thread) + managerPrivate->thread->disconnect(); // Recurse QMetaObject::invokeMethod(q, "start", Qt::QueuedConnection, diff --git a/src/network/access/qspdyprotocolhandler.cpp b/src/network/access/qspdyprotocolhandler.cpp index 413b03cc22..445a2a1c29 100644 --- a/src/network/access/qspdyprotocolhandler.cpp +++ b/src/network/access/qspdyprotocolhandler.cpp @@ -458,7 +458,7 @@ bool QSpdyProtocolHandler::uncompressHeader(const QByteArray &input, QByteArray break; } default: { - qWarning() << "got unexpected zlib return value:" << zlibRet; + qWarning("got unexpected zlib return value: %d", zlibRet); return false; } } @@ -849,7 +849,7 @@ void QSpdyProtocolHandler::handleControlFrame(const QByteArray &frameHeaders) // break; } default: - qWarning() << "cannot handle frame of type" << type; + qWarning("cannot handle frame of type %d", int(type)); } } @@ -1073,7 +1073,7 @@ void QSpdyProtocolHandler::handleSETTINGS(char flags, quint32 /*length*/, const break; } default: - qWarning() << "found unknown settings value" << value; + qWarning("found unknown settings value %u", uint(value)); } } } @@ -1112,7 +1112,7 @@ void QSpdyProtocolHandler::handleGOAWAY(char /*flags*/, quint32 /*length*/, break; } default: - qWarning() << "unexpected status code" << statusCode; + qWarning("unexpected status code %d", int(statusCode)); errorCode = QNetworkReply::ProtocolUnknownError; } diff --git a/src/network/kernel/kernel.pri b/src/network/kernel/kernel.pri index 811d8b6f0d..b50ca0d1c4 100644 --- a/src/network/kernel/kernel.pri +++ b/src/network/kernel/kernel.pri @@ -53,10 +53,10 @@ win32: { mac { LIBS_PRIVATE += -framework SystemConfiguration -framework CoreFoundation - !ios: LIBS_PRIVATE += -framework CoreServices + !uikit: LIBS_PRIVATE += -framework CoreServices } -mac:!ios:SOURCES += kernel/qnetworkproxy_mac.cpp +osx:SOURCES += kernel/qnetworkproxy_mac.cpp else:win32:SOURCES += kernel/qnetworkproxy_win.cpp else:contains(QT_CONFIG, libproxy) { SOURCES += kernel/qnetworkproxy_libproxy.cpp diff --git a/src/network/kernel/qauthenticator.cpp b/src/network/kernel/qauthenticator.cpp index a73f01a2fc..959db6e9d9 100644 --- a/src/network/kernel/qauthenticator.cpp +++ b/src/network/kernel/qauthenticator.cpp @@ -1455,15 +1455,9 @@ static bool q_NTLM_SSPI_library_load() if (pSecurityFunctionTable == NULL) { securityDLLHandle = LoadLibrary(L"secur32.dll"); if (securityDLLHandle != NULL) { -#if defined(Q_OS_WINCE) - INIT_SECURITY_INTERFACE pInitSecurityInterface = - (INIT_SECURITY_INTERFACE)GetProcAddress(securityDLLHandle, - L"InitSecurityInterfaceW"); -#else INIT_SECURITY_INTERFACE pInitSecurityInterface = (INIT_SECURITY_INTERFACE)GetProcAddress(securityDLLHandle, "InitSecurityInterfaceW"); -#endif if (pInitSecurityInterface != NULL) pSecurityFunctionTable = pInitSecurityInterface(); } diff --git a/src/network/kernel/qhostaddress.cpp b/src/network/kernel/qhostaddress.cpp index 13ecfac3f5..95f90af49e 100644 --- a/src/network/kernel/qhostaddress.cpp +++ b/src/network/kernel/qhostaddress.cpp @@ -73,7 +73,7 @@ QT_BEGIN_NAMESPACE // sockaddr_in6 size changed between old and new SDK // Only the new version is the correct one, so always // use this structure. -#if defined(Q_OS_WINCE) || defined(Q_OS_WINRT) +#if defined(Q_OS_WINRT) # if !defined(u_char) # define u_char unsigned char # endif @@ -151,20 +151,37 @@ void QHostAddressPrivate::setAddress(quint32 a_) /// parses v4-mapped addresses or the AnyIPv6 address and stores in \a a; /// returns true if the address was one of those -static bool convertToIpv4(quint32& a, const Q_IPV6ADDR &a6) +static bool convertToIpv4(quint32& a, const Q_IPV6ADDR &a6, const QHostAddress::ConversionMode mode) { + if (mode == QHostAddress::StrictConversion) + return false; + const uchar *ptr = a6.c; if (qFromUnaligned<quint64>(ptr) != 0) return false; - if (qFromBigEndian<quint32>(ptr + 8) == 0) { - // is it AnyIPv6? - a = 0; - return qFromBigEndian<quint32>(ptr + 12) == 0; + + const quint32 mid = qFromBigEndian<quint32>(ptr + 8); + if ((mid == 0xffff) && (mode & QHostAddress::ConvertV4MappedToIPv4)) { + a = qFromBigEndian<quint32>(ptr + 12); + return true; } - if (qFromBigEndian<quint32>(ptr + 8) != 0xFFFF) + if (mid != 0) return false; - a = qFromBigEndian<quint32>(ptr + 12); - return true; + + const quint32 low = qFromBigEndian<quint32>(ptr + 12); + if ((low == 0) && (mode & QHostAddress::ConvertUnspecifiedAddress)) { + a = 0; + return true; + } + if ((low == 1) && (mode & QHostAddress::ConvertLocalHost)) { + a = INADDR_LOOPBACK; + return true; + } + if ((low != 1) && (mode & QHostAddress::ConvertV4CompatToIPv4)) { + a = low; + return true; + } + return false; } void QHostAddressPrivate::setAddress(const quint8 *a_) @@ -173,7 +190,8 @@ void QHostAddressPrivate::setAddress(const quint8 *a_) isParsed = true; memcpy(a6.c, a_, sizeof(a6)); a = 0; - convertToIpv4(a, a6); + convertToIpv4(a, a6, (QHostAddress::ConvertV4MappedToIPv4 + | QHostAddress::ConvertUnspecifiedAddress)); } void QHostAddressPrivate::setAddress(const Q_IPV6ADDR &a_) @@ -385,6 +403,20 @@ void QNetmaskAddress::setPrefixLength(QAbstractSocket::NetworkLayerProtocol prot \value Any The dual stack any-address. A socket bound with this address will listen on both IPv4 and IPv6 interfaces. */ +/*! \enum QHostAddress::ConversionModeFlag + + \since 5.8 + + \value StrictConversion Don't convert IPv6 addresses to IPv4 when comparing two QHostAddress objects of different protocols, so they will always be considered different. + \value ConvertV4MappedToIPv4 Convert IPv4-mapped IPv6 addresses (RFC 4291 sect. 2.5.5.2) when comparing. Therefore QHostAddress("::ffff:192.168.1.1") will compare equal to QHostAddress("192.168.1.1"). + \value ConvertV4CompatToIPv4 Convert IPv4-compatible IPv6 addresses (RFC 4291 sect. 2.5.5.1) when comparing. Therefore QHostAddress("::192.168.1.1") will compare equal to QHostAddress("192.168.1.1"). + \value ConvertLocalHost Convert the IPv6 loopback addresses to its IPv4 equivalent when comparing. Therefore e.g. QHostAddress("::1") will compare equal to QHostAddress("127.0.0.1"). + \value ConvertUnspecifiedAddress All unspecified addresses will compare equal, namely AnyIPv4, AnyIPv6 and Any. + \value TolerantConversion Sets all three preceding flags. + + \sa isEqual() + */ + /*! Constructs a null host address object, i.e. an address which is not valid for any host or interface. \sa clear() @@ -690,7 +722,9 @@ quint32 QHostAddress::toIPv4Address(bool *ok) const quint32 dummy; if (ok) *ok = d->protocol == QAbstractSocket::IPv4Protocol || d->protocol == QAbstractSocket::AnyIPProtocol - || (d->protocol == QAbstractSocket::IPv6Protocol && convertToIpv4(dummy, d->a6)); + || (d->protocol == QAbstractSocket::IPv6Protocol + && convertToIpv4(dummy, d->a6, ConversionMode(QHostAddress::ConvertV4MappedToIPv4 + | QHostAddress::ConvertUnspecifiedAddress))); return d->a; } @@ -812,19 +846,73 @@ void QHostAddress::setScopeId(const QString &id) /*! Returns \c true if this host address is the same as the \a other address - given; otherwise returns \c false. + given; otherwise returns \c false. This operator just calls isEqual(other, StrictConversion). + + \sa isEqual() */ bool QHostAddress::operator==(const QHostAddress &other) const { + return isEqual(other, StrictConversion); +} + +/*! + \since 5.8 + + Returns \c true if this host address is the same as the \a other address + given; otherwise returns \c false. + + The parameter \a mode controls which conversions are preformed between addresses + of differing protocols. If no \a mode is given, \c TolerantConversion is performed + by default. + + \sa ConversionMode, operator==() + */ +bool QHostAddress::isEqual(const QHostAddress &other, ConversionMode mode) const +{ QT_ENSURE_PARSED(this); QT_ENSURE_PARSED(&other); - if (d->protocol == QAbstractSocket::IPv4Protocol) - return other.d->protocol == QAbstractSocket::IPv4Protocol && d->a == other.d->a; + if (d->protocol == QAbstractSocket::IPv4Protocol) { + switch (other.d->protocol) { + case QAbstractSocket::IPv4Protocol: + return d->a == other.d->a; + case QAbstractSocket::IPv6Protocol: + quint32 a4; + return convertToIpv4(a4, other.d->a6, mode) && (a4 == d->a); + case QAbstractSocket::AnyIPProtocol: + return (mode & QHostAddress::ConvertUnspecifiedAddress) && d->a == 0; + case QAbstractSocket::UnknownNetworkLayerProtocol: + return false; + } + } + if (d->protocol == QAbstractSocket::IPv6Protocol) { - return other.d->protocol == QAbstractSocket::IPv6Protocol - && memcmp(&d->a6, &other.d->a6, sizeof(Q_IPV6ADDR)) == 0; + switch (other.d->protocol) { + case QAbstractSocket::IPv4Protocol: + quint32 a4; + return convertToIpv4(a4, d->a6, mode) && (a4 == other.d->a); + case QAbstractSocket::IPv6Protocol: + return memcmp(&d->a6, &other.d->a6, sizeof(Q_IPV6ADDR)) == 0; + case QAbstractSocket::AnyIPProtocol: + return (mode & QHostAddress::ConvertUnspecifiedAddress) + && (other.d->a6_64.c[0] == 0) && (other.d->a6_64.c[1] == 0); + case QAbstractSocket::UnknownNetworkLayerProtocol: + return false; + } + } + + if ((d->protocol == QAbstractSocket::AnyIPProtocol) + && (mode & QHostAddress::ConvertUnspecifiedAddress)) { + switch (other.d->protocol) { + case QAbstractSocket::IPv4Protocol: + return other.d->a == 0; + case QAbstractSocket::IPv6Protocol: + return (other.d->a6_64.c[0] == 0) && (other.d->a6_64.c[1] == 0); + default: + break; + } } + return d->protocol == other.d->protocol; } diff --git a/src/network/kernel/qhostaddress.h b/src/network/kernel/qhostaddress.h index 8236a71986..8cf6876511 100644 --- a/src/network/kernel/qhostaddress.h +++ b/src/network/kernel/qhostaddress.h @@ -79,6 +79,16 @@ public: AnyIPv6, AnyIPv4 }; + enum ConversionModeFlag { + ConvertV4MappedToIPv4 = 1, + ConvertV4CompatToIPv4 = 2, + ConvertUnspecifiedAddress = 4, + ConvertLocalHost = 8, + TolerantConversion = 0xff, + + StrictConversion = 0 + }; + Q_DECLARE_FLAGS(ConversionMode, ConversionModeFlag) QHostAddress(); explicit QHostAddress(quint32 ip4Addr); @@ -118,6 +128,7 @@ public: QString scopeId() const; void setScopeId(const QString &id); + bool isEqual(const QHostAddress &address, ConversionMode mode = TolerantConversion) const; bool operator ==(const QHostAddress &address) const; bool operator ==(SpecialAddress address) const; inline bool operator !=(const QHostAddress &address) const @@ -139,6 +150,7 @@ public: protected: QScopedPointer<QHostAddressPrivate> d; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(QHostAddress::ConversionMode) Q_DECLARE_SHARED_NOT_MOVABLE_UNTIL_QT6(QHostAddress) inline bool operator ==(QHostAddress::SpecialAddress address1, const QHostAddress &address2) diff --git a/src/network/kernel/qhostinfo_win.cpp b/src/network/kernel/qhostinfo_win.cpp index b98f36a612..9e5d556f2b 100644 --- a/src/network/kernel/qhostinfo_win.cpp +++ b/src/network/kernel/qhostinfo_win.cpp @@ -81,11 +81,7 @@ static bool resolveLibraryInternal() { // Attempt to resolve getaddrinfo(); without it we'll have to fall // back to gethostbyname(), which has no IPv6 support. -#if defined(Q_OS_WINCE) - local_getaddrinfo = (getaddrinfoProto) QSystemLibrary::resolve(QLatin1String("ws2"), "getaddrinfo"); - local_freeaddrinfo = (freeaddrinfoProto) QSystemLibrary::resolve(QLatin1String("ws2"), "freeaddrinfo"); - local_getnameinfo = (getnameinfoProto) QSystemLibrary::resolve(QLatin1String("ws2"), "getnameinfo"); -#elif defined (Q_OS_WINRT) +#if defined (Q_OS_WINRT) local_getaddrinfo = (getaddrinfoProto) &getaddrinfo; local_freeaddrinfo = (freeaddrinfoProto) &freeaddrinfo; local_getnameinfo = (getnameinfoProto) getnameinfo; @@ -116,11 +112,6 @@ static void translateWSAError(int error, QHostInfo *results) QHostInfo QHostInfoAgent::fromName(const QString &hostName) { -#if defined(Q_OS_WINCE) - static QBasicMutex qPrivCEMutex; - QMutexLocker locker(&qPrivCEMutex); -#endif - QSysInfo::machineHostName(); // this initializes ws2_32.dll // Load res_init on demand. diff --git a/src/network/kernel/qnetworkinterface_win.cpp b/src/network/kernel/qnetworkinterface_win.cpp index 0a82eac417..3002b2497b 100644 --- a/src/network/kernel/qnetworkinterface_win.cpp +++ b/src/network/kernel/qnetworkinterface_win.cpp @@ -81,19 +81,11 @@ static void resolveLibs() HINSTANCE iphlpapiHnd = GetModuleHandle(L"iphlpapi"); Q_ASSERT(iphlpapiHnd); -#if defined(Q_OS_WINCE) - // since Windows Embedded Compact 7 - ptrConvertInterfaceIndexToLuid = (PtrConvertInterfaceIndexToLuid)GetProcAddress(iphlpapiHnd, L"ConvertInterfaceIndexToLuid"); - ptrConvertInterfaceLuidToName = (PtrConvertInterfaceLuidToName)GetProcAddress(iphlpapiHnd, L"ConvertInterfaceLuidToNameW"); - ptrConvertInterfaceLuidToIndex = (PtrConvertInterfaceLuidToIndex)GetProcAddress(iphlpapiHnd, L"ConvertInterfaceLuidToIndex"); - ptrConvertInterfaceNameToLuid = (PtrConvertInterfaceNameToLuid)GetProcAddress(iphlpapiHnd, L"ConvertInterfaceNameToLuidW"); -#else // since Windows Vista ptrConvertInterfaceIndexToLuid = (PtrConvertInterfaceIndexToLuid)GetProcAddress(iphlpapiHnd, "ConvertInterfaceIndexToLuid"); ptrConvertInterfaceLuidToName = (PtrConvertInterfaceLuidToName)GetProcAddress(iphlpapiHnd, "ConvertInterfaceLuidToNameW"); ptrConvertInterfaceLuidToIndex = (PtrConvertInterfaceLuidToIndex)GetProcAddress(iphlpapiHnd, "ConvertInterfaceLuidToIndex"); ptrConvertInterfaceNameToLuid = (PtrConvertInterfaceNameToLuid)GetProcAddress(iphlpapiHnd, "ConvertInterfaceNameToLuidW"); -#endif done = true; } } diff --git a/src/network/kernel/qnetworkproxy_mac.cpp b/src/network/kernel/qnetworkproxy_mac.cpp index 06a6fbac45..37126298c6 100644 --- a/src/network/kernel/qnetworkproxy_mac.cpp +++ b/src/network/kernel/qnetworkproxy_mac.cpp @@ -234,60 +234,55 @@ QList<QNetworkProxy> macQueryInternal(const QNetworkProxyQuery &query) QCFType<CFStringRef> cfPacLocation = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, pacLocationSetting, NULL, NULL, kCFStringEncodingUTF8); - if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { - QCFType<CFDataRef> pacData; - QCFType<CFURLRef> pacUrl = CFURLCreateWithString(kCFAllocatorDefault, cfPacLocation, NULL); - if (!pacUrl) { - qWarning("Invalid PAC URL \"%s\"", qPrintable(QCFString::toQString(cfPacLocation))); - return result; - } - SInt32 errorCode; - if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, pacUrl, &pacData, NULL, NULL, &errorCode)) { - QString pacLocation = QCFString::toQString(cfPacLocation); - qWarning("Unable to get the PAC script at \"%s\" (%s)", qPrintable(pacLocation), cfurlErrorDescription(errorCode)); - return result; - } - if (!pacData) { - qWarning("\"%s\" returned an empty PAC script", qPrintable(QCFString::toQString(cfPacLocation))); - return result; - } - QCFType<CFStringRef> pacScript = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, pacData, kCFStringEncodingISOLatin1); - if (!pacScript) { - // This should never happen, but the documentation says it may return NULL if there was a problem creating the object. - QString pacLocation = QCFString::toQString(cfPacLocation); - qWarning("Unable to read the PAC script at \"%s\"", qPrintable(pacLocation)); - return result; - } - - QByteArray encodedURL = query.url().toEncoded(); // converted to UTF-8 - if (encodedURL.isEmpty()) { - return result; // Invalid URL, abort - } - - QCFType<CFURLRef> targetURL = CFURLCreateWithBytes(kCFAllocatorDefault, (UInt8*)encodedURL.data(), encodedURL.size(), kCFStringEncodingUTF8, NULL); - if (!targetURL) { - return result; // URL creation problem, abort - } - - QCFType<CFErrorRef> pacError; - QCFType<CFArrayRef> proxies = CFNetworkCopyProxiesForAutoConfigurationScript(pacScript, targetURL, &pacError); - if (!proxies) { - QString pacLocation = QCFString::toQString(cfPacLocation); - QCFType<CFStringRef> pacErrorDescription = CFErrorCopyDescription(pacError); - qWarning("Execution of PAC script at \"%s\" failed: %s", qPrintable(pacLocation), qPrintable(QCFString::toQString(pacErrorDescription))); - return result; - } - - CFIndex size = CFArrayGetCount(proxies); - for (CFIndex i = 0; i < size; ++i) { - CFDictionaryRef proxy = (CFDictionaryRef)CFArrayGetValueAtIndex(proxies, i); - result << proxyFromDictionary(proxy); - } + QCFType<CFDataRef> pacData; + QCFType<CFURLRef> pacUrl = CFURLCreateWithString(kCFAllocatorDefault, cfPacLocation, NULL); + if (!pacUrl) { + qWarning("Invalid PAC URL \"%s\"", qPrintable(QCFString::toQString(cfPacLocation))); return result; - } else { + } + SInt32 errorCode; + if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, pacUrl, &pacData, NULL, NULL, &errorCode)) { QString pacLocation = QCFString::toQString(cfPacLocation); - qWarning("Mac system proxy: PAC script at \"%s\" not handled", qPrintable(pacLocation)); + qWarning("Unable to get the PAC script at \"%s\" (%s)", qPrintable(pacLocation), cfurlErrorDescription(errorCode)); + return result; + } + if (!pacData) { + qWarning("\"%s\" returned an empty PAC script", qPrintable(QCFString::toQString(cfPacLocation))); + return result; + } + QCFType<CFStringRef> pacScript = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, pacData, kCFStringEncodingISOLatin1); + if (!pacScript) { + // This should never happen, but the documentation says it may return NULL if there was a problem creating the object. + QString pacLocation = QCFString::toQString(cfPacLocation); + qWarning("Unable to read the PAC script at \"%s\"", qPrintable(pacLocation)); + return result; + } + + QByteArray encodedURL = query.url().toEncoded(); // converted to UTF-8 + if (encodedURL.isEmpty()) { + return result; // Invalid URL, abort + } + + QCFType<CFURLRef> targetURL = CFURLCreateWithBytes(kCFAllocatorDefault, (UInt8*)encodedURL.data(), encodedURL.size(), kCFStringEncodingUTF8, NULL); + if (!targetURL) { + return result; // URL creation problem, abort + } + + QCFType<CFErrorRef> pacError; + QCFType<CFArrayRef> proxies = CFNetworkCopyProxiesForAutoConfigurationScript(pacScript, targetURL, &pacError); + if (!proxies) { + QString pacLocation = QCFString::toQString(cfPacLocation); + QCFType<CFStringRef> pacErrorDescription = CFErrorCopyDescription(pacError); + qWarning("Execution of PAC script at \"%s\" failed: %s", qPrintable(pacLocation), qPrintable(QCFString::toQString(pacErrorDescription))); + return result; + } + + CFIndex size = CFArrayGetCount(proxies); + for (CFIndex i = 0; i < size; ++i) { + CFDictionaryRef proxy = (CFDictionaryRef)CFArrayGetValueAtIndex(proxies, i); + result << proxyFromDictionary(proxy); } + return result; } } diff --git a/src/network/kernel/qnetworkproxy_win.cpp b/src/network/kernel/qnetworkproxy_win.cpp index 513eeaac12..2727bd9257 100644 --- a/src/network/kernel/qnetworkproxy_win.cpp +++ b/src/network/kernel/qnetworkproxy_win.cpp @@ -53,7 +53,6 @@ #include <qt_windows.h> #include <wininet.h> #include <lmcons.h> -#include "qnetworkfunctions_wince.h" /* * Information on the WinHTTP DLL: @@ -123,7 +122,6 @@ static PtrWinHttpGetIEProxyConfigForCurrentUser ptrWinHttpGetIEProxyConfigForCur static PtrWinHttpCloseHandle ptrWinHttpCloseHandle = 0; -#ifndef Q_OS_WINCE static bool currentProcessIsService() { typedef BOOL (WINAPI *PtrGetUserName)(LPTSTR lpBuffer, LPDWORD lpnSize); @@ -153,7 +151,6 @@ static bool currentProcessIsService() } return false; } -#endif // ! Q_OS_WINCE static QStringList splitSpaceSemicolon(const QString &source) { @@ -361,7 +358,7 @@ static QList<QNetworkProxy> parseServerList(const QNetworkProxyQuery &query, con return removeDuplicateProxies(result); } -#if !defined(Q_OS_WINCE) && !defined(Q_OS_WINRT) +#if !defined(Q_OS_WINRT) namespace { class QRegistryWatcher { public: @@ -412,7 +409,7 @@ private: QVector<HKEY> m_registryHandles; }; } // namespace -#endif // !defined(Q_OS_WINCE) && !defined(Q_OS_WINRT) +#endif // !defined(Q_OS_WINRT) class QWindowsSystemProxy { @@ -431,7 +428,7 @@ public: QStringList proxyServerList; QStringList proxyBypass; QList<QNetworkProxy> defaultResult; -#if !defined(Q_OS_WINCE) && !defined(Q_OS_WINRT) +#if !defined(Q_OS_WINRT) QRegistryWatcher proxySettingsWatcher; #endif bool initialized; @@ -467,7 +464,7 @@ void QWindowsSystemProxy::reset() void QWindowsSystemProxy::init() { bool proxySettingsChanged = false; -#if !defined(Q_OS_WINCE) && !defined(Q_OS_WINRT) +#if !defined(Q_OS_WINRT) proxySettingsChanged = proxySettingsWatcher.hasChanged(); #endif @@ -477,12 +474,7 @@ void QWindowsSystemProxy::init() reset(); -#ifdef Q_OS_WINCE - // Windows CE does not have any of the following API - return; -#else - -#if !defined(Q_OS_WINCE) && !defined(Q_OS_WINRT) +#if !defined(Q_OS_WINRT) proxySettingsWatcher.clear(); // needs reset to trigger a new detection proxySettingsWatcher.addLocation(HKEY_CURRENT_USER, QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")); proxySettingsWatcher.addLocation(HKEY_LOCAL_MACHINE, QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")); @@ -570,7 +562,6 @@ void QWindowsSystemProxy::init() } functional = isAutoConfig || !proxyServerList.isEmpty(); -#endif } QList<QNetworkProxy> QNetworkProxyFactory::systemProxyForQuery(const QNetworkProxyQuery &query) diff --git a/src/network/socket/qabstractsocket.cpp b/src/network/socket/qabstractsocket.cpp index 098739adc3..cbae297278 100644 --- a/src/network/socket/qabstractsocket.cpp +++ b/src/network/socket/qabstractsocket.cpp @@ -563,6 +563,7 @@ QAbstractSocketPrivate::QAbstractSocketPrivate() cachedSocketDescriptor(-1), readBufferMaxSize(0), isBuffered(false), + hasPendingData(false), connectTimer(0), disconnectTimer(0), hostLookupId(-1), @@ -593,6 +594,7 @@ void QAbstractSocketPrivate::resetSocketLayer() qDebug("QAbstractSocketPrivate::resetSocketLayer()"); #endif + hasPendingData = false; if (socketEngine) { socketEngine->close(); socketEngine->disconnect(); @@ -683,14 +685,20 @@ bool QAbstractSocketPrivate::canReadNotification() qDebug("QAbstractSocketPrivate::canReadNotification()"); #endif - if (!isBuffered) - socketEngine->setReadNotificationEnabled(false); + if (!isBuffered) { + if (hasPendingData) { + socketEngine->setReadNotificationEnabled(false); + return true; + } + hasPendingData = true; + } // If buffered, read data from the socket into the read buffer qint64 newBytes = 0; if (isBuffered) { // Return if there is no space in the buffer if (readBufferMaxSize && buffer.size() >= readBufferMaxSize) { + socketEngine->setReadNotificationEnabled(false); #if defined (QABSTRACTSOCKET_DEBUG) qDebug("QAbstractSocketPrivate::canReadNotification() buffer is full"); #endif @@ -708,11 +716,6 @@ bool QAbstractSocketPrivate::canReadNotification() return false; } newBytes = buffer.size() - newBytes; - - // If read buffer is full, disable the read socket notifier. - if (readBufferMaxSize && buffer.size() == readBufferMaxSize) { - socketEngine->setReadNotificationEnabled(false); - } } // Only emit readyRead() if there is data available. @@ -728,10 +731,6 @@ bool QAbstractSocketPrivate::canReadNotification() return true; } - // turn the socket engine off if we've reached the buffer size limit - if (socketEngine && isBuffered) - socketEngine->setReadNotificationEnabled(readBufferMaxSize == 0 || readBufferMaxSize > q->bytesAvailable()); - return true; } @@ -788,12 +787,8 @@ bool QAbstractSocketPrivate::canWriteNotification() #if defined (QABSTRACTSOCKET_DEBUG) qDebug("QAbstractSocketPrivate::canWriteNotification() flushing"); #endif - bool dataWasWritten = writeToSocket(); - - if (socketEngine && writeBuffer.isEmpty() && socketEngine->bytesToWrite() == 0) - socketEngine->setWriteNotificationEnabled(false); - return dataWasWritten; + return writeToSocket(); } /*! \internal @@ -833,8 +828,12 @@ bool QAbstractSocketPrivate::writeToSocket() #endif // this covers the case when the buffer was empty, but we had to wait for the socket engine to finish - if (state == QAbstractSocket::ClosingState) + if (state == QAbstractSocket::ClosingState) { q->disconnectFromHost(); + } else { + if (socketEngine) + socketEngine->setWriteNotificationEnabled(false); + } return false; } @@ -872,8 +871,7 @@ bool QAbstractSocketPrivate::writeToSocket() emit q->channelBytesWritten(0, written); } - if (writeBuffer.isEmpty() && socketEngine && socketEngine->isWriteNotificationEnabled() - && !socketEngine->bytesToWrite()) + if (writeBuffer.isEmpty() && socketEngine && !socketEngine->bytesToWrite()) socketEngine->setWriteNotificationEnabled(false); if (state == QAbstractSocket::ClosingState) q->disconnectFromHost(); @@ -1146,12 +1144,10 @@ void QAbstractSocketPrivate::_q_connectToNextAddress() */ void QAbstractSocketPrivate::_q_testConnection() { - if (socketEngine) { - if (threadData->hasEventDispatcher()) { - if (connectTimer) - connectTimer->stop(); - } + if (connectTimer) + connectTimer->stop(); + if (socketEngine) { if (socketEngine->state() == QAbstractSocket::ConnectedState) { // Fetch the parameters if our connection is completed; // otherwise, fall out and try the next address. @@ -1168,11 +1164,6 @@ void QAbstractSocketPrivate::_q_testConnection() addresses.clear(); } - if (threadData->hasEventDispatcher()) { - if (connectTimer) - connectTimer->stop(); - } - #if defined(QABSTRACTSOCKET_DEBUG) qDebug("QAbstractSocketPrivate::_q_testConnection() connection failed," " checking for alternative addresses"); @@ -2381,11 +2372,6 @@ void QAbstractSocket::abort() return; } #endif - if (d->connectTimer) { - d->connectTimer->stop(); - delete d->connectTimer; - d->connectTimer = 0; - } d->abortCalled = true; close(); @@ -2432,15 +2418,7 @@ bool QAbstractSocket::atEnd() const // Note! docs copied to QSslSocket::flush() bool QAbstractSocket::flush() { - Q_D(QAbstractSocket); -#ifndef QT_NO_SSL - // Manual polymorphism; flush() isn't virtual, but QSslSocket overloads - // it. - if (QSslSocket *socket = qobject_cast<QSslSocket *>(this)) - return socket->flush(); -#endif - Q_CHECK_SOCKETENGINE(false); - return d->flush(); + return d_func()->flush(); } /*! \reimp @@ -2463,8 +2441,9 @@ qint64 QAbstractSocket::readData(char *data, qint64 maxSize) d->setError(d->socketEngine->error(), d->socketEngine->errorString()); d->resetSocketLayer(); d->state = QAbstractSocket::UnconnectedState; - } else if (!d->socketEngine->isReadNotificationEnabled()) { + } else { // Only do this when there was no error + d->hasPendingData = false; d->socketEngine->setReadNotificationEnabled(true); } @@ -2830,12 +2809,12 @@ void QAbstractSocket::setReadBufferSize(qint64 size) if (d->readBufferMaxSize == size) return; d->readBufferMaxSize = size; - if (!d->emittedReadyRead && d->socketEngine) { - // ensure that the read notification is enabled if we've now got - // room in the read buffer - // but only if we're not inside canReadNotification -- that will take care on its own - if ((size == 0 || d->buffer.size() < size) && d->state == QAbstractSocket::ConnectedState) // Do not change the notifier unless we are connected. - d->socketEngine->setReadNotificationEnabled(true); + + // Do not change the notifier unless we are connected. + if (d->socketEngine && d->state == QAbstractSocket::ConnectedState) { + // Ensure that the read notification is enabled if we've now got + // room in the read buffer. + d->socketEngine->setReadNotificationEnabled(size == 0 || d->buffer.size() < size); } } diff --git a/src/network/socket/qabstractsocket_p.h b/src/network/socket/qabstractsocket_p.h index b718c21ff5..3164c96c1e 100644 --- a/src/network/socket/qabstractsocket_p.h +++ b/src/network/socket/qabstractsocket_p.h @@ -128,13 +128,12 @@ public: inline void resolveProxy(quint16 port) { resolveProxy(QString(), port); } void resetSocketLayer(); - bool flush(); + virtual bool flush(); bool initSocketLayer(QAbstractSocket::NetworkLayerProtocol protocol); virtual void configureCreatedSocket(); void startConnectingByName(const QString &host); void fetchConnectionParameters(); - void setupSocketNotifiers(); bool readFromSocket(); bool writeToSocket(); void emitReadyRead(); @@ -144,6 +143,7 @@ public: qint64 readBufferMaxSize; bool isBuffered; + bool hasPendingData; QTimer *connectTimer; QTimer *disconnectTimer; diff --git a/src/network/socket/qnativesocketengine_p.h b/src/network/socket/qnativesocketengine_p.h index 19e9e1d9b7..5a05d7c98c 100644 --- a/src/network/socket/qnativesocketengine_p.h +++ b/src/network/socket/qnativesocketengine_p.h @@ -66,21 +66,20 @@ QT_BEGIN_NAMESPACE #ifdef Q_OS_WIN -#define QT_SOCKLEN_T int -#define QT_SOCKOPTLEN_T int +# define QT_SOCKLEN_T int +# define QT_SOCKOPTLEN_T int // The following definitions are copied from the MinGW header mswsock.h which // was placed in the public domain. The WSASendMsg and WSARecvMsg functions // were introduced with Windows Vista, so some Win32 headers are lacking them. // There are no known versions of Windows CE or Embedded that contain them. -#ifndef Q_OS_WINCE # ifndef WSAID_WSARECVMSG typedef INT (WINAPI *LPFN_WSARECVMSG)(SOCKET s, LPWSAMSG lpMsg, LPDWORD lpdwNumberOfBytesRecvd, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); # define WSAID_WSARECVMSG {0xf689d7c8,0x6f1f,0x436b,{0x8a,0x53,0xe5,0x4f,0xe3,0x51,0xc3,0x22}} -# endif +# endif // !WSAID_WSARECVMSG # ifndef WSAID_WSASENDMSG typedef struct { LPWSAMSG lpMsg; @@ -96,9 +95,8 @@ typedef INT (WSAAPI *LPFN_WSASENDMSG)(SOCKET s, LPWSAMSG lpMsg, DWORD dwFlags, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); # define WSAID_WSASENDMSG {0xa441e712,0x754f,0x43ca,{0x84,0xa7,0x0d,0xee,0x44,0xcf,0x60,0x6d}} -# endif -#endif -#endif +# endif // !WSAID_WSASENDMSG +#endif // Q_OS_WIN union qt_sockaddr { sockaddr a; @@ -210,7 +208,7 @@ public: QSocketNotifier *readNotifier, *writeNotifier, *exceptNotifier; -#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) +#if defined(Q_OS_WIN) LPFN_WSASENDMSG sendmsg; LPFN_WSARECVMSG recvmsg; # endif diff --git a/src/network/socket/qnativesocketengine_win.cpp b/src/network/socket/qnativesocketengine_win.cpp index 5ffe7b11b8..0c5b8d9264 100644 --- a/src/network/socket/qnativesocketengine_win.cpp +++ b/src/network/socket/qnativesocketengine_win.cpp @@ -387,7 +387,6 @@ bool QNativeSocketEnginePrivate::createNewSocket(QAbstractSocket::SocketType soc return false; } -#if !defined(Q_OS_WINCE) if (socketType == QAbstractSocket::UdpSocket) { // enable new behavior using // SIO_UDP_CONNRESET @@ -414,7 +413,6 @@ bool QNativeSocketEnginePrivate::createNewSocket(QAbstractSocket::SocketType soc &sendmsgguid, sizeof(sendmsgguid), &sendmsg, sizeof(sendmsg), &bytesReturned, NULL, NULL) == SOCKET_ERROR) sendmsg = 0; -#endif socketDescriptor = socket; if (socket != INVALID_SOCKET) { @@ -1091,7 +1089,6 @@ qint64 QNativeSocketEnginePrivate::nativeBytesAvailable() const bool QNativeSocketEnginePrivate::nativeHasPendingDatagrams() const { -#if !defined(Q_OS_WINCE) // Create a sockaddr struct and reset its port number. qt_sockaddr storage; QT_SOCKLEN_T storageSize = sizeof(storage); @@ -1118,18 +1115,6 @@ bool QNativeSocketEnginePrivate::nativeHasPendingDatagrams() const result = true; } -#else // Q_OS_WINCE - bool result = false; - fd_set readS; - FD_ZERO(&readS); - FD_SET((SOCKET)socketDescriptor, &readS); - timeval timeout; - timeout.tv_sec = 0; - timeout.tv_usec = 5000; - int available = ::select(1, &readS, 0, 0, &timeout); - result = available > 0; -#endif - #if defined (QNATIVESOCKETENGINE_DEBUG) qDebug("QNativeSocketEnginePrivate::nativeHasPendingDatagrams() == %s", result ? "true" : "false"); @@ -1141,7 +1126,6 @@ bool QNativeSocketEnginePrivate::nativeHasPendingDatagrams() const qint64 QNativeSocketEnginePrivate::nativePendingDatagramSize() const { qint64 ret = -1; -#if !defined(Q_OS_WINCE) int recvResult = 0; DWORD flags; DWORD bufferCount = 5; @@ -1186,18 +1170,6 @@ qint64 QNativeSocketEnginePrivate::nativePendingDatagramSize() const if (buf) delete[] buf; -#else // Q_OS_WINCE - DWORD size = -1; - DWORD bytesReturned; - int ioResult = WSAIoctl(socketDescriptor, FIONREAD, 0,0, &size, sizeof(size), &bytesReturned, 0, 0); - if (ioResult == SOCKET_ERROR) { - int err = WSAGetLastError(); - WS_ERROR_DEBUG(err); - } else { - ret = qint64(size); - } -#endif - #if defined (QNATIVESOCKETENGINE_DEBUG) qDebug("QNativeSocketEnginePrivate::nativePendingDatagramSize() == %lli", ret); #endif @@ -1205,12 +1177,6 @@ qint64 QNativeSocketEnginePrivate::nativePendingDatagramSize() const return ret; } -#ifdef Q_OS_WINCE -// Windows CE has no support for sendmsg or recvmsg. We set it to null here to simplify the code below. -static int (*const recvmsg)(...) = 0; -static int (*const sendmsg)(...) = 0; -#endif - qint64 QNativeSocketEnginePrivate::nativeReceiveDatagram(char *data, qint64 maxLength, QIpPacketHeader *header, QAbstractSocketEngine::PacketHeaderOptions options) { @@ -1330,12 +1296,7 @@ qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 l memset(&msg, 0, sizeof(msg)); memset(&aa, 0, sizeof(aa)); -#if !defined(Q_OS_WINCE) buf.buf = len ? (char*)data : 0; -#else - char tmp; - buf.buf = len ? (char*)data : &tmp; -#endif msg.lpBuffers = &buf; msg.dwBufferCount = 1; msg.name = &aa.a; @@ -1497,9 +1458,6 @@ qint64 QNativeSocketEnginePrivate::nativeRead(char *data, qint64 maxLength) buf.len = maxLength; DWORD flags = 0; DWORD bytesRead = 0; -#if defined(Q_OS_WINCE) - WSASetLastError(0); -#endif if (::WSARecv(socketDescriptor, &buf, 1, &bytesRead, &flags, 0,0) == SOCKET_ERROR) { int err = WSAGetLastError(); WS_ERROR_DEBUG(err); @@ -1613,11 +1571,7 @@ int QNativeSocketEnginePrivate::nativeSelect(int timeout, tv.tv_sec = timeout / 1000; tv.tv_usec = (timeout % 1000) * 1000; -#if !defined(Q_OS_WINCE) ret = select(socketDescriptor + 1, &fdread, &fdwrite, &fdexception, timeout < 0 ? 0 : &tv); -#else - ret = select(1, &fdread, &fdwrite, &fdexception, timeout < 0 ? 0 : &tv); -#endif //... but if it is actually set, pretend it did not happen if (ret > 0 && FD_ISSET((SOCKET)socketDescriptor, &fdexception)) diff --git a/src/network/socket/qsocks5socketengine.cpp b/src/network/socket/qsocks5socketengine.cpp index a57a1dca2c..ee3e0d9f0e 100644 --- a/src/network/socket/qsocks5socketengine.cpp +++ b/src/network/socket/qsocks5socketengine.cpp @@ -64,11 +64,7 @@ static const int MaxWriteBufferSize = 128*1024; //#define QSOCKS5SOCKETLAYER_DEBUG #define MAX_DATA_DUMP 256 -#if !defined(Q_OS_WINCE) #define SOCKS5_BLOCKING_BIND_TIMEOUT 5000 -#else -#define SOCKS5_BLOCKING_BIND_TIMEOUT 10000 -#endif #define Q_INIT_CHECK(returnValue) do { \ if (!d->data) { \ diff --git a/src/network/socket/qtcpserver.cpp b/src/network/socket/qtcpserver.cpp index de1dc29cfb..d9ffdbd214 100644 --- a/src/network/socket/qtcpserver.cpp +++ b/src/network/socket/qtcpserver.cpp @@ -543,8 +543,11 @@ QTcpSocket *QTcpServer::nextPendingConnection() if (d->pendingConnections.isEmpty()) return 0; - if (!d->socketEngine->isReadNotificationEnabled()) + if (!d->socketEngine) { + qWarning("QTcpServer::nextPendingConnection() called while not listening"); + } else if (!d->socketEngine->isReadNotificationEnabled()) { d->socketEngine->setReadNotificationEnabled(true); + } return d->pendingConnections.takeFirst(); } diff --git a/src/network/socket/qudpsocket.cpp b/src/network/socket/qudpsocket.cpp index c406009069..083648bc23 100644 --- a/src/network/socket/qudpsocket.cpp +++ b/src/network/socket/qudpsocket.cpp @@ -398,7 +398,8 @@ qint64 QUdpSocket::readDatagram(char *data, qint64 maxSize, QHostAddress *addres readBytes = d->socketEngine->readDatagram(data, maxSize); } - d_func()->socketEngine->setReadNotificationEnabled(true); + d->hasPendingData = false; + d->socketEngine->setReadNotificationEnabled(true); if (readBytes < 0) d->setErrorAndEmit(d->socketEngine->error(), d->socketEngine->errorString()); return readBytes; diff --git a/src/network/socket/socket.pri b/src/network/socket/socket.pri index f50a7b1229..2d80f38bec 100644 --- a/src/network/socket/socket.pri +++ b/src/network/socket/socket.pri @@ -43,7 +43,7 @@ win32:!winrt:SOURCES += socket/qnativesocketengine_win.cpp \ socket/qlocalsocket_win.cpp \ socket/qlocalserver_win.cpp -win32:!wince:!winrt:LIBS_PRIVATE += -ladvapi32 +win32:!winrt:LIBS_PRIVATE += -ladvapi32 winrt { SOURCES += socket/qnativesocketengine_winrt.cpp \ @@ -54,15 +54,6 @@ winrt { DEFINES += QT_LOCALSOCKET_TCP } -wince { - SOURCES -= socket/qlocalsocket_win.cpp \ - socket/qlocalserver_win.cpp - SOURCES += socket/qlocalsocket_tcp.cpp \ - socket/qlocalserver_tcp.cpp - - DEFINES += QT_LOCALSOCKET_TCP -} - integrity: { SOURCES -= socket/qlocalsocket_unix.cpp \ socket/qlocalserver_unix.cpp diff --git a/src/network/ssl/qsslconfiguration.cpp b/src/network/ssl/qsslconfiguration.cpp index c8040dec7f..e009824a69 100644 --- a/src/network/ssl/qsslconfiguration.cpp +++ b/src/network/ssl/qsslconfiguration.cpp @@ -119,7 +119,8 @@ const char QSslConfiguration::NextProtocolHttp1_1[] = "http/1.1"; /*! \enum QSslConfiguration::NextProtocolNegotiationStatus - Describes the status of the Next Protocol Negotiation (NPN). + Describes the status of the Next Protocol Negotiation (NPN) or + Application-Layer Protocol Negotiation (ALPN). \value NextProtocolNegotiationNone No application protocol has been negotiated (yet). @@ -209,9 +210,11 @@ bool QSslConfiguration::operator==(const QSslConfiguration &other) const d->privateKey == other.d->privateKey && d->sessionCipher == other.d->sessionCipher && d->sessionProtocol == other.d->sessionProtocol && + d->preSharedKeyIdentityHint == other.d->preSharedKeyIdentityHint && d->ciphers == other.d->ciphers && d->ellipticCurves == other.d->ellipticCurves && d->ephemeralServerKey == other.d->ephemeralServerKey && + d->dhParams == other.d->dhParams && d->caCertificates == other.d->caCertificates && d->protocol == other.d->protocol && d->peerVerifyMode == other.d->peerVerifyMode && @@ -254,6 +257,7 @@ bool QSslConfiguration::isNull() const d->ciphers.count() == 0 && d->ellipticCurves.isEmpty() && d->ephemeralServerKey.isNull() && + d->dhParams == QSslDiffieHellmanParameters::defaultParameters() && d->localCertificateChain.isEmpty() && d->privateKey.isNull() && d->peerCertificate.isNull() && @@ -261,6 +265,7 @@ bool QSslConfiguration::isNull() const d->sslOptions == QSslConfigurationPrivate::defaultSslOptions && d->sslSession.isNull() && d->sslSessionTicketLifeTimeHint == -1 && + d->preSharedKeyIdentityHint.isNull() && d->nextAllowedProtocols.isEmpty() && d->nextNegotiatedProtocol.isNull() && d->nextProtocolNegotiationStatus == QSslConfiguration::NextProtocolNegotiationNone); @@ -811,11 +816,65 @@ QVector<QSslEllipticCurve> QSslConfiguration::supportedEllipticCurves() } /*! + \since 5.8 + + Returns the identity hint. + + \sa setPreSharedKeyIdentityHint() +*/ +QByteArray QSslConfiguration::preSharedKeyIdentityHint() const +{ + return d->preSharedKeyIdentityHint; +} + +/*! + \since 5.8 + + Sets the identity hint for a preshared key authentication. This will affect the next + initiated handshake; calling this function on an already-encrypted socket + will not affect the socket's identity hint. + + The identity hint is used in QSslSocket::SslServerMode only! +*/ +void QSslConfiguration::setPreSharedKeyIdentityHint(const QByteArray &hint) +{ + d->preSharedKeyIdentityHint = hint; +} + +/*! + \since 5.8 + + Retrieves the current set of Diffie-Hellman parameters. + + If no Diffie-Hellman parameters have been set, the QSslConfiguration object + defaults to using the 1024-bit MODP group from RFC 2409. + */ +QSslDiffieHellmanParameters QSslConfiguration::diffieHellmanParameters() const +{ + return d->dhParams; +} + +/*! + \since 5.8 + + Sets a custom set of Diffie-Hellman parameters to be used by this socket when functioning as + a server. + + If no Diffie-Hellman parameters have been set, the QSslConfiguration object + defaults to using the 1024-bit MODP group from RFC 2409. + */ +void QSslConfiguration::setDiffieHellmanParameters(const QSslDiffieHellmanParameters &dhparams) +{ + d->dhParams = dhparams; +} + +/*! \since 5.3 This function returns the protocol negotiated with the server - if the Next Protocol Negotiation (NPN) TLS extension was enabled. - In order for the NPN extension to be enabled, setAllowedNextProtocols() + if the Next Protocol Negotiation (NPN) or Application-Layer Protocol + Negotiation (ALPN) TLS extension was enabled. + In order for the NPN/ALPN extension to be enabled, setAllowedNextProtocols() needs to be called explicitly before connecting to the server. If no protocol could be negotiated or the extension was not enabled, @@ -832,9 +891,10 @@ QByteArray QSslConfiguration::nextNegotiatedProtocol() const \since 5.3 This function sets the allowed \a protocols to be negotiated with the - server through the Next Protocol Negotiation (NPN) TLS extension; each + server through the Next Protocol Negotiation (NPN) or Application-Layer + Protocol Negotiation (ALPN) TLS extension; each element in \a protocols must define one allowed protocol. - The function must be called explicitly before connecting to send the NPN + The function must be called explicitly before connecting to send the NPN/ALPN extension in the SSL handshake. Whether or not the negotiation succeeded can be queried through nextProtocolNegotiationStatus(). @@ -854,8 +914,8 @@ void QSslConfiguration::setAllowedNextProtocols(QList<QByteArray> protocols) \since 5.3 This function returns the allowed protocols to be negotiated with the - server through the Next Protocol Negotiation (NPN) TLS extension, as set - by setAllowedNextProtocols(). + server through the Next Protocol Negotiation (NPN) or Application-Layer + Protocol Negotiation (ALPN) TLS extension, as set by setAllowedNextProtocols(). \sa nextNegotiatedProtocol(), nextProtocolNegotiationStatus(), setAllowedNextProtocols(), QSslConfiguration::NextProtocolSpdy3_0, QSslConfiguration::NextProtocolHttp1_1 */ @@ -867,7 +927,8 @@ QList<QByteArray> QSslConfiguration::allowedNextProtocols() const /*! \since 5.3 - This function returns the status of the Next Protocol Negotiation (NPN). + This function returns the status of the Next Protocol Negotiation (NPN) + or Application-Layer Protocol Negotiation (ALPN). If the feature has not been enabled through setAllowedNextProtocols(), this function returns NextProtocolNegotiationNone. The status will be set before emitting the encrypted() signal. diff --git a/src/network/ssl/qsslconfiguration.h b/src/network/ssl/qsslconfiguration.h index f0754d7ef5..61246e1009 100644 --- a/src/network/ssl/qsslconfiguration.h +++ b/src/network/ssl/qsslconfiguration.h @@ -69,6 +69,7 @@ class QSslCertificate; class QSslCipher; class QSslKey; class QSslEllipticCurve; +class QSslDiffieHellmanParameters; class QSslConfigurationPrivate; class Q_NETWORK_EXPORT QSslConfiguration @@ -141,6 +142,12 @@ public: void setEllipticCurves(const QVector<QSslEllipticCurve> &curves); static QVector<QSslEllipticCurve> supportedEllipticCurves(); + QByteArray preSharedKeyIdentityHint() const; + void setPreSharedKeyIdentityHint(const QByteArray &hint); + + QSslDiffieHellmanParameters diffieHellmanParameters() const; + void setDiffieHellmanParameters(const QSslDiffieHellmanParameters &dhparams); + static QSslConfiguration defaultConfiguration(); static void setDefaultConfiguration(const QSslConfiguration &configuration); diff --git a/src/network/ssl/qsslconfiguration_p.h b/src/network/ssl/qsslconfiguration_p.h index 364bba92ec..139a9fc32b 100644 --- a/src/network/ssl/qsslconfiguration_p.h +++ b/src/network/ssl/qsslconfiguration_p.h @@ -73,6 +73,7 @@ #include "qsslcipher.h" #include "qsslkey.h" #include "qsslellipticcurve.h" +#include "qssldiffiehellmanparameters.h" QT_BEGIN_NAMESPACE @@ -87,8 +88,10 @@ public: allowRootCertOnDemandLoading(true), peerSessionShared(false), sslOptions(QSslConfigurationPrivate::defaultSslOptions), + dhParams(QSslDiffieHellmanParameters::defaultParameters()), sslSessionTicketLifeTimeHint(-1), ephemeralServerKey(), + preSharedKeyIdentityHint(), nextProtocolNegotiationStatus(QSslConfiguration::NextProtocolNegotiationNone) { } @@ -117,11 +120,15 @@ public: QVector<QSslEllipticCurve> ellipticCurves; + QSslDiffieHellmanParameters dhParams; + QByteArray sslSession; int sslSessionTicketLifeTimeHint; QSslKey ephemeralServerKey; + QByteArray preSharedKeyIdentityHint; + QList<QByteArray> nextAllowedProtocols; QByteArray nextNegotiatedProtocol; QSslConfiguration::NextProtocolNegotiationStatus nextProtocolNegotiationStatus; diff --git a/src/network/ssl/qsslcontext_openssl.cpp b/src/network/ssl/qsslcontext_openssl.cpp index f132d0228c..5a80d08e24 100644 --- a/src/network/ssl/qsslcontext_openssl.cpp +++ b/src/network/ssl/qsslcontext_openssl.cpp @@ -41,6 +41,7 @@ #include <QtNetwork/qsslsocket.h> +#include <QtNetwork/qssldiffiehellmanparameters.h> #include <QtCore/qmutex.h> #include "private/qssl_p.h" @@ -48,6 +49,7 @@ #include "private/qsslsocket_p.h" #include "private/qsslsocket_openssl_p.h" #include "private/qsslsocket_openssl_symbols_p.h" +#include "private/qssldiffiehellmanparameters_p.h" QT_BEGIN_NAMESPACE @@ -55,22 +57,6 @@ QT_BEGIN_NAMESPACE extern int q_X509Callback(int ok, X509_STORE_CTX *ctx); extern QString getErrorsFromOpenSsl(); -static DH *get_dh1024() -{ - // Default DH params - // 1024-bit MODP Group - // From RFC 2409 - QByteArray params = QByteArray::fromBase64( - QByteArrayLiteral("MIGHAoGBAP//////////yQ/aoiFowjTExmKLgNwc0SkCTgiKZ8x0Agu+pjsTmyJR" \ - "Sgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHCReSFtXZiXn7G9ExC6aY37WsL" \ - "/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7OZTgf//////////AgEC")); - - const char *ptr = params.constData(); - DH *dh = q_d2i_DHparams(NULL, reinterpret_cast<const unsigned char **>(&ptr), params.length()); - - return dh; -} - QSslContext::QSslContext() : ctx(0), pkey(0), @@ -325,10 +311,23 @@ init_context: sslContext->setSessionASN1(configuration.sessionTicket()); // Set temp DH params - DH *dh = 0; - dh = get_dh1024(); - q_SSL_CTX_set_tmp_dh(sslContext->ctx, dh); - q_DH_free(dh); + QSslDiffieHellmanParameters dhparams = configuration.diffieHellmanParameters(); + + if (!dhparams.isValid()) { + sslContext->errorStr = QSslSocket::tr("Diffie-Hellman parameters are not valid"); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + + if (!dhparams.isEmpty()) { + const QByteArray ¶ms = dhparams.d.data()->derData; + const char *ptr = params.constData(); + DH *dh = q_d2i_DHparams(NULL, reinterpret_cast<const unsigned char **>(&ptr), params.length()); + if (dh == NULL) + qFatal("q_d2i_DHparams failed to convert QSslDiffieHellmanParameters to DER form"); + q_SSL_CTX_set_tmp_dh(sslContext->ctx, dh); + q_DH_free(dh); + } #ifndef OPENSSL_NO_EC #if OPENSSL_VERSION_NUMBER >= 0x10002000L @@ -345,6 +344,11 @@ init_context: } #endif // OPENSSL_NO_EC +#ifndef OPENSSL_NO_PSK + if (!client) + q_SSL_CTX_use_psk_identity_hint(sslContext->ctx, sslContext->sslConfiguration.preSharedKeyIdentityHint().constData()); +#endif // OPENSSL_NO_PSK + const QVector<QSslEllipticCurve> qcurves = sslContext->sslConfiguration.ellipticCurves(); if (!qcurves.isEmpty()) { #if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(OPENSSL_NO_EC) @@ -458,6 +462,23 @@ SSL* QSslContext::createSsl() m_npnContext.data = reinterpret_cast<unsigned char *>(m_supportedNPNVersions.data()); m_npnContext.len = m_supportedNPNVersions.count(); m_npnContext.status = QSslConfiguration::NextProtocolNegotiationNone; +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (q_SSLeay() >= 0x10002000L) { + // Callback's type has a parameter 'const unsigned char ** out' + // since it was introduced in 1.0.2. Internally, OpenSSL's own code + // (tests/examples) cast it to unsigned char * (since it's 'out'). + // We just re-use our NPN callback and cast here: + typedef int (*alpn_callback_t) (SSL *, const unsigned char **, unsigned char *, + const unsigned char *, unsigned int, void *); + // With ALPN callback is for a server side only, for a client m_npnContext.status + // will stay in NextProtocolNegotiationNone. + q_SSL_CTX_set_alpn_select_cb(ctx, alpn_callback_t(next_proto_cb), &m_npnContext); + // Client: + q_SSL_set_alpn_protos(ssl, m_npnContext.data, m_npnContext.len); + } +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L ... + + // And in case our peer does not support ALPN, but supports NPN: q_SSL_CTX_set_next_proto_select_cb(ctx, next_proto_cb, &m_npnContext); } #endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ... diff --git a/src/network/ssl/qssldiffiehellmanparameters.cpp b/src/network/ssl/qssldiffiehellmanparameters.cpp new file mode 100644 index 0000000000..d411ea0a5d --- /dev/null +++ b/src/network/ssl/qssldiffiehellmanparameters.cpp @@ -0,0 +1,307 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Mikkel Krautz <mikkel@krautz.dk> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +/*! + \class QSslDiffieHellmanParameters + \brief The QSslDiffieHellmanParameters class provides an interface for Diffie-Hellman parameters for servers. + \since 5.8 + + \reentrant + \ingroup network + \ingroup ssl + \ingroup shared + \inmodule QtNetwork + + QSslDiffieHellmanParameters provides an interface for setting Diffie-Hellman parameters to servers based on QSslSocket. + + \sa QSslSocket, QSslCipher, QSslConfiguration +*/ + +#include "qssldiffiehellmanparameters.h" +#include "qssldiffiehellmanparameters_p.h" +#include "qsslsocket.h" +#include "qsslsocket_p.h" + +#include <QtCore/qcoreapplication.h> +#include <QtCore/qatomic.h> +#include <QtCore/qbytearray.h> +#include <QtCore/qbytearraymatcher.h> +#include <QtCore/qiodevice.h> +#include <QtCore/qdebug.h> + +QT_BEGIN_NAMESPACE + +/*! + Returns the default QSslDiffieHellmanParameters used by QSslSocket. + + This is currently the 1024-bit MODP group from RFC 2459, also + known as the Second Oakley Group. +*/ +QSslDiffieHellmanParameters QSslDiffieHellmanParameters::defaultParameters() +{ + // The 1024-bit MODP group from RFC 2459 (Second Oakley Group) + return QSslDiffieHellmanParameters( + QByteArray::fromBase64(QByteArrayLiteral( + "MIGHAoGBAP//////////yQ/aoiFowjTExmKLgNwc0SkCTgiKZ8x0Agu+pjsTmyJR" + "Sgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHCReSFtXZiXn7G9ExC6aY37WsL" + "/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7OZTgf//////////AgEC" + )), + QSsl::Der + ); +} + +/*! + Constructs an empty QSslDiffieHellmanParameters instance. + + If an empty QSslDiffieHellmanParameters instance is set on a + QSslConfiguration object, Diffie-Hellman negotiation will + be disabled. + + \sa isValid() + \sa QSslConfiguration +*/ +QSslDiffieHellmanParameters::QSslDiffieHellmanParameters() + : d(new QSslDiffieHellmanParametersPrivate) +{ +} + +/*! + Constructs a QSslDiffieHellmanParameters object using + the byte array \a encoded in either PEM or DER form. + + After construction, the isValid() method should be used to + check whether the Diffie-Hellman parameters were valid and + loaded correctly. + + \sa isValid() + \sa QSslConfiguration +*/ +QSslDiffieHellmanParameters::QSslDiffieHellmanParameters(const QByteArray &encoded, QSsl::EncodingFormat encoding) + : d(new QSslDiffieHellmanParametersPrivate) +{ + switch (encoding) { + case QSsl::Der: + d->decodeDer(encoded); + break; + case QSsl::Pem: + d->decodePem(encoded); + break; + } +} + +/*! + Constructs a QSslDiffieHellmanParameters object by + reading from \a device in either PEM or DER form. + + After construction, the isValid() method should be used + to check whether the Diffie-Hellman parameters were valid + and loaded correctly. + + \sa isValid() + \sa QSslConfiguration +*/ +QSslDiffieHellmanParameters::QSslDiffieHellmanParameters(QIODevice *device, QSsl::EncodingFormat encoding) + : d(new QSslDiffieHellmanParametersPrivate) +{ + if (!device) + return; + + const QByteArray encoded = device->readAll(); + + switch (encoding) { + case QSsl::Der: + d->decodeDer(encoded); + break; + case QSsl::Pem: + d->decodePem(encoded); + break; + } +} + +/*! + Constructs an identical copy of \a other. +*/ +QSslDiffieHellmanParameters::QSslDiffieHellmanParameters(const QSslDiffieHellmanParameters &other) : d(other.d) +{ +} + +/*! + Destroys the QSslDiffieHellmanParameters object. +*/ +QSslDiffieHellmanParameters::~QSslDiffieHellmanParameters() +{ +} + +/*! + Copies the contents of \a other into this QSslDiffieHellmanParameters, making the two QSslDiffieHellmanParameters + identical. + + Returns a reference to this QSslDiffieHellmanParameters. +*/ +QSslDiffieHellmanParameters &QSslDiffieHellmanParameters::operator=(const QSslDiffieHellmanParameters &other) +{ + d = other.d; + return *this; +} + +/*! + \fn QSslDiffieHellmanParameters &QSslDiffieHellmanParameters::operator=(QSslDiffieHellmanParameters &&other) + + Move-assigns \a other to this QSslDiffieHellmanParameters instance. +*/ + +/*! + \fn void QSslDiffieHellmanParameters::swap(QSslDiffieHellmanParameters &other) + + Swaps this QSslDiffieHellmanParameters with \a other. This function is very fast and + never fails. +*/ + +/*! + Returns \c true if this is a an empty QSslDiffieHellmanParameters instance. + + Setting an empty QSslDiffieHellmanParameters instance on a QSslSocket-based + server will disable Diffie-Hellman key exchange. +*/ +bool QSslDiffieHellmanParameters::isEmpty() const Q_DECL_NOTHROW +{ + return d->derData.isNull() && d->error == QSslDiffieHellmanParameters::NoError; +} + +/*! + Returns \c true if this is a valid QSslDiffieHellmanParameters; otherwise false. + + This method should be used after constructing a QSslDiffieHellmanParameters + object to determine its validity. + + If a QSslDiffieHellmanParameters object is not valid, you can use the error() + method to determine what error prevented the object from being constructed. + + \sa clear() + \sa error() +*/ +bool QSslDiffieHellmanParameters::isValid() const Q_DECL_NOTHROW +{ + return d->error == QSslDiffieHellmanParameters::NoError; +} + +/*! + \enum QSslDiffieHellmanParameters::Error + + Describes a QSslDiffieHellmanParameters error. + + \value ErrorInvalidInputData The given input data could not be used to + construct a QSslDiffieHellmanParameters + object. + + \value ErrorUnsafeParameters The Diffie-Hellman parameters are unsafe + and should not be used. +*/ + +/*! + Returns the error that caused the QSslDiffieHellmanParameters object + to be invalid. +*/ +QSslDiffieHellmanParameters::Error QSslDiffieHellmanParameters::error() const Q_DECL_NOTHROW +{ + return d->error; +} + +/*! + Returns a human-readable description of the error that caused the + QSslDiffieHellmanParameters object to be invalid. +*/ +QString QSslDiffieHellmanParameters::errorString() const Q_DECL_NOTHROW +{ + switch (d->error) { + case QSslDiffieHellmanParameters::NoError: + return QCoreApplication::translate("QSslDiffieHellmanParameter", "No error"); + case QSslDiffieHellmanParameters::InvalidInputDataError: + return QCoreApplication::translate("QSslDiffieHellmanParameter", "Invalid input data"); + case QSslDiffieHellmanParameters::UnsafeParametersError: + return QCoreApplication::translate("QSslDiffieHellmanParameter", "The given Diffie-Hellman parameters are deemed unsafe"); + } + + Q_UNREACHABLE(); + return QString(); +} + +/*! + \relates QSslDiffieHellmanParameters + + Returns \c true if \a lhs is equal to \a rhs; otherwise returns \c false. +*/ +bool operator==(const QSslDiffieHellmanParameters &lhs, const QSslDiffieHellmanParameters &rhs) Q_DECL_NOTHROW +{ + return lhs.d->derData == rhs.d->derData; +} + +#ifndef QT_NO_DEBUG_STREAM +/*! + \relates QSslDiffieHellmanParameters + + Writes the set of Diffie-Hellman parameters in \a dhparm into the debug object \a debug for + debugging purposes. + + The Diffie-Hellman parameters will be represented in Base64-encoded DER form. + + \sa {Debugging Techniques} +*/ +QDebug operator<<(QDebug debug, const QSslDiffieHellmanParameters &dhparam) +{ + QDebugStateSaver saver(debug); + debug.resetFormat().nospace(); + debug << "QSslDiffieHellmanParameters(" << dhparam.d->derData.toBase64() << ')'; + return debug; +} +#endif + +/*! + \relates QHash + + Returns an hash value for \a dhparam, using \a seed to seed + the calculation. +*/ +uint qHash(const QSslDiffieHellmanParameters &dhparam, uint seed) Q_DECL_NOTHROW +{ + return qHash(dhparam.d->derData, seed); +} + +QT_END_NAMESPACE diff --git a/src/network/ssl/qssldiffiehellmanparameters.h b/src/network/ssl/qssldiffiehellmanparameters.h new file mode 100644 index 0000000000..aa40be83a6 --- /dev/null +++ b/src/network/ssl/qssldiffiehellmanparameters.h @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Mikkel Krautz <mikkel@krautz.dk> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QSSLDIFFIEHELLMANPARAMETERS_H +#define QSSLDIFFIEHELLMANPARAMETERS_H + +#include <QtNetwork/qssl.h> +#include <QtCore/qnamespace.h> +#include <QtCore/qbytearray.h> +#include <QtCore/qshareddata.h> + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_SSL + +class QIODevice; +class QSslContext; +class QSslDiffieHellmanParametersPrivate; + +class QSslDiffieHellmanParameters; +// qHash is a friend, but we can't use default arguments for friends (§8.3.6.4) +Q_NETWORK_EXPORT uint qHash(const QSslDiffieHellmanParameters &dhparam, uint seed = 0) Q_DECL_NOTHROW; + +#ifndef QT_NO_DEBUG_STREAM +class QDebug; +Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, const QSslDiffieHellmanParameters &dhparams); +#endif + +Q_NETWORK_EXPORT bool operator==(const QSslDiffieHellmanParameters &lhs, const QSslDiffieHellmanParameters &rhs) Q_DECL_NOTHROW; + +inline bool operator!=(const QSslDiffieHellmanParameters &lhs, const QSslDiffieHellmanParameters &rhs) Q_DECL_NOTHROW +{ + return !operator==(lhs, rhs); +} + +class QSslDiffieHellmanParameters +{ +public: + enum Error { + NoError, + InvalidInputDataError, + UnsafeParametersError + }; + + Q_NETWORK_EXPORT static QSslDiffieHellmanParameters defaultParameters(); + + Q_NETWORK_EXPORT QSslDiffieHellmanParameters(); + Q_NETWORK_EXPORT explicit QSslDiffieHellmanParameters(const QByteArray &encoded, QSsl::EncodingFormat format = QSsl::Pem); + Q_NETWORK_EXPORT explicit QSslDiffieHellmanParameters(QIODevice *device, QSsl::EncodingFormat format = QSsl::Pem); + Q_NETWORK_EXPORT QSslDiffieHellmanParameters(const QSslDiffieHellmanParameters &other); + Q_NETWORK_EXPORT ~QSslDiffieHellmanParameters(); + Q_NETWORK_EXPORT QSslDiffieHellmanParameters &operator=(const QSslDiffieHellmanParameters &other); +#ifdef Q_COMPILER_RVALUE_REFS + QSslDiffieHellmanParameters &operator=(QSslDiffieHellmanParameters &&other) Q_DECL_NOTHROW { swap(other); return *this; } +#endif + + void swap(QSslDiffieHellmanParameters &other) Q_DECL_NOTHROW { qSwap(d, other.d); } + + Q_NETWORK_EXPORT bool isEmpty() const Q_DECL_NOTHROW; + Q_NETWORK_EXPORT bool isValid() const Q_DECL_NOTHROW; + Q_NETWORK_EXPORT QSslDiffieHellmanParameters::Error error() const Q_DECL_NOTHROW; + Q_NETWORK_EXPORT QString errorString() const Q_DECL_NOTHROW; + +private: + QExplicitlySharedDataPointer<QSslDiffieHellmanParametersPrivate> d; + friend class QSslContext; + friend Q_NETWORK_EXPORT bool operator==(const QSslDiffieHellmanParameters &lhs, const QSslDiffieHellmanParameters &rhs) Q_DECL_NOTHROW; +#ifndef QT_NO_DEBUG_STREAM + friend Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, const QSslDiffieHellmanParameters &dhparam); +#endif + friend Q_NETWORK_EXPORT uint qHash(const QSslDiffieHellmanParameters &dhparam, uint seed) Q_DECL_NOTHROW; +}; + +Q_DECLARE_SHARED(QSslDiffieHellmanParameters) + +#endif // QT_NO_SSL + +QT_END_NAMESPACE + +#endif diff --git a/src/network/ssl/qssldiffiehellmanparameters_dummy.cpp b/src/network/ssl/qssldiffiehellmanparameters_dummy.cpp new file mode 100644 index 0000000000..42222f3f38 --- /dev/null +++ b/src/network/ssl/qssldiffiehellmanparameters_dummy.cpp @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Mikkel Krautz <mikkel@krautz.dk> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qssldiffiehellmanparameters.h" +#include "qssldiffiehellmanparameters_p.h" + +#include <QtCore/qglobal.h> +#include <QtCore/qbytearray.h> + +QT_BEGIN_NAMESPACE + +void QSslDiffieHellmanParametersPrivate::decodeDer(const QByteArray &) +{ + Q_UNIMPLEMENTED(); +} + +void QSslDiffieHellmanParametersPrivate::decodePem(const QByteArray &) +{ + Q_UNIMPLEMENTED(); +} + +QT_END_NAMESPACE diff --git a/src/network/ssl/qssldiffiehellmanparameters_openssl.cpp b/src/network/ssl/qssldiffiehellmanparameters_openssl.cpp new file mode 100644 index 0000000000..949da1b7df --- /dev/null +++ b/src/network/ssl/qssldiffiehellmanparameters_openssl.cpp @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Mikkel Krautz <mikkel@krautz.dk> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qssldiffiehellmanparameters.h" +#include "qssldiffiehellmanparameters_p.h" +#include "qsslsocket_openssl_symbols_p.h" +#include "qsslsocket.h" +#include "qsslsocket_p.h" + +#include <QtCore/qatomic.h> +#include <QtCore/qbytearray.h> +#include <QtCore/qiodevice.h> +#ifndef QT_NO_DEBUG_STREAM +#include <QtCore/qdebug.h> +#endif + +// For q_BN_is_word. +#include <openssl/bn.h> + +QT_BEGIN_NAMESPACE + +static bool isSafeDH(DH *dh) +{ + int status = 0; + int bad = 0; + + QSslSocketPrivate::ensureInitialized(); + + // Mark p < 1024 bits as unsafe. + if (q_BN_num_bits(dh->p) < 1024) { + return false; + } + + if (q_DH_check(dh, &status) != 1) + return false; + + // From https://wiki.openssl.org/index.php/Diffie-Hellman_parameters: + // + // The additional call to BN_mod_word(dh->p, 24) + // (and unmasking of DH_NOT_SUITABLE_GENERATOR) + // is performed to ensure your program accepts + // IETF group parameters. OpenSSL checks the prime + // is congruent to 11 when g = 2; while the IETF's + // primes are congruent to 23 when g = 2. + // Without the test, the IETF parameters would + // fail validation. For details, see Diffie-Hellman + // Parameter Check (when g = 2, must p mod 24 == 11?). + if (q_BN_is_word(dh->g, DH_GENERATOR_2)) { + long residue = q_BN_mod_word(dh->p, 24); + if (residue == 11 || residue == 23) + status &= ~DH_NOT_SUITABLE_GENERATOR; + } + + bad |= DH_CHECK_P_NOT_PRIME; + bad |= DH_CHECK_P_NOT_SAFE_PRIME; + bad |= DH_NOT_SUITABLE_GENERATOR; + + return !(status & bad); +} + +void QSslDiffieHellmanParametersPrivate::decodeDer(const QByteArray &der) +{ + if (der.isEmpty()) { + error = QSslDiffieHellmanParameters::InvalidInputDataError; + return; + } + + const unsigned char *data = reinterpret_cast<const unsigned char *>(der.data()); + int len = der.size(); + + QSslSocketPrivate::ensureInitialized(); + + DH *dh = q_d2i_DHparams(NULL, &data, len); + if (dh) { + if (isSafeDH(dh)) + derData = der; + else + error = QSslDiffieHellmanParameters::UnsafeParametersError; + } else { + error = QSslDiffieHellmanParameters::InvalidInputDataError; + } + + q_DH_free(dh); +} + +void QSslDiffieHellmanParametersPrivate::decodePem(const QByteArray &pem) +{ + if (pem.isEmpty()) { + error = QSslDiffieHellmanParameters::InvalidInputDataError; + return; + } + + if (!QSslSocket::supportsSsl()) { + error = QSslDiffieHellmanParameters::InvalidInputDataError; + return; + } + + QSslSocketPrivate::ensureInitialized(); + + BIO *bio = q_BIO_new_mem_buf(const_cast<char *>(pem.data()), pem.size()); + if (!bio) { + error = QSslDiffieHellmanParameters::InvalidInputDataError; + return; + } + + DH *dh = Q_NULLPTR; + q_PEM_read_bio_DHparams(bio, &dh, 0, 0); + + if (dh) { + if (isSafeDH(dh)) { + char *buf = Q_NULLPTR; + int len = q_i2d_DHparams(dh, reinterpret_cast<unsigned char **>(&buf)); + if (len > 0) + derData = QByteArray(buf, len); + else + error = QSslDiffieHellmanParameters::InvalidInputDataError; + } else { + error = QSslDiffieHellmanParameters::UnsafeParametersError; + } + } else { + error = QSslDiffieHellmanParameters::InvalidInputDataError; + } + + q_DH_free(dh); + q_BIO_free(bio); +} + +QT_END_NAMESPACE diff --git a/src/network/ssl/qssldiffiehellmanparameters_p.h b/src/network/ssl/qssldiffiehellmanparameters_p.h new file mode 100644 index 0000000000..a5da4e51fc --- /dev/null +++ b/src/network/ssl/qssldiffiehellmanparameters_p.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Mikkel Krautz <mikkel@krautz.dk> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QSSLDIFFIEHELLMANPARAMETERS_P_H +#define QSSLDIFFIEHELLMANPARAMETERS_P_H + +#include "qsslkey.h" + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qssldiffiehellmanparameters.cpp. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#include <QSharedData> + +#include "qssldiffiehellmanparameters.h" +#include "qsslsocket_p.h" // includes wincrypt.h + +QT_BEGIN_NAMESPACE + +class QSslDiffieHellmanParametersPrivate : public QSharedData +{ +public: + QSslDiffieHellmanParametersPrivate() : error(QSslDiffieHellmanParameters::NoError) {}; + + void decodeDer(const QByteArray &der); + void decodePem(const QByteArray &pem); + + QSslDiffieHellmanParameters::Error error; + QByteArray derData; +}; + +QT_END_NAMESPACE + +#endif // QSSLDIFFIEHELLMANPARAMETERS_P_H diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp index 472db3aa81..580b0fbdde 100644 --- a/src/network/ssl/qsslsocket.cpp +++ b/src/network/ssl/qsslsocket.cpp @@ -836,15 +836,7 @@ bool QSslSocket::atEnd() const // Note! docs copied from QAbstractSocket::flush() bool QSslSocket::flush() { - Q_D(QSslSocket); -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "QSslSocket::flush()"; -#endif - if (d->mode != UnencryptedMode) - // encrypt any unencrypted bytes in our buffer - d->transmit(); - - return d->plainSocket ? d->plainSocket->flush() : false; + return d_func()->flush(); } /*! @@ -923,6 +915,8 @@ void QSslSocket::setSslConfiguration(const QSslConfiguration &configuration) d->configuration.privateKey = configuration.privateKey(); d->configuration.ciphers = configuration.ciphers(); d->configuration.ellipticCurves = configuration.ellipticCurves(); + d->configuration.preSharedKeyIdentityHint = configuration.preSharedKeyIdentityHint(); + d->configuration.dhParams = configuration.diffieHellmanParameters(); d->configuration.caCertificates = configuration.caCertificates(); d->configuration.peerVerifyDepth = configuration.peerVerifyDepth(); d->configuration.peerVerifyMode = configuration.peerVerifyMode(); @@ -2615,6 +2609,22 @@ QByteArray QSslSocketPrivate::peek(qint64 maxSize) /*! \internal */ +bool QSslSocketPrivate::flush() +{ +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcSsl) << "QSslSocketPrivate::flush()"; +#endif + if (mode != QSslSocket::UnencryptedMode) { + // encrypt any unencrypted bytes in our buffer + transmit(); + } + + return plainSocket && plainSocket->flush(); +} + +/*! + \internal +*/ bool QSslSocketPrivate::rootCertOnDemandLoadingSupported() { return s_loadRootCertsOnDemand; diff --git a/src/network/ssl/qsslsocket.h b/src/network/ssl/qsslsocket.h index c069ff2f9d..1f2ed7687b 100644 --- a/src/network/ssl/qsslsocket.h +++ b/src/network/ssl/qsslsocket.h @@ -116,7 +116,7 @@ public: bool canReadLine() const Q_DECL_OVERRIDE; void close() Q_DECL_OVERRIDE; bool atEnd() const Q_DECL_OVERRIDE; - bool flush(); + bool flush(); // ### Qt6: remove me (implementation moved to private flush()) void abort(); // From QAbstractSocket: diff --git a/src/network/ssl/qsslsocket_mac.cpp b/src/network/ssl/qsslsocket_mac.cpp index c164342166..194acbeacc 100644 --- a/src/network/ssl/qsslsocket_mac.cpp +++ b/src/network/ssl/qsslsocket_mac.cpp @@ -68,57 +68,18 @@ QT_BEGIN_NAMESPACE static SSLContextRef qt_createSecureTransportContext(QSslSocket::SslMode mode) { const bool isServer = mode == QSslSocket::SslServerMode; - SSLContextRef context = Q_NULLPTR; - -#ifndef Q_OS_OSX const SSLProtocolSide side = isServer ? kSSLServerSide : kSSLClientSide; // We never use kSSLDatagramType, so it's kSSLStreamType unconditionally. - context = SSLCreateContext(Q_NULLPTR, side, kSSLStreamType); + SSLContextRef context = SSLCreateContext(Q_NULLPTR, side, kSSLStreamType); if (!context) qCWarning(lcSsl) << "SSLCreateContext failed"; -#else // Q_OS_OSX - -#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_8, __IPHONE_NA) - if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_8) { - const SSLProtocolSide side = isServer ? kSSLServerSide : kSSLClientSide; - // We never use kSSLDatagramType, so it's kSSLStreamType unconditionally. - context = SSLCreateContext(Q_NULLPTR, side, kSSLStreamType); - if (!context) - qCWarning(lcSsl) << "SSLCreateContext failed"; - } else { -#else - { -#endif - const OSStatus errCode = SSLNewContext(isServer, &context); - if (errCode != noErr || !context) - qCWarning(lcSsl) << "SSLNewContext failed with error:" << errCode; - } -#endif // !Q_OS_OSX - return context; } static void qt_releaseSecureTransportContext(SSLContextRef context) { - if (!context) - return; - -#ifndef Q_OS_OSX - CFRelease(context); -#else - -#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_8, __IPHONE_NA) - if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_8) { + if (context) CFRelease(context); - } else { -#else - { -#endif // QT_MAC_PLATFORM_... - const OSStatus errCode = SSLDisposeContext(context); - if (errCode != noErr) - qCWarning(lcSsl) << "SSLDisposeContext failed with error:" << errCode; - } -#endif // !Q_OS_OSX } static bool qt_setSessionProtocol(SSLContextRef context, const QSslConfigurationPrivate &configuration, @@ -132,7 +93,6 @@ static bool qt_setSessionProtocol(SSLContextRef context, const QSslConfiguration OSStatus err = noErr; -#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_8, __IPHONE_5_0) if (configuration.protocol == QSsl::SslV3) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "requesting : SSLv3"; @@ -210,117 +170,10 @@ static bool qt_setSessionProtocol(SSLContextRef context, const QSslConfiguration #endif return false; } -#endif return err == noErr; } -#ifdef Q_OS_OSX - -static bool qt_setSessionProtocolOSX(SSLContextRef context, const QSslConfigurationPrivate &configuration, - QTcpSocket *plainSocket) -{ - // This function works with (now) deprecated API that does not even exist on - // iOS but is the only API we have on OS X below 10.8 - - // Without SSLSetProtocolVersionMin/Max functions it's quite difficult - // to have the required result: - // If we use SSLSetProtocolVersion - any constant except the ones with 'Only' suffix - - // allows a negotiation and we can not set the lower limit. - // SSLSetProtocolVersionEnabled supports only a limited subset of constants, if you believe their docs: - // kSSLProtocol2 - // kSSLProtocol3 - // kTLSProtocol1 - // kSSLProtocolAll - // Here we can only have a look into the SecureTransport's code and hope that what we see there - // and what we have on 10.7 is similar: - // SSLSetProtocoLVersionEnabled actually accepts other constants also, - // called twice with two different protocols it sets a range, - // called once with a protocol (when all protocols were disabled) - // - only this protocol is enabled (without a lower limit negotiation). - - Q_ASSERT(context); - -#ifndef QSSLSOCKET_DEBUG - Q_UNUSED(plainSocket) -#endif - - OSStatus err = noErr; - - // First, disable ALL: - if (SSLSetProtocolVersionEnabled(context, kSSLProtocolAll, false) != noErr) - return false; - - if (configuration.protocol == QSsl::SslV3) { - #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "requesting : SSLv3"; - #endif - err = SSLSetProtocolVersion(context, kSSLProtocol3Only); - } else if (configuration.protocol == QSsl::TlsV1_0) { - #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "requesting : TLSv1.0"; - #endif - err = SSLSetProtocolVersion(context, kTLSProtocol1Only); - } else if (configuration.protocol == QSsl::TlsV1_1) { - #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "requesting : TLSv1.1"; - #endif - err = SSLSetProtocolVersionEnabled(context, kTLSProtocol11, true); - } else if (configuration.protocol == QSsl::TlsV1_2) { - #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "requesting : TLSv1.2"; - #endif - err = SSLSetProtocolVersionEnabled(context, kTLSProtocol12, true); - } else if (configuration.protocol == QSsl::AnyProtocol) { - #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "requesting : any"; - #endif - err = SSLSetProtocolVersionEnabled(context, kSSLProtocolAll, true); - } else if (configuration.protocol == QSsl::TlsV1SslV3) { - #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "requesting : SSLv3 - TLSv1.2"; - #endif - err = SSLSetProtocolVersionEnabled(context, kTLSProtocol12, true); - if (err == noErr) - err = SSLSetProtocolVersionEnabled(context, kSSLProtocol3, true); - } else if (configuration.protocol == QSsl::SecureProtocols) { - #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "requesting : TLSv1 - TLSv1.2"; - #endif - err = SSLSetProtocolVersionEnabled(context, kTLSProtocol12, true); - if (err == noErr) - err = SSLSetProtocolVersionEnabled(context, kTLSProtocol1, true); - } else if (configuration.protocol == QSsl::TlsV1_0OrLater) { - #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "requesting : TLSv1 - TLSv1.2"; - #endif - err = SSLSetProtocolVersionEnabled(context, kTLSProtocol12, true); - if (err == noErr) - err = SSLSetProtocolVersionEnabled(context, kTLSProtocol1, true); - } else if (configuration.protocol == QSsl::TlsV1_1OrLater) { - #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "requesting : TLSv1.1 - TLSv1.2"; - #endif - err = SSLSetProtocolVersionEnabled(context, kTLSProtocol12, true); - if (err == noErr) - err = SSLSetProtocolVersionEnabled(context, kTLSProtocol11, true); - } else if (configuration.protocol == QSsl::TlsV1_2OrLater) { - #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "requesting : TLSv1.2"; - #endif - err = SSLSetProtocolVersionEnabled(context, kTLSProtocol12, true); - } else { - #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "no protocol version found in the configuration"; - #endif - return false; - } - - return err == noErr; -} - -#endif // Q_OS_OSX - QSecureTransportContext::QSecureTransportContext(SSLContextRef c) : context(c) { @@ -351,7 +204,7 @@ bool QSslSocketPrivate::s_loadedCiphersAndCerts = false; bool QSslSocketPrivate::s_loadRootCertsOnDemand = false; -#ifndef Q_OS_IOS // dhparam is not used on iOS. (see the SSLSetDiffieHellmanParams call below) +#if !defined(QT_PLATFORM_UIKIT) // dhparam is not used on iOS or tvOS. (see the SSLSetDiffieHellmanParams call below) static const uint8_t dhparam[] = "\x30\x82\x01\x08\x02\x82\x01\x01\x00\x97\xea\xd0\x46\xf7\xae\xa7\x76\x80" "\x9c\x74\x56\x98\xd8\x56\x97\x2b\x20\x6c\x77\xe2\x82\xbb\xc8\x84\xbe\xe7" @@ -370,8 +223,8 @@ static const uint8_t dhparam[] = "\x90\x0b\x35\x64\xff\xd9\xe3\xac\xf2\xf2\xeb\x3a\x63\x02\x01\x02"; #endif -// No ioErr on iOS. (defined in MacErrors.h on OS X) -#ifdef Q_OS_IOS +// No ioErr on iOS/tvOS. (defined in MacErrors.h on OS X) +#if defined(QT_PLATFORM_UIKIT) # define ioErr -36 #endif @@ -959,21 +812,6 @@ bool QSslSocketBackendPrivate::initSslContext() return false; } -#ifdef Q_OS_OSX - if (QSysInfo::MacintoshVersion < QSysInfo::MV_10_8) { - // Starting from OS X 10.8 SSLSetSessionOption with kSSLSessionOptionBreakOnServerAuth/ - // kSSLSessionOptionBreakOnClientAuth disables automatic certificate validation. - // But for OS X versions below 10.8 we have to do it explicitly: - const OSStatus err = SSLSetEnableCertVerify(context, false); - if (err != noErr) { - destroySslContext(); - setErrorAndEmit(QSslSocket::SslInternalError, - QStringLiteral("SSLSetEnableCertVerify failed: %1").arg(err)); - return false; - } - } -#endif - if (mode == QSslSocket::SslClientMode) { // enable Server Name Indication (SNI) QString tlsHostName(verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName); @@ -1011,7 +849,7 @@ bool QSslSocketBackendPrivate::initSslContext() return false; } } -#ifndef Q_OS_IOS +#if !defined(QT_PLATFORM_UIKIT) // No SSLSetDiffieHellmanParams on iOS; calling it is optional according to docs. SSLSetDiffieHellmanParams(context, dhparam, sizeof(dhparam)); #endif @@ -1128,20 +966,7 @@ bool QSslSocketBackendPrivate::setSessionProtocol() return false; } -#ifndef Q_OS_OSX return qt_setSessionProtocol(context, configuration, plainSocket); -#else - -#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_8, __IPHONE_NA) - if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_8) { - return qt_setSessionProtocol(context, configuration, plainSocket); - } else { -#else - { -#endif - return qt_setSessionProtocolOSX(context, configuration, plainSocket); - } -#endif } bool QSslSocketBackendPrivate::canIgnoreTrustVerificationFailure() const diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp index b3820aea97..aca7507d13 100644 --- a/src/network/ssl/qsslsocket_openssl.cpp +++ b/src/network/ssl/qsslsocket_openssl.cpp @@ -201,6 +201,15 @@ static unsigned int q_ssl_psk_client_callback(SSL *ssl, Q_ASSERT(d); return d->tlsPskClientCallback(hint, identity, max_identity_len, psk, max_psk_len); } + +static unsigned int q_ssl_psk_server_callback(SSL *ssl, + const char *identity, + unsigned char *psk, unsigned int max_psk_len) +{ + QSslSocketBackendPrivate *d = reinterpret_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(ssl, QSslSocketBackendPrivate::s_indexForSSLExtraData)); + Q_ASSERT(d); + return d->tlsPskServerCallback(identity, psk, max_psk_len); +} #endif } // extern "C" @@ -436,8 +445,12 @@ bool QSslSocketBackendPrivate::initSslContext() #if OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_PSK) // Set the client callback for PSK - if (q_SSLeay() >= 0x10001000L && mode == QSslSocket::SslClientMode) - q_SSL_set_psk_client_callback(ssl, &q_ssl_psk_client_callback); + if (q_SSLeay() >= 0x10001000L) { + if (mode == QSslSocket::SslClientMode) + q_SSL_set_psk_client_callback(ssl, &q_ssl_psk_client_callback); + else if (mode == QSslSocket::SslServerMode) + q_SSL_set_psk_server_callback(ssl, &q_ssl_psk_server_callback); + } #endif return true; @@ -522,15 +535,9 @@ void QSslSocketPrivate::ensureCiphersAndCertsLoaded() #if defined(Q_OS_WIN) HINSTANCE hLib = LoadLibraryW(L"Crypt32"); if (hLib) { -#if defined(Q_OS_WINCE) - ptrCertOpenSystemStoreW = (PtrCertOpenSystemStoreW)GetProcAddress(hLib, L"CertOpenStore"); - ptrCertFindCertificateInStore = (PtrCertFindCertificateInStore)GetProcAddress(hLib, L"CertFindCertificateInStore"); - ptrCertCloseStore = (PtrCertCloseStore)GetProcAddress(hLib, L"CertCloseStore"); -#else ptrCertOpenSystemStoreW = (PtrCertOpenSystemStoreW)GetProcAddress(hLib, "CertOpenSystemStoreW"); ptrCertFindCertificateInStore = (PtrCertFindCertificateInStore)GetProcAddress(hLib, "CertFindCertificateInStore"); ptrCertCloseStore = (PtrCertCloseStore)GetProcAddress(hLib, "CertCloseStore"); -#endif if (!ptrCertOpenSystemStoreW || !ptrCertFindCertificateInStore || !ptrCertCloseStore) qCWarning(lcSsl, "could not resolve symbols in crypt32 library"); // should never happen } else { @@ -691,15 +698,7 @@ QList<QSslCertificate> QSslSocketPrivate::systemCaCertificates() #if defined(Q_OS_WIN) if (ptrCertOpenSystemStoreW && ptrCertFindCertificateInStore && ptrCertCloseStore) { HCERTSTORE hSystemStore; -#if defined(Q_OS_WINCE) - hSystemStore = ptrCertOpenSystemStoreW(CERT_STORE_PROV_SYSTEM_W, - 0, - 0, - CERT_STORE_NO_CRYPT_RELEASE_FLAG|CERT_SYSTEM_STORE_CURRENT_USER, - L"ROOT"); -#else hSystemStore = ptrCertOpenSystemStoreW(0, L"ROOT"); -#endif if(hSystemStore) { PCCERT_CONTEXT pc = NULL; while(1) { @@ -1278,6 +1277,31 @@ unsigned int QSslSocketBackendPrivate::tlsPskClientCallback(const char *hint, return pskLength; } +unsigned int QSslSocketBackendPrivate::tlsPskServerCallback(const char *identity, + unsigned char *psk, unsigned int max_psk_len) +{ + QSslPreSharedKeyAuthenticator authenticator; + + // Fill in some read-only fields (for the user) + authenticator.d->identityHint = configuration.preSharedKeyIdentityHint; + authenticator.d->identity = identity; + authenticator.d->maximumIdentityLength = 0; // user cannot set an identity + authenticator.d->maximumPreSharedKeyLength = int(max_psk_len); + + // Let the client provide the remaining bits... + Q_Q(QSslSocket); + emit q->preSharedKeyAuthenticationRequired(&authenticator); + + // No PSK set? Return now to make the handshake fail + if (authenticator.preSharedKey().isEmpty()) + return 0; + + // Copy data back into OpenSSL + const int pskLength = qMin(authenticator.preSharedKey().length(), authenticator.maximumPreSharedKeyLength()); + ::memcpy(psk, authenticator.preSharedKey().constData(), pskLength); + return pskLength; +} + #ifdef Q_OS_WIN void QSslSocketBackendPrivate::fetchCaRootForCert(const QSslCertificate &cert) @@ -1579,7 +1603,22 @@ void QSslSocketBackendPrivate::continueHandshake() } else { const unsigned char *proto = 0; unsigned int proto_len = 0; - q_SSL_get0_next_proto_negotiated(ssl, &proto, &proto_len); +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (q_SSLeay() >= 0x10002000L) { + q_SSL_get0_alpn_selected(ssl, &proto, &proto_len); + if (proto_len && mode == QSslSocket::SslClientMode) { + // Client does not have a callback that sets it ... + configuration.nextProtocolNegotiationStatus = QSslConfiguration::NextProtocolNegotiationNegotiated; + } + } + + if (!proto_len) { // Test if NPN was more lucky ... +#else + { +#endif + q_SSL_get0_next_proto_negotiated(ssl, &proto, &proto_len); + } + if (proto_len) configuration.nextNegotiatedProtocol = QByteArray(reinterpret_cast<const char *>(proto), proto_len); else diff --git a/src/network/ssl/qsslsocket_openssl_android.cpp b/src/network/ssl/qsslsocket_openssl_android.cpp index d73ed8995e..b5d2458d56 100644 --- a/src/network/ssl/qsslsocket_openssl_android.cpp +++ b/src/network/ssl/qsslsocket_openssl_android.cpp @@ -70,6 +70,7 @@ QList<QByteArray> QSslSocketPrivate::fetchSslCertificateData() QJNIEnvironmentPrivate env; jobjectArray jcertificates = static_cast<jobjectArray>(certificates.object()); const jint nCertificates = env->GetArrayLength(jcertificates); + certificateData.reserve(static_cast<int>(nCertificates)); for (int i = 0; i < nCertificates; ++i) { jbyteArray jCert = static_cast<jbyteArray>(env->GetObjectArrayElement(jcertificates, i)); diff --git a/src/network/ssl/qsslsocket_openssl_p.h b/src/network/ssl/qsslsocket_openssl_p.h index 0674c05d71..c6572315f0 100644 --- a/src/network/ssl/qsslsocket_openssl_p.h +++ b/src/network/ssl/qsslsocket_openssl_p.h @@ -143,6 +143,7 @@ public: bool checkSslErrors(); void storePeerCertificates(); unsigned int tlsPskClientCallback(const char *hint, char *identity, unsigned int max_identity_len, unsigned char *psk, unsigned int max_psk_len); + unsigned int tlsPskServerCallback(const char *identity, unsigned char *psk, unsigned int max_psk_len); #ifdef Q_OS_WIN void fetchCaRootForCert(const QSslCertificate &cert); void _q_caRootLoaded(QSslCertificate,QSslCertificate) Q_DECL_OVERRIDE; diff --git a/src/network/ssl/qsslsocket_openssl_symbols.cpp b/src/network/ssl/qsslsocket_openssl_symbols.cpp index d420f78dc7..0f9241e470 100644 --- a/src/network/ssl/qsslsocket_openssl_symbols.cpp +++ b/src/network/ssl/qsslsocket_openssl_symbols.cpp @@ -151,6 +151,10 @@ DEFINEFUNC3(int, BIO_read, BIO *a, a, void *b, b, int c, c, return -1, return) DEFINEFUNC(BIO_METHOD *, BIO_s_mem, void, DUMMYARG, return 0, return) DEFINEFUNC3(int, BIO_write, BIO *a, a, const void *b, b, int c, c, return -1, return) DEFINEFUNC(int, BN_num_bits, const BIGNUM *a, a, return 0, return) +#if OPENSSL_VERSION_NUMBER >= 0x10100000L +DEFINEFUNC2(int, BN_is_word, BIGNUM *a, a, BN_ULONG w, w, return 0, return) +#endif +DEFINEFUNC2(BN_ULONG, BN_mod_word, const BIGNUM *a, a, BN_ULONG w, w, return -1, return) #ifndef OPENSSL_NO_EC DEFINEFUNC(const EC_GROUP*, EC_KEY_get0_group, const EC_KEY* k, k, return 0, return) DEFINEFUNC(int, EC_GROUP_get_degree, const EC_GROUP* g, g, return 0, return) @@ -207,6 +211,7 @@ DEFINEFUNC4(RSA *, PEM_read_bio_RSAPrivateKey, BIO *a, a, RSA **b, b, pem_passwo #ifndef OPENSSL_NO_EC DEFINEFUNC4(EC_KEY *, PEM_read_bio_ECPrivateKey, BIO *a, a, EC_KEY **b, b, pem_password_cb *c, c, void *d, d, return 0, return) #endif +DEFINEFUNC4(DH *, PEM_read_bio_DHparams, BIO *a, a, DH **b, b, pem_password_cb *c, c, void *d, d, return 0, return) DEFINEFUNC7(int, PEM_write_bio_DSAPrivateKey, BIO *a, a, DSA *b, b, const EVP_CIPHER *c, c, unsigned char *d, d, int e, e, pem_password_cb *f, f, void *g, g, return 0, return) DEFINEFUNC7(int, PEM_write_bio_RSAPrivateKey, BIO *a, a, RSA *b, b, const EVP_CIPHER *c, c, unsigned char *d, d, int e, e, pem_password_cb *f, f, void *g, g, return 0, return) #ifndef OPENSSL_NO_EC @@ -300,6 +305,8 @@ DEFINEFUNC2(void *, SSL_get_ex_data, const SSL *ssl, ssl, int idx, idx, return N #endif #if OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_PSK) DEFINEFUNC2(void, SSL_set_psk_client_callback, SSL* ssl, ssl, q_psk_client_callback_t callback, callback, return, DUMMYARG) +DEFINEFUNC2(void, SSL_set_psk_server_callback, SSL* ssl, ssl, q_psk_server_callback_t callback, callback, return, DUMMYARG) +DEFINEFUNC2(int, SSL_CTX_use_psk_identity_hint, SSL_CTX* ctx, ctx, const char *hint, hint, return 0, return) #endif #if OPENSSL_VERSION_NUMBER >= 0x10000000L #ifndef OPENSSL_NO_SSL2 @@ -418,10 +425,24 @@ DEFINEFUNC3(void, SSL_CTX_set_next_proto_select_cb, SSL_CTX *s, s, void *arg, arg, return, DUMMYARG) DEFINEFUNC3(void, SSL_get0_next_proto_negotiated, const SSL *s, s, const unsigned char **data, data, unsigned *len, len, return, DUMMYARG) +#if OPENSSL_VERSION_NUMBER >= 0x10002000L +DEFINEFUNC3(int, SSL_set_alpn_protos, SSL *s, s, const unsigned char *protos, protos, + unsigned protos_len, protos_len, return -1, return) +DEFINEFUNC3(void, SSL_CTX_set_alpn_select_cb, SSL_CTX *s, s, + int (*cb) (SSL *ssl, const unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, void *arg), cb, + void *arg, arg, return, DUMMYARG) +DEFINEFUNC3(void, SSL_get0_alpn_selected, const SSL *s, s, const unsigned char **data, data, + unsigned *len, len, return, DUMMYARG) +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L ... #endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ... DEFINEFUNC(DH *, DH_new, DUMMYARG, DUMMYARG, return 0, return) DEFINEFUNC(void, DH_free, DH *dh, dh, return, DUMMYARG) DEFINEFUNC3(DH *, d2i_DHparams, DH**a, a, const unsigned char **pp, pp, long length, length, return 0, return) +DEFINEFUNC2(int, i2d_DHparams, DH *a, a, unsigned char **p, p, return -1, return) +DEFINEFUNC2(int, DH_check, DH *dh, dh, int *codes, codes, return 0, return) DEFINEFUNC3(BIGNUM *, BN_bin2bn, const unsigned char *s, s, int len, len, BIGNUM *ret, ret, return 0, return) #ifndef OPENSSL_NO_EC DEFINEFUNC(EC_KEY *, EC_KEY_dup, const EC_KEY *ec, ec, return 0, return) @@ -768,6 +789,10 @@ bool q_resolveOpenSslSymbols() RESOLVEFUNC(EC_GROUP_get_degree) #endif RESOLVEFUNC(BN_num_bits) +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + RESOLVEFUNC(BN_is_word) +#endif + RESOLVEFUNC(BN_mod_word) RESOLVEFUNC(CRYPTO_free) RESOLVEFUNC(CRYPTO_num_locks) RESOLVEFUNC(CRYPTO_set_id_callback) @@ -816,6 +841,7 @@ bool q_resolveOpenSslSymbols() #ifndef OPENSSL_NO_EC RESOLVEFUNC(PEM_read_bio_ECPrivateKey) #endif + RESOLVEFUNC(PEM_read_bio_DHparams) RESOLVEFUNC(PEM_write_bio_DSAPrivateKey) RESOLVEFUNC(PEM_write_bio_RSAPrivateKey) #ifndef OPENSSL_NO_EC @@ -889,6 +915,8 @@ bool q_resolveOpenSslSymbols() #endif #if OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_PSK) RESOLVEFUNC(SSL_set_psk_client_callback) + RESOLVEFUNC(SSL_set_psk_server_callback) + RESOLVEFUNC(SSL_CTX_use_psk_identity_hint) #endif RESOLVEFUNC(SSL_write) #ifndef OPENSSL_NO_SSL2 @@ -975,6 +1003,8 @@ bool q_resolveOpenSslSymbols() RESOLVEFUNC(DH_new) RESOLVEFUNC(DH_free) RESOLVEFUNC(d2i_DHparams) + RESOLVEFUNC(i2d_DHparams) + RESOLVEFUNC(DH_check) RESOLVEFUNC(BN_bin2bn) #ifndef OPENSSL_NO_EC RESOLVEFUNC(EC_KEY_dup) diff --git a/src/network/ssl/qsslsocket_openssl_symbols_p.h b/src/network/ssl/qsslsocket_openssl_symbols_p.h index 36e041b6cb..c0ddb8e888 100644 --- a/src/network/ssl/qsslsocket_openssl_symbols_p.h +++ b/src/network/ssl/qsslsocket_openssl_symbols_p.h @@ -227,6 +227,21 @@ int q_BIO_read(BIO *a, void *b, int c); BIO_METHOD *q_BIO_s_mem(); int q_BIO_write(BIO *a, const void *b, int c); int q_BN_num_bits(const BIGNUM *a); +#if OPENSSL_VERSION_NUMBER >= 0x10100000L +int q_BN_is_word(BIGNUM *a, BN_ULONG w); +#else +// BN_is_word is implemented purely as a +// macro in OpenSSL < 1.1. It doesn't +// call any functions. +// +// The implementation of BN_is_word is +// 100% the same between 1.0.0, 1.0.1 +// and 1.0.2. +// +// Users are required to include <openssl/bn.h>. +#define q_BN_is_word BN_is_word +#endif // OPENSSL_VERSION_NUMBER >= 0x10100000L +BN_ULONG q_BN_mod_word(const BIGNUM *a, BN_ULONG w); #ifndef OPENSSL_NO_EC const EC_GROUP* q_EC_KEY_get0_group(const EC_KEY* k); int q_EC_GROUP_get_degree(const EC_GROUP* g); @@ -284,6 +299,7 @@ RSA *q_PEM_read_bio_RSAPrivateKey(BIO *a, RSA **b, pem_password_cb *c, void *d); #ifndef OPENSSL_NO_EC EC_KEY *q_PEM_read_bio_ECPrivateKey(BIO *a, EC_KEY **b, pem_password_cb *c, void *d); #endif +DH *q_PEM_read_bio_DHparams(BIO *a, DH **b, pem_password_cb *c, void *d); int q_PEM_write_bio_DSAPrivateKey(BIO *a, DSA *b, const EVP_CIPHER *c, unsigned char *d, int e, pem_password_cb *f, void *g); int q_PEM_write_bio_RSAPrivateKey(BIO *a, RSA *b, const EVP_CIPHER *c, unsigned char *d, @@ -376,6 +392,9 @@ void *q_SSL_get_ex_data(const SSL *ssl, int idx); #ifndef OPENSSL_NO_PSK typedef unsigned int (*q_psk_client_callback_t)(SSL *ssl, const char *hint, char *identity, unsigned int max_identity_len, unsigned char *psk, unsigned int max_psk_len); void q_SSL_set_psk_client_callback(SSL *ssl, q_psk_client_callback_t callback); +typedef unsigned int (*q_psk_server_callback_t)(SSL *ssl, const char *identity, unsigned char *psk, unsigned int max_psk_len); +void q_SSL_set_psk_server_callback(SSL *ssl, q_psk_server_callback_t callback); +int q_SSL_CTX_use_psk_identity_hint(SSL_CTX *ctx, const char *hint); #endif // OPENSSL_NO_PSK #if OPENSSL_VERSION_NUMBER >= 0x10000000L #ifndef OPENSSL_NO_SSL2 @@ -472,6 +491,8 @@ STACK_OF(X509) *q_X509_STORE_CTX_get_chain(X509_STORE_CTX *ctx); DH *q_DH_new(); void q_DH_free(DH *dh); DH *q_d2i_DHparams(DH **a, const unsigned char **pp, long length); +int q_i2d_DHparams(DH *a, unsigned char **p); +int q_DH_check(DH *dh, int *codes); BIGNUM *q_BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret); #define q_SSL_CTX_set_tmp_dh(ctx, dh) q_SSL_CTX_ctrl((ctx), SSL_CTRL_SET_TMP_DH, 0, (char *)dh) @@ -518,6 +539,9 @@ DSA *q_d2i_DSAPrivateKey(DSA **a, unsigned char **pp, long length); #define q_PEM_write_bio_DSAPrivateKey(bp,x,enc,kstr,klen,cb,u) \ PEM_ASN1_write_bio((int (*)(void*, unsigned char**))q_i2d_DSAPrivateKey,PEM_STRING_DSA,\ bp,(char *)x,enc,kstr,klen,cb,u) +#define q_PEM_read_bio_DHparams(bp, dh, cb, u) \ + (DH *)q_PEM_ASN1_read_bio( \ + (void *(*)(void**, const unsigned char**, long int))q_d2i_DHparams, PEM_STRING_DHPARAMS, bp, (void **)x, cb, u) #endif #define q_SSL_CTX_set_options(ctx,op) q_SSL_CTX_ctrl((ctx),SSL_CTRL_OPTIONS,(op),NULL) #define q_SSL_CTX_set_mode(ctx,op) q_SSL_CTX_ctrl((ctx),SSL_CTRL_MODE,(op),NULL) @@ -558,6 +582,19 @@ void q_SSL_CTX_set_next_proto_select_cb(SSL_CTX *s, void *arg); void q_SSL_get0_next_proto_negotiated(const SSL *s, const unsigned char **data, unsigned *len); +#if OPENSSL_VERSION_NUMBER >= 0x10002000L +int q_SSL_set_alpn_protos(SSL *ssl, const unsigned char *protos, + unsigned protos_len); +void q_SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx, + int (*cb) (SSL *ssl, + const unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, + void *arg), void *arg); +void q_SSL_get0_alpn_selected(const SSL *ssl, const unsigned char **data, + unsigned *len); +#endif #endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ... // Helper function diff --git a/src/network/ssl/qsslsocket_p.h b/src/network/ssl/qsslsocket_p.h index 5d4d52cd6d..e791b9d166 100644 --- a/src/network/ssl/qsslsocket_p.h +++ b/src/network/ssl/qsslsocket_p.h @@ -89,11 +89,7 @@ QT_BEGIN_NAMESPACE #endif #if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) -#if defined(Q_OS_WINCE) - typedef HCERTSTORE (WINAPI *PtrCertOpenSystemStoreW)(LPCSTR, DWORD, HCRYPTPROV_LEGACY, DWORD, const void*); -#else typedef HCERTSTORE (WINAPI *PtrCertOpenSystemStoreW)(HCRYPTPROV_LEGACY, LPCWSTR); -#endif typedef PCCERT_CONTEXT (WINAPI *PtrCertFindCertificateInStore)(HCERTSTORE, DWORD, DWORD, DWORD, const void*, PCCERT_CONTEXT); typedef BOOL (WINAPI *PtrCertCloseStore)(HCERTSTORE, DWORD); #endif // Q_OS_WIN && !Q_OS_WINRT @@ -193,6 +189,7 @@ public: virtual qint64 peek(char *data, qint64 maxSize) Q_DECL_OVERRIDE; virtual QByteArray peek(qint64 maxSize) Q_DECL_OVERRIDE; + bool flush() Q_DECL_OVERRIDE; // Platform specific functions virtual void startClientEncryption() = 0; diff --git a/src/network/ssl/qsslsocket_winrt.cpp b/src/network/ssl/qsslsocket_winrt.cpp index edc8ca0bbb..f5dc9fcdcd 100644 --- a/src/network/ssl/qsslsocket_winrt.cpp +++ b/src/network/ssl/qsslsocket_winrt.cpp @@ -215,7 +215,9 @@ QList<QSslCipher> QSslSocketBackendPrivate::defaultCiphers() const QString protocolStrings[] = { QStringLiteral("SSLv3"), QStringLiteral("TLSv1"), QStringLiteral("TLSv1.1"), QStringLiteral("TLSv1.2") }; const QSsl::SslProtocol protocols[] = { QSsl::SslV3, QSsl::TlsV1_0, QSsl::TlsV1_1, QSsl::TlsV1_2 }; - for (int i = 0; i < ARRAYSIZE(protocols); ++i) { + const int size = static_cast<int>(ARRAYSIZE(protocols)); + ciphers.reserve(size); + for (int i = 0; i < size; ++i) { QSslCipher cipher; cipher.d->isNull = false; cipher.d->name = QStringLiteral("WINRT"); diff --git a/src/network/ssl/ssl.pri b/src/network/ssl/ssl.pri index c70664ef9b..edbbeadf51 100644 --- a/src/network/ssl/ssl.pri +++ b/src/network/ssl/ssl.pri @@ -1,5 +1,5 @@ # OpenSSL support; compile in QSslSocket. -contains(QT_CONFIG, ssl) | contains(QT_CONFIG, openssl) | contains(QT_CONFIG, openssl-linked) { +contains(QT_CONFIG, ssl) { HEADERS += ssl/qasn1element_p.h \ ssl/qssl.h \ ssl/qssl_p.h \ @@ -9,6 +9,8 @@ contains(QT_CONFIG, ssl) | contains(QT_CONFIG, openssl) | contains(QT_CONFIG, op ssl/qsslconfiguration_p.h \ ssl/qsslcipher.h \ ssl/qsslcipher_p.h \ + ssl/qssldiffiehellmanparameters.h \ + ssl/qssldiffiehellmanparameters_p.h \ ssl/qsslellipticcurve.h \ ssl/qsslerror.h \ ssl/qsslkey.h \ @@ -24,6 +26,7 @@ contains(QT_CONFIG, ssl) | contains(QT_CONFIG, openssl) | contains(QT_CONFIG, op ssl/qsslcertificate.cpp \ ssl/qsslconfiguration.cpp \ ssl/qsslcipher.cpp \ + ssl/qssldiffiehellmanparameters.cpp \ ssl/qsslellipticcurve.cpp \ ssl/qsslkey_p.cpp \ ssl/qsslerror.cpp \ @@ -35,6 +38,7 @@ contains(QT_CONFIG, ssl) | contains(QT_CONFIG, openssl) | contains(QT_CONFIG, op HEADERS += ssl/qsslsocket_winrt_p.h SOURCES += ssl/qsslcertificate_qt.cpp \ ssl/qsslcertificate_winrt.cpp \ + ssl/qssldiffiehellmanparameters_dummy.cpp \ ssl/qsslkey_qt.cpp \ ssl/qsslkey_winrt.cpp \ ssl/qsslsocket_winrt.cpp \ @@ -44,43 +48,45 @@ contains(QT_CONFIG, ssl) | contains(QT_CONFIG, openssl) | contains(QT_CONFIG, op contains(QT_CONFIG, securetransport) { HEADERS += ssl/qsslsocket_mac_p.h SOURCES += ssl/qsslcertificate_qt.cpp \ + ssl/qssldiffiehellmanparameters_dummy.cpp \ ssl/qsslkey_qt.cpp \ ssl/qsslkey_mac.cpp \ ssl/qsslsocket_mac_shared.cpp \ ssl/qsslsocket_mac.cpp \ ssl/qsslellipticcurve_dummy.cpp } -} -contains(QT_CONFIG, openssl) | contains(QT_CONFIG, openssl-linked) { - HEADERS += ssl/qsslcontext_openssl_p.h \ - ssl/qsslsocket_openssl_p.h \ - ssl/qsslsocket_openssl_symbols_p.h - SOURCES += ssl/qsslcertificate_openssl.cpp \ - ssl/qsslcontext_openssl.cpp \ - ssl/qsslellipticcurve_openssl.cpp \ - ssl/qsslkey_openssl.cpp \ - ssl/qsslsocket_openssl.cpp \ - ssl/qsslsocket_openssl_symbols.cpp + contains(QT_CONFIG, openssl) | contains(QT_CONFIG, openssl-linked) { + HEADERS += ssl/qsslcontext_openssl_p.h \ + ssl/qsslsocket_openssl_p.h \ + ssl/qsslsocket_openssl_symbols_p.h + SOURCES += ssl/qsslcertificate_openssl.cpp \ + ssl/qsslcontext_openssl.cpp \ + ssl/qssldiffiehellmanparameters_openssl.cpp \ + ssl/qsslellipticcurve_openssl.cpp \ + ssl/qsslkey_openssl.cpp \ + ssl/qsslsocket_openssl.cpp \ + ssl/qsslsocket_openssl_symbols.cpp - darwin:SOURCES += ssl/qsslsocket_mac_shared.cpp + darwin:SOURCES += ssl/qsslsocket_mac_shared.cpp - android: SOURCES += ssl/qsslsocket_openssl_android.cpp + android: SOURCES += ssl/qsslsocket_openssl_android.cpp - # Add optional SSL libs - # Static linking of OpenSSL with msvc: - # - Binaries http://slproweb.com/products/Win32OpenSSL.html - # - also needs -lUser32 -lAdvapi32 -lGdi32 -lCrypt32 - # - libs in <OPENSSL_DIR>\lib\VC\static - # - configure: -openssl -openssl-linked -I <OPENSSL_DIR>\include -L <OPENSSL_DIR>\lib\VC\static OPENSSL_LIBS="-lUser32 -lAdvapi32 -lGdi32" OPENSSL_LIBS_DEBUG="-lssleay32MDd -llibeay32MDd" OPENSSL_LIBS_RELEASE="-lssleay32MD -llibeay32MD" + # Add optional SSL libs + # Static linking of OpenSSL with msvc: + # - Binaries http://slproweb.com/products/Win32OpenSSL.html + # - also needs -lUser32 -lAdvapi32 -lGdi32 -lCrypt32 + # - libs in <OPENSSL_DIR>\lib\VC\static + # - configure: -openssl -openssl-linked -I <OPENSSL_DIR>\include -L <OPENSSL_DIR>\lib\VC\static OPENSSL_LIBS="-lUser32 -lAdvapi32 -lGdi32" OPENSSL_LIBS_DEBUG="-lssleay32MDd -llibeay32MDd" OPENSSL_LIBS_RELEASE="-lssleay32MD -llibeay32MD" - CONFIG(debug, debug|release) { - LIBS_PRIVATE += $$OPENSSL_LIBS_DEBUG - } else { - LIBS_PRIVATE += $$OPENSSL_LIBS_RELEASE - } + CONFIG(debug, debug|release) { + LIBS_PRIVATE += $$OPENSSL_LIBS_DEBUG + } else { + LIBS_PRIVATE += $$OPENSSL_LIBS_RELEASE + } - QMAKE_CXXFLAGS += $$OPENSSL_CFLAGS - LIBS_PRIVATE += $$OPENSSL_LIBS - win32: LIBS_PRIVATE += -lcrypt32 + QMAKE_CXXFLAGS += $$OPENSSL_CFLAGS + LIBS_PRIVATE += $$OPENSSL_LIBS + win32: LIBS_PRIVATE += -lcrypt32 + } } |