Rev 1938 Rev 4988
Line 1... Line 1...
1 <?php 1 <?php
2 # vim:et:ts=3:sts=3:sw=3:fdm=marker: 2 // WebSVN - Subversion repository viewing via the web using PHP
3   3 // Copyright (C) 2004-2006 Tim Armes
4 // WebSVN - Subversion repository viewing via the web using PHP 4 //
5 // Copyright © 2004-2006 Tim Armes, Matt Sicker 5 // This program is free software; you can redistribute it and/or modify
6 // 6 // it under the terms of the GNU General Public License as published by
7 // This program is free software; you can redistribute it and/or modify 7 // the Free Software Foundation; either version 2 of the License, or
8 // it under the terms of the GNU General Public License as published by 8 // (at your option) any later version.
9 // the Free Software Foundation; either version 2 of the License, or 9 //
10 // (at your option) any later version. 10 // This program is distributed in the hope that it will be useful,
11 // 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // This program is distributed in the hope that it will be useful, 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // GNU General Public License for more details.
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 //
15 // GNU General Public License for more details. 15 // You should have received a copy of the GNU General Public License
16 // 16 // along with this program; if not, write to the Free Software
17 // You should have received a copy of the GNU General Public License 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 // along with this program; if not, write to the Free Software 18 //
19 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 // --
20 // 20 //
21 // -- 21 // dl.php
22 // 22 //
23 // dl.php 23 // Allow for file and directory downloads, creating zip/tar/gz files if needed.
24 // 24  
25 // Create gz/tar files of the requested item 25 require_once 'include/setup.php';
26   26 require_once 'include/svnlook.php';
27 require_once("include/setup.inc"); 27 require_once 'include/utils.php';
28 require_once("include/svnlook.inc"); 28  
29 require_once("include/utils.inc"); 29 if (!defined('USE_AUTOLOADER')) {
30   30 @include_once 'Archive/Tar.php';
31 // Make sure that this operation is allowed 31 }
32   32  
33 if (!$rep->isDownloadAllowed($path)) 33 function setDirectoryTimestamp($dir, $timestamp) {
34 exit; 34 global $config;
35   35 // Changing the timestamp of a directory in Windows is only supported in PHP 5.3.0+
36 $svnrep = new SVNRepository($rep); 36 touch($dir, $timestamp);
37   37 if (is_dir($dir)) {
38 if ($path{0} != "/") 38 // Set timestamp for all contents, recursing into subdirectories
39 $ppath = "/".$path; 39 $handle = opendir($dir);
40 else 40 if ($handle) {
41 $ppath = $path; 41 while (($file = readdir($handle)) !== false) {
42   42 if ($file == '.' || $file == '..') {
43 // If there's no revision info, go to the lastest revision for this path 43 continue;
44 $history = $svnrep->getLog($path, "", "", true); 44 }
45 $youngest = $history->entries[0]->rev; 45 $f = $dir.DIRECTORY_SEPARATOR.$file;
46   46 if (is_dir($f)) {
47 if (empty($rev)) 47 setDirectoryTimestamp($f, $timestamp);
48 $rev = $youngest; 48 }
49   49 }
50 // Create a temporary directory. Here we have an unavoidable but highly 50 closedir($handle);
51 // unlikely to occure race condition 51 }
52   52 }
53 if (count(glob("/tmp/wsvn-*"))<=40) // Omezeni na pocet soucasne stahovanych souboru 53 }
54 { 54  
55   55 // Make sure that downloading the specified file/directory is permitted
56 // Ošetření doběhnutí skriptu i když klient klikne jinam (smazání tmp) 56  
57 ignore_user_abort(true); 57 if (!$rep)
58   58 {
59 $tmpname1 = tempnam("temp", "wsvn-"); 59 http_response_code(404);
60 $tmpname = $tmpname1; 60 exit;
61 $tmpname .= "dir"; 61 }
62 if (mkdir($tmpname)) 62  
63 { 63 if (!$rep->isDownloadAllowed($path)) {
64 // Get the name of the directory being archived 64 http_response_code(403);
65 $arcname = substr($path, 0, -1); 65 error_log('Unable to download resource at path: '.$path);
66 $arcname = basename($arcname); 66 print 'Unable to download resource at path: '.xml_entities($path);
67 if (empty($arcname)) 67 exit;
68 $arcname = $rep->name; 68 }
69 69  
70 $svnrep->exportDirectory($path, $tmpname.DIRECTORY_SEPARATOR.$arcname, $rev); 70 $svnrep = new SVNRepository($rep);
71 71  
72 // ZIP it up 72 // Fetch information about a revision (if unspecified, the latest) for this path
73 chdir($tmpname); 73 if (empty($rev))
74 if (! connection_aborted()) 74 {
75 exec("zip -r ".quote("$arcname")." ."); 75 $history = $svnrep->getLog($path, 'HEAD', '', true, 1, $peg);
76 76 }
77 $size = filesize("$arcname.zip"); 77 else if ($rev == $peg)
78 78 {
79 // Give the file to the browser 79 $history = $svnrep->getLog($path, '', 1, true, 1, $peg);
80 80 }
81 if (!connection_aborted() && $fp = @fopen("$arcname.zip","rb")) 81 else
82 { 82 {
83 header("Content-Type: application/zip"); 83 $history = $svnrep->getLog($path, $rev, $rev - 1, true, 1, $peg);
84 header("Content-Length: $size"); 84 }
85 header("Content-Disposition: attachment; filename=\"".$rep->name."-$arcname.zip\""); 85  
86 // Use a loop to transfer the data 4KB at a time. 86 $logEntry = ($history && !empty($history->entries)) ? $history->entries[0] : null;
87 while(!feof($fp)) 87  
88 { 88 if (!$logEntry)
89 echo fread($fp, 4096); 89 {
90 ob_flush(); 90 http_response_code(404);
91 } 91 error_log('Unable to download resource at path: '.$path);
92 } 92 print 'Unable to download resource at path: '.xml_entities($path);
93 else 93 exit(0);
94 { 94 }
95 print "Unable to open file $arcname.zip"; 95  
96 } 96 if (empty($rev))
97 97 {
98 fclose($fp); 98 $rev = $logEntry->rev;
99 99 }
100 chdir(".."); 100  
101 101 // Create a temporary filename to be used for a directory to archive a download.
102 // Delete the directory. Why doesn't PHP have a generic recursive directory 102 // Here we have an unavoidable but highly unlikely to occur race condition.
103 // deletion command? It's stupid. 103 $tempDir = tempnamWithCheck($config->getTempDir(), 'websvn');
104 104  
105 // if ($config->serverIsWindows) 105 @unlink($tempDir);
106 // { 106 mkdir($tempDir);
107 // $cmd = quoteCommand("rmdir /S /Q ".quote($tmpname), false); 107 // Create the name of the directory being archived
108 // } 108 $archiveName = $path;
109 // else 109 $isDir = (substr($archiveName, -1) == '/');
110 // { 110  
111 $cmd = quoteCommand("rm -fr ".quote($tmpname), false); 111 if ($isDir)
112 // } 112 {
113 113 $archiveName = substr($archiveName, 0, -1);
114 @exec($cmd); 114 }
115 } 115  
116 unlink($tmpname1); // Pomocny soubor smazeme az po smazani adresare 116 $archiveName = basename($archiveName);
117 } 117  
118 else 118 if ($archiveName == '')
119 { 119 {
120 print "We are sorry. The server is overloaded..."; 120 $archiveName = $rep->name;
121 } 121 }
122 ?> 122  
-   123 $plainfilename = $archiveName;
-   124 $archiveName .= '.r'.$rev;
-   125  
-   126 // Export the requested path from SVN repository to the temp directory
-   127 $svnExportResult = $svnrep->exportRepositoryPath($path, $tempDir.DIRECTORY_SEPARATOR.$archiveName, $rev, $peg);
-   128  
-   129 if ($svnExportResult != 0)
-   130 {
-   131 http_response_code(500);
-   132 error_log('svn export failed for: '.$archiveName);
-   133 print 'svn export failed for "'.xml_entities($archiveName).'".';
-   134 removeDirectory($tempDir);
-   135 exit(0);
-   136 }
-   137  
-   138 // For security reasons, disallow direct downloads of filenames that
-   139 // are a symlink, since they may be a symlink to anywhere (/etc/passwd)
-   140 // Deciding whether the symlink is relative and legal within the
-   141 // repository would be nice but seems to error prone at this moment.
-   142 if ( is_link($tempDir.DIRECTORY_SEPARATOR.$archiveName) )
-   143 {
-   144 http_response_code(500);
-   145 error_log('to be downloaded file is symlink, aborting: '.$archiveName);
-   146 print 'Download of symlinks disallowed: "'.xml_entities($archiveName).'".';
-   147 removeDirectory($tempDir);
-   148 exit(0);
-   149 }
-   150  
-   151 // Set timestamp of exported directory (and subdirectories) to timestamp of
-   152 // the revision so every archive of a given revision has the same timestamp.
-   153 $revDate = $logEntry->date;
-   154 $timestamp = mktime(substr($revDate, 11, 2), // hour
-   155 substr($revDate, 14, 2), // minute
-   156 substr($revDate, 17, 2), // second
-   157 substr($revDate, 5, 2), // month
-   158 substr($revDate, 8, 2), // day
-   159 substr($revDate, 0, 4)); // year
-   160 setDirectoryTimestamp($tempDir, $timestamp);
-   161  
-   162 // Change to temp directory so that only relative paths are stored in archive.
-   163 $oldcwd = getcwd();
-   164 chdir($tempDir);
-   165  
-   166 if ($isDir)
-   167 {
-   168 $downloadMode = $config->getDefaultDirectoryDlMode();
-   169 }
-   170 else
-   171 {
-   172 $downloadMode = $config->getDefaultFileDlMode();
-   173 }
-   174  
-   175 // $_REQUEST parameter can override dlmode
-   176 if (!empty($_REQUEST['dlmode']))
-   177 {
-   178 $downloadMode = $_REQUEST['dlmode'];
-   179  
-   180 if (substr($logEntry->path, -1) == '/')
-   181 {
-   182 if (!in_array($downloadMode, $config->validDirectoryDlModes))
-   183 {
-   184 $downloadMode = $config->getDefaultDirectoryDlMode();
-   185 }
-   186 }
-   187 else
-   188 {
-   189 if (!in_array($downloadMode, $config->validFileDlModes))
-   190 {
-   191 $downloadMode = $config->getDefaultFileDlMode();
-   192 }
-   193 }
-   194 }
-   195  
-   196 $downloadArchive = $archiveName;
-   197  
-   198 if ($downloadMode == 'plain')
-   199 {
-   200 $downloadMimeType = 'application/octet-stream';
-   201  
-   202 }
-   203 else if ($downloadMode == 'zip')
-   204 {
-   205 $downloadMimeType = 'application/zip';
-   206 $downloadArchive .= '.zip';
-   207 // Create zip file
-   208 $cmd = $config->zip.' --symlinks -r '.quote($downloadArchive).' '.quote($archiveName);
-   209 execCommand($cmd, $retcode);
-   210  
-   211 if ($retcode != 0)
-   212 {
-   213 error_log('Unable to call zip command: '.$cmd);
-   214 print 'Unable to call zip command. See webserver error log for details.';
-   215 }
-   216 }
-   217 else
-   218 {
-   219 $downloadMimeType = 'application/gzip';
-   220 $downloadArchive .= '.tar.gz';
-   221 $tarArchive = $archiveName.'.tar';
-   222  
-   223 // Create the tar file
-   224 $retcode = 0;
-   225  
-   226 if (class_exists('Archive_Tar'))
-   227 {
-   228 $tar = new Archive_Tar($tarArchive);
-   229 $created = $tar->create(array($archiveName));
-   230  
-   231 if (!$created)
-   232 {
-   233 $retcode = 1;
-   234 http_response_code(500);
-   235 print 'Unable to create tar archive.';
-   236 }
-   237 }
-   238 else
-   239 {
-   240 $cmd = $config->tar.' -cf '.quote($tarArchive).' '.quote($archiveName);
-   241 execCommand($cmd, $retcode);
-   242  
-   243 if ($retcode != 0)
-   244 {
-   245 http_response_code(500);
-   246 error_log('Unable to call tar command: '.$cmd);
-   247 print 'Unable to call tar command. See webserver error log for details.';
-   248 }
-   249 }
-   250  
-   251 if ($retcode != 0)
-   252 {
-   253 chdir($oldcwd);
-   254 removeDirectory($tempDir);
-   255 exit(0);
-   256 }
-   257  
-   258 // Set timestamp of tar file to timestamp of revision
-   259 touch($tarArchive, $timestamp);
-   260  
-   261 // GZIP it up
-   262 if (function_exists('gzopen'))
-   263 {
-   264 $srcHandle = fopen($tarArchive, 'rb');
-   265 $dstHandle = gzopen($downloadArchive, 'wb');
-   266  
-   267 if (!$srcHandle || !$dstHandle)
-   268 {
-   269 http_response_code(500);
-   270 print 'Unable to open file for gz-compression.';
-   271 chdir($oldcwd);
-   272 removeDirectory($tempDir);
-   273 exit(0);
-   274 }
-   275  
-   276 while (!feof($srcHandle))
-   277 {
-   278 gzwrite($dstHandle, fread($srcHandle, 1024 * 512));
-   279 }
-   280  
-   281 fclose($srcHandle);
-   282 gzclose($dstHandle);
-   283 }
-   284 else
-   285 {
-   286 $cmd = $config->gzip.' '.quote($tarArchive);
-   287 $retcode = 0;
-   288 execCommand($cmd, $retcode);
-   289  
-   290 if ($retcode != 0)
-   291 {
-   292 http_response_code(500);
-   293 error_log('Unable to call gzip command: '.$cmd);
-   294 print 'Unable to call gzip command. See webserver error log for details.';
-   295 chdir($oldcwd);
-   296 removeDirectory($tempDir);
-   297 exit(0);
-   298 }
-   299 }
-   300 }
-   301  
-   302 // Give the file to the browser
-   303 if (is_readable($downloadArchive))
-   304 {
-   305 if ($downloadMode == 'plain')
-   306 {
-   307 $downloadFilename = $plainfilename;
-   308 }
-   309 else
-   310 {
-   311 $downloadFilename = $rep->name.'-'.$downloadArchive;
-   312 }
-   313  
-   314 header('Content-Type: '.$downloadMimeType);
-   315 header('Content-Length: '.filesize($downloadArchive));
-   316 header("Content-Disposition: attachment; filename*=UTF-8''".rawurlencode($downloadFilename));
-   317 readfile($downloadArchive);
-   318 }
-   319 else
-   320 {
-   321 http_response_code(404);
-   322 print 'Unable to open file: '.xml_entities($downloadArchive);
-   323 }
-   324  
-   325 chdir($oldcwd);
-   326 removeDirectory($tempDir);