--- loncom/publisher/lonupload.pm	2013/07/09 21:35:06	1.65
+++ loncom/publisher/lonupload.pm	2023/07/23 11:54:56	1.71
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to upload files into construction space
 #
-# $Id: lonupload.pm,v 1.65 2013/07/09 21:35:06 raeburn Exp $
+# $Id: lonupload.pm,v 1.71 2023/07/23 11:54:56 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -130,7 +130,7 @@ use Apache::lonnet;
 use HTML::Entities();
 use Apache::lonlocal;
 use Apache::lonnet;
-use LONCAPA();
+use LONCAPA qw(:DEFAULT :match);
 
 my $DEBUG=0;
 
@@ -150,8 +150,12 @@ sub upfile_store {
     
     chomp($env{'form.upfile'});
   
-    my $datatoken=$env{'user.name'}.'_'.$env{'user.domain'}.
-		  '_upload_'.$fname.'_'.time.'_'.$$;
+    my $datatoken;
+    if (($env{'user.name'} =~ /^$match_username$/) && ($env{'user.domain'} =~ /^$match_domain$/)) {
+        $datatoken=$env{'user.name'}.'_'.$env{'user.domain'}.
+                   '_upload_'.$fname.'_'.time.'_'.$$;
+    }
+    return if ($datatoken eq '');
     {
        my $fh=Apache::File->new('>'.$r->dir_config('lonDaemons').
                                    '/tmp/'.$datatoken.'.tmp');
@@ -172,6 +176,7 @@ sub phaseone {
     # Check for file to be uploaded
     $env{'form.upfile.filename'}=~s/\\/\//g;
     $env{'form.upfile.filename'}=~s/^.*\/([^\/]+)$/$1/;
+    $env{'form.upfile.filename'}=~s/(\s+$|^\s+)//g;
     if (!$env{'form.upfile.filename'}) {
         $r->print('<p class="LC_warning">'.&mt('No upload file specified.').'</p>'.
                   &earlyout($fn,$uname,$udom));
@@ -210,6 +215,14 @@ sub phaseone {
 
 # Split part that I can change from the part that I cannot change
     my ($fn1,$fn2)=($fn=~/^(\/priv\/[^\/]+\/[^\/]+\/)(.*)$/);
+# Check for pattern: .number.extension which is reserved for LON-CAPA versioning. 
+# Check for disallowed characters: #?&%:<>`|, and remove
+    if ($fn2 ne '') {
+        ($fn2,my $warning) = &check_filename($fn2);
+        if ($warning ne '') {
+            $r->print($warning);
+        }
+    }
     # Display additional options for upload
     # and upload button
     $r->print(
@@ -277,9 +290,12 @@ sub phasetwo {
 	my $base = &File::Basename::basename($fn);
 	my $path = &File::Basename::dirname($fn);
 	$base    = &HTML::Entities::encode($base,'<>&"');
-	my $url  = $path."/".$base; 
+	my $url  = $path."/".$base;
 	&Debug($r, "URL is now ".$url);
-	my $datatoken=$env{'form.datatoken'};
+	my $datatoken;
+        if ($env{'form.datatoken'} =~ /^$match_username\_$match_domain\_upload_\w*_\d+_\d+$/) {
+            $datatoken = $env{'form.datatoken'};
+        }
 	if (($fn) && ($datatoken)) {
             if ($env{'form.cancel'}) {
                 my $source=$r->dir_config('lonDaemons').'/tmp/'.$datatoken.'.tmp';
@@ -393,7 +409,7 @@ sub check_extension {
                         if ($pathchg) {
                             if ($mode eq 'testbank') {
                                 $returnflag = 'embedded';
-                                $result .=  '<p>'.&mt('Or [_1]continue[_2] the testbank import without modifying the references(s).','<a href="javascript:document.testbankForm.submit();">','</a>').'</p>';
+                                $result .=  '<p>'.&mt('Or [_1]continue[_2] the testbank import without modifying the reference(s).','<a href="javascript:document.testbankForm.submit();">','</a>').'</p>';
                             }
                         }
                     }
@@ -414,6 +430,47 @@ sub check_extension {
     return ($result,$returnflag);
 }
 
+sub check_filename {
+    my ($fname) = @_;
+    my $warning;
+    if ($fname =~/[#\?&%":<>`|]/) {
+        $fname =~s/[#\?&%":<>`|]//g;
+        $warning .= '<p class="LC_warning">'
+                   .&mt('Removed one or more disallowed characters from filename')
+                   .'</p>';
+    }
+    if ($fname=~ /\.(\d+)\.(\w+)$/) {
+        my $num = $1;
+        $warning .= '<p class="LC_warning">'
+                   .&mt('Bad filename [_1]','<span class="LC_filename">'.$fname.'</span>')
+                   .'<br />'
+                   .&mt('[_1](name).(number).(extension)[_2] not allowed.','<tt>','</tt>')
+                   .'<br />'
+                   .&mt('Replacing the [_1].number.[_2] with [_1]_letter.[_2] in requested filename.','<tt>','</tt>')
+                   .'</p>';
+        if ($num eq '0') {
+            $fname =~ s/\.(\d+)(\.\w+)$/_A$2/;
+        } else {
+            my $letts = '';
+            my %digletter = reverse &Apache::lonnet::letter_to_digits();
+            if ($num >= 100) {
+                $num = substr($num,-2);
+            }
+            foreach my $digit (split('',$num)) {
+                $letts .= $digletter{$digit};
+            }
+            $fname =~ s/\.(\d+)(\.\w+)$/_$letts$2/;
+        }
+    }
+    if ($fname =~/___/) {
+        $fname =~s/_+/_/g;
+        $warning .= '<p class="LC_warning">'
+                    .&mt('Changed ___ to a single _ in filename')
+                    .'</p>';
+    }
+    return ($fname,$warning);
+}
+
 sub phasethree {
     my ($r,$fn,$uname,$udom,$mode) = @_;
 
@@ -427,6 +484,8 @@ sub phasethree {
     my $dir_root = $r->dir_config('lonDocRoot').$url_root;
     my $path = &File::Basename::dirname($fn);
     $path =~ s{^\Q$url_root\E}{};
+    my $dirpath = $url_root.$path.'/';
+    $dirpath=~s{/+}{/}g;
     my $filename = &HTML::Entities::encode($env{'form.filename'},'<>&"');
     my $state = &embedded_form_elems('modify_orightml',$filename,$mode).
                 '<input type="hidden" name="phase" value="four" />';
@@ -437,7 +496,7 @@ sub phasethree {
     if ($mode ne 'imsimport' && $mode ne 'testbank') {
         $result .= '<br /><h3><a href="'.$fn.'">'.
                   &mt('View main file').'</a></h3>'.
-                  '<h3><a href="'.$url_root.$path.'">'.
+                  '<h3><a href="'.$dirpath.'">'.
                   &mt('Back to Directory').'</a></h3><br />';
     }
     return ($result,$returnflag);
@@ -466,13 +525,15 @@ sub phasefour {
     my $dir_root = $r->dir_config('lonDocRoot').$url_root;
     my $path = &File::Basename::dirname($fn);
     $path =~ s{^\Q$url_root\E}{};
+    my $dirpath = $url_root.$path.'/';
+    $dirpath=~s{/+}{/}g;
     my $outcome = 
         &Apache::loncommon::modify_html_refs($mode,$path,$uname,$udom,$dir_root);
     $result .= $outcome;
     if ($mode ne 'imsimport' && $mode ne 'testbank') {
         $result .= '<br /><h3><a href="'.$fn.'">'.
                   &mt('View main file').'</a></h3>'.
-                  '<h3><a href="'.$url_root.$path.'">'.
+                  '<h3><a href="'.$dirpath.'">'.
                   &mt('Back to Directory').'</a></h3><br />';
     }
     return $result;
@@ -492,12 +553,28 @@ sub handler {
 
     my $r=shift;
     my $javascript = '';
-    my $fn=$env{'form.filename'};
+    my $fn;
+    my $warning;
 
     if ($env{'form.filename1'}) {
-       $fn=$env{'form.filename1'}.$env{'form.filename2'};
+        my $fn1 = $env{'form.filename1'};
+        my $fn2 = $env{'form.filename2'};
+        $fn2 =~ s/(\s+$|^\s+)//g;
+        $fn2 =~ s/\/+/\//g;
+        ($fn2,$warning) = &check_filename($fn2);
+        $fn = $fn1.$fn2;
+    } else {
+        $fn = $env{'form.filename'};
     }
     $fn=~s/\/+/\//g;
+    if ($fn =~ m{/\.\./}) {
+        $warning .= '<p class="LC_warning">'
+                   .&mt('Path modified as a result of one or more instances of /../')
+                   .'</p>';
+        while ($fn =~ m{/\.\./}) {
+            $fn =~ s{/[^/]+/\.\./}{/}g;
+        }
+    }
 
     unless ($fn) {
         $r->log_reason($env{'user.name'}.' at '.$env{'user.domain'}.
@@ -508,8 +585,8 @@ sub handler {
     my ($uname,$udom)=&Apache::lonnet::constructaccess($fn);
 
     unless (($uname) && ($udom)) {
-        $r->log_reason($uname.' at '.$udom.
-                       ' trying to publish file '.$env{'form.filename'}.
+        $r->log_reason($env{'user.name'}.' at '.$env{'user.domain'}.
+                       ' trying to upload file '.$fn.
                        ' - not authorized',
                        $r->filename);
         return HTTP_NOT_ACCEPTABLE;
@@ -547,22 +624,38 @@ ENDJS
     $trailfile =~ s{^/(priv/)}{$londocroot/$1};
 
     # Breadcrumbs
-    my $brcrum = [{'href' => &Apache::loncommon::authorspace($fn),
-                   'text' => 'Authoring Space'},
+    my $text = 'Authoring Space';
+    my $href = &Apache::loncommon::authorspace($fn);
+    my $crsauthor;
+    if ($env{'request.course.id'}) {
+        my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+        my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+        if ($href eq "/priv/$cdom/$cnum/") {
+            $text = 'Course Authoring Space';
+            $crsauthor = 1;
+        }
+    }
+    my $brcrum = [{'href' => $href,
+                   'text' => $text},
                   {'href' => '/adm/upload',
-                   'text' => 'Upload file to Authoring Space'}];
-    $r->print(&Apache::loncommon::start_page('Upload file to Authoring Space',
+                   'text' => 'Upload file to '.$text}];
+    $r->print(&Apache::loncommon::start_page('Upload file to '.$text,
                                              $javascript,
                                              {'bread_crumbs' => $brcrum,})
              .&Apache::loncommon::head_subbox(
                 &Apache::loncommon::CSTR_pageheader($trailfile))
     );
-  
-    if (($uname ne $env{'user.name'}) || ($udom ne $env{'user.domain'})) {
-        $r->print('<p class="LC_info">'
-                 .&mt('Co-Author [_1]',$uname.':'.$udom)
-                 .'</p>'
-        );
+
+    unless ($crsauthor) {
+        if (($uname ne $env{'user.name'}) || ($udom ne $env{'user.domain'})) {
+            $r->print('<p class="LC_info">'
+                     .&mt('Co-Author [_1]',$uname.':'.$udom)
+                     .'</p>'
+            );
+        }
+    }
+    if ($warning) {
+        $r->print($warning);
     }
     if ($env{'form.phase'} eq 'four') {
         my $output = &phasefour($r,$fn,$uname,$udom,'author');
@@ -578,7 +671,7 @@ ENDJS
     }
 
     $r->print(&Apache::loncommon::end_page());
-    return OK;  
+    return OK;
 }
 
 1;