summaryrefslogtreecommitdiffstats
path: root/xbmc/utils/HttpRangeUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/utils/HttpRangeUtils.cpp')
-rw-r--r--xbmc/utils/HttpRangeUtils.cpp424
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