@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3final class AphrontFileResponse extends AphrontResponse {
4
5 private $content;
6 private $contentIterator;
7 private $contentLength;
8 private $compressResponse;
9
10 private $mimeType;
11
12 /**
13 * Download filename
14 *
15 * This is NULL as default or a string.
16 *
17 * @var string|null
18 */
19 private $download;
20
21 private $rangeMin;
22 private $rangeMax;
23 private $allowOrigins = array();
24
25 public function addAllowOrigin($origin) {
26 $this->allowOrigins[] = $origin;
27 return $this;
28 }
29
30 /**
31 * Set a download filename
32 *
33 * @param string $download
34 * @return self
35 */
36 public function setDownload($download) {
37
38 // Make sure we have a populated string
39 if (!phutil_nonempty_string($download)) {
40 $download = 'untitled';
41 }
42
43 $this->download = $download;
44 return $this;
45 }
46
47 /**
48 * Get the download filename
49 *
50 * If this was never set, NULL is given.
51 *
52 * @return string|null
53 */
54 public function getDownload() {
55 return $this->download;
56 }
57
58 public function setMimeType($mime_type) {
59 $this->mimeType = $mime_type;
60 return $this;
61 }
62
63 public function getMimeType() {
64 return $this->mimeType;
65 }
66
67 public function setContent($content) {
68 $this->setContentLength(strlen($content));
69 $this->content = $content;
70 return $this;
71 }
72
73 public function setContentIterator($iterator) {
74 $this->contentIterator = $iterator;
75 return $this;
76 }
77
78 public function buildResponseString() {
79 return $this->content;
80 }
81
82 public function getContentIterator() {
83 if ($this->contentIterator) {
84 return $this->contentIterator;
85 }
86 return parent::getContentIterator();
87 }
88
89 public function setContentLength($length) {
90 $this->contentLength = $length;
91 return $this;
92 }
93
94 public function getContentLength() {
95 return $this->contentLength;
96 }
97
98 public function setCompressResponse($compress_response) {
99 $this->compressResponse = $compress_response;
100 return $this;
101 }
102
103 public function getCompressResponse() {
104 return $this->compressResponse;
105 }
106
107 public function setRange($min, $max) {
108 $this->rangeMin = $min;
109 $this->rangeMax = $max;
110 return $this;
111 }
112
113 public function getHeaders() {
114 $headers = array(
115 array('Content-Type', $this->getMimeType()),
116 // This tells clients that we can support requests with a "Range" header,
117 // which allows downloads to be resumed, in some browsers, some of the
118 // time, if the stars align.
119 array('Accept-Ranges', 'bytes'),
120 );
121
122 if ($this->rangeMin !== null || $this->rangeMax !== null) {
123 $len = $this->getContentLength();
124 $min = $this->rangeMin;
125
126 $max = $this->rangeMax;
127 if ($max === null) {
128 $max = ($len - 1);
129 }
130
131 $headers[] = array('Content-Range', "bytes {$min}-{$max}/{$len}");
132 $content_len = ($max - $min) + 1;
133 } else {
134 $content_len = $this->getContentLength();
135 }
136
137 if (!$this->shouldCompressResponse()) {
138 $headers[] = array('Content-Length', $content_len);
139 }
140
141 if (phutil_nonempty_string($this->getDownload())) {
142 $headers[] = array('X-Download-Options', 'noopen');
143
144 $filename = $this->getDownload();
145 $filename = addcslashes($filename, '"\\');
146 $headers[] = array(
147 'Content-Disposition',
148 'attachment; filename="'.$filename.'"',
149 );
150 }
151
152 if ($this->allowOrigins) {
153 $headers[] = array(
154 'Access-Control-Allow-Origin',
155 implode(',', $this->allowOrigins),
156 );
157 }
158
159 $headers = array_merge(parent::getHeaders(), $headers);
160 return $headers;
161 }
162
163 protected function shouldCompressResponse() {
164 return $this->getCompressResponse();
165 }
166
167 public function parseHTTPRange($range) {
168 $begin = null;
169 $end = null;
170
171 $matches = null;
172 if (preg_match('/^bytes=(\d+)-(\d*)$/', $range, $matches)) {
173 // Note that the "Range" header specifies bytes differently than
174 // we do internally: the range 0-1 has 2 bytes (byte 0 and byte 1).
175 $begin = (int)$matches[1];
176
177 // The "Range" may be "200-299" or "200-", meaning "until end of file".
178 if (strlen($matches[2])) {
179 $range_end = (int)$matches[2];
180 $end = $range_end + 1;
181 } else {
182 $range_end = null;
183 }
184
185 $this->setHTTPResponseCode(206);
186 $this->setRange($begin, $range_end);
187 }
188
189 return array($begin, $end);
190 }
191
192}