diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
commit | c04dcc2e7d834218ef2d4194331e383402495ae1 (patch) | |
tree | 7333e38d10d75386e60f336b80c2443c1166031d /xbmc/utils/HttpRangeUtils.cpp | |
parent | Initial commit. (diff) | |
download | kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip |
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/utils/HttpRangeUtils.cpp')
-rw-r--r-- | xbmc/utils/HttpRangeUtils.cpp | 424 |
1 files changed, 424 insertions, 0 deletions
diff --git a/xbmc/utils/HttpRangeUtils.cpp b/xbmc/utils/HttpRangeUtils.cpp new file mode 100644 index 0000000..18ad32b --- /dev/null +++ b/xbmc/utils/HttpRangeUtils.cpp @@ -0,0 +1,424 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include <algorithm> + +#include "HttpRangeUtils.h" +#include "Util.h" +#ifdef HAS_WEB_SERVER +#include "network/httprequesthandler/IHTTPRequestHandler.h" +#endif // HAS_WEB_SERVER +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +#include <inttypes.h> + +#define HEADER_NEWLINE "\r\n" +#define HEADER_SEPARATOR HEADER_NEWLINE HEADER_NEWLINE +#define HEADER_BOUNDARY "--" + +#define HEADER_CONTENT_RANGE_VALUE "{}" +#define HEADER_CONTENT_RANGE_VALUE_UNKNOWN "*" +#define HEADER_CONTENT_RANGE_FORMAT_BYTES "bytes " HEADER_CONTENT_RANGE_VALUE "-" HEADER_CONTENT_RANGE_VALUE "/" +#define CONTENT_RANGE_FORMAT_TOTAL HEADER_CONTENT_RANGE_FORMAT_BYTES HEADER_CONTENT_RANGE_VALUE +#define CONTENT_RANGE_FORMAT_TOTAL_UNKNOWN HEADER_CONTENT_RANGE_FORMAT_BYTES HEADER_CONTENT_RANGE_VALUE_UNKNOWN + +CHttpRange::CHttpRange(uint64_t firstPosition, uint64_t lastPosition) + : m_first(firstPosition), + m_last(lastPosition) +{ } + +bool CHttpRange::operator<(const CHttpRange &other) const +{ + return (m_first < other.m_first) || + (m_first == other.m_first && m_last < other.m_last); +} + +bool CHttpRange::operator==(const CHttpRange &other) const +{ + return m_first == other.m_first && m_last == other.m_last; +} + +bool CHttpRange::operator!=(const CHttpRange &other) const +{ + return !(*this == other); +} + +uint64_t CHttpRange::GetLength() const +{ + if (!IsValid()) + return 0; + + return m_last - m_first + 1; +} + +void CHttpRange::SetLength(uint64_t length) +{ + m_last = m_first + length - 1; +} + +bool CHttpRange::IsValid() const +{ + return m_last >= m_first; +} + +CHttpResponseRange::CHttpResponseRange() + : CHttpRange(), + m_data(NULL) +{ } + +CHttpResponseRange::CHttpResponseRange(uint64_t firstPosition, uint64_t lastPosition) + : CHttpRange(firstPosition, lastPosition), + m_data(NULL) +{ } + +CHttpResponseRange::CHttpResponseRange(const void* data, uint64_t firstPosition, uint64_t lastPosition) + : CHttpRange(firstPosition, lastPosition), + m_data(data) +{ } + +CHttpResponseRange::CHttpResponseRange(const void* data, uint64_t length) + : CHttpRange(0, length - 1), + m_data(data) +{ } + +bool CHttpResponseRange::operator==(const CHttpResponseRange &other) const +{ + if (!CHttpRange::operator==(other)) + return false; + + return m_data == other.m_data; +} + +bool CHttpResponseRange::operator!=(const CHttpResponseRange &other) const +{ + return !(*this == other); +} + +void CHttpResponseRange::SetData(const void* data, uint64_t length) +{ + if (length == 0) + return; + + m_first = 0; + + SetData(data); + SetLength(length); +} + +void CHttpResponseRange::SetData(const void* data, uint64_t firstPosition, uint64_t lastPosition) +{ + SetData(data); + SetFirstPosition(firstPosition); + SetLastPosition(lastPosition); +} + +bool CHttpResponseRange::IsValid() const +{ + if (!CHttpRange::IsValid()) + return false; + + return m_data != NULL; +} + +CHttpRanges::CHttpRanges() +: m_ranges() +{ } + +CHttpRanges::CHttpRanges(const HttpRanges& httpRanges) +: m_ranges(httpRanges) +{ + SortAndCleanup(); +} + +bool CHttpRanges::Get(size_t index, CHttpRange& range) const +{ + if (index >= Size()) + return false; + + range = m_ranges.at(index); + return true; +} + +bool CHttpRanges::GetFirst(CHttpRange& range) const +{ + if (m_ranges.empty()) + return false; + + range = m_ranges.front(); + return true; +} + +bool CHttpRanges::GetLast(CHttpRange& range) const +{ + if (m_ranges.empty()) + return false; + + range = m_ranges.back(); + return true; +} + +bool CHttpRanges::GetFirstPosition(uint64_t& position) const +{ + if (m_ranges.empty()) + return false; + + position = m_ranges.front().GetFirstPosition(); + return true; +} + +bool CHttpRanges::GetLastPosition(uint64_t& position) const +{ + if (m_ranges.empty()) + return false; + + position = m_ranges.back().GetLastPosition(); + return true; +} + +uint64_t CHttpRanges::GetLength() const +{ + uint64_t length = 0; + for (HttpRanges::const_iterator range = m_ranges.begin(); range != m_ranges.end(); ++range) + length += range->GetLength(); + + return length; +} + +bool CHttpRanges::GetTotalRange(CHttpRange& range) const +{ + if (m_ranges.empty()) + return false; + + uint64_t firstPosition, lastPosition; + if (!GetFirstPosition(firstPosition) || !GetLastPosition(lastPosition)) + return false; + + range.SetFirstPosition(firstPosition); + range.SetLastPosition(lastPosition); + + return range.IsValid(); +} + +void CHttpRanges::Add(const CHttpRange& range) +{ + if (!range.IsValid()) + return; + + m_ranges.push_back(range); + + SortAndCleanup(); +} + +void CHttpRanges::Remove(size_t index) +{ + if (index >= Size()) + return; + + m_ranges.erase(m_ranges.begin() + index); +} + +void CHttpRanges::Clear() +{ + m_ranges.clear(); +} + +bool CHttpRanges::Parse(const std::string& header) +{ + return Parse(header, std::numeric_limits<uint64_t>::max()); +} + +bool CHttpRanges::Parse(const std::string& header, uint64_t totalLength) +{ + m_ranges.clear(); + + if (header.empty() || totalLength == 0 || !StringUtils::StartsWithNoCase(header, "bytes=")) + return false; + + uint64_t lastPossiblePosition = totalLength - 1; + + // remove "bytes=" from the beginning + std::string rangesValue = header.substr(6); + + // split the value of the "Range" header by "," + std::vector<std::string> rangeValues = StringUtils::Split(rangesValue, ","); + + for (std::vector<std::string>::const_iterator range = rangeValues.begin(); range != rangeValues.end(); ++range) + { + // there must be a "-" in the range definition + if (range->find("-") == std::string::npos) + return false; + + std::vector<std::string> positions = StringUtils::Split(*range, "-"); + if (positions.size() != 2) + return false; + + bool hasStart = false; + uint64_t start = 0; + bool hasEnd = false; + uint64_t end = 0; + + // parse the start and end positions + if (!positions.front().empty()) + { + if (!StringUtils::IsNaturalNumber(positions.front())) + return false; + + start = str2uint64(positions.front(), 0); + hasStart = true; + } + if (!positions.back().empty()) + { + if (!StringUtils::IsNaturalNumber(positions.back())) + return false; + + end = str2uint64(positions.back(), 0); + hasEnd = true; + } + + // nothing defined at all + if (!hasStart && !hasEnd) + return false; + + // make sure that the end position makes sense + if (hasEnd) + end = std::min(end, lastPossiblePosition); + + if (!hasStart && hasEnd) + { + // the range is defined as the number of bytes from the end + start = totalLength - end; + end = lastPossiblePosition; + } + else if (hasStart && !hasEnd) + end = lastPossiblePosition; + + // make sure the start position makes sense + if (start > lastPossiblePosition) + return false; + + // make sure that the start position is smaller or equal to the end position + if (end < start) + return false; + + m_ranges.push_back(CHttpRange(start, end)); + } + + if (m_ranges.empty()) + return false; + + SortAndCleanup(); + return !m_ranges.empty(); +} + +void CHttpRanges::SortAndCleanup() +{ + // sort the ranges by their first position + std::sort(m_ranges.begin(), m_ranges.end()); + + // check for overlapping ranges + for (HttpRanges::iterator range = m_ranges.begin() + 1; range != m_ranges.end();) + { + HttpRanges::iterator previous = range - 1; + + // check if the current and the previous range overlap + if (previous->GetLastPosition() + 1 >= range->GetFirstPosition()) + { + // combine the previous and the current ranges by setting the last position of the previous range + // to the last position of the current range + previous->SetLastPosition(range->GetLastPosition()); + + // then remove the current range which is not needed anymore + range = m_ranges.erase(range); + } + else + ++range; + } +} + +std::string HttpRangeUtils::GenerateContentRangeHeaderValue(const CHttpRange* range) +{ + if (range == NULL) + return ""; + + return StringUtils::Format(CONTENT_RANGE_FORMAT_TOTAL, range->GetFirstPosition(), range->GetLastPosition(), range->GetLength()); +} + +std::string HttpRangeUtils::GenerateContentRangeHeaderValue(uint64_t start, uint64_t end, uint64_t total) +{ + if (total > 0) + return StringUtils::Format(CONTENT_RANGE_FORMAT_TOTAL, start, end, total); + + return StringUtils::Format(CONTENT_RANGE_FORMAT_TOTAL_UNKNOWN, start, end); +} + +#ifdef HAS_WEB_SERVER + +std::string HttpRangeUtils::GenerateMultipartBoundary() +{ + static char chars[] = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + // create a string of length 30 to 40 and pre-fill it with "-" + size_t count = static_cast<size_t>(CUtil::GetRandomNumber()) % 11 + 30; + std::string boundary(count, '-'); + + for (size_t i = static_cast<size_t>(CUtil::GetRandomNumber()) % 5 + 8; i < count; i++) + boundary.replace(i, 1, 1, chars[static_cast<size_t>(CUtil::GetRandomNumber()) % 64]); + + return boundary; +} + +std::string HttpRangeUtils::GenerateMultipartBoundaryContentType(const std::string& multipartBoundary) +{ + if (multipartBoundary.empty()) + return ""; + + return "multipart/byteranges; boundary=" + multipartBoundary; +} + +std::string HttpRangeUtils::GenerateMultipartBoundaryWithHeader(const std::string& multipartBoundary, const std::string& contentType) +{ + if (multipartBoundary.empty()) + return ""; + + std::string boundaryWithHeader = HEADER_BOUNDARY + multipartBoundary + HEADER_NEWLINE; + if (!contentType.empty()) + boundaryWithHeader += MHD_HTTP_HEADER_CONTENT_TYPE ": " + contentType + HEADER_NEWLINE; + + return boundaryWithHeader; +} + +std::string HttpRangeUtils::GenerateMultipartBoundaryWithHeader(const std::string& multipartBoundary, const std::string& contentType, const CHttpRange* range) +{ + if (multipartBoundary.empty() || range == NULL) + return ""; + + return GenerateMultipartBoundaryWithHeader(GenerateMultipartBoundaryWithHeader(multipartBoundary, contentType), range); +} + +std::string HttpRangeUtils::GenerateMultipartBoundaryWithHeader(const std::string& multipartBoundaryWithContentType, const CHttpRange* range) +{ + if (multipartBoundaryWithContentType.empty() || range == NULL) + return ""; + + std::string boundaryWithHeader = multipartBoundaryWithContentType; + boundaryWithHeader += MHD_HTTP_HEADER_CONTENT_RANGE ": " + GenerateContentRangeHeaderValue(range); + boundaryWithHeader += HEADER_SEPARATOR; + + return boundaryWithHeader; +} + +std::string HttpRangeUtils::GenerateMultipartBoundaryEnd(const std::string& multipartBoundary) +{ + if (multipartBoundary.empty()) + return ""; + + return HEADER_NEWLINE HEADER_BOUNDARY + multipartBoundary + HEADER_BOUNDARY; +} + +#endif // HAS_WEB_SERVER |