11#define LOC QString("HTTPRange: ")
13auto sumrange = [](uint64_t Cum,
HTTPRange Range) {
return ((Range.second + 1) - Range.first) + Cum; };
17 if (!Response ||
Request.isEmpty())
21 auto * data = std::get_if<HTTPData>(&(Response->
m_response));
25 size = (*data)->size();
27 size = (*file)->size();
32 HTTPRanges& ranges = data ? (*data)->m_ranges : (*file)->m_ranges;
33 int64_t& partialsize = data ? (*data)->m_partialSize : (*file)->m_partialSize;
44 auto * data = std::get_if<HTTPData>(&(Response->
m_response));
48 size = (*data)->size();
50 size = (*file)->size();
54 HTTPRanges& ranges = data ? (*data)->m_ranges : (*file)->m_ranges;
55 if (ranges.size() < 2)
58 auto & mime = data ? (*data)->m_mimeType : (*file)->m_mimeType;
60 for (
auto & range : ranges)
62 auto header = QString(
"\r\n--%1\r\nContent-Type: %2\r\nContent-Range: %3\r\n\r\n")
71 int64_t headersize = 0;
73 headersize += header->size();
76 (*data)->m_multipartHeaders =
headers;
77 (*data)->m_multipartHeaderSize = headersize;
81 (*file)->m_multipartHeaders =
headers;
82 (*file)->m_multipartHeaderSize = headersize;
88 return QString(
"bytes %1-%2/%3").arg(Range.first).arg(Range.second).arg(Size);
95 if (Ranges.size() == 1)
103 auto * data = std::get_if<HTTPData>(&Data);
104 auto *
file = std::get_if<HTTPFile>(&Data);
108 int64_t partialsize = data ? (*data)->m_partialSize : (*file)->m_partialSize;
109 auto written =
static_cast<uint64_t
>(data ? (*data)->m_written : (*file)->m_written);
110 HTTPRanges& ranges = data ? (*data)->m_ranges : (*file)->m_ranges;
114 for (
const auto& range : ranges)
116 uint64_t newsum =
sumrange(oldsum, range);
117 if (oldsum <= written && written < newsum)
120 if ((oldsum == written) && !
headers.empty())
127 ToWrite = std::min(Available,
static_cast<int64_t
>(newsum - written));
130 Offset =
static_cast<int64_t
>(range.first - oldsum);
136 if (((
static_cast<int64_t
>(written) + ToWrite) >= partialsize) && !
headers.empty())
138 result.second =
headers.back();
170 LOG(VB_HTTP, LOG_DEBUG,
LOC + QString(
"Parsing: '%1'").arg(
Request));
173 QStringList initial =
Request.toLower().split(
"=");
174 if (initial.size() != 2)
176 LOG(VB_HTTP, LOG_INFO,
LOC + QString(
"Failed to parse ranges: '%1'").arg(
Request));
181 if (!initial.at(0).contains(
"bytes"))
183 LOG(VB_HTTP, LOG_INFO,
LOC + QString(
"Unkown range units: '%1'").arg(initial.at(0)));
188 QStringList rangelist = initial.at(1).split(
",", Qt::SkipEmptyParts);
191 if (rangelist.isEmpty())
193 LOG(VB_HTTP, LOG_INFO,
LOC + QString(
"Failed to find ranges: '%1'").arg(initial.at(1)));
199 for (
auto & range : rangelist)
201 QStringList parts = range.split(
"-");
202 if (parts.size() != 2)
204 LOG(VB_HTTP, LOG_INFO,
LOC + QString(
"Failed to parse range: '%1'").arg(range));
208 bool validrange =
false;
209 bool startvalid =
false;
210 bool endvalid =
false;
211 bool startstr = !parts.at(0).isEmpty();
212 bool endstr = !parts.at(1).isEmpty();
213 uint64_t start = parts.at(0).toULongLong(&startvalid);
214 uint64_t end = parts.at(1).toULongLong(&endvalid);
217 if (startstr && endstr && startvalid && endvalid)
219 validrange = ((end < static_cast<uint64_t>(TotalSize)) && (start <= end));
222 else if (startstr && startvalid)
224 end =
static_cast<uint64_t
>(TotalSize - 1);
225 validrange = start <= end;
228 else if (endstr && endvalid)
231 end =
static_cast<uint64_t
>(TotalSize) - 1;
232 start =
static_cast<uint64_t
>(TotalSize) - size;
233 validrange = start <= end;
238 LOG(VB_HTTP, LOG_INFO,
LOC + QString(
"Invalid HTTP range: '%1'").arg(range));
242 ranges.emplace_back(start, end);
249 static const int s_overhead = 90;
250 if (ranges.size() > 1)
253 {
return (First.first == Second.first) && (First.second == Second.second); };
255 {
return First.first < Second.first; };
258 std::ranges::sort(ranges, lessthan);
263 for (
const auto & range : ranges)
264 debug.append(QString(
"%1:%2").arg(range.first).arg(range.second));
265 LOG(VB_HTTP, LOG_INFO,
LOC + QString(
"Sorted ranges: %1").arg(
debug.join(
" ")));
269 bool finished =
false;
273 for (
uint i = 0; i < (ranges.size() - 1); ++i)
275 if ((ranges[i].second + s_overhead) >= ranges[i + 1].first)
278 ranges[i + 1].first = ranges[i].first;
280 uint64_t end = std::max(ranges[i].second, ranges[i + 1].second);
281 ranges[i].second = ranges[i + 1].second = end;
285 auto [first, last] = std::ranges::unique(ranges, equals);
286 ranges.erase(first, last);
291 PartialSize = std::accumulate(ranges.cbegin(), ranges.cend(),
static_cast<uint64_t
>(0),
sumrange);
297 for (
const auto & range : ranges)
298 debug.append(QString(
"%1:%2").arg(range.first).arg(range.second));
299 LOG(VB_HTTP, LOG_INFO,
LOC + QString(
"Final ranges : %1").arg(
debug.join(
" ")));
300 LOG(VB_HTTP, LOG_INFO,
LOC + QString(
"Bytes to send: %1").arg(PartialSize));
static QString GetRangeHeader(HTTPRanges &Ranges, int64_t Size)
static MythHTTPStatus ParseRanges(const QString &Request, int64_t TotalSize, HTTPRanges &Ranges, int64_t &PartialSize)
Parse a range request header.
static void BuildMultipartHeaders(MythHTTPResponse *Response)
static HTTPMulti HandleRangeWrite(HTTPVariant Data, int64_t Available, int64_t &ToWrite, int64_t &Offset)
static void HandleRangeRequest(MythHTTPResponse *Response, const QString &Request)
static QString GetContentType(const MythMimeType &Mime)
std::vector< HTTPRange > HTTPRanges
std::pair< uint64_t, uint64_t > HTTPRange
@ HTTPRequestedRangeNotSatisfiable
std::variant< std::monostate, HTTPData, HTTPFile > HTTPVariant
std::vector< HTTPData > HTTPContents
std::pair< HTTPData, HTTPData > HTTPMulti
static QString s_multipartBoundary
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
VERBOSE_PREAMBLE Most debug(nodatabase, notimestamp, noextra)") VERBOSE_MAP(VB_GENERAL