Rev Author Line No. Line
4988 kaklik 1 <?php
2 // WebSVN - Subversion repository viewing via the web using PHP
3 // Copyright (C) 2004-2006 Tim Armes
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 //
19 // --
20 //
21 // dl.php
22 //
23 // Allow for file and directory downloads, creating zip/tar/gz files if needed.
24  
25 require_once 'include/setup.php';
26 require_once 'include/svnlook.php';
27 require_once 'include/utils.php';
28  
29 if (!defined('USE_AUTOLOADER')) {
30 @include_once 'Archive/Tar.php';
31 }
32  
33 function setDirectoryTimestamp($dir, $timestamp) {
34 global $config;
35 // Changing the timestamp of a directory in Windows is only supported in PHP 5.3.0+
36 touch($dir, $timestamp);
37 if (is_dir($dir)) {
38 // Set timestamp for all contents, recursing into subdirectories
39 $handle = opendir($dir);
40 if ($handle) {
41 while (($file = readdir($handle)) !== false) {
42 if ($file == '.' || $file == '..') {
43 continue;
44 }
45 $f = $dir.DIRECTORY_SEPARATOR.$file;
46 if (is_dir($f)) {
47 setDirectoryTimestamp($f, $timestamp);
48 }
49 }
50 closedir($handle);
51 }
52 }
53 }
54  
55 // Make sure that downloading the specified file/directory is permitted
56  
57 if (!$rep)
58 {
59 http_response_code(404);
60 exit;
61 }
62  
63 if (!$rep->isDownloadAllowed($path)) {
64 http_response_code(403);
65 error_log('Unable to download resource at path: '.$path);
66 print 'Unable to download resource at path: '.xml_entities($path);
67 exit;
68 }
69  
70 $svnrep = new SVNRepository($rep);
71  
72 // Fetch information about a revision (if unspecified, the latest) for this path
73 if (empty($rev))
74 {
75 $history = $svnrep->getLog($path, 'HEAD', '', true, 1, $peg);
76 }
77 else if ($rev == $peg)
78 {
79 $history = $svnrep->getLog($path, '', 1, true, 1, $peg);
80 }
81 else
82 {
83 $history = $svnrep->getLog($path, $rev, $rev - 1, true, 1, $peg);
84 }
85  
86 $logEntry = ($history && !empty($history->entries)) ? $history->entries[0] : null;
87  
88 if (!$logEntry)
89 {
90 http_response_code(404);
91 error_log('Unable to download resource at path: '.$path);
92 print 'Unable to download resource at path: '.xml_entities($path);
93 exit(0);
94 }
95  
96 if (empty($rev))
97 {
98 $rev = $logEntry->rev;
99 }
100  
101 // Create a temporary filename to be used for a directory to archive a download.
102 // Here we have an unavoidable but highly unlikely to occur race condition.
103 $tempDir = tempnamWithCheck($config->getTempDir(), 'websvn');
104  
105 @unlink($tempDir);
106 mkdir($tempDir);
107 // Create the name of the directory being archived
108 $archiveName = $path;
109 $isDir = (substr($archiveName, -1) == '/');
110  
111 if ($isDir)
112 {
113 $archiveName = substr($archiveName, 0, -1);
114 }
115  
116 $archiveName = basename($archiveName);
117  
118 if ($archiveName == '')
119 {
120 $archiveName = $rep->name;
121 }
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);