1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
<?php
/*
* This file is part of the ICanBoogie package.
*
* (c) Olivier Laviale <olivier.laviale@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace ICanBoogie\HTTP;
use ICanBoogie\Accessor\AccessorTrait;
/**
* Representation of a request range.
*
* @property bool $is_satisfiable Whether the range is satisfiable.
* @property bool $is_total Whether the range is actually the total.
* @property int $length Length of the range, suitable for the `Content-Length` header field.
* @property int $max_length Maximum bytes to copy, suitable for the `stream_copy_to_stream()` function.
* @property int $offset The offset where to start to copy data, suitable for the `stream_copy_to_stream()` function.
*/
class RequestRange
{
use AccessorTrait;
/**
* Creates a new instance.
*
* @param Headers $headers
* @param int $total
* @param string $etag
*
* @return RequestRange|null A new instance, or `null` if the range is not defined or deprecated
* (because `If-Range` doesn't match `$etag`).
*/
static public function from(Headers $headers, $total, $etag)
{
$range = (string) $headers['Range'];
if (!$range)
{
return null;
}
$if_range = (string) $headers['If-Range'];
if ($if_range && $if_range !== $etag)
{
return null;
}
$range = self::resolve_range($range, $total);
if (!$range)
{
return null;
}
return new static($range[0], $range[1], $total);
}
/**
* Resolves the range.
*
* @param string $range
* @param int $total
*
* @return array|null An array with `[ $start, $end ]`, or `null` if the range is invalid.
*/
static private function resolve_range($range, $total)
{
if (!preg_match('/^bytes\=(\d*)\-(\d*)$/', $range, $matches))
{
return null;
}
list(, $start, $end) = $matches;
if ($start === '' && $end === '')
{
return null;
}
$end = $end === '' ? $total - 1 : (int) $end;
if ($start === '')
{
$start = $total - $end;
$end = $total - 1;
}
else
{
$start = (int) $start;
}
return [ $start, $end ];
}
/**
* @var int
*/
private $start;
/**
* @var int
*/
private $end;
/**
* @var int
*/
private $total;
/**
* @param int $start
* @param int $end
* @param int $total
*/
protected function __construct($start, $end, $total)
{
$this->start = $start;
$this->end = $end;
$this->total = $total;
}
/**
* Formats the instance as a string suitable for the `Content-Range` header field.
*
* @return string
*/
public function __toString()
{
return sprintf('bytes %s-%s/%s', $this->start, $this->end, $this->total);
}
/**
* Whether the range is satisfiable.
*
* @return bool
*/
protected function get_is_satisfiable()
{
$start = $this->start;
$end = $this->end;
return !($start < 0 || $start >= $end || $end > $this->total - 1);
}
/**
* Whether the range is actually the total.
*
* @return bool
*/
protected function get_is_total()
{
return $this->start === 0 && $this->end === $this->total - 1;
}
/**
* Returns the length of the range, suitable for the `Content-Length` header field.
*
* @return int
*/
protected function get_length()
{
return $this->end - $this->start + 1;
}
/**
* Maximum bytes to copy, suitable for the `stream_copy_to_stream()` function.
*
* @return int
*/
protected function get_max_length()
{
return $this->end < $this->total ? $this->length : -1;
}
/**
* Returns the offset where to start to copy data, suitable for the
* `stream_copy_to_stream()` function.
*
* @return int
*/
protected function get_offset()
{
return $this->start;
}
}