/[drupal]/contributions/modules/filerequest/downloadhandler.php
ViewVC logotype

Contents of /contributions/modules/filerequest/downloadhandler.php

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.4 - (show annotations) (download) (as text)
Mon Jul 3 14:03:54 2006 UTC (3 years, 4 months ago) by elmuerte
Branch: MAIN
CVS Tags: HEAD
Branch point for: DRUPAL-4-7
Changes since 1.3: +335 -181 lines
File MIME type: text/x-php
4.7 port
1 <?php
2
3 /*
4 Somewhat magic script that will push the server file to the client, taking
5 into account client requests like ranges.
6
7 This is a modified version of the original of charon-dl
8 http://sf.net/projects/charon-dl
9 */
10
11 /*
12 Charon - Download System
13 Copyright (C) 2005, Michiel "El Muerte" Hendriks
14
15 This program is free software; you can redistribute it and/or modify
16 it under the terms of the GNU General Public License as published by
17 the Free Software Foundation; either version 2 of the License, or
18 (at your option) any later version.
19
20 This program is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
24
25 You should have received a copy of the GNU General Public License
26 along with this program; if not, write to the Free Software
27 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 */
29 /* $Id: downloadhandler.php,v 1.3.2.7 2006/04/04 10:58:08 elmuerte Exp $ */
30
31
32 define("READBUFFER_SIZE", 4096); // read buffer size
33 define("USE_GZIP", true); // use GZip compression for text/* files
34
35 define("WATERMARK", 1); // add a watermark to the image
36 define("SUBSTITUTE", 2); // replace the image with an other one
37
38 define("UPSAMPLE_PALLET", true); // if set upsample pallet images, produces better results
39 define("DOWNSAMPLE_TO_ORIGIN", true); // if set to true downsample the watermarked
40 // image to the original format, otherwise it will
41 // be converted to a jpg
42
43 /*
44 not OS safe
45 */
46 if (!function_exists("mime_content_type")) {
47 function mime_content_type($f) {
48 $f = escapeshellarg($f);
49 return trim( exec("file -bi ".$f) );
50 }
51 }
52
53 /**
54 * Return true if the file is an image file.
55 *
56 * @param string the filename
57 * @return boolean true if it's an image we want to process
58 */
59 function __fr_is_image_file($filename) {
60 // only png, jpg, gif are supported
61 return (in_array(StrToLower(preg_replace("#^.*\.([^.]*)$#", "\\1", $filename)), array("jpg", "jpeg", "jpe", "png", "gif")));
62 }
63
64 /**
65 * Reports the leech
66 */
67 function __fr_report_leech() {
68 global $__fr_reporting_leech;
69 $__fr_reporting_leech = true;
70 register_shutdown_function("__fr_delayed_report");
71 }
72
73 function __fr_delayed_report() {
74 global $__fr_reporting_leech;
75 if (!$__fr_reporting_leech) return;
76 if (!function_exists('watchdog')) return;
77 watchdog('download leech', "File: <i>".$_GET["file"]."</i><br />\nSource: <i>".$_SERVER["HTTP_REFERER"]."</i>", WATCHDOG_NOTICE);
78 }
79
80 /**
81 * Detect leeching sites
82 *
83 * @param mixed array containing the configuration
84 * @return bool false if it's a leech
85 */
86 function __fr_can_download_file(&$config) {
87 $config["watermark"] = false;
88 if (!$config["filereq_antileech_enabled"]) return true;
89 if (!empty($_SERVER["HTTP_REFERER"])) {
90 if (!preg_match("#^http(s)?://".$config["filereq_antileech_regex"]."/#i", $_SERVER["HTTP_REFERER"])) {
91
92 if (!isset($config["filereq_nowatchdog"])) __fr_report_leech();
93
94 if (__fr_is_image_file($config["filename"])) {
95 switch (intval($config["filereq_antileech_image_mode"])) {
96 case WATERMARK:
97 $config["watermark"] = __DRUPAL_BASE_DIR.$config["filereq_antileech_image_file"];
98 return true;
99 case SUBSTITUTE:
100 $config["filename"] = __DRUPAL_BASE_DIR.$config["filereq_antileech_image_file"];
101 return true;
102 }
103 }
104
105 return false;
106 }
107 }
108 // empty referer is good, for now
109 return true;
110 }
111
112 /**
113 * Send a file to the client. Absolute file path required!
114 * @param bool forces the file to be send as an attachment (no inline viewing)
115 * @param function is a function in the form: function($filename) and returns the mimetype
116 * @return bool true if the download was handled
117 */
118 function __fr_process_download($filename, $forcedl=false, $watermark=false, $mimeOverride=null)
119 {
120 global $__fr_reporting_leech;
121
122 if (!file_exists($filename)) {
123 trigger_error("Requested download file does not exist: <i>".$filename."</i>", E_USER_ERROR);
124 return false;
125 }
126 if (!is_readable($filename)) {
127 trigger_error("Requested file is not readable: <i>".$filename."</i>", E_USER_ERROR);
128 return false;
129 }
130
131 $fi["path"] = $filename;
132 $fi["name"] = basename($filename); // name reported to the browser
133 $fi["size"] = filesize($filename);
134 $fi["time"] = filemtime($filename);
135 $fi["ranges"] = array();
136
137 if ($forcedl) $contentDisposition = "attachment"; // inline/attachment
138 else {
139 //$_SERVER["HTTP_ACCEPT"];
140 $contentDisposition = "inline"; // TODO: improve?
141 }
142
143 $do_watermark = false;
144 if (file_exists($watermark) && __fr_is_image_file($fi["name"])) {
145 $do_watermark = true;
146 $fi["name"] = preg_replace("#^(.*)(\.[^.]*)$#", "\\1_branded\\2", $fi["name"]);
147 }
148
149 header("Content-Type: "); // unset content type
150
151 if (!$do_watermark && isset($_SERVER["HTTP_RANGE"])) {
152 // process ranges
153 if (preg_match("/^bytes=(.*)$/", trim($_SERVER["HTTP_RANGE"]), $ranges)) {
154 $ranges = explode(",", $ranges[1]);
155 for ($i = 0; $i < count($ranges); $i++) {
156 if (preg_match("/^([0-9]*)-([0-9]*)$/", $ranges[$i], $r)) {
157 if ($r[1] == "") { // -X : last X bytes
158 $fi["ranges"][] = array("start" => $fi["size"]-intval($r[2]), "stop" => $fi["size"]-1, "count" => intval($r[2]));
159 }
160 else if ($r[2] == "") { // X- : skip first X bytes
161 $fi["ranges"][] = array("start" => intval($r[1]), "stop" => $fi["size"]-1, "count" => $fi["size"]-intval($r[1]));
162 }
163 else if ($r[1] == "" && $r[2] == "") {
164 $fi["ranges"] = array();
165 break;
166 }
167 else { // X-Y : X to Y bytes (0 = 1st byte)
168 $fi["ranges"][] = array("start" => intval($r[1]), "stop" => intval($r[2]), "count" => intval($r[2])-intval($r[1])+1);
169 }
170 }
171 else {
172 $fi["ranges"] = array();
173 break;
174 }
175 }
176 }
177
178 for ($i = 0; $i < count($fi["ranges"]); $i++) {
179 if (($fi["ranges"][$i]["start"] < 0)
180 || ($fi["ranges"][$i]["stop"] >= $fi["size"])) {
181 $fi["ranges"] = array();
182 break;
183 }
184 }
185
186 if (count($fi["ranges"]) == 0) { // no valid ranges specified
187 header("HTTP/1.1 416 Requested range not satisfiable");
188 header("Content-Range: bytes */".$fi["size"]);
189 return true;
190 }
191 }
192
193 if ($_SERVER["HTTP_IF_MODIFIED_SINCE"]) {
194 if ($fi["time"] > strftime($_SERVER["HTTP_IF_MODIFIED_SINCE"])) {
195 header("HTTP/1.1 304 Not Modified");
196 $__fr_reporting_leech = false; // disable watchdog reporting
197 return true;
198 }
199 }
200
201 // figure out mime last
202 $fi["mime"] = "";
203 if (is_callable($mimeOverride)) $fi["mime"] = $mimeOverride(basename($filename));
204 if (empty($fi["mime"])) {
205 if (function_exists("mime_content_type")) $fi["mime"] = mime_content_type($filename);
206 }
207 if (!preg_match("#^(\w*)/(\w*)$#i", $fi["mime"])) $fi["mime"] = "application/octet-stream";
208
209 // send headers
210
211 if (count($fi["ranges"]) > 0) {
212 header("HTTP/1.1 206 Partial content");
213 if (count($fi["ranges"]) == 1) {
214 header("Content-Range: bytes ".$fi["ranges"][0]["start"]."-".$fi["ranges"][0]["stop"]."/".$fi["size"]);
215 header("Content-Length: ".$fi["ranges"][0]["count"]);
216 }
217 else {
218 $byteRangeBoundary = "_BYTE_RANGE_".md5(microtime());
219 header("Content-Type: multipart/byteranges; boundary=".$byteRangeBoundary);
220 }
221 }
222 else {
223 header("HTTP/1.1 200 Ok");
224 if (!$do_watermark) header("Content-Length: ".$fi["size"]); // we don't know the final size when watermarking
225 }
226
227 if (!$do_watermark) header("Accept-Ranges: bytes");
228 if (count($fi["ranges"]) <= 1) header("Content-Type: ".$fi["mime"]);
229 /*if (!$do_watermark)*/ header("Last-Modified: ".gmdate("D, d M Y H:i:s \G\M\T", $fi["time"]));
230 header("Content-Disposition: ".$contentDisposition."; filename=\"".$fi["name"]."\"");
231
232 // encode text files when accepted
233 if (USE_GZIP && preg_match("#^text/#i", $fi["mime"]) && extension_loaded("zlib")
234 && in_array("gzip", explode(",", $_SERVER["HTTP_ACCEPT_ENCODING"]))) {
235 ob_start("ob_gzhandler");
236 ob_implicit_flush(false); // content length header needs to be updated
237 }
238
239 if ($_SERVER["REQUEST_METHOD"] == "HEAD") return true; // no body for you
240
241 if ($do_watermark) {
242 header("Expires: ".gmdate("D, d M Y H:i:s \G\M\T", mktime()+900)); // expire watermark in 15 minutes
243 header("X-Watermarked: true");
244 return __fr_add_watermark($filename, $watermark);
245 }
246
247 $fp = fopen($filename, "rb");
248 if (!$fp) {
249 header("HTTP/1.1 500 Internal Server Error");
250 echo "Unable to open file for reading.";
251 return true;
252 }
253
254 if (count($fi["ranges"]) == 0) {
255 while (!connection_status() && !feof($fp)) {
256 echo fread($fp, READBUFFER_SIZE);
257 }
258 }
259 else if (count($fi["ranges"]) == 1) {
260 fseek($fp, $fi["ranges"][0]["start"], SEEK_SET);
261 while (!connection_status() && (ftell($fp) <= $fi["ranges"][0]["stop"]-READBUFFER_SIZE)) {
262 echo fread($fp, READBUFFER_SIZE);
263 }
264 if ($fi["ranges"][0]["stop"]-ftell($fp) > 1) echo fread($fp, $fi["ranges"][0]["stop"]-ftell($fp)+1);
265 }
266 else {
267 for ($i = 0; $i < count($fi["ranges"]); $i++) {
268 echo "\r\n";
269 echo "--".$byteRangeBoundary."\r\n";
270 echo "Content-Type: ".$fi["mime"]."\r\n";
271 echo "Content-Range: bytes ".$fi["ranges"][$i]["start"]."-".$fi["ranges"][$i]["stop"]."/".$fi["size"]."\r\n";
272 echo "Content-Disposition: ".$contentDisposition."; filename=".$fi["name"]."\r\n";
273 echo "\r\n";
274
275 fseek($fp, $fi["ranges"][$i]["start"], SEEK_SET);
276 while (!connection_status() && (ftell($fp) <= $fi["ranges"][$i]["stop"]-READBUFFER_SIZE)) {
277 echo fread($fp, READBUFFER_SIZE);
278 }
279 if ($fi["ranges"][$i]["stop"]-ftell($fp) > 1) echo fread($fp, $fi["ranges"][$i]["stop"]-ftell($fp)+1);
280 }
281 echo "\r\n";
282 echo "--".$byteRangeBoundary."--";
283 }
284
285 fclose($fp);
286 return true;
287 }
288
289
290 function __fr_imagetruecolortopalette_ex( $image, $dither, $ncolors )
291 {
292 if (function_exists('imagecolormatch')) {
293 $width = imagesx( $image );
294 $height = imagesy( $image );
295 $colors_handle = ImageCreateTrueColor( $width, $height );
296 ImageCopyMerge( $colors_handle, $image, 0, 0, 0, 0, $width, $height, 100 );
297 }
298 ImageTrueColorToPalette( $image, $dither, $ncolors );
299 if (function_exists('imagecolormatch')) {
300 ImageColorMatch( $colors_handle, $image );
301 ImageDestroy( $colors_handle );
302 }
303 }
304
305 /*
306 Source of this routine:
307 http://www.php.net/manual/en/function.imagecopymerge.php
308
309 slightly modified
310 */
311
312 function __fr_add_watermark($sourcefile, $watermarkfile) {
313 #
314 # $sourcefile = Filename of the picture to be watermarked.
315 # $watermarkfile = Filename of the 24-bit PNG watermark file.
316 #
317
318 //Get the resource ids of the pictures
319 $watermarkfile_id = imagecreatefrompng($watermarkfile);
320
321 imageAlphaBlending($watermarkfile_id, false);
322 imageSaveAlpha($watermarkfile_id, true);
323
324 $fileType = getimagesize($sourcefile);
325
326 switch($fileType[2]) {
327 case 1:
328 $sourcefile_id = imagecreatefromgif($sourcefile);
329 break;
330
331 case 2:
332 $sourcefile_id = imagecreatefromjpeg($sourcefile);
333 break;
334
335 case 3:
336 $sourcefile_id = imagecreatefrompng($sourcefile);
337 break;
338
339 }
340
341 //Get the sizes of both pix
342 $sourcefile_width=imageSX($sourcefile_id);
343 $sourcefile_height=imageSY($sourcefile_id);
344 $watermarkfile_width=imageSX($watermarkfile_id);
345 $watermarkfile_height=imageSY($watermarkfile_id);
346
347 $dest_x = ( $sourcefile_width / 2 ) - ( $watermarkfile_width / 2 );
348 $dest_y = ( $sourcefile_height / 2 ) - ( $watermarkfile_height / 2 );
349
350 $upsampled = 0;
351
352 // if a gif, we have to upsample it to a truecolor image
353 if(UPSAMPLE_PALLET && !imageistruecolor($sourcefile_id)) {
354 // create an empty truecolor container
355 $tempimage = imagecreatetruecolor($sourcefile_width, $sourcefile_height);
356
357 $upsampled = imagecolorstotal($sourcefile_id);
358
359 // copy the 8-bit gif into the truecolor image
360 imagecopy($tempimage, $sourcefile_id, 0, 0, 0, 0, $sourcefile_width, $sourcefile_height);
361 imagedestroy($sourcefile_id);
362
363 // copy the source_id int
364 $sourcefile_id = $tempimage;
365 }
366
367 imagecopy($sourcefile_id, $watermarkfile_id, $dest_x, $dest_y, 0, 0, $watermarkfile_width, $watermarkfile_height);
368
369 if ($upsampled) {
370 if (DOWNSAMPLE_TO_ORIGIN && function_exists('imagetruecolortopalette')) {
371 __fr_imagetruecolortopalette_ex($sourcefile_id, true, 256);
372 }
373 else {
374 $fileType[2] = 2; // force to JPEG to reduce size
375 }
376 }
377
378 //Create a jpeg out of the modified picture
379 switch($fileType[2]) {
380
381 // remember we don't need gif any more, so we use only png or jpeg.
382 // See the upsaple code immediately above to see how we handle gifs
383 case 3:
384 header("Content-type: image/png");
385 imagepng ($sourcefile_id);
386 break;
387
388 case 1:
389 if (imagetypes() & IMG_GIF) {
390 header("Content-type: image/gif");
391 imagegif ($sourcefile_id);
392 break;
393 }
394
395 default:
396 header("Content-type: image/jpg");
397 imagejpeg ($sourcefile_id);
398 }
399
400 imagedestroy($sourcefile_id);
401 imagedestroy($watermarkfile_id);
402 }

  ViewVC Help
Powered by ViewVC 1.1.2