MythTV  master
mythhttpranges.cpp
Go to the documentation of this file.
1 // MythTV
2 #include "mythlogging.h"
3 #include "http/mythhttpranges.h"
4 #include "http/mythhttpdata.h"
5 #include "http/mythhttpfile.h"
7 
8 #define LOC QString("HTTPRange: ")
9 
10 auto sumrange = [](uint64_t Cum, HTTPRange Range) { return ((Range.second + 1) - Range.first) + Cum; };
11 
13 {
14  if (!Response || Request.isEmpty())
15  return;
16 
17  // Check content type and size first
18  auto * data = std::get_if<HTTPData>(&(Response->m_response));
19  auto * file = std::get_if<HTTPFile>(&(Response->m_response));
20  int64_t size = data ? (*data)->size() : file ? (*file)->size() : 0;
21  if (size < 1)
22  return;
23 
24  // Parse
25  HTTPRanges& ranges = data ? (*data)->m_ranges : (*file)->m_ranges;
26  int64_t& partialsize = data ? (*data)->m_partialSize : (*file)->m_partialSize;
27  MythHTTPStatus status = MythHTTPRanges::ParseRanges(Request, size, ranges, partialsize);
28  if ((status == HTTPRequestedRangeNotSatisfiable) || (status == HTTPPartialContent))
29  Response->m_status = status;
30 }
31 
33 {
34  if (!Response || (Response->m_status != HTTPPartialContent))
35  return;
36 
37  auto * data = std::get_if<HTTPData>(&(Response->m_response));
38  auto * file = std::get_if<HTTPFile>(&(Response->m_response));
39  int64_t size = data ? (*data)->size() : file ? (*file)->size() : 0;
40  if (size < 1)
41  return;
42 
43  HTTPRanges& ranges = data ? (*data)->m_ranges : (*file)->m_ranges;
44  if (ranges.size() < 2)
45  return;
46 
47  auto & mime = data ? (*data)->m_mimeType : (*file)->m_mimeType;
49  for (auto & range : ranges)
50  {
51  auto header = QString("\r\n--%1\r\nContent-Type: %2\r\nContent-Range: %3\r\n\r\n")
53  MythHTTPRanges::GetRangeHeader(range, size));
54  headers.emplace_back(MythHTTPData::Create(qPrintable(header)));
55 
56  }
57  headers.emplace_back(MythHTTPData::Create(qPrintable(QString("\r\n--%1--")
58  .arg(s_multipartBoundary))));
59  std::reverse(headers.begin(), headers.end());
60  int64_t headersize = 0;
61  for (auto & header : headers)
62  headersize += header->size();
63  if (data)
64  {
65  (*data)->m_multipartHeaders = headers;
66  (*data)->m_multipartHeaderSize = headersize;
67  }
68  if (file)
69  {
70  (*file)->m_multipartHeaders = headers;
71  (*file)->m_multipartHeaderSize = headersize;
72  }
73 }
74 
75 QString MythHTTPRanges::GetRangeHeader(HTTPRange& Range, int64_t Size)
76 {
77  return QString("bytes %1-%2/%3").arg(Range.first).arg(Range.second).arg(Size);
78 }
79 
80 QString MythHTTPRanges::GetRangeHeader(HTTPRanges& Ranges, int64_t Size)
81 {
82  if (Ranges.empty())
83  return "ErRoR";
84  if (Ranges.size() == 1)
85  return MythHTTPRanges::GetRangeHeader(Ranges[0], Size);
86  return "multipart/byteranges; boundary=" + s_multipartBoundary;
87 }
88 
89 HTTPMulti MythHTTPRanges::HandleRangeWrite(HTTPVariant Data, int64_t Available, int64_t &ToWrite, int64_t &Offset)
90 {
91  HTTPMulti result { nullptr, nullptr };
92  auto * data = std::get_if<HTTPData>(&Data);
93  auto * file = std::get_if<HTTPFile>(&Data);
94  if (!(data || file))
95  return result;
96 
97  int64_t partialsize = data ? (*data)->m_partialSize : (*file)->m_partialSize;
98  auto written = static_cast<uint64_t>(data ? (*data)->m_written : (*file)->m_written);
99  HTTPRanges& ranges = data ? (*data)->m_ranges : (*file)->m_ranges;
100  HTTPContents& headers = data ? (*data)->m_multipartHeaders : (*file)->m_multipartHeaders;
101 
102  uint64_t oldsum = 0;
103  for (const auto& range : ranges)
104  {
105  uint64_t newsum = sumrange(oldsum, range);
106  if (oldsum <= written && written < newsum)
107  {
108  // This is the start of a multipart range. Add the start headers.
109  if ((oldsum == written) && !headers.empty())
110  {
111  result.first = headers.back();
112  headers.pop_back();
113  }
114 
115  // Restrict the write to the remainder of this range if necessary
116  ToWrite = std::min(Available, static_cast<int64_t>(newsum - written));
117 
118  // We need to ensure we send from the correct offset in the data
119  Offset = static_cast<int64_t>(range.first - oldsum);
120  if (file)
121  Offset += written;
122 
123  // This is the end of the multipart ranges. Add the closing header
124  // (We add this first so we can pop the contents when sending the headers)
125  if (((static_cast<int64_t>(written) + ToWrite) >= partialsize) && !headers.empty())
126  {
127  result.second = headers.back();
128  headers.pop_back();
129  }
130 
131  return result;
132  }
133  oldsum = newsum;
134  }
135  return result;
136 }
137 
149 MythHTTPStatus MythHTTPRanges::ParseRanges(const QString& Request, int64_t TotalSize,
150  HTTPRanges& Ranges, int64_t& PartialSize)
151 {
152  MythHTTPStatus result = HTTPOK;
153  Ranges.clear();
154 
155  // Just don't...
156  if (TotalSize < 2)
157  return result;
158 
159  LOG(VB_HTTP, LOG_DEBUG, LOC + QString("Parsing: '%1'").arg(Request));
160 
161  // Split unit and range(s)
162  QStringList initial = Request.toLower().split("=");
163  if (initial.size() != 2)
164  {
165  LOG(VB_HTTP, LOG_INFO, LOC + QString("Failed to parse ranges: '%1'").arg(Request));
166  return result;
167  }
168 
169  // We only recognise bytes
170  if (!initial.at(0).contains("bytes"))
171  {
172  LOG(VB_HTTP, LOG_INFO, LOC + QString("Unkown range units: '%1'").arg(initial.at(0)));
173  return result;
174  }
175 
176  // Split out individual ranges
177  QStringList rangelist = initial.at(1).split(",", Qt::SkipEmptyParts);
178 
179  // No ranges
180  if (rangelist.isEmpty())
181  {
182  LOG(VB_HTTP, LOG_INFO, LOC + QString("Failed to find ranges: '%1'").arg(initial.at(1)));
183  return result;
184  }
185 
186  // Iterate over items
187  HTTPRanges ranges;
188  for (auto & range : rangelist)
189  {
190  QStringList parts = range.split("-");
191  if (parts.size() != 2)
192  {
193  LOG(VB_HTTP, LOG_INFO, LOC + QString("Failed to parse range: '%1'").arg(range));
194  return result;
195  }
196 
197  bool validrange = false;
198  bool startvalid = false;
199  bool endvalid = false;
200  bool startstr = !parts.at(0).isEmpty();
201  bool endstr = !parts.at(1).isEmpty();
202  uint64_t start = parts.at(0).toULongLong(&startvalid);
203  uint64_t end = parts.at(1).toULongLong(&endvalid);
204 
205  // Regular range
206  if (startstr && endstr && startvalid && endvalid)
207  {
208  validrange = ((end < static_cast<uint64_t>(TotalSize)) && (start <= end));
209  }
210  // Start only
211  else if (startstr && startvalid)
212  {
213  end = static_cast<uint64_t>(TotalSize - 1);
214  validrange = start <= end;
215  }
216  // End only
217  else if (endstr && endvalid)
218  {
219  uint64_t size = end;
220  end = static_cast<uint64_t>(TotalSize) - 1;
221  start = static_cast<uint64_t>(TotalSize) - size;
222  validrange = start <= end;
223  }
224 
225  if (!validrange)
226  {
227  LOG(VB_HTTP, LOG_INFO, LOC + QString("Invalid HTTP range: '%1'").arg(range));
229  }
230 
231  ranges.emplace_back(start, end);
232  }
233 
234  // Rationalise so that we have the simplest, most efficient list of ranges:
235  // - sort
236  // - merge overlaps (also allowing for minimum multipart header overhead)
237  // - remove duplicates
238  static const int s_overhead = 90; // rough worst case overhead per part for multipart requests
239  if (ranges.size() > 1)
240  {
241  auto equals = [](HTTPRange First, HTTPRange Second)
242  { return (First.first == Second.first) && (First.second == Second.second); };
243  auto lessthan = [](HTTPRange First, HTTPRange Second)
244  { return First.first < Second.first; };
245 
246  // we MUST sort first
247  std::sort(ranges.begin(), ranges.end(), lessthan);
248 
249  if (VERBOSE_LEVEL_CHECK(VB_HTTP, LOG_INFO))
250  {
251  QStringList debug;
252  for (const auto & range : ranges)
253  debug.append(QString("%1:%2").arg(range.first).arg(range.second));
254  LOG(VB_HTTP, LOG_INFO, LOC + QString("Sorted ranges: %1").arg(debug.join(" ")));
255  }
256 
257  // merge, de-duplicate, repeat...
258  bool finished = false;
259  while (!finished)
260  {
261  finished = true;
262  for (uint i = 0; i < (ranges.size() - 1); ++i)
263  {
264  if ((ranges[i].second + s_overhead) >= ranges[i + 1].first)
265  {
266  finished = false;
267  ranges[i + 1].first = ranges[i].first;
268  // N.B we have sorted by start byte - not end
269  uint64_t end = std::max(ranges[i].second, ranges[i + 1].second);
270  ranges[i].second = ranges[i + 1].second = end;
271  }
272  }
273 
274  auto last = std::unique(ranges.begin(), ranges.end(), equals);
275  ranges.erase(last, ranges.end());
276  }
277  }
278 
279  // Sum the expected number of bytes to be sent
280  PartialSize = std::accumulate(ranges.cbegin(), ranges.cend(), static_cast<uint64_t>(0), sumrange);
281  Ranges = ranges;
282 
283  if (VERBOSE_LEVEL_CHECK(VB_HTTP, LOG_INFO))
284  {
285  QStringList debug;
286  for (const auto & range : ranges)
287  debug.append(QString("%1:%2").arg(range.first).arg(range.second));
288  LOG(VB_HTTP, LOG_INFO, LOC + QString("Final ranges : %1").arg(debug.join(" ")));
289  LOG(VB_HTTP, LOG_INFO, LOC + QString("Bytes to send: %1").arg(PartialSize));
290  }
291 
292  return HTTPPartialContent;
293 }
HTTPOK
@ HTTPOK
Definition: mythhttptypes.h:107
MythHTTPRanges::BuildMultipartHeaders
static void BuildMultipartHeaders(MythHTTPResponse *Response)
Definition: mythhttpranges.cpp:32
s_multipartBoundary
static QString s_multipartBoundary
Definition: mythhttptypes.h:152
HTTPVariant
std::variant< std::monostate, HTTPData, HTTPFile > HTTPVariant
Definition: mythhttptypes.h:41
VERBOSE_LEVEL_CHECK
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
MythHTTPResponse
Definition: mythhttpresponse.h:16
mythhttpranges.h
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythHTTPRanges::HandleRangeWrite
static HTTPMulti HandleRangeWrite(HTTPVariant Data, int64_t Available, int64_t &ToWrite, int64_t &Offset)
Definition: mythhttpranges.cpp:89
build_compdb.file
file
Definition: build_compdb.py:55
mythhttpdata.h
mythhttpfile.h
MythHTTPResponse::m_status
MythHTTPStatus m_status
Definition: mythhttpresponse.h:52
mythlogging.h
HTTPMulti
std::pair< HTTPData, HTTPData > HTTPMulti
Definition: mythhttptypes.h:48
HTTPContents
std::vector< HTTPData > HTTPContents
Definition: mythhttptypes.h:37
MythHTTPStatus
MythHTTPStatus
Definition: mythhttptypes.h:104
MythHTTPData::Create
static HTTPData Create()
Definition: mythhttpdata.cpp:4
MythHTTPResponse::m_response
HTTPVariant m_response
Definition: mythhttpresponse.h:57
debug
VERBOSE_PREAMBLE Most debug(nodatabase, notimestamp, noextra)") VERBOSE_MAP(VB_GENERAL
MythHTTP::GetContentType
static QString GetContentType(const MythMimeType &Mime)
Definition: mythhttptypes.h:324
HTTPRequestedRangeNotSatisfiable
@ HTTPRequestedRangeNotSatisfiable
Definition: mythhttptypes.h:118
mythhttpresponse.h
music163.headers
dictionary headers
Definition: music163.py:26
uint
unsigned int uint
Definition: compat.h:81
MythHTTPRanges::ParseRanges
static MythHTTPStatus ParseRanges(const QString &Request, int64_t TotalSize, HTTPRanges &Ranges, int64_t &PartialSize)
Parse a range request header.
Definition: mythhttpranges.cpp:149
hardwareprofile.distros.mythtv_data.request.Request
def Request(url=None)
Definition: distros/mythtv_data/request.py:62
MythHTTPRanges::GetRangeHeader
static QString GetRangeHeader(HTTPRanges &Ranges, int64_t Size)
Definition: mythhttpranges.cpp:80
HTTPRange
std::pair< uint64_t, uint64_t > HTTPRange
Definition: mythhttpranges.h:9
HTTPRanges
std::vector< HTTPRange > HTTPRanges
Definition: mythhttpranges.h:10
sumrange
auto sumrange
Definition: mythhttpranges.cpp:10
LOC
#define LOC
Definition: mythhttpranges.cpp:8
HTTPPartialContent
@ HTTPPartialContent
Definition: mythhttptypes.h:109
MythHTTPRanges::HandleRangeRequest
static void HandleRangeRequest(MythHTTPResponse *Response, const QString &Request)
Definition: mythhttpranges.cpp:12