Rev 728 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
604 | daniel-mar | 1 | <?php |
2 | |||
3 | /* |
||
4 | * VtsBrowserDownload.class.php |
||
5 | * Copyright 2021 Daniel Marschall, ViaThinkSoft |
||
6 | * Revision: 2021-05-21 |
||
7 | * |
||
8 | * Licensed under the Apache License, Version 2.0 (the "License"); |
||
9 | * you may not use this file except in compliance with the License. |
||
10 | * You may obtain a copy of the License at |
||
11 | * |
||
12 | * http://www.apache.org/licenses/LICENSE-2.0 |
||
13 | * |
||
14 | * Unless required by applicable law or agreed to in writing, software |
||
15 | * distributed under the License is distributed on an "AS IS" BASIS, |
||
16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||
17 | * See the License for the specific language governing permissions and |
||
18 | * limitations under the License. |
||
19 | */ |
||
20 | |||
21 | class VtsBrowserDownload { |
||
22 | |||
23 | private static function wellKnownInlineFile($file_extension) { |
||
24 | // Windows Firefox: Browser decided wheater to display or download by looking at the mime type (inline disposition with explicit filename works) |
||
25 | // Windows Chrome: Browser decided wheater to display or download by looking at the mime type (inline disposition with explicit filename DOES NOT WORK) |
||
26 | |||
27 | //$file_extension = strtolower($file_extension); |
||
28 | //$array_listen = array('txt', 'mp3', 'wav', 'mid', 'ogg', 'pdf', 'avi', 'mov', 'mp4', 'mpeg', 'mpg', 'swf', 'gif', 'jpg', 'jpeg', 'png'); |
||
29 | //return in_array($file_extension, $array_listen); |
||
30 | |||
31 | return true; |
||
32 | } |
||
33 | |||
34 | private static function getMimeType($file_extension) { |
||
35 | $file_extension = strtolower($file_extension); |
||
728 | daniel-mar | 36 | if (!class_exists('VtsFileTypeDetect')) { |
37 | // https://github.com/danielmarschall/fileformats |
||
38 | throw new Exception("Require 'fileformats' package"); |
||
39 | } |
||
604 | daniel-mar | 40 | return VtsFileTypeDetect::getMimeType('dummy.'.$file_extension); |
41 | } |
||
42 | |||
43 | public static function output_file($file, $mime_type='', $inline_mode=2/*2=auto*/) { |
||
44 | // Partitally taken from: |
||
45 | // - https://stackoverflow.com/a/13821992/488539 |
||
46 | // - https://stackoverflow.com/a/32885706/488539 |
||
47 | |||
48 | if (connection_status() != 0) return false; |
||
49 | |||
50 | $file_extension = pathinfo($file, PATHINFO_EXTENSION); |
||
51 | |||
52 | if(!is_readable($file)) throw new Exception('File not found or inaccessible!'); |
||
53 | $size = filesize($file); |
||
54 | $name = rawurldecode(basename($file)); |
||
55 | |||
56 | if ($mime_type == '') { |
||
57 | $mime_type = self::getMimeType($file_extension); |
||
58 | if (!$mime_type) $mime_type='application/force-download'; |
||
59 | } |
||
60 | |||
61 | |||
62 | |||
63 | while (ob_get_level() > 0) @ob_end_clean(); |
||
64 | |||
65 | switch ($inline_mode) { |
||
66 | |||
67 | case 0: |
||
68 | $disposition = 'attachment'; |
||
69 | break; |
||
70 | |||
71 | case 1: |
||
72 | $disposition = 'inline'; |
||
73 | break; |
||
74 | |||
75 | case 2: |
||
76 | $disposition = self::wellKnownInlineFile($file_extension) ? 'inline' : 'attachment'; |
||
77 | break; |
||
78 | |||
79 | default: |
||
80 | throw new Exception('Invalid value for inline_mode'); |
||
81 | } |
||
82 | |||
83 | if(ini_get('zlib.output_compression')){ |
||
84 | ini_set('zlib.output_compression', 'Off'); |
||
85 | } |
||
86 | header('Content-Type: ' . $mime_type); |
||
87 | |||
88 | $ua = isset($_SERVER['HTTP_USER_AGENT']) ? strtoupper($_SERVER['HTTP_USER_AGENT']) : ''; |
||
89 | if (strstr($ua, 'MSIE')) { |
||
90 | $name_msie = preg_replace('/\./', '%2e', $name, substr_count($name, '.') - 1); |
||
91 | header('Content-Disposition: '.$disposition.';filename="'.$name_msie.'"'); |
||
92 | } else if (strstr($ua, 'FIREFOX')) { |
||
93 | // TODO: Implement "encodeRFC5987ValueChars" described at https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent ? |
||
94 | header('Content-Disposition: '.$disposition.';filename*="UTF-8\'\''.utf8_encode($name).'"'); |
||
95 | } else { |
||
96 | // Note: There is possibly a bug in Google Chrome: https://stackoverflow.com/questions/61866508/chrome-ignores-content-disposition-filename |
||
97 | header('Content-Disposition: '.$disposition.';filename="'.$name.'"'); |
||
98 | } |
||
99 | |||
100 | header('Content-Transfer-Encoding: binary'); |
||
101 | header('Accept-Ranges: bytes'); |
||
102 | header('Cache-Control: public'); |
||
103 | |||
104 | if (isset($_SERVER['HTTP_RANGE'])) { |
||
105 | list($a, $range) = explode("=",$_SERVER['HTTP_RANGE'],2); |
||
106 | list($range) = explode(",",$range,2); |
||
107 | list($range, $range_end) = explode("-", $range); |
||
108 | $range=intval($range); |
||
109 | if(!$range_end) { |
||
110 | $range_end=$size-1; |
||
111 | } else { |
||
112 | $range_end=intval($range_end); |
||
113 | } |
||
114 | |||
115 | $new_length = $range_end-$range+1; |
||
849 | daniel-mar | 116 | http_response_code(206); // 206 Partial Content |
604 | daniel-mar | 117 | header("Content-Length: $new_length"); |
118 | header("Content-Range: bytes $range-$range_end/$size"); |
||
119 | } else { |
||
120 | $range = 0; |
||
121 | $etag = md5_file($file); |
||
122 | header("Etag: $etag"); |
||
123 | if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && (trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag)) { |
||
849 | daniel-mar | 124 | http_response_code(304); // 304 Not Modified |
604 | daniel-mar | 125 | return true; |
126 | } |
||
127 | |||
128 | $new_length=$size; |
||
129 | header("Content-Length: ".$size); |
||
130 | header('Content-MD5: '.$etag); // RFC 2616 clause 14.15 |
||
131 | } |
||
132 | |||
133 | set_time_limit(0); |
||
134 | |||
135 | $chunksize = 1*(1024*1024); |
||
136 | $bytes_send = 0; |
||
137 | if ($file = fopen($file, 'r')) { |
||
138 | if(isset($_SERVER['HTTP_RANGE'])) |
||
139 | fseek($file, $range); |
||
140 | |||
141 | while(!feof($file) && |
||
142 | (!connection_aborted()) && // connection_status() == 0 |
||
143 | ($bytes_send<$new_length)) |
||
144 | { |
||
145 | $buffer = fread($file, $chunksize); |
||
146 | echo($buffer); |
||
147 | flush(); |
||
148 | $bytes_send += strlen($buffer); |
||
149 | } |
||
150 | fclose($file); |
||
151 | } else { |
||
152 | throw new Exception("Cannot open file $file"); |
||
153 | } |
||
154 | return((connection_status() == 0) and !connection_aborted()); |
||
155 | } |
||
156 | |||
157 | } |