Rev 1938 Rev 4988
Line 1... Line 1...
1 <?php 1 <?php
2 # vim:et:ts=3:sts=3:sw=3:fdm=marker: -  
3   -  
4 // WebSVN - Subversion repository viewing via the web using PHP 2 // WebSVN - Subversion repository viewing via the web using PHP
5 // Copyright © 2004-2006 Tim Armes, Matt Sicker 3 // Copyright (C) 2004-2006 Tim Armes
6 // 4 //
7 // This program is free software; you can redistribute it and/or modify 5 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License as published by 6 // it under the terms of the GNU General Public License as published by
9 // the Free Software Foundation; either version 2 of the License, or 7 // the Free Software Foundation; either version 2 of the License, or
10 // (at your option) any later version. 8 // (at your option) any later version.
Line 20... Line 18...
20 // 18 //
21 // -- 19 // --
22 // 20 //
23 // dl.php 21 // dl.php
24 // 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)) {
25 // Create gz/tar files of the requested item 47 setDirectoryTimestamp($f, $timestamp);
-   48 }
-   49 }
-   50 closedir($handle);
-   51 }
-   52 }
-   53 }
26   54  
27 require_once("include/setup.inc"); -  
28 require_once("include/svnlook.inc"); 55 // Make sure that downloading the specified file/directory is permitted
29 require_once("include/utils.inc"); -  
30   56  
-   57 if (!$rep)
-   58 {
31 // Make sure that this operation is allowed 59 http_response_code(404);
-   60 exit;
-   61 }
32   62  
33 if (!$rep->isDownloadAllowed($path)) 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);
34 exit; 67 exit;
-   68 }
35   69  
36 $svnrep = new SVNRepository($rep); 70 $svnrep = new SVNRepository($rep);
37   71  
-   72 // Fetch information about a revision (if unspecified, the latest) for this path
38 if ($path{0} != "/") 73 if (empty($rev))
-   74 {
-   75 $history = $svnrep->getLog($path, 'HEAD', '', true, 1, $peg);
-   76 }
39 $ppath = "/".$path; 77 else if ($rev == $peg)
-   78 {
-   79 $history = $svnrep->getLog($path, '', 1, true, 1, $peg);
-   80 }
40 else 81 else
-   82 {
41 $ppath = $path; 83 $history = $svnrep->getLog($path, $rev, $rev - 1, true, 1, $peg);
-   84 }
-   85  
-   86 $logEntry = ($history && !empty($history->entries)) ? $history->entries[0] : null;
42   87  
-   88 if (!$logEntry)
-   89 {
-   90 http_response_code(404);
43 // If there's no revision info, go to the lastest revision for this path 91 error_log('Unable to download resource at path: '.$path);
44 $history = $svnrep->getLog($path, "", "", true); 92 print 'Unable to download resource at path: '.xml_entities($path);
45 $youngest = $history->entries[0]->rev; 93 exit(0);
-   94 }
46   95  
47 if (empty($rev)) 96 if (empty($rev))
-   97 {
48 $rev = $youngest; 98 $rev = $logEntry->rev;
-   99 }
49   100  
50 // Create a temporary directory. Here we have an unavoidable but highly 101 // Create a temporary filename to be used for a directory to archive a download.
51 // unlikely to occure race condition 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) == '/');
52   110  
53 if (count(glob("/tmp/wsvn-*"))<=40) // Omezeni na pocet soucasne stahovanych souboru 111 if ($isDir)
54 { 112 {
-   113 $archiveName = substr($archiveName, 0, -1);
-   114 }
55   115  
56 // Ošetření doběhnutí skriptu i když klient klikne jinam (smazání tmp) -  
57 ignore_user_abort(true); 116 $archiveName = basename($archiveName);
58   117  
59 $tmpname1 = tempnam("temp", "wsvn-"); -  
60 $tmpname = $tmpname1; -  
61 $tmpname .= "dir"; -  
62 if (mkdir($tmpname)) 118 if ($archiveName == '')
63 { 119 {
64 // Get the name of the directory being archived -  
65 $arcname = substr($path, 0, -1); -  
66 $arcname = basename($arcname); 120 $archiveName = $rep->name;
-   121 }
-   122  
67 if (empty($arcname)) 123 $plainfilename = $archiveName;
68 $arcname = $rep->name; 124 $archiveName .= '.r'.$rev;
69 125  
-   126 // Export the requested path from SVN repository to the temp directory
70 $svnrep->exportDirectory($path, $tmpname.DIRECTORY_SEPARATOR.$arcname, $rev); 127 $svnExportResult = $svnrep->exportRepositoryPath($path, $tempDir.DIRECTORY_SEPARATOR.$archiveName, $rev, $peg);
71 128  
72 // ZIP it up 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).'".';
73 chdir($tmpname); 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 {
74 if (! connection_aborted()) 144 http_response_code(500);
-   145 error_log('to be downloaded file is symlink, aborting: '.$archiveName);
75 exec("zip -r ".quote("$arcname")." ."); 146 print 'Download of symlinks disallowed: "'.xml_entities($archiveName).'".';
-   147 removeDirectory($tempDir);
-   148 exit(0);
-   149 }
76 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
77 $size = filesize("$arcname.zip"); 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);
78 165  
-   166 if ($isDir)
-   167 {
-   168 $downloadMode = $config->getDefaultDirectoryDlMode();
-   169 }
-   170 else
-   171 {
-   172 $downloadMode = $config->getDefaultFileDlMode();
-   173 }
-   174  
79 // Give the file to the browser 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);
80 210  
81 if (!connection_aborted() && $fp = @fopen("$arcname.zip","rb")) 211 if ($retcode != 0)
82 { 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 {
83 header("Content-Type: application/zip"); 219 $downloadMimeType = 'application/gzip';
84 header("Content-Length: $size"); 220 $downloadArchive .= '.tar.gz';
85 header("Content-Disposition: attachment; filename=\"".$rep->name."-$arcname.zip\""); 221 $tarArchive = $archiveName.'.tar';
-   222  
86 // Use a loop to transfer the data 4KB at a time. 223 // Create the tar file
-   224 $retcode = 0;
-   225  
87 while(!feof($fp)) 226 if (class_exists('Archive_Tar'))
88 { 227 {
89 echo fread($fp, 4096); 228 $tar = new Archive_Tar($tarArchive);
-   229 $created = $tar->create(array($archiveName));
-   230  
-   231 if (!$created)
-   232 {
-   233 $retcode = 1;
90 ob_flush(); 234 http_response_code(500);
-   235 print 'Unable to create tar archive.';
91 } 236 }
92 } 237 }
93 else 238 else
94 { 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);
95 print "Unable to open file $arcname.zip"; 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);
96 } 256 }
97 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.';
98 fclose($fp); 271 chdir($oldcwd);
-   272 removeDirectory($tempDir);
-   273 exit(0);
-   274 }
99 275  
100 chdir(".."); 276 while (!feof($srcHandle))
-   277 {
-   278 gzwrite($dstHandle, fread($srcHandle, 1024 * 512));
-   279 }
101 280  
-   281 fclose($srcHandle);
-   282 gzclose($dstHandle);
-   283 }
-   284 else
-   285 {
102 // Delete the directory. Why doesn't PHP have a generic recursive directory 286 $cmd = $config->gzip.' '.quote($tarArchive);
-   287 $retcode = 0;
103 // deletion command? It's stupid. 288 execCommand($cmd, $retcode);
104 289  
105 // if ($config->serverIsWindows) 290 if ($retcode != 0)
106 // { 291 {
-   292 http_response_code(500);
-   293 error_log('Unable to call gzip command: '.$cmd);
107 // $cmd = quoteCommand("rmdir /S /Q ".quote($tmpname), false); 294 print 'Unable to call gzip command. See webserver error log for details.';
108 // } 295 chdir($oldcwd);
-   296 removeDirectory($tempDir);
109 // else 297 exit(0);
110 // { 298 }
111 $cmd = quoteCommand("rm -fr ".quote($tmpname), false); -  
112 // } 299 }
-   300 }
113 301  
114 @exec($cmd); 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;
115 } 312 }
-   313  
-   314 header('Content-Type: '.$downloadMimeType);
116 unlink($tmpname1); // Pomocny soubor smazeme az po smazani adresare 315 header('Content-Length: '.filesize($downloadArchive));
-   316 header("Content-Disposition: attachment; filename*=UTF-8''".rawurlencode($downloadFilename));
-   317 readfile($downloadArchive);
117 } 318 }
118 else 319 else
119 { 320 {
-   321 http_response_code(404);
120 print "We are sorry. The server is overloaded..."; 322 print 'Unable to open file: '.xml_entities($downloadArchive);
121 } 323 }
122 ?> 324  
-   325 chdir($oldcwd);
-   326 removeDirectory($tempDir);