summaryrefslogtreecommitdiffstats
path: root/xbmc/utils/HttpRangeUtils.cpp
diff options
context:
space:
mode:
authormanuel <manuel@mausz.at>2020-10-19 00:52:24 +0200
committermanuel <manuel@mausz.at>2020-10-19 00:52:24 +0200
commitbe933ef2241d79558f91796cc5b3a161f72ebf9c (patch)
treefe3ab2f130e20c99001f2d7a81d610c78c96a3f4 /xbmc/utils/HttpRangeUtils.cpp
parent5f8335c1e49ce108ef3481863833c98efa00411b (diff)
downloadkodi-pvr-build-be933ef2241d79558f91796cc5b3a161f72ebf9c.tar.gz
kodi-pvr-build-be933ef2241d79558f91796cc5b3a161f72ebf9c.tar.bz2
kodi-pvr-build-be933ef2241d79558f91796cc5b3a161f72ebf9c.zip
sync with upstream
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..e25ccf6
--- /dev/null
+++ b/xbmc/utils/HttpRangeUtils.cpp
@@ -0,0 +1,424 @@
1/*
2 * Copyright (C) 2015-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include <algorithm>
10
11#include "HttpRangeUtils.h"
12#include "Util.h"
13#ifdef HAS_WEB_SERVER
14#include "network/httprequesthandler/IHTTPRequestHandler.h"
15#endif // HAS_WEB_SERVER
16#include "utils/StringUtils.h"
17#include "utils/Variant.h"
18
19#include <inttypes.h>
20
21#define HEADER_NEWLINE "\r\n"
22#define HEADER_SEPARATOR HEADER_NEWLINE HEADER_NEWLINE
23#define HEADER_BOUNDARY "--"
24
25#define HEADER_CONTENT_RANGE_VALUE "%" PRIu64
26#define HEADER_CONTENT_RANGE_VALUE_UNKNOWN "*"
27#define HEADER_CONTENT_RANGE_FORMAT_BYTES "bytes " HEADER_CONTENT_RANGE_VALUE "-" HEADER_CONTENT_RANGE_VALUE "/"
28#define CONTENT_RANGE_FORMAT_TOTAL HEADER_CONTENT_RANGE_FORMAT_BYTES HEADER_CONTENT_RANGE_VALUE
29#define CONTENT_RANGE_FORMAT_TOTAL_UNKNOWN HEADER_CONTENT_RANGE_FORMAT_BYTES HEADER_CONTENT_RANGE_VALUE_UNKNOWN
30
31CHttpRange::CHttpRange(uint64_t firstPosition, uint64_t lastPosition)
32 : m_first(firstPosition),
33 m_last(lastPosition)
34{ }
35
36bool CHttpRange::operator<(const CHttpRange &other) const
37{
38 return (m_first < other.m_first) ||
39 (m_first == other.m_first && m_last < other.m_last);
40}
41
42bool CHttpRange::operator==(const CHttpRange &other) const
43{
44 return m_first == other.m_first && m_last == other.m_last;
45}
46
47bool CHttpRange::operator!=(const CHttpRange &other) const
48{
49 return !(*this == other);
50}
51
52uint64_t CHttpRange::GetLength() const
53{
54 if (!IsValid())
55 return 0;
56
57 return m_last - m_first + 1;
58}
59
60void CHttpRange::SetLength(uint64_t length)
61{
62 m_last = m_first + length - 1;
63}
64
65bool CHttpRange::IsValid() const
66{
67 return m_last >= m_first;
68}
69
70CHttpResponseRange::CHttpResponseRange()
71 : CHttpRange(),
72 m_data(NULL)
73{ }
74
75CHttpResponseRange::CHttpResponseRange(uint64_t firstPosition, uint64_t lastPosition)
76 : CHttpRange(firstPosition, lastPosition),
77 m_data(NULL)
78{ }
79
80CHttpResponseRange::CHttpResponseRange(const void* data, uint64_t firstPosition, uint64_t lastPosition)
81 : CHttpRange(firstPosition, lastPosition),
82 m_data(data)
83{ }
84
85CHttpResponseRange::CHttpResponseRange(const void* data, uint64_t length)
86 : CHttpRange(0, length - 1),
87 m_data(data)
88{ }
89
90bool CHttpResponseRange::operator==(const CHttpResponseRange &other) const
91{
92 if (!CHttpRange::operator==(other))
93 return false;
94
95 return m_data == other.m_data;
96}
97
98bool CHttpResponseRange::operator!=(const CHttpResponseRange &other) const
99{
100 return !(*this == other);
101}
102
103void CHttpResponseRange::SetData(const void* data, uint64_t length)
104{
105 if (length == 0)
106 return;
107
108 m_first = 0;
109
110 SetData(data);
111 SetLength(length);
112}
113
114void CHttpResponseRange::SetData(const void* data, uint64_t firstPosition, uint64_t lastPosition)
115{
116 SetData(data);
117 SetFirstPosition(firstPosition);
118 SetLastPosition(lastPosition);
119}
120
121bool CHttpResponseRange::IsValid() const
122{
123 if (!CHttpRange::IsValid())
124 return false;
125
126 return m_data != NULL;
127}
128
129CHttpRanges::CHttpRanges()
130: m_ranges()
131{ }
132
133CHttpRanges::CHttpRanges(const HttpRanges& httpRanges)
134: m_ranges(httpRanges)
135{
136 SortAndCleanup();
137}
138
139bool CHttpRanges::Get(size_t index, CHttpRange& range) const
140{
141 if (index >= Size())
142 return false;
143
144 range = m_ranges.at(index);
145 return true;
146}
147
148bool CHttpRanges::GetFirst(CHttpRange& range) const
149{
150 if (m_ranges.empty())
151 return false;
152
153 range = m_ranges.front();
154 return true;
155}
156
157bool CHttpRanges::GetLast(CHttpRange& range) const
158{
159 if (m_ranges.empty())
160 return false;
161
162 range = m_ranges.back();
163 return true;
164}
165
166bool CHttpRanges::GetFirstPosition(uint64_t& position) const
167{
168 if (m_ranges.empty())
169 return false;
170
171 position = m_ranges.front().GetFirstPosition();
172 return true;
173}
174
175bool CHttpRanges::GetLastPosition(uint64_t& position) const
176{
177 if (m_ranges.empty())
178 return false;
179
180 position = m_ranges.back().GetLastPosition();
181 return true;
182}
183
184uint64_t CHttpRanges::GetLength() const
185{
186 uint64_t length = 0;
187 for (HttpRanges::const_iterator range = m_ranges.begin(); range != m_ranges.end(); ++range)
188 length += range->GetLength();
189
190 return length;
191}
192
193bool CHttpRanges::GetTotalRange(CHttpRange& range) const
194{
195 if (m_ranges.empty())
196 return false;
197
198 uint64_t firstPosition, lastPosition;
199 if (!GetFirstPosition(firstPosition) || !GetLastPosition(lastPosition))
200 return false;
201
202 range.SetFirstPosition(firstPosition);
203 range.SetLastPosition(lastPosition);
204
205 return range.IsValid();
206}
207
208void CHttpRanges::Add(const CHttpRange& range)
209{
210 if (!range.IsValid())
211 return;
212
213 m_ranges.push_back(range);
214
215 SortAndCleanup();
216}
217
218void CHttpRanges::Remove(size_t index)
219{
220 if (index >= Size())
221 return;
222
223 m_ranges.erase(m_ranges.begin() + index);
224}
225
226void CHttpRanges::Clear()
227{
228 m_ranges.clear();
229}
230
231bool CHttpRanges::Parse(const std::string& header)
232{
233 return Parse(header, std::numeric_limits<uint64_t>::max());
234}
235
236bool CHttpRanges::Parse(const std::string& header, uint64_t totalLength)
237{
238 m_ranges.clear();
239
240 if (header.empty() || totalLength == 0 || !StringUtils::StartsWithNoCase(header, "bytes="))
241 return false;
242
243 uint64_t lastPossiblePosition = totalLength - 1;
244
245 // remove "bytes=" from the beginning
246 std::string rangesValue = header.substr(6);
247
248 // split the value of the "Range" header by ","
249 std::vector<std::string> rangeValues = StringUtils::Split(rangesValue, ",");
250
251 for (std::vector<std::string>::const_iterator range = rangeValues.begin(); range != rangeValues.end(); ++range)
252 {
253 // there must be a "-" in the range definition
254 if (range->find("-") == std::string::npos)
255 return false;
256
257 std::vector<std::string> positions = StringUtils::Split(*range, "-");
258 if (positions.size() != 2)
259 return false;
260
261 bool hasStart = false;
262 uint64_t start = 0;
263 bool hasEnd = false;
264 uint64_t end = 0;
265
266 // parse the start and end positions
267 if (!positions.front().empty())
268 {
269 if (!StringUtils::IsNaturalNumber(positions.front()))
270 return false;
271
272 start = str2uint64(positions.front(), 0);
273 hasStart = true;
274 }
275 if (!positions.back().empty())
276 {
277 if (!StringUtils::IsNaturalNumber(positions.back()))
278 return false;
279
280 end = str2uint64(positions.back(), 0);
281 hasEnd = true;
282 }
283
284 // nothing defined at all
285 if (!hasStart && !hasEnd)
286 return false;
287
288 // make sure that the end position makes sense
289 if (hasEnd)
290 end = std::min(end, lastPossiblePosition);
291
292 if (!hasStart && hasEnd)
293 {
294 // the range is defined as the number of bytes from the end
295 start = totalLength - end;
296 end = lastPossiblePosition;
297 }
298 else if (hasStart && !hasEnd)
299 end = lastPossiblePosition;
300
301 // make sure the start position makes sense
302 if (start > lastPossiblePosition)
303 return false;
304
305 // make sure that the start position is smaller or equal to the end position
306 if (end < start)
307 return false;
308
309 m_ranges.push_back(CHttpRange(start, end));
310 }
311
312 if (m_ranges.empty())
313 return false;
314
315 SortAndCleanup();
316 return !m_ranges.empty();
317}
318
319void CHttpRanges::SortAndCleanup()
320{
321 // sort the ranges by their first position
322 std::sort(m_ranges.begin(), m_ranges.end());
323
324 // check for overlapping ranges
325 for (HttpRanges::iterator range = m_ranges.begin() + 1; range != m_ranges.end();)
326 {
327 HttpRanges::iterator previous = range - 1;
328
329 // check if the current and the previous range overlap
330 if (previous->GetLastPosition() + 1 >= range->GetFirstPosition())
331 {
332 // combine the previous and the current ranges by setting the last position of the previous range
333 // to the last position of the current range
334 previous->SetLastPosition(range->GetLastPosition());
335
336 // then remove the current range which is not needed anymore
337 range = m_ranges.erase(range);
338 }
339 else
340 ++range;
341 }
342}
343
344std::string HttpRangeUtils::GenerateContentRangeHeaderValue(const CHttpRange* range)
345{
346 if (range == NULL)
347 return "";
348
349 return StringUtils::Format(CONTENT_RANGE_FORMAT_TOTAL, range->GetFirstPosition(), range->GetLastPosition(), range->GetLength());
350}
351
352std::string HttpRangeUtils::GenerateContentRangeHeaderValue(uint64_t start, uint64_t end, uint64_t total)
353{
354 if (total > 0)
355 return StringUtils::Format(CONTENT_RANGE_FORMAT_TOTAL, start, end, total);
356
357 return StringUtils::Format(CONTENT_RANGE_FORMAT_TOTAL_UNKNOWN, start, end);
358}
359
360#ifdef HAS_WEB_SERVER
361
362std::string HttpRangeUtils::GenerateMultipartBoundary()
363{
364 static char chars[] = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
365
366 // create a string of length 30 to 40 and pre-fill it with "-"
367 size_t count = static_cast<size_t>(CUtil::GetRandomNumber()) % 11 + 30;
368 std::string boundary(count, '-');
369
370 for (size_t i = static_cast<size_t>(CUtil::GetRandomNumber()) % 5 + 8; i < count; i++)
371 boundary.replace(i, 1, 1, chars[static_cast<size_t>(CUtil::GetRandomNumber()) % 64]);
372
373 return boundary;
374}
375
376std::string HttpRangeUtils::GenerateMultipartBoundaryContentType(const std::string& multipartBoundary)
377{
378 if (multipartBoundary.empty())
379 return "";
380
381 return "multipart/byteranges; boundary=" + multipartBoundary;
382}
383
384std::string HttpRangeUtils::GenerateMultipartBoundaryWithHeader(const std::string& multipartBoundary, const std::string& contentType)
385{
386 if (multipartBoundary.empty())
387 return "";
388
389 std::string boundaryWithHeader = HEADER_BOUNDARY + multipartBoundary + HEADER_NEWLINE;
390 if (!contentType.empty())
391 boundaryWithHeader += MHD_HTTP_HEADER_CONTENT_TYPE ": " + contentType + HEADER_NEWLINE;
392
393 return boundaryWithHeader;
394}
395
396std::string HttpRangeUtils::GenerateMultipartBoundaryWithHeader(const std::string& multipartBoundary, const std::string& contentType, const CHttpRange* range)
397{
398 if (multipartBoundary.empty() || range == NULL)
399 return "";
400
401 return GenerateMultipartBoundaryWithHeader(GenerateMultipartBoundaryWithHeader(multipartBoundary, contentType), range);
402}
403
404std::string HttpRangeUtils::GenerateMultipartBoundaryWithHeader(const std::string& multipartBoundaryWithContentType, const CHttpRange* range)
405{
406 if (multipartBoundaryWithContentType.empty() || range == NULL)
407 return "";
408
409 std::string boundaryWithHeader = multipartBoundaryWithContentType;
410 boundaryWithHeader += MHD_HTTP_HEADER_CONTENT_RANGE ": " + GenerateContentRangeHeaderValue(range);
411 boundaryWithHeader += HEADER_SEPARATOR;
412
413 return boundaryWithHeader;
414}
415
416std::string HttpRangeUtils::GenerateMultipartBoundaryEnd(const std::string& multipartBoundary)
417{
418 if (multipartBoundary.empty())
419 return "";
420
421 return HEADER_NEWLINE HEADER_BOUNDARY + multipartBoundary + HEADER_BOUNDARY;
422}
423
424#endif // HAS_WEB_SERVER