--- loncom/lonnet/perl/lonnet.pm	2007/09/12 03:40:35	1.911
+++ loncom/lonnet/perl/lonnet.pm	2007/12/21 04:14:24	1.932
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.911 2007/09/12 03:40:35 raeburn Exp $
+# $Id: lonnet.pm,v 1.932 2007/12/21 04:14:24 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -320,7 +320,10 @@ sub convert_and_load_session_env {
     my ($lonidsdir,$handle)=@_;
     my @profile;
     {
-	open(my $idf,"$lonidsdir/$handle.id");
+	my $opened = open(my $idf,'+<',"$lonidsdir/$handle.id");
+	if (!$opened) {
+	    return 0;
+	}
 	flock($idf,LOCK_SH);
 	@profile=<$idf>;
 	close($idf);
@@ -359,7 +362,10 @@ sub transfer_profile_to_env {
 
     my $convert;
     {
-    	open(my $idf,"$lonidsdir/$handle.id");
+    	my $opened = open(my $idf,'+<',"$lonidsdir/$handle.id");
+	if (!$opened) {
+	    return;
+	}
 	flock($idf,LOCK_SH);
 	if (tie(my %disk_env,'GDBM_File',"$lonidsdir/$handle.id",
 		&GDBM_READER(),0640)) {
@@ -391,6 +397,34 @@ sub transfer_profile_to_env {
     }
 }
 
+# ---------------------------------------------------- Check for valid session 
+sub check_for_valid_session {
+    my ($r) = @_;
+    my %cookies=CGI::Cookie->parse($r->header_in('Cookie'));
+    my $lonid=$cookies{'lonID'};
+    return undef if (!$lonid);
+
+    my $handle=&LONCAPA::clean_handle($lonid->value);
+    my $lonidsdir=$r->dir_config('lonIDsDir');
+    return undef if (!-e "$lonidsdir/$handle.id");
+
+    my $opened = open(my $idf,'+<',"$lonidsdir/$handle.id");
+    return undef if (!$opened);
+
+    flock($idf,LOCK_SH);
+    my %disk_env;
+    if (!tie(%disk_env,'GDBM_File',"$lonidsdir/$handle.id",
+	    &GDBM_READER(),0640)) {
+	return undef;	
+    }
+
+    if (!defined($disk_env{'user.name'})
+	|| !defined($disk_env{'user.domain'})) {
+	return undef;
+    }
+    return $handle;
+}
+
 sub timed_flock {
     my ($file,$lock_type) = @_;
     my $failed=0;
@@ -425,8 +459,9 @@ sub appenv {
             $env{$key}=$newenv{$key};
         }
     }
-    open(my $env_file,$env{'user.environment'});
-    if (&timed_flock($env_file,LOCK_EX)
+    my $opened = open(my $env_file,'+<',$env{'user.environment'});
+    if ($opened
+	&& &timed_flock($env_file,LOCK_EX)
 	&&
 	tie(my %disk_env,'GDBM_File',$env{'user.environment'},
 	    (&GDBM_WRITER()|&GDBM_NOLOCK()),0640)) {
@@ -446,16 +481,17 @@ sub delenv {
                 "Attempt to delete from environment ".$delthis);
         return 'error';
     }
-    open(my $env_file,$env{'user.environment'});
-    if (&timed_flock($env_file,LOCK_EX)
+    my $opened = open(my $env_file,'+<',$env{'user.environment'});
+    if ($opened
+	&& &timed_flock($env_file,LOCK_EX)
 	&&
 	tie(my %disk_env,'GDBM_File',$env{'user.environment'},
 	    (&GDBM_WRITER()|&GDBM_NOLOCK()),0640)) {
 	foreach my $key (keys(%disk_env)) {
 	    if ($key=~/^$delthis/) { 
-                delete($env{$key});
-                delete($disk_env{$key});
-            }
+		delete($env{$key});
+		delete($disk_env{$key});
+	    }
 	}
 	untie(%disk_env);
     }
@@ -477,7 +513,6 @@ sub get_env_multiple {
 }
 
 # ------------------------------------------ Find out current server userload
-# there is a copy in lond
 sub userload {
     my $numusers=0;
     {
@@ -485,7 +520,8 @@ sub userload {
 	my $filename;
 	my $curtime=time;
 	while ($filename=readdir(LONIDS)) {
-	    if ($filename eq '.' || $filename eq '..') {next;}
+	    next if ($filename eq '.' || $filename eq '..');
+	    next if ($filename =~ /publicuser_\d+\.id/);
 	    my ($mtime)=(stat($perlvar{'lonIDsDir'}.'/'.$filename))[9];
 	    if ($curtime-$mtime < 1800) { $numusers++; }
 	}
@@ -582,6 +618,27 @@ sub compare_server_load {
     }
     return ($spare_server,$lowest_load);
 }
+
+# --------------------------- ask offload servers if user already has a session
+sub find_existing_session {
+    my ($udom,$uname) = @_;
+    foreach my $try_server (@{ $spareid{'primary'} },
+			    @{ $spareid{'default'} }) {
+	return $try_server if (&has_user_session($try_server, $udom, $uname));
+    }
+    return;
+}
+
+# -------------------------------- ask if server already has a session for user
+sub has_user_session {
+    my ($lonid,$udom,$uname) = @_;
+    my $result = &reply(join(':','userhassession',
+			     map {&escape($_)} ($udom,$uname)),$lonid);
+    return 1 if ($result eq 'ok');
+
+    return 0;
+}
+
 # --------------------------------------------- Try to change a user's password
 
 sub changepass {
@@ -950,6 +1007,114 @@ sub usersearch {
     return %results;
 }
 
+sub get_instuser {
+    my ($udom,$uname,$id) = @_;
+    my $homeserver = &domain($udom,'primary');
+    my ($outcome,%results);
+    if ($homeserver ne '') {
+        my $queryid=&reply("querysend:getinstuser:".&escape($uname).':'.
+                           &escape($id).':'.&escape($udom),$homeserver);
+        my $host=&hostname($homeserver);
+        if ($queryid !~/^\Q$host\E\_/) {
+            &logthis('get_instuser invalid queryid: '.$queryid.' for host: '.$homeserver.'in domain '.$udom);
+            return;
+        }
+        my $response = &get_query_reply($queryid);
+        my $maxtries = 5;
+        my $tries = 1;
+        while (($response=~/^timeout/) && ($tries < $maxtries)) {
+            $response = &get_query_reply($queryid);
+            $tries ++;
+        }
+        if (!&error($response) && $response ne 'refused') {
+            if ($response eq 'unavailable') {
+                $outcome = $response;
+            } else {
+                $outcome = 'ok';
+                my @matches = split(/\n/,$response);
+                foreach my $match (@matches) {
+                    my ($key,$value) = split(/=/,$match);
+                    $results{&unescape($key)} = &thaw_unescape($value);
+                }
+            }
+        }
+    }
+    my %userinfo;
+    if (ref($results{$uname}) eq 'HASH') {
+        %userinfo = %{$results{$uname}};
+    } 
+    return ($outcome,%userinfo);
+}
+
+sub inst_rulecheck {
+    my ($udom,$uname,$id,$item,$rules) = @_;
+    my %returnhash;
+    if ($udom ne '') {
+        if (ref($rules) eq 'ARRAY') {
+            @{$rules} = map {&escape($_);} (@{$rules});
+            my $rulestr = join(':',@{$rules});
+            my $homeserver=&domain($udom,'primary');
+            if (($homeserver ne '') && ($homeserver ne 'no_host')) {
+                my $response;
+                if ($item eq 'username') {                
+                    $response=&unescape(&reply('instrulecheck:'.&escape($udom).
+                                              ':'.&escape($uname).':'.$rulestr,
+                                              $homeserver));
+                } elsif ($item eq 'id') {
+                    $response=&unescape(&reply('instidrulecheck:'.&escape($udom).
+                                              ':'.&escape($id).':'.$rulestr,
+                                              $homeserver));
+                }
+                if ($response ne 'refused') {
+                    my @pairs=split(/\&/,$response);
+                    foreach my $item (@pairs) {
+                        my ($key,$value)=split(/=/,$item,2);
+                        $key = &unescape($key);
+                        next if ($key =~ /^error: 2 /);
+                        $returnhash{$key}=&thaw_unescape($value);
+                    }
+                }
+            }
+        }
+    }
+    return %returnhash;
+}
+
+sub inst_userrules {
+    my ($udom,$check) = @_;
+    my (%ruleshash,@ruleorder);
+    if ($udom ne '') {
+        my $homeserver=&domain($udom,'primary');
+        if (($homeserver ne '') && ($homeserver ne 'no_host')) {
+            my $response;
+            if ($check eq 'id') {
+                $response=&reply('instidrules:'.&escape($udom),
+                                 $homeserver);
+            } else {
+                $response=&reply('instuserrules:'.&escape($udom),
+                                 $homeserver);
+            }
+            if (($response ne 'refused') && ($response ne 'error') && 
+                ($response ne 'unknown_cmd') && 
+                ($response ne 'no_such_host')) {
+                my ($hashitems,$orderitems) = split(/:/,$response);
+                my @pairs=split(/\&/,$hashitems);
+                foreach my $item (@pairs) {
+                    my ($key,$value)=split(/=/,$item,2);
+                    $key = &unescape($key);
+                    next if ($key =~ /^error: 2 /);
+                    $ruleshash{$key}=&thaw_unescape($value);
+                }
+                my @esc_order = split(/\&/,$orderitems);
+                foreach my $item (@esc_order) {
+                    push(@ruleorder,&unescape($item));
+                }
+            }
+        }
+    }
+    return (\%ruleshash,\@ruleorder);
+}
+
 # --------------------------------------------------- Assign a key to a student
 
 sub assign_access_key {
@@ -1232,13 +1397,15 @@ sub do_cache_new {
 	$memcache->disconnect_all();
     }
     # need to make a copy of $value
-    #&make_room($id,$value,$debug);
+    &make_room($id,$value,$debug);
     return $value;
 }
 
 sub make_room {
     my ($id,$value,$debug)=@_;
-    $remembered{$id}=$value;
+
+    $remembered{$id}= (ref($value)) ? &Storable::dclone($value)
+                                    : $value;
     if ($to_remember<0) { return; }
     $accessed{$id}=[&gettimeofday()];
     if (scalar(keys(%remembered)) <= $to_remember) { return; }
@@ -1468,7 +1635,7 @@ sub ssi_body {
                                      &ssi($filelink,%form));
     $output=~s|//(\s*<!--)? BEGIN LON-CAPA Internal.+?// END LON-CAPA Internal\s*(-->)?\s||gs;
     $output=~s/^.*?\<body[^\>]*\>//si;
-    $output=~s/(.*)\<\/body\s*\>.*?$/$1/si;
+    $output=~s/\<\/body\s*\>.*?$//si;
     return $output;
 }
 
@@ -2019,7 +2186,7 @@ sub flushcourselogs {
 # times and course titles for all courseids
 #
     my %courseidbuffer=();
-    foreach my $crsid (keys %courselogs) {
+    foreach my $crsid (keys(%courselogs)) {
         if (&reply('log:'.$coursedombuf{$crsid}.':'.$coursenumbuf{$crsid}.':'.
 		          &escape($courselogs{$crsid}),
 		          $coursehombuf{$crsid}) eq 'ok') {
@@ -2032,23 +2199,21 @@ sub flushcourselogs {
                delete $courselogs{$crsid};
             }
         }
-        if ($courseidbuffer{$coursehombuf{$crsid}}) {
-           $courseidbuffer{$coursehombuf{$crsid}}.='&'.
-			 &escape($crsid).'='.&escape($coursedescrbuf{$crsid}).
-                         ':'.&escape($courseinstcodebuf{$crsid}).':'.&escape($courseownerbuf{$crsid}).':'.&escape($coursetypebuf{$crsid});
-        } else {
-           $courseidbuffer{$coursehombuf{$crsid}}=
-			 &escape($crsid).'='.&escape($coursedescrbuf{$crsid}).
-                         ':'.&escape($courseinstcodebuf{$crsid}).':'.&escape($courseownerbuf{$crsid}).':'.&escape($coursetypebuf{$crsid});
-        }
+        $courseidbuffer{$coursehombuf{$crsid}}{$crsid} = {
+            'description' => &escape($coursedescrbuf{$crsid}),
+            'inst_code'    => &escape($courseinstcodebuf{$crsid}),
+            'type'        => &escape($coursetypebuf{$crsid}),
+            'owner'       => &escape($courseownerbuf{$crsid}),
+        };
     }
 #
 # Write course id database (reverse lookup) to homeserver of courses 
 # Is used in pickcourse
 #
     foreach my $crs_home (keys(%courseidbuffer)) {
-        &courseidput(&host_domain($crs_home),$courseidbuffer{$crs_home},
-		     $crs_home);
+        my $response = &courseidput(&host_domain($crs_home),
+                                    $courseidbuffer{$crs_home},
+                                    $crs_home,'timeonly');
     }
 #
 # File accesses
@@ -2312,7 +2477,13 @@ sub get_my_roles {
         }
         if (ref($roles) eq 'ARRAY') {
             if (!grep(/^\Q$role\E$/,@{$roles})) {
-                next;
+                if ($role =~ /^cr\//) {
+                    if (!grep(/^cr$/,@{$roles})) {
+                        next;
+                    }
+                } else {
+                    next;
+                }
             }
         }
 	$returnhash{$username.':'.$domain.':'.$role}=$tstart.':'.$tend;
@@ -2354,31 +2525,77 @@ sub getannounce {
 #
 
 sub courseidput {
-    my ($domain,$what,$coursehome)=@_;
-    return &reply('courseidput:'.$domain.':'.$what,$coursehome);
+    my ($domain,$storehash,$coursehome,$caller) = @_;
+    my $outcome;
+    if ($caller eq 'timeonly') {
+        my $cids = '';
+        foreach my $item (keys(%$storehash)) {
+            $cids.=&escape($item).'&';
+        }
+        $cids=~s/\&$//;
+        $outcome = &reply('courseidputhash:'.$domain.':'.$caller.':'.$cids,
+                          $coursehome);       
+    } else {
+        my $items = '';
+        foreach my $item (keys(%$storehash)) {
+            $items.= &escape($item).'='.
+                     &freeze_escape($$storehash{$item}).'&';
+        }
+        $items=~s/\&$//;
+        $outcome = &reply('courseidputhash:'.$domain.':'.$caller.':'.$items,
+                          $coursehome);
+    }
+    if ($outcome eq 'unknown_cmd') {
+        my $what;
+        foreach my $cid (keys(%$storehash)) {
+            $what .= &escape($cid).'=';
+            foreach my $item ('description','inst_code','owner','type') {
+                $what .= &escape($storehash->{$item}).':';
+            }
+            $what =~ s/\:$/&/;
+        }
+        $what =~ s/\&$//;  
+        return &reply('courseidput:'.$domain.':'.$what,$coursehome);
+    } else {
+        return $outcome;
+    }
 }
 
 sub courseiddump {
-    my ($domfilter,$descfilter,$sincefilter,$instcodefilter,$ownerfilter,$coursefilter,$hostidflag,$hostidref,$typefilter,$regexp_ok)=@_;
-    my %returnhash=();
-    unless ($domfilter) { $domfilter=''; }
+    my ($domfilter,$descfilter,$sincefilter,$instcodefilter,$ownerfilter,
+        $coursefilter,$hostidflag,$hostidref,$typefilter,$regexp_ok)=@_;
+    my $as_hash = 1;
+    my %returnhash;
+    if (!$domfilter) { $domfilter=''; }
     my %libserv = &all_library();
     foreach my $tryserver (keys(%libserv)) {
         if ( (  $hostidflag == 1 
 	        && grep(/^\Q$tryserver\E$/,@{$hostidref}) ) 
 	     || (!defined($hostidflag)) ) {
 
-	    if ($domfilter eq ''
-		|| (&host_domain($tryserver) eq $domfilter)) {
-	        foreach my $line (
-                 split(/\&/,&reply('courseiddump:'.&host_domain($tryserver).':'.
-			       $sincefilter.':'.&escape($descfilter).':'.
-                               &escape($instcodefilter).':'.&escape($ownerfilter).':'.&escape($coursefilter).':'.&escape($typefilter).':'.&escape($regexp_ok),
-                               $tryserver))) {
-		    my ($key,$value)=split(/\=/,$line,2);
-                    if (($key) && ($value)) {
-		        $returnhash{&unescape($key)}=$value;
-                    }
+	    if (($domfilter eq '') ||
+		(&host_domain($tryserver) eq $domfilter)) {
+                my $rep = 
+                  &reply('courseiddump:'.&host_domain($tryserver).':'.
+                         $sincefilter.':'.&escape($descfilter).':'.
+                         &escape($instcodefilter).':'.&escape($ownerfilter).
+                         ':'.&escape($coursefilter).':'.&escape($typefilter).
+                         ':'.&escape($regexp_ok).':'.$as_hash,$tryserver);
+                my @pairs=split(/\&/,$rep);
+                foreach my $item (@pairs) {
+                    my ($key,$value)=split(/\=/,$item,2);
+                    $key = &unescape($key);
+                    next if ($key =~ /^error: 2 /);
+                    my $result = &thaw_unescape($value);
+                    if (ref($result) eq 'HASH') {
+                        $returnhash{$key}=$result;
+                    } else {
+                        my @responses = split(/:/,$value);
+                        my @items = ('description','inst_code','owner','type');
+                        for (my $i=0; $i<@responses; $i++) {
+                            $returnhash{$key}{$items[$i]} = &unescape($responses[$i]);
+                        }
+                    } 
                 }
             }
         }
@@ -2424,7 +2641,10 @@ sub get_domain_roles {
     if (undef($enddate) || $enddate eq '') {
         $enddate = '.';
     }
-    my $rolelist = join(':',@{$roles});
+    my $rolelist;
+    if (ref($roles) eq 'ARRAY') {
+        $rolelist = join(':',@{$roles});
+    }
     my %personnel = ();
 
     my %servers = &get_servers($dom,'library');
@@ -2450,7 +2670,9 @@ sub get_first_access {
     my ($symb,$courseid,$udom,$uname)=&whichuser();
     if ($argsymb) { $symb=$argsymb; }
     my ($map,$id,$res)=&decode_symb($symb);
-    if ($type eq 'map') {
+    if ($type eq 'course') {
+	$res='course';
+    } elsif ($type eq 'map') {
 	$res=&symbread($map);
     } else {
 	$res=$symb;
@@ -2463,7 +2685,9 @@ sub set_first_access {
     my ($type)=@_;
     my ($symb,$courseid,$udom,$uname)=&whichuser();
     my ($map,$id,$res)=&decode_symb($symb);
-    if ($type eq 'map') {
+    if ($type eq 'course') {
+	$res='course';
+    } elsif ($type eq 'map') {
 	$res=&symbread($map);
     } else {
 	$res=$symb;
@@ -4824,10 +5048,16 @@ sub auto_instcode_defaults {
 } 
 
 sub auto_validate_class_sec {
-    my ($cdom,$cnum,$owner,$inst_class) = @_;
+    my ($cdom,$cnum,$owners,$inst_class) = @_;
     my $homeserver = &homeserver($cnum,$cdom);
+    my $ownerlist;
+    if (ref($owners) eq 'ARRAY') {
+        $ownerlist = join(',',@{$owners});
+    } else {
+        $ownerlist = $owners;
+    }
     my $response=&reply('autovalidateclass_sec:'.$inst_class.':'.
-                        &escape($owner).':'.$cdom,$homeserver);
+                        &escape($ownerlist).':'.$cdom,$homeserver);
     return $response;
 }
 
@@ -5020,11 +5250,21 @@ sub assignrole {
     } else {
         my $cwosec=$url;
         $cwosec=~s/^\/($match_domain)\/($match_courseid)\/.*/$1\/$2/;
-        unless ((&allowed('c'.$role,$cwosec)) || &allowed('c'.$role,$udom)) { 
-           &logthis('Refused assignrole: '.
-             $udom.' '.$uname.' '.$url.' '.$role.' '.$end.' '.$start.' by '.
-		    $env{'user.name'}.' at '.$env{'user.domain'});
-           return 'refused'; 
+        if (!(&allowed('c'.$role,$cwosec)) && !(&allowed('c'.$role,$udom))) {
+            my $refused;
+            if (($env{'request.course.sec'}  ne '') && ($role eq 'st')) {
+                if (!(&allowed('c'.$role,$url))) {
+                    $refused = 1;
+                }
+            } else {
+                $refused = 1;
+            }
+            if ($refused) { 
+                &logthis('Refused assignrole: '.$udom.' '.$uname.' '.$url.
+                         ' '.$role.' '.$end.' '.$start.' by '.
+	  	         $env{'user.name'}.' at '.$env{'user.domain'});
+                return 'refused';
+            }
         }
         $mrole=$role;
     }
@@ -5356,10 +5596,15 @@ sub createcourse {
     }
 # ----------------------------------------------------------------- Course made
 # log existence
-    &courseidput($udom,&escape($udom.'_'.$uname).'='.&escape($description).
-                 ':'.&escape($inst_code).':'.&escape($course_owner).':'.
-                  &escape($crstype),$uhome);
-    &flushcourselogs();
+    my $newcourse = {
+                    $udom.'_'.$uname => {
+                                     description => $description,
+                                     inst_code   => $inst_code,
+                                     owner       => $course_owner,
+                                     type        => $crstype,
+                                                },
+                    };
+    &courseidput($udom,$newcourse,$uhome,'notime');
 # set toplevel url
     my $topurl=$url;
     unless ($nonstandard) {
@@ -5389,7 +5634,7 @@ ENDINITMAP
 sub is_course {
     my ($cdom,$cnum) = @_;
     my %courses = &courseiddump($cdom,'.',1,'.','.',$cnum,undef,
-				undef,'.');
+				undef,'.',undef,1);
     if (exists($courses{$cdom.'_'.$cnum})) {
         return 1;
     }
@@ -6161,8 +6406,8 @@ sub resdata {
     }
     if (!ref($result)) { return $result; }    
     foreach my $item (@which) {
-	if (defined($result->{$item})) {
-	    return $result->{$item};
+	if (defined($result->{$item->[0]})) {
+	    return [$result->{$item->[0]},$item->[1]];
 	}
     }
     return undef;
@@ -6374,24 +6619,27 @@ sub EXT {
 # ----------------------------------------------------------- first, check user
 
 	    my $userreply=&resdata($uname,$udom,'user',
-				       ($courselevelr,$courselevelm,
-					$courselevel));
-	    if (defined($userreply)) { return $userreply; }
+				       ([$courselevelr,'resource'],
+					[$courselevelm,'map'     ],
+					[$courselevel, 'course'  ]));
+	    if (defined($userreply)) { return &get_reply($userreply); }
 
 # ------------------------------------------------ second, check some of course
             my $coursereply;
             if (@groups > 0) {
                 $coursereply = &check_group_parms($courseid,\@groups,$symbparm,
                                        $mapparm,$spacequalifierrest);
-                if (defined($coursereply)) { return $coursereply; }
+                if (defined($coursereply)) { return &get_reply($coursereply); }
             }
 
 	    $coursereply=&resdata($env{'course.'.$courseid.'.num'},
-				     $env{'course.'.$courseid.'.domain'},
-				     'course',
-				     ($seclevelr,$seclevelm,$seclevel,
-				      $courselevelr));
-	    if (defined($coursereply)) { return $coursereply; }
+				  $env{'course.'.$courseid.'.domain'},
+				  'course',
+				  ([$seclevelr,   'resource'],
+				   [$seclevelm,   'map'     ],
+				   [$seclevel,    'course'  ],
+				   [$courselevelr,'resource']));
+	    if (defined($coursereply)) { return &get_reply($coursereply); }
 
 # ------------------------------------------------------ third, check map parms
 	    my %parmhash=();
@@ -6402,7 +6650,7 @@ sub EXT {
 		$thisparm=$parmhash{$symbparm};
 		untie(%parmhash);
 	    }
-	    if ($thisparm) { return $thisparm; }
+	    if ($thisparm) { return &get_reply([$thisparm,'resource']); }
 	}
 # ------------------------------------------ fourth, look in resource metadata
 
@@ -6415,18 +6663,19 @@ sub EXT {
 	    $filename=$env{'request.filename'};
 	}
 	my $metadata=&metadata($filename,$spacequalifierrest);
-	if (defined($metadata)) { return $metadata; }
+	if (defined($metadata)) { return &get_reply([$metadata,'resource']); }
 	$metadata=&metadata($filename,'parameter_'.$spacequalifierrest);
-	if (defined($metadata)) { return $metadata; }
+	if (defined($metadata)) { return &get_reply([$metadata,'resource']); }
 
-# ---------------------------------------------- fourth, look in rest pf course
+# ---------------------------------------------- fourth, look in rest of course
 	if ($symbparm && defined($courseid) && 
 	    $courseid eq $env{'request.course.id'}) {
 	    my $coursereply=&resdata($env{'course.'.$courseid.'.num'},
 				     $env{'course.'.$courseid.'.domain'},
 				     'course',
-				     ($courselevelm,$courselevel));
-	    if (defined($coursereply)) { return $coursereply; }
+				     ([$courselevelm,'map'   ],
+				      [$courselevel, 'course']));
+	    if (defined($coursereply)) { return &get_reply($coursereply); }
 	}
 # ------------------------------------------------------------------ Cascade up
 	unless ($space eq '0') {
@@ -6434,14 +6683,13 @@ sub EXT {
 	    my $id=pop(@parts);
 	    my $part=join('_',@parts);
 	    if ($part eq '') { $part='0'; }
-	    my $partgeneral=&EXT('resource.'.$part.'.'.$qualifierrest,
+	    my @partgeneral=&EXT('resource.'.$part.'.'.$qualifierrest,
 				 $symbparm,$udom,$uname,$section,1);
-	    if (defined($partgeneral)) { return $partgeneral; }
+	    if (@partgeneral) { return &get_reply(\@partgeneral); }
 	}
 	if ($recurse) { return undef; }
 	my $pack_def=&packages_tab_default($filename,$varname);
-	if (defined($pack_def)) { return $pack_def; }
-
+	if (defined($pack_def)) { return &get_reply([$pack_def,'resource']); }
 # ---------------------------------------------------- Any other user namespace
     } elsif ($realm eq 'environment') {
 # ----------------------------------------------------------------- environment
@@ -6469,15 +6717,23 @@ sub EXT {
     return '';
 }
 
+sub get_reply {
+    my ($reply_value) = @_;
+    if (wantarray) {
+	return @$reply_value;
+    }
+    return $reply_value->[0];
+}
+
 sub check_group_parms {
     my ($courseid,$groups,$symbparm,$mapparm,$what) = @_;
     my @groupitems = ();
     my $resultitem;
-    my @levels = ($symbparm,$mapparm,$what);
+    my @levels = ([$symbparm,'resource'],[$mapparm,'map'],[$what,'course']);
     foreach my $group (@{$groups}) {
         foreach my $level (@levels) {
-             my $item = $courseid.'.['.$group.'].'.$level;
-             push(@groupitems,$item);
+             my $item = $courseid.'.['.$group.'].'.$level->[0];
+             push(@groupitems,[$item,$level->[1]]);
         }
     }
     my $coursereply = &resdata($env{'course.'.$courseid.'.num'},
@@ -6570,8 +6826,11 @@ sub metadata {
     if (($uri eq '') || 
 	(($uri =~ m|^/*adm/|) && 
 	     ($uri !~ m|^adm/includes|) && ($uri !~ m|/bulletinboard$|)) ||
-        ($uri =~ m|/$|) || ($uri =~ m|/.meta$|) || ($uri =~ /^~/) ||
-	($uri =~ m|home/$match_username/public_html/|)) {
+        ($uri =~ m|/$|) || ($uri =~ m|/.meta$|) ) {
+	return undef;
+    }
+    if (($uri =~ /^~/ || $uri =~ m{home/$match_username/public_html/}) 
+	&& &Apache::lonxml::get_state('target') =~ /^(|meta)$/) {
 	return undef;
     }
     my $filename=$uri;
@@ -6592,6 +6851,7 @@ sub metadata {
 #	if (! exists($metacache{$uri})) {
 #	    $metacache{$uri}={};
 #	}
+	my $cachetime = 60*60;
         if ($liburi) {
 	    $liburi=&declutter($liburi);
             $filename=$liburi;
@@ -6602,7 +6862,13 @@ sub metadata {
         my %metathesekeys=();
         unless ($filename=~/\.meta$/) { $filename.='.meta'; }
 	my $metastring;
-	if ($uri !~ m -^(editupload)/-) {
+	if ($uri =~ /^~/ || $uri =~ m{home/$match_username/public_html/}) {
+	    my $which = &hreflocation('','/'.($liburi || $uri));
+	    $metastring = 
+		&Apache::lonnet::ssi_body($which,
+					  ('grade_target' => 'meta'));
+	    $cachetime = 1; # only want this cached in the child not long term
+	} elsif ($uri !~ m -^(editupload)/-) {
 	    my $file=&filelocation('',&clutter($filename));
 	    #push(@{$metaentry{$uri.'.file'}},$file);
 	    $metastring=&getfile($file);
@@ -6769,7 +7035,7 @@ sub metadata {
 	$metaentry{':keys'} = join(',',keys(%metathesekeys));
 	&metadata_generate_part0(\%metathesekeys,\%metaentry,$uri);
 	$metaentry{':allpossiblekeys'}=join(',',keys %metathesekeys);
-	&do_cache_new('meta',$uri,\%metaentry,60*60);
+	&do_cache_new('meta',$uri,\%metaentry,$cachetime);
 # this is the end of "was not already recently cached
     }
     return $metaentry{':'.$what};
@@ -7717,7 +7983,13 @@ sub filelocation {
         }
     }
     $location=~s://+:/:g; # remove duplicate /
-    while ($location=~m:/\.\./:) {$location=~ s:/[^/]+/\.\./:/:g;} #remove dir/..
+    while ($location=~m{/\.\./}) {
+	if ($location =~ m{/[^/]+/\.\./}) {
+	    $location=~ s{/[^/]+/\.\./}{/}g;
+	} else {
+	    $location=~ s{/\.\./}{/}g;
+	}
+    } #remove dir/..
     while ($location=~m:/\./:) {$location=~ s:/\./:/:g;} #remove /./
     return $location;
 }
@@ -7738,6 +8010,9 @@ sub hreflocation {
 	$file=~s-^/home/httpd/lonUsers/($match_domain)/./././($match_name)/userfiles/
 	    -/uploaded/$1/$2/-x;
     }
+    if ($file=~ m{^/userfiles/}) {
+	$file =~ s{^/userfiles/}{/uploaded/};
+    }
     return $file;
 }