forked from libyerman/queue-stats
-
Notifications
You must be signed in to change notification settings - Fork 0
/
sendfile.class.php
executable file
·193 lines (169 loc) · 5.73 KB
/
sendfile.class.php
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
<?php
/**
* Class Sendfile
* Send local file to download
* Support "206 Partial Content"
*/
class Sendfile {
# Локаль
public $Locale = 'ru_RU.utf-8';
# Скорость в Кб/сек
public $Speed = 2048;
# Content Type
public $ContentType = null;
# Content Type автоопределение
public $ContentTypeAuto = true;
# Content Disposition
public $ContentInline = false;
# Имя файла для сохранения
public $FileName = null;
# Путь к файлу
public $Path = null;
/**
* Parse HTTP Range header
* http://tools.ietf.org/html/rfc2616#section-14.35
* return array of Range on success
* false on syntactically invalid byte-range-spec
* empty array on unsatisfiable bytes-range-set
* @param int $entity_body_length
* @param string range_header
* @return array|bool
*/
private function parseRangeRequest($entity_body_length, $range_header) {
$range_list = array();
if ($entity_body_length == 0) {
return $range_list; // mark unsatisfiable
}
// The only range unit defined by HTTP/1.1 is "bytes". HTTP/1.1
// implementations MAY ignore ranges specified using other units.
// Range unit "bytes" is case-insensitive
if (preg_match('#bytes=([^;]+)#i', $range_header, $match)) {
$range_set = $match[1];
} else {
return false;
}
// Wherever this construct is used, null elements are allowed, but do
// not contribute to the count of elements present. That is,
// "(element), , (element) " is permitted, but counts as only two elements.
$range_spec_list = preg_split('#,#', $range_set, null, PREG_SPLIT_NO_EMPTY);
foreach ($range_spec_list as $i => $range_spec) {
$range_spec = trim($range_spec);
if (preg_match('#^(\d+)\-$#', $range_spec, $match)) {
$first_byte_pos = $match[1];
if ($first_byte_pos > $entity_body_length) {
continue;
}
$first_pos = $first_byte_pos;
$last_pos = $entity_body_length - 1;
} else if (preg_match('#^(\d+)\-(\d+)$#', $range_spec, $match)) {
$first_byte_pos = $match[1];
$last_byte_pos = $match[2];
// If the last-byte-pos value is present, it MUST be greater than or
// equal to the first-byte-pos in that byte-range-spec
if ($last_byte_pos < $first_byte_pos) {
return false;
}
$first_pos = $first_byte_pos;
$last_pos = min($entity_body_length - 1, $last_byte_pos);
} else if (preg_match('#^\-(\d+)$#', $range_spec, $match)) {
$suffix_length = $match[1];
if ($suffix_length == 0) {
continue;
}
$first_pos = $entity_body_length - min($entity_body_length, $suffix_length);
$last_pos = $entity_body_length - 1;
} else {
return false;
}
$range_list[$i]['firstPos'] = $first_pos;
$range_list[$i]['lastPos'] = $last_pos;
$range_list[$i]['chunkSize'] = $last_pos-$first_pos;
}
return $range_list;
}
# Получить имя файла
private function getFileName($path) {
$pi = pathinfo($path);
return $pi['basename'];
}
# Задать локаль
private function setLocale() {
setlocale(LC_ALL, $this->Locale);
putenv('LC_ALL=' . $this->Locale);
}
# Получить mime
private function getMime($path) {
$res = 'application/octet-stream';
# Включено автоопределение
if ($this->ContentTypeAuto === true && class_exists('\finfo')) {
$finfo = new \finfo(FILEINFO_MIME_TYPE);
$res = $finfo->file($path);
}
return $res;
}
# Отправка файла
public function send() {
$this->setLocale();
if (isset($this->Path) && file_exists($this->Path) && is_file($this->Path)) {
$this->FileName = ($this->FileName) ? $this->FileName : $this->getFileName($this->Path);
$contentDisp = ($this->ContentInline === true) ? 'inline' : 'attachment';
$this->ContentType = ($this->ContentType) ? $this->ContentType : $this->getMime($this->Path);
$this->Speed = $this->Speed * 1024;
$fileSize = filesize($this->Path);
# Чистка буфера
ob_end_clean();
# Нет HTTP_RANGE
if (@getenv('HTTP_RANGE') == '') {
$f = fopen($this->Path, 'rb');
header('Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
header('Pragma: no-cache');
header('HTTP/1.1 200 OK');
header('Connection: close');
header('Content-Type: ' . $this->ContentType);
header('Accept-Ranges: bytes');
header('Content-Disposition: '.$contentDisp.'; filename="' . $this->FileName . '"');
// header('Content-Length: ' . $fileSize);
while (!feof($f)) {
set_time_limit(0);
if (connection_aborted()) {
break;
}
echo fread($f, $this->Speed);
ob_flush();
flush();
sleep(1);
}
fclose($f);
} else {
$range = $this->parseRangeRequest($fileSize, strip_tags(getenv('HTTP_RANGE')));
if ($range && $range !== false) {
$f = fopen($this->Path, 'rb');
# Установить позицию чтения в файле
fseek($f, $range[0]['firstPos']);
header('Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
header('Pragma: no-cache');
header('HTTP/1.1 206 Partial Content');
header('Connection: close');
header('Content-Type: ' . $this->ContentType);
header('Accept-Ranges: bytes');
header('Content-Disposition: '.$contentDisp.'; filename="' . $this->FileName . '"');
header('Content-Range: bytes ' . $range[0]['firstPos'] . '-'. $range[0]['lastPos'] . '/'. $fileSize);
// header('Content-Length: ' . $range[0]['chunkSize']);
while (!feof($f)) {
set_time_limit(0);
if (connection_aborted()) {
break;
}
echo fread($f, $this->Speed);
ob_flush();
flush();
sleep(1);
}
fclose($f);
}
}
} else {
header('HTTP/1.1 404 Not Found');
}
}
}