Annotation of loncom/cgi/archive.pl, revision 1.1
1.1 ! raeburn 1: #!/usr/bin/perl
! 2: #
! 3: # $Id: archive.pl,v 1.1 2024/05/02 18:33:17 raeburn Exp $
! 4: #
! 5: # Copyright Michigan State University Board of Trustees
! 6: #
! 7: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
! 8: #
! 9: # LON-CAPA is free software; you can redistribute it and/or modify
! 10: # it under the terms of the GNU General Public License as published by
! 11: # the Free Software Foundation; either version 2 of the License, or
! 12: # (at your option) any later version.
! 13: #
! 14: # LON-CAPA is distributed in the hope that it will be useful,
! 15: # but WITHOUT ANY WARRANTY; without even the implied warranty of
! 16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
! 17: # GNU General Public License for more details.
! 18: #
! 19: # You should have received a copy of the GNU General Public License
! 20: # along with LON-CAPA; if not, write to the Free Software
! 21: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
! 22: #
! 23: # /home/httpd/cgi-bin/archive.pl
! 24: #
! 25: # http://www.lon-capa.org/
! 26: #
! 27: # The LearningOnline Network with CAPA
! 28: #
! 29: # A CGI script which creates a compressed archive file of the current
! 30: # directory in Authoring Space, with optional (a) recursion into
! 31: # sub-directories, (b) filtering by filetype and (c) encryption.
! 32: # Supported formats are: tar.gz, tar.bz2, tar.xz and zip.
! 33: ####
! 34: use strict;
! 35: use lib '/home/httpd/lib/perl';
! 36: use File::Find;
! 37: use Apache::lonnet;
! 38: use Apache::loncommon;
! 39: use Apache::lonlocal;
! 40: use LONCAPA::loncgi;
! 41: use Cwd;
! 42: use HTML::Entities;
! 43:
! 44: $|++;
! 45:
! 46: my $lock;
! 47:
! 48: our %excluded = (
! 49: bak => 1,
! 50: save => 1,
! 51: log => 1,
! 52: );
! 53:
! 54: our $maxdepth = 0;
! 55: our %included = ();
! 56: our $alltypes = '';
! 57: our $recurse = '';
! 58: our $includeother = '';
! 59: our $prefix = '';
! 60: our $totalfiles = 0;
! 61: our $totalsize = 0;
! 62: our $totalsubdirs = 0;
! 63: our %subdirs = ();
! 64: our $fh;
! 65:
! 66: if (!&LONCAPA::loncgi::check_cookie_and_load_env()) {
! 67: &Apache::lonlocal::get_language_handle();
! 68: print(&LONCAPA::loncgi::missing_cookie_msg());
! 69: } else {
! 70: &Apache::lonlocal::get_language_handle();
! 71: my %lt = &Apache::lonlocal::texthash (
! 72: indi => 'Invalid directory name',
! 73: outo => 'Output of command:',
! 74: comp => 'Archive creation complete.',
! 75: erro => 'An error occurred.',
! 76: cctf => 'Cannot create tar file',
! 77: dtf => 'Download tar file',
! 78: );
! 79: # Get the identifier and set a lock
! 80: my %perlvar=%{&LONCAPA::Configuration::read_conf('loncapa.conf')};
! 81: my $londocroot = $Apache::lonnet::perlvar{'lonDocRoot'};
! 82: &Apache::lonlocal::get_language_handle();
! 83: &Apache::loncommon::content_type(undef,'text/html');
! 84: my $identifier = $ENV{'QUERY_STRING'};
! 85: my ($hashref,$dir,$dirurl,$jsdirurl,$auname,$audom,$allowed,$error,$encrypt,$enckey,$format,$compress);
! 86: my @posstypes = qw(problem library sty sequence page task rights meta xml html xhtml htm xhtm css js tex txt gif jpg jpeg png svg other);
! 87: if (($identifier) && (exists($env{'cgi.'.$identifier.'.archive'}))) {
! 88: $hashref = &Apache::lonnet::thaw_unescape($env{'cgi.'.$identifier.'.archive'});
! 89: if (ref($hashref) eq 'HASH') {
! 90: $dir = $hashref->{'dir'};
! 91: # check for traversal
! 92: if (-d $dir) {
! 93: $dirurl = $dir;
! 94: ($auname,$audom) = &Apache::lonnet::constructaccess($dir);
! 95: if (($auname ne '') && ($audom ne '')) {
! 96: $dirurl =~ s/^\Q$londocroot\E//;
! 97: $prefix = $londocroot.$dirurl;
! 98: $maxdepth = $prefix =~ tr{/}{};
! 99: $jsdirurl = &js_escape($dirurl);
! 100: if (($auname eq $env{'user.name'}) && ($audom eq $env{'user.domain'}) &&
! 101: ($env{'environment.authorarchive'})) {
! 102: $allowed = 1;
! 103: if ($hashref->{'recurse'}) {
! 104: $recurse = 1;
! 105: } else {
! 106: $recurse = 0;
! 107: }
! 108: if ($hashref->{'types'} eq 'all') {
! 109: $alltypes = 1;
! 110: } else {
! 111: $alltypes = 0;
! 112: my %possincluded;
! 113: map { $possincluded{$_} = 1; } split(/,/,$hashref->{'types'});
! 114: $includeother = 0;
! 115: foreach my $type (@posstypes) {
! 116: if ($type eq 'other') {
! 117: if ($possincluded{$type}) {
! 118: $includeother = 1;
! 119: } else {
! 120: $includeother = 0;
! 121: }
! 122: } else {
! 123: if ($possincluded{$type}) {
! 124: $included{$type} = 1;
! 125: } else {
! 126: $excluded{$type} = 1;
! 127: }
! 128: }
! 129: }
! 130: }
! 131: if ((exists($hashref->{'encrypt'}) && $hashref->{'encrypt'} ne '')) {
! 132: $encrypt = 1;
! 133: $enckey = $hashref->{'encrypt'};
! 134: }
! 135: if ((exists($hashref->{'format'}) && $hashref->{'format'} =~ /^zip$/i)) {
! 136: $format = lc($hashref->{'format'});
! 137: } else {
! 138: $format = 'tar';
! 139: }
! 140: unless ($format eq 'zip') {
! 141: if ((exists($hashref->{'compress'})) && ($hashref->{'compress'} =~ /^(xz|bzip2)$/i)) {
! 142: $compress = lc($hashref->{'compress'});
! 143: } else {
! 144: $compress = 'gzip';
! 145: }
! 146: }
! 147: }
! 148: }
! 149: } else {
! 150: $error = 'indi';
! 151: }
! 152: } else {
! 153: $error = 'nohash';
! 154: }
! 155: # delete cgi.$identifier.archive from %env
! 156: &Apache::lonnet::delenv('cgi.'.$identifier.'.archive');
! 157: } else {
! 158: $error = 'noid';
! 159: }
! 160: $env{'request.noversionuri'} = '/cgi-bin/archive.pl';
! 161: my ($brcrum,$title);
! 162: if ($error) {
! 163: $brcrum = [{'href' => '',
! 164: 'text' => 'Missing information'}];
! 165: } elsif (!$allowed) {
! 166: $brcrum = [{'href' => '',
! 167: 'text' => 'Access denied'}];
! 168: } else {
! 169: # Breadcrumbs
! 170: $title = 'Creating archive file';
! 171: $brcrum = [{'href' => $dirurl,
! 172: 'text' => 'Authoring Space'},
! 173: {'href' => "javascript:gocstr('/adm/cfile?action=archive','$jsdirurl');",
! 174: 'text' => 'File Operation'},
! 175: {'href' => '',
! 176: 'text' => $title}];
! 177: }
! 178: my $js;
! 179: print &Apache::loncommon::start_page($title,
! 180: $js,
! 181: {'bread_crumbs' => $brcrum,})."\n".
! 182: '<form name="constspace" method="post" action="">'."\n".
! 183: '<input type="hidden" name="filename" value="" />'."\n";
! 184: if ($error) {
! 185: print "&mt('Cannot create archive file -- \n";
! 186: } elsif ($allowed) {
! 187: my (%location_of,@tocheck);
! 188: if ($format ne '') {
! 189: push(@tocheck,$format);
! 190: }
! 191: if ($compress ne '') {
! 192: push(@tocheck,$compress);
! 193: }
! 194: foreach my $program (@tocheck) {
! 195: foreach my $dir ('/bin/','/usr/bin/','/usr/local/bin/','/sbin/',
! 196: '/usr/sbin/') {
! 197: if (-x $dir.$program) {
! 198: $location_of{$program} = $dir.$program;
! 199: last;
! 200: }
! 201: }
! 202: }
! 203: if (exists($location_of{$format})) {
! 204: my $suffix;
! 205: if ($format eq 'zip') {
! 206: $suffix = 'zip';
! 207: } else {
! 208: $suffix = 'tar';
! 209: if (exists($location_of{$compress})) {
! 210: if ($compress eq 'bzip2') {
! 211: $suffix .= '.bz2';
! 212: } elsif ($compress eq 'gzip') {
! 213: $suffix .= '.gz';
! 214: } elsif ($compress eq 'xz') {
! 215: $suffix .= '.xz';
! 216: }
! 217: }
! 218: }
! 219: my $namesdest = $perlvar{'lonPrtDir'}.'/'.$env{'user.name'}.'_'.$env{'user.domain'}.'_archive_'.$identifier.'.txt';
! 220: my $filesdest = $perlvar{'lonPrtDir'}.'/'.$env{'user.name'}.'_'.$env{'user.domain'}.'_archive_'.$identifier.'.'.$suffix;
! 221: my $filesurl = '/prtspool/'.$env{'user.name'}.'_'.$env{'user.domain'}.'_archive_'.$identifier.'.'.$suffix;
! 222: unless ($lock) { $lock=&Apache::lonnet::set_lock(&mt('Archiving [_1]',$dirurl)); }
! 223: if (open($fh,'>',$namesdest)) {
! 224: find(
! 225: {preprocess => \&filter_files,
! 226: wanted => \&store_names,
! 227: no_chdir => 1,
! 228: },$dir);
! 229: close($fh);
! 230: if (($totalfiles) || ($totalsubdirs)) {
! 231: print '<p>'.
! 232: &mt('Archiving: [quant,_1,file,files] with total size: [_2] bytes in [quant,_3,subdirectory,subdirectories] ...',
! 233: $totalfiles,$totalsize,$totalsubdirs).
! 234: '</p>';
! 235: my ($cwd,@args);
! 236: if ($format eq 'zip') {
! 237: $cwd = &Cwd::getcwd();
! 238: @args = ('zip',$filesdest,'-v','-r','.','-i@'.$namesdest);
! 239: chdir $prefix;
! 240: } else {
! 241: @args = ('tar',"--create","--verbose");
! 242: if (($compress ne '') && (exists($location_of{$compress}))) {
! 243: push(@args,"--$compress");
! 244: }
! 245: push(@args,("--file=$filesdest","--directory=$prefix","--files-from=$namesdest"));
! 246: }
! 247: if (open(my $pipe,'-|',@args)) {
! 248: my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin('',$totalfiles);
! 249: while (<$pipe>) {
! 250: &Apache::lonhtmlcommon::Increment_PrgWin('',\%prog_state,'last file');
! 251: }
! 252: &Apache::lonhtmlcommon::Close_PrgWin('',\%prog_state);
! 253: close($pipe);
! 254: if (!-e $filesdest) {
! 255: print '<p>'.&mt('No archive file available for download').'</p>'."\n";
! 256: }
! 257: } else {
! 258: print '<p>'.&mt('Could not call [_1] command',$format).'</p>'."\n";
! 259: }
! 260: if (($format eq 'zip') && ($cwd ne '')) {
! 261: chdir $cwd;
! 262: }
! 263: } else {
! 264: print '<p>'.&mt('No files match the requested types so no archive file was created.').'</p>'."\n";
! 265: }
! 266: unlink($namesdest);
! 267: } else {
! 268: print '<p>'.&mt('Could not store list of files to archive').'</p>'."\n";
! 269: }
! 270: if ($lock) { &Apache::lonnet::remove_lock($lock); }
! 271: } else {
! 272: print '<p>'.&mt('Could not find location of [_1] command',$format).'</p>'."\n";
! 273: }
! 274: }
! 275: if ($dirurl) {
! 276: print '<br /><br />'.
! 277: &Apache::lonhtmlcommon::actionbox(['<a href="'.&HTML::Entities::encode($dirurl,'\'"&<>').'">'.
! 278: &mt('Return to Directory').'</a>']);
! 279: }
! 280: print '</form>'.&Apache::loncommon::end_page();
! 281:
! 282: # Code to delete archive file after successful download
! 283: %included = ();
! 284: $alltypes = '';
! 285: $recurse = '';
! 286: $includeother = '';
! 287: $prefix = '';
! 288: $totalfiles = 0;
! 289: $totalsize = 0;
! 290: $totalsubdirs = 0;
! 291: %excluded = (
! 292: bak => 1,
! 293: save => 1,
! 294: log => 1,
! 295: );
! 296: }
! 297:
! 298: sub filter_files {
! 299: my @PossibleFiles = @_;
! 300: my @ChosenFiles;
! 301: foreach my $file (@PossibleFiles) {
! 302: if (-d $File::Find::dir."/".$file) {
! 303: if (!$recurse) {
! 304: my $depth = $File::Find::dir =~ tr[/][];
! 305: next unless ($depth < $maxdepth-1);
! 306: }
! 307: push(@ChosenFiles,$file);
! 308: } else {
! 309: my ($extension) = ($file =~ /\.([^.]+)$/);
! 310: if ((!$excluded{$extension}) && ($alltypes || $includeother || $included{$extension})) {
! 311: push(@ChosenFiles,$file);
! 312: }
! 313: }
! 314: }
! 315: return @ChosenFiles;
! 316: }
! 317:
! 318: sub store_names {
! 319: my $filename = $File::Find::name;
! 320: if (-d $filename) {
! 321: unless ("$filename/" eq $prefix) {
! 322: if ($recurse) {
! 323: $subdirs{$filename} = 1;
! 324: $totalsubdirs ++;
! 325: }
! 326: }
! 327: next;
! 328: }
! 329: $totalfiles ++;
! 330: $totalsize += -s $filename;
! 331: $filename =~ s{^$prefix}{};
! 332: print $fh "$filename\n";
! 333: }
! 334:
! 335: sub js {
! 336: my $output = <<'END';
! 337: const xhrButtonSuccess = document.querySelector(".xhr.success");
! 338: const xhrButtonError = document.querySelector(".xhr.error");
! 339: const xhrButtonAbort = document.querySelector(".xhr.abort");
! 340: const log = document.querySelector(".event-log");
! 341:
! 342: function handleEvent(e) {
! 343: log.textContent = `${log.textContent}${e.type}: ${e.loaded} bytes transferred\n`;
! 344: }
! 345:
! 346: function addListeners(xhr) {
! 347: xhr.addEventListener("loadstart", handleEvent);
! 348: xhr.addEventListener("load", handleEvent);
! 349: xhr.addEventListener("loadend", handleEvent);
! 350: xhr.addEventListener("progress", handleEvent);
! 351: xhr.addEventListener("error", handleEvent);
! 352: xhr.addEventListener("abort", handleEvent);
! 353: }
! 354:
! 355: function runXHR(url) {
! 356: log.textContent = "";
! 357:
! 358: const xhr = new XMLHttpRequest();
! 359: addListeners(xhr);
! 360: xhr.open("GET", url);
! 361: xhr.send();
! 362: return xhr;
! 363: }
! 364:
! 365: xhrButtonSuccess.addEventListener("click", () => {
! 366: runXHR(
! 367: "https://somewhere",
! 368: );
! 369: });
! 370:
! 371: xhrButtonError.addEventListener("click", () => {
! 372: runXHR("http://i-dont-exist");
! 373: });
! 374:
! 375: xhrButtonAbort.addEventListener("click", () => {
! 376: runXHR(
! 377: "https://somewhere",
! 378: ).abort();
! 379: });
! 380:
! 381: END
! 382:
! 383: }
! 384:
! 385:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>