Roxen.git / server / modules / filesystems / filesystem.pike

version» Context lines:

Roxen.git/server/modules/filesystems/filesystem.pike:1:   // This is a roxen module. Copyright © 1996 - 2009, Roxen IS.      // This is a virtual "file-system".   // It will be located somewhere in the name-space of the server.   // Also inherited by some of the other filesystems.      inherit "module";   inherit "socket";    - constant cvs_version= "$Id: filesystem.pike,v 1.161 2010/06/29 13:30:31 grubba Exp $"; + constant cvs_version= "$Id$";   constant thread_safe=1;      #include <module.h>   #include <roxen.h>   #include <stat.h>   #include <request_trace.h>         //<locale-token project="mod_filesystem">LOCALE</locale-token>   #define LOCALE(X,Y) _DEF_LOCALE("mod_filesystem",X,Y)
Roxen.git/server/modules/filesystems/filesystem.pike:25:   #else   # define FILESYSTEM_WERR(X)   #endif      #ifdef QUOTA_DEBUG   # define QUOTA_WERR(X) werror("QUOTA: "+X+"\n")   #else   # define QUOTA_WERR(X)   #endif    - #if constant(System.normalize_path) - #define NORMALIZE_PATH(X) System.normalize_path(X) - #else /* !constant(System.normalize_path) */ - #define NORMALIZE_PATH(X) (X) - #endif /* constant(System.normalize_path) */ -  +    constant module_type = MODULE_LOCATION;   LocaleString module_name = LOCALE(51,"File systems: Normal File system");   LocaleString module_doc =   LOCALE(2,"This is the basic file system module that makes it possible "    "to mount a directory structure in the virtual file system of "    "your site.");   constant module_unique = 0;    -  + // Statistics.   int redirects, accesses, errors, dirlists;   int puts, deletes, mkdirs, moves, chmods;    -  + #if constant(System.normalize_path) + // NB: System.normalize_path() throws errors on nonexisting files. + protected string normalize_path(string path) + { +  // Get rid of segments with ".." and ".". +  path = combine_path(path); +  +  if (Stdio.exist(path)) { +  // The easy case. +  return System.normalize_path(path); +  } +  +  if (has_suffix(path, "/") && Stdio.exist(path + ".")) { +  // The almost easy case. +  path = System.normalize_path(path + "."); + #ifdef __NT__ +  path += "\\"; + #else +  path += "/"; + #endif +  return path; +  } +  +  // Try finding a valid prefix with binary search. +  array(string) a = path/"/"; +  int lo, hi = sizeof(a); + #ifdef __NT__ +  if (has_suffix(a[0], ":")) { +  // Avoid statting devices. +  lo = 1; +  } + #endif +  while (lo < hi) { +  int m = (lo + hi)/2; +  if (Stdio.exist(a[..m] * "/")) { +  if (lo == m) break; +  lo = m; +  } else { +  hi = m; +  } +  } +  +  // NB: If hi is zero then the entire path is invalid, +  // so no need to call System.normalize_path(). +  if (hi) { +  path = System.normalize_path(a[..lo] * "/") + "/" + (a[lo+1..] * "/"); +  } +  + #ifdef __NT__ +  return replace(path, "/", "\\"); + #else +  return path; + #endif + } + #define NORMALIZE_PATH(X) normalize_path(X) + #else /* !constant(System.normalize_path) */ + #define NORMALIZE_PATH(X) (X) + #endif /* constant(System.normalize_path) */ +    protected mapping http_low_answer(int errno, string data, string|void desc)   {    mapping res = Roxen.http_low_answer(errno, data);       if (desc) {    res->rettext = desc;    }       return res;   }
Roxen.git/server/modules/filesystems/filesystem.pike:239:       defvar("internal_files", ({}), LOCALE(43,"Internal files"), TYPE_STRING_LIST,    LOCALE(44,"A list of glob patterns that matches files which should be "    "considered internal. Internal files cannot be requested "    "directly from a browser, won't show up in directory listings "    "and can never be uploaded, moved or deleted by a browser."    "They can only be accessed internally, e.g. with the RXML tags"    " <tt>&lt;insert&gt;</tt> and <tt>&lt;use&gt;</tt>."));   }    + // Cached defvar values. + // + // NB: path and normalized_path have been recoded according to + // path_encoding.   string path, mountpoint, charset, path_encoding, normalized_path;   int stat_cache, dotfiles, access_as_user, no_symlinks, tilde;   array(string) internal_files; -  +    UserDB access_as_user_db;   int access_as_user_throw;   void start()   {    tilde = query("tilde");    charset = query("charset");    path_encoding = query("path_encoding");    no_symlinks = query("no_symlinks");    access_as_user = query("access_as_user");    access_as_user_throw = query("access_as_user_throw");    access_as_user_db =    my_configuration()->find_user_database( query("access_as_user_db") );    dotfiles = query(".files"); -  path = query("searchpath"); +  path = roxen_path(encode_path(query("searchpath")));    mountpoint = query("mountpoint");    stat_cache = query("stat_cache"); -  internal_files = query("internal_files"); +  internal_files = map(query("internal_files"), encode_path);    -  +  if (sizeof(path) && !has_suffix(path, "/")) path += "/";    -  +    #if constant(System.normalize_path)    if (catch { -  if ((<'/','\\'>)[path[-1]]) { +     normalized_path = System.normalize_path(path + "."); -  } else { -  normalized_path = System.normalize_path(path); -  } +     }) { -  report_error(LOCALE(1, "Path verification of %s failed.\n"), mountpoint); +  report_error(LOCALE(1, "Path normalization of %s: %s failed.\n"), +  path, mountpoint); + #ifdef __NT__ +  normalized_path = replace(path, "/", "\\"); + #else    normalized_path = path; -  + #endif    }   #else /* !constant(System.normalize_path) */    normalized_path = path;   #endif /* constant(System.normalize_path) */ -  if ((normalized_path == "") || !(<'/','\\'>)[normalized_path[-1]]) { +  if ((normalized_path != "") && !(<'/','\\'>)[normalized_path[-1]]) {   #ifdef __NT__    normalized_path += "\\";   #else /* !__NT__ */    normalized_path += "/";   #endif /* __NT__ */    }    FILESYSTEM_WERR("Online at "+query("mountpoint")+" (path="+path+")");    cache_expire("stat_cache");   }   
Roxen.git/server/modules/filesystems/filesystem.pike:368:    }      #define SETUID_NT(X) \    if( access_as_user && !id->misc->internal_get ) \    { \    User uid = id->conf->authenticate( id,access_as_user_db ); \    if( uid && uid->uid() ) \    privs=Privs(X, uid->uid(), uid->gid() ); \    }    + // NB: The stat_cache is a lookup from real_path to stat. +    mixed stat_file( string f, RequestID id )   {    Stat fs;       FILESYSTEM_WERR("stat_file for \""+f+"\"" +    (id->misc->internal_get ? " (internal)" : ""));    -  f = path+f; +  string norm_f = real_path(f, id); +  if (!norm_f) return 0;    -  if (FILTER_INTERNAL_FILE (f, id)) -  return 0; -  +     if(stat_cache && !id->pragma["no-cache"] && -  (fs=cache_lookup("stat_cache",f))) +  (fs = cache_lookup("stat_cache", norm_f)))    return fs[0];    object privs;    SETUID_NT("Statting file");       /* No security currently in this function */ -  fs = file_stat(decode_path(f)); +  fs = file_stat(norm_f);    privs = 0;    if(!stat_cache) return fs; -  cache_set("stat_cache", f, ({fs})); +  cache_set("stat_cache", norm_f, ({fs}));    return fs;   }    - string decode_path( string p ) + //! Convert to filesystem encoding. + //! + //! @note + //! Note that the @expr{"iso-8859-1"@} encoding will perform + //! conversion to utf-8 for wide strings OSes other than NT. + string encode_path( string p )   {    if( path_encoding != "iso-8859-1" ) -  p = Locale.Charset.encoder( path_encoding )->feed( p )->drain(); +  p = Charset.encoder( path_encoding )->feed( p )->drain();   #ifndef __NT__    if( String.width( p ) != 8 )    p = string_to_utf8( p );   #else -  +  // NB: stat() on NT doesn't like trailing slashes (cf [bug 76]).    while( strlen(p) && p[-1] == '/' )    p = p[..strlen(p)-2];   #endif    return p;   }    - string real_path(string f, RequestID id) + //! Convert from filesystem encoding. + string decode_path(string p)   { -  f = normalized_path + f; -  if (FILTER_INTERNAL_FILE(f, id)) return 0; -  catch { -  f = NORMALIZE_PATH(decode_path(f)); -  if (has_prefix(f, normalized_path) || +    #ifdef __NT__ -  (f+"\\" == normalized_path) +  // The filesystem on NT uses wide characters. +  return p; + #else +  // While filesystems on other OSes typically are 8bit. +  switch(lower_case(path_encoding)) { +  case "iso-8859-1": +  return p; +  case "utf8": case "utf-8": +  // NB: We assume that the filesystem will normalize +  // the path as appropriate. +  return Unicode.normalize(utf8_to_string(p), "NFC"); +  default: +  return Charset.decoder(path_encoding)->feed(p)->drain(); +  } + #endif /* !__NT__ */ + } +  + protected string low_real_path(string f, RequestID id) + { +  string norm_f; +  +  if (mixed err = catch { +  /* NOTE: NORMALIZE_PATH() may throw errors. */ +  norm_f = NORMALIZE_PATH(path + encode_path(f)); + #if constant(System.normalize_path) +  if (!has_prefix(norm_f, normalized_path) && + #ifdef __NT__ +  (norm_f+"\\" != normalized_path)   #else /* !__NT__ */ -  (f+"/" == normalized_path) +  (norm_f+"/" != normalized_path)   #endif /* __NT__ */    ) { -  return f; -  } -  }; +  errors++; +  report_error(LOCALE(52, "Path verification of %O failed:\n" +  "%O is not a prefix of %O\n" +  ), f, normalized_path, norm_f);    return 0;    }    -  +  string oldf = f; +  // Regenerate f from norm_f. +  f = decode_path(replace(norm_f[sizeof(normalized_path)..], "\\", "/")); +  if (has_suffix(oldf, "/") && !has_suffix(f, "/")) { +  // Restore the "/" stripped by encode_path() on NT. +  f += "/"; +  } + #endif /* constant(System.normalize_path) */ +  }) { +  errors++; +  report_error(LOCALE(71, "Path normalization failure for %O:\n" +  "%s\n"), +  f, describe_backtrace(err)); +  } +  return norm_f; + } +  + string real_path(string f, RequestID id) + { +  string norm_f = low_real_path(f, id); +  if (!norm_f) return 0; +  if (FILTER_INTERNAL_FILE(norm_f, id)) return 0; +  return norm_f; + } +    string real_file( string f, RequestID id )   {    if(stat_file( f, id )) {    return real_path(f, id);    }   }      // We support locking if put is enabled.   mapping(string:mixed) lock_file(string path, DAVLock lock, RequestID id)   {    if (!query("put")) return 0;    if (query("check_auth") && (!id->conf->authenticate( id )) ) {    TRACE_LEAVE("LOCK: Permission denied");    return    // FIXME: Sane realm.    Roxen.http_auth_required("foo",    "<h1>Permission to 'LOCK' files denied</h1>",    id);    } -  register_lock(path, lock, id); +  register_lock(encode_path(path), lock, id);    return 0;   }      mapping(string:mixed) unlock_file(string path, DAVLock lock, RequestID|int(0..0) id)   {    if (!query("put")) return 0;    if (id && query("check_auth") && (!id->conf->authenticate( id )) ) {    TRACE_LEAVE("UNLOCK: Permission denied");    return    // FIXME: Sane realm.    Roxen.http_auth_required("foo",    "<h1>Permission to 'UNLOCK' files denied</h1>",    id);    } -  unregister_lock(path, lock, id); +  unregister_lock(encode_path(path), lock, id);    return 0;   }      int dir_filter_function(string f, RequestID id)   {    if(f[0]=='.' && !dotfiles) return 0;    if(!tilde && Roxen.backup_extension(f)) return 0;    return 1;   }      array(string) list_lock_files() {    return query("nobrowse");   }    - protected mapping(string:mixed)|int(0..1) write_access(string path, + protected variant mapping(string:mixed)|int(0..1) write_access(string path,    int(0..1) recursive,    RequestID id)   {    SIMPLE_TRACE_ENTER(this, "write_access(%O, %O, %O)\n", path, recursive, id);    if(query("check_auth") && (!id->conf->authenticate( id ) ) ) {    SIMPLE_TRACE_LEAVE("%s: Authentication required.", id->method);    // FIXME: Sane realm.    // FIXME: Recursion and htaccess?    return    Roxen.http_auth_required("foo",    sprintf("<h1>Permission to '%s' denied</h1>",    id->method), id);    }    TRACE_LEAVE("Fall back to the default write access checks."); -  return ::write_access(path, recursive, id); +  return ::write_access(encode_path(path), recursive, id);   }      array find_dir( string f, RequestID id )   {    array dir;       FILESYSTEM_WERR("find_dir for \""+f+"\"" +    (id->misc->internal_get ? " (internal)" : ""));    -  +  string norm_f = real_path(f, id); +     object privs;    SETUID_NT("Read dir");    -  if (catch { -  f = NORMALIZE_PATH(decode_path(path + f)); -  } || !(dir = get_dir(f))) { +  if (!(dir = get_dir(norm_f))) {    privs = 0;    return 0;    }    privs = 0;       if(!query("dir"))    // Access to this dir is allowed.    if(! has_value(dir, ".www_browsable"))    {    errors++;
Roxen.git/server/modules/filesystems/filesystem.pike:534:    return 0;    }       dirlists++;       // Pass _all_ files, hide none.    if(tilde && dotfiles &&    (!sizeof( internal_files ) || id->misc->internal_get))    return dir;    +  FILESYSTEM_WERR(sprintf("raw dir: %O\n", dir)); +     dir = Array.filter(dir, dir_filter_function, id);    -  +  FILESYSTEM_WERR(sprintf("filtered dir: %O\n", dir)); +  +  if (path_encoding != "iso-8859-1") { +  dir = map(dir, decode_path); +  } +  +  FILESYSTEM_WERR(sprintf("decoded dir: %O\n", dir)); +     if (!id->misc->internal_get)    foreach (internal_files, string globstr)    dir -= glob (globstr, dir);       return dir;   }      void recursive_rm(string real_dir, string virt_dir,    int(0..1) check_status_needed, RequestID id)   {    SIMPLE_TRACE_ENTER(this, "Deleting all files in directory %O...", real_dir);    foreach(get_dir(real_dir) || ({}), string fname) {    string real_fname = combine_path(real_dir, fname); -  string virt_fname = virt_dir + "/" + fname; +  string virt_fname = virt_dir + "/" + decode_path(fname);       Stat stat = file_stat(real_fname);    if (!stat) {    id->set_status_for_path(virt_fname, 404);    TRACE_LEAVE("File not found.");    continue;    }    SIMPLE_TRACE_ENTER(this, "Deleting %s %O.",    stat->isdir?"directory":"file", real_fname);    int(0..1)|mapping sub_status;
Roxen.git/server/modules/filesystems/filesystem.pike:684:    if (putting[from] != 0x7fffffff) {    putting[from] -= bytes;    }    if(putting[from] <= 0) {    putting[from] = 0; // Paranoia    done_with_put( id_arr );    }    }   }    - int _file_size(string X, RequestID id) + int _file_size(string f, RequestID id)   {    Stat fs; -  +  string norm_f = real_path(f, id); +     if( stat_cache )    {    array(Stat) cached_fs;    if(!id->pragma["no-cache"] && -  (cached_fs = cache_lookup("stat_cache", X))) +  (cached_fs = cache_lookup("stat_cache", norm_f)))    {    id->misc->stat = cached_fs[0];    return cached_fs[0] ? cached_fs[0][ST_SIZE] : -1;    }    } -  if(fs = file_stat(decode_path(X))) +  if(fs = file_stat(norm_f))    {    id->misc->stat = fs; -  if( stat_cache ) cache_set("stat_cache",(X),({fs})); +  if( stat_cache ) cache_set("stat_cache", norm_f, ({fs}));    return fs[ST_SIZE];    } else if( stat_cache ) -  cache_set("stat_cache",(X),({0})); +  cache_set("stat_cache", norm_f, ({0}));    return -1;   }    -  + //! Return @expr{1@} if the (virtual) @[path] from + //! the (real) @[root] follows symbolic links.   int contains_symlinks(string root, string path)   { -  +  if (has_suffix(root, "/")) { +  root = root[..<1]; +  }    foreach(path/"/" - ({ "" }), path) { -  root += "/" + path; +  root += "/" + encode_path(path);    Stat rr; -  if (rr = file_stat(decode_path(root), 1)) { +  if (rr = file_stat(root, 1)) {    if (rr[1] == -3) {    return(1);    }    } else {    return(0);    }    }    return(0);   }   
Roxen.git/server/modules/filesystems/filesystem.pike:750:    return Roxen.http_status(405, "Bad path.");    }       if(!query("put"))    {    TRACE_LEAVE(sprintf("%s disallowed (since PUT is disallowed)",    id->method));    return Roxen.http_status(405, "Disallowed.");    }    -  // FIXME: Is this the correct filename? -  int size = _file_size(norm_f, id); +  int size = _file_size(coll, id);       if (size != -1) {    TRACE_LEAVE(sprintf("%s failed. Directory name already exists. ",    id->method));    if (id->method == "MKCOL") {    return Roxen.http_status(405,    "Collection already exists.");    }    return 0;    }       // Disallow if the name is locked, or if the parent directory is locked. -  mapping(string:mixed) ret = write_access(coll, 0, id) || -  write_access(combine_path(coll, ".."), 0, id); +  mapping(string:mixed) ret = +  write_access(({coll, combine_path(coll, "..")}), 0, id);    if (ret) return ret;       mkdirs++;    object privs;    SETUID_TRACE("Creating directory/collection", 0);       if (query("no_symlinks") && (contains_symlinks(path, coll))) {    privs = 0;    errors++;    report_error(LOCALE(46,"Creation of %O failed. Permission denied.\n"),
Roxen.git/server/modules/filesystems/filesystem.pike:818:    return _file_size(f, id) == orig_size;    }   }      mixed find_file( string f, RequestID id )   {    TRACE_ENTER("find_file(\""+f+"\")", 0);    object o;    int size;    string tmp; -  string oldf = f; +     object privs;    int code;       FILESYSTEM_WERR("Request for \""+f+"\"" +    (id->misc->internal_get ? " (internal)" : ""));       /* only used for the quota system, thus rather unessesary to do for    each request....    */ - #define URI combine_path(mountpoint + "/" + oldf, ".") + #define URI combine_path(mountpoint, f, ".")    -  string norm_f; +  string norm_f = real_path(f, id);    -  catch { -  /* NOTE: NORMALIZE_PATH() may throw errors. */ -  f = norm_f = NORMALIZE_PATH(f = decode_path(path + f)); - #if constant(System.normalize_path) -  if (!has_prefix(norm_f, normalized_path) && - #ifdef __NT__ -  (norm_f+"\\" != normalized_path) - #else /* !__NT__ */ -  (norm_f+"/" != normalized_path) - #endif /* __NT__ */ -  ) { -  errors++; -  report_error(LOCALE(52, "Path verification of %O failed:\n" -  "%O is not a prefix of %O\n" -  ), oldf, normalized_path, norm_f); -  TRACE_LEAVE(""); +  if (!norm_f) {    TRACE_LEAVE("Permission denied."); -  return Roxen.http_status(403, "File exists, but access forbidden " -  "by user"); +  return Roxen.http_status(403, "Access forbidden by user");    }    -  /* Adjust not_query */ -  id->not_query = mountpoint + replace(norm_f[sizeof(normalized_path)..], -  "\\", "/"); -  if (sizeof(oldf) && (oldf[-1] == '/')) { -  id->not_query += "/"; -  } - #endif /* constant(System.normalize_path) */ -  }; -  +     // NOTE: Sets id->misc->stat.    size = _file_size( f, id );       FILESYSTEM_WERR(sprintf("_file_size(%O, %O) ==> %d\n", f, id, size));       /*    * FIXME: Should probably move path-info extraction here.    * /grubba 1998-08-26    */   
Roxen.git/server/modules/filesystems/filesystem.pike:891:    case 3:    case 4:    TRACE_LEAVE("No file");    return 0; /* Is no-file */       case 2:    TRACE_LEAVE("Is directory");    return -1; /* Is dir */       default: -  if( oldf[ -1 ] == '/' || /* Trying to access file with '/' appended */ +  if( f[ -1 ] == '/' || /* Trying to access file with '/' appended */    !norm_f) { /* Or a file that is not normalizable. */    return 0;    }       if(!id->misc->internal_get)    {    if (!dotfiles    && sizeof (tmp = (id->not_query/"/")[-1])    && tmp[0] == '.')    {
Roxen.git/server/modules/filesystems/filesystem.pike:920:    }       TRACE_ENTER("Opening file \"" + f + "\"", 0);       SETUID_TRACE("Open file", 1);       o = Stdio.File( );    if(!o->open(norm_f, "r" )) o = 0;    privs = 0;    -  if(!o || (no_symlinks && (contains_symlinks(path, oldf)))) +  if(!o || (no_symlinks && (contains_symlinks(path, f))))    {    errors++;    report_error(LOCALE(45,"Open of %s failed. Permission denied.\n"),f);       TRACE_LEAVE("");    TRACE_LEAVE("Permission denied.");    return Roxen.http_status(403, "File exists, but access forbidden "    "by user");    }   
Roxen.git/server/modules/filesystems/filesystem.pike:968:    // RFC 2518 8.3.1:    // If a server receives a MKCOL request entity type it does not support    // or understand it MUST respond with a 415 (Unsupported Media Type)    // status code.    SIMPLE_TRACE_LEAVE ("MKCOL failed since the request has content.");    return Roxen.http_status(415, "Unsupported media type.");    }    /* FALL_THROUGH */    case "MKDIR":   #if 1 -  return make_collection(oldf, id); +  return make_collection(f, id);   #else /* !1 */    if(!query("put"))    {    id->misc->error_code = 405;    TRACE_LEAVE(sprintf("%s disallowed (since PUT is disallowed)",    id->method));    return 0;    }       if (FILTER_INTERNAL_FILE (f, id)) {
Roxen.git/server/modules/filesystems/filesystem.pike:995:    if (size != -1) {    TRACE_LEAVE(sprintf("%s failed. Directory name already exists. ",    id->method));    if (id->method == "MKCOL") {    return Roxen.http_status(405,    "Collection already exists.");    }    return 0;    }    -  if (mapping(string:mixed) ret = write_access(oldf, 0, id)) { +  if (mapping(string:mixed) ret = write_access(f, 0, id)) {    TRACE_LEAVE("MKCOL: Write access denied.");    return ret;    }       mkdirs++;    SETUID_TRACE("Creating directory/collection", 0);    -  if (query("no_symlinks") && (contains_symlinks(path, oldf))) { +  if (query("no_symlinks") && (contains_symlinks(path, f))) {    privs = 0;    errors++;    report_error(LOCALE(46,"Creation of %O failed. Permission denied.\n"), -  oldf); +  f);    TRACE_LEAVE(sprintf("%s: Contains symlinks. Permission denied",    id->method));    return Roxen.http_status(403, "Permission denied.");    }       code = mkdir(f);    int err_code = errno();       TRACE_ENTER(sprintf("%s: Accepted", id->method), 0);   
Roxen.git/server/modules/filesystems/filesystem.pike:1052:    2   #endif    ) {    return Roxen.http_status(409, "Missing intermediate.");    } else {    return Roxen.http_status(507, "Failed.");    }    }    return 0;    } - #endif /* 1 */ + #endif /* !1 */    break;       case "PUT":    if(!query("put"))    {    id->misc->error_code = 405;    TRACE_LEAVE("PUT disallowed");    return 0;    }       if (FILTER_INTERNAL_FILE (f, id)) {    id->misc->error_code = 405;    TRACE_LEAVE("PUT of internal file is disallowed");    return 0;    }    -  if (mapping(string:mixed) ret = write_access(oldf, 0, id)) { +  if (mapping(string:mixed) ret = write_access(f, 0, id)) {    TRACE_LEAVE("PUT: Locked");    return ret;    }       puts++;       QUOTA_WERR("Checking quota.\n");    if (id->misc->quota_obj && (id->misc->len > 0) &&    !id->misc->quota_obj->check_quota(URI, id->misc->len)) {    errors++;    report_warning(LOCALE(47,"Creation of %O failed. Out of quota.\n"),f);    TRACE_LEAVE("PUT: Out of quota.");    return Roxen.http_status(507, "Out of disk quota.");    }    -  if (query("no_symlinks") && (contains_symlinks(path, oldf))) { +  if (query("no_symlinks") && (contains_symlinks(path, f))) {    errors++;    report_error(LOCALE(46,"Creation of %O failed. Permission denied.\n"),f);    TRACE_LEAVE("PUT: Contains symlinks. Permission denied");    return Roxen.http_status(403, "Permission denied.");    }       SETUID_TRACE("Saving file", 0);    -  rm(f); -  mkdirhier(f); +  rm(norm_f); +  mkdirhier(norm_f);       if (id->misc->quota_obj) {    QUOTA_WERR("Checking if the file already existed.");    if (size > 0) {    QUOTA_WERR("Deallocating " + size + "bytes.");    id->misc->quota_obj->deallocate(URI, size);    }    }    -  object to = open(f, "wct"); -  int err = errno(); +  object to = Stdio.File();       TRACE_ENTER("PUT: Accepted", 0);       /* Clear the stat-cache for this file */    if (stat_cache) { -  cache_set("stat_cache", f, 0); +  cache_set("stat_cache", norm_f, 0);    }    -  if(!to) +  if(!to->open(norm_f, "wct", 0666))    { -  +  int err = to->errno();    privs = 0;    TRACE_LEAVE("PUT: Open failed");    return errno_to_status (err, 1, id);    }       // FIXME: Race-condition. -  string msg = safe_chmod(f, 0666 & ~(id->misc->umask || 022)); +  string msg = safe_chmod(norm_f, 0666 & ~(id->misc->umask || 022));    privs = 0;       Stdio.File my_fd = id->connection();       putting[my_fd] = id->misc->len;    if(strlen(id->data))    {    // Note: What if sizeof(id->data) > id->misc->len ?    // This is not a problem, since that has been handled    // by the protocol module.    if (id->misc->len > 0) {    putting[my_fd] -= strlen(id->data);    }    int bytes = to->write( id->data );    if (id->misc->quota_obj) {    QUOTA_WERR("Allocating " + bytes + "bytes."); -  if (!id->misc->quota_obj->allocate(f, bytes)) { +  if (!id->misc->quota_obj->allocate(URI, bytes)) {    TRACE_LEAVE("PUT: A string");    TRACE_LEAVE("PUT: Out of quota");    return Roxen.http_status(507, "Out of disk quota.");    }    }    }    if(!putting[my_fd]) { -  +  m_delete (putting, my_fd);    TRACE_LEAVE("PUT: Just a string");    TRACE_LEAVE("Put: Success");    if (size < 0) {    return Roxen.http_status(201, "Created.");    } else {    // FIXME: Isn't 204 better? /mast    return Roxen.http_string_answer("Ok");    }    }   
Roxen.git/server/modules/filesystems/filesystem.pike:1183:    TRACE_LEAVE("CHMOD disallowed (since PUT is disallowed)");    return 0;    }       if (FILTER_INTERNAL_FILE (f, id)) {    id->misc->error_code = 405;    TRACE_LEAVE("CHMOD of internal file is disallowed");    return 0;    }    -  if (mapping(string:mixed) ret = write_access(oldf, 0, id)) { +  if (mapping(string:mixed) ret = write_access(f, 0, id)) {    TRACE_LEAVE("CHMOD: Locked");    return ret;    }       SETUID_TRACE("CHMODing file", 0);    -  if (query("no_symlinks") && (contains_symlinks(path, oldf))) { +  if (query("no_symlinks") && (contains_symlinks(path, f))) {    privs = 0;    errors++;    TRACE_LEAVE("CHMOD: Contains symlinks. Permission denied");    return Roxen.http_status(403, "Permission denied.");    }    -  string msg = safe_chmod(f, id->misc->mode & 0777); +  string msg = safe_chmod(norm_f, id->misc->mode & 0777);    int err_code = errno();    privs = 0;       chmods++;       TRACE_ENTER("CHMOD: Accepted", 0);       if (stat_cache) {    cache_set("stat_cache", f, 0);    }
Roxen.git/server/modules/filesystems/filesystem.pike:1264:    string relative_from = id->misc->move_from[sizeof(mountpoint)..];       if (FILTER_INTERNAL_FILE (movefrom, id) ||    FILTER_INTERNAL_FILE (f, id)) {    id->misc->error_code = 405;    TRACE_LEAVE("MV to or from internal file is disallowed");    return 0;    }       if (query("no_symlinks") && -  ((contains_symlinks(path, oldf)) || +  ((contains_symlinks(path, f)) ||    (contains_symlinks(path, id->misc->move_from)))) {    errors++;    TRACE_LEAVE("MV: Contains symlinks. Permission denied");    return Roxen.http_status(403, "Permission denied.");    }       // FIXME: What about moving of directories containing locked files? -  if (mapping(string:mixed) ret = write_access(oldf, 0, id) || -  write_access(relative_from, 0, id)) { +  if (mapping(string:mixed) ret = +  write_access(({ f, relative_from }), 0, id)) {    TRACE_LEAVE("MV: Locked");    return ret;    }       SETUID_TRACE("Moving file", 0);    -  code = mv(movefrom, f); +  code = mv(movefrom, norm_f);    int err_code = errno();    privs = 0;       moves++;       TRACE_ENTER("MV: Accepted", 0);       /* Clear the stat-cache for this file */    if (stat_cache) {    cache_set("stat_cache", movefrom, 0); -  cache_set("stat_cache", f, 0); +  cache_set("stat_cache", norm_f, 0);    }       if(!code)    {    TRACE_LEAVE("MV: Move failed");    return errno_to_status (err_code, 1, id);    }    TRACE_LEAVE("MV: Success");    TRACE_LEAVE("Success");    return Roxen.http_string_answer("Ok");
Roxen.git/server/modules/filesystems/filesystem.pike:1337:    return 0;    }       // FIXME: The code below doesn't allow for this module being overloaded.    if (!has_prefix(new_uri, mountpoint)) {    id->misc->error_code = 405;    TRACE_LEAVE("MOVE: Dest file on other filesystem.");    return(0);    }    new_uri = new_uri[sizeof(mountpoint)..]; -  string moveto = path + "/" + new_uri; +  string moveto = path + "/" + encode_path(new_uri);       // Workaround for Linux, Tru64 and FreeBSD.    if (has_suffix(moveto, "/")) {    moveto = moveto[..sizeof(moveto)-2];    }       if (FILTER_INTERNAL_FILE (f, id) || -  FILTER_INTERNAL_FILE (moveto, id)) { +  FILTER_INTERNAL_FILE (new_uri, id)) {    id->misc->error_code = 405;    TRACE_LEAVE("MOVE to or from internal file is disallowed");    return 0;    }       if (query("no_symlinks") && -  ((contains_symlinks(path, f)) || +  ((contains_symlinks(path, norm_f)) ||    (contains_symlinks(path, moveto)))) {    privs = 0;    errors++;    TRACE_LEAVE("MOVE: Contains symlinks. Permission denied");    return Roxen.http_status(403, "Permission denied.");    }    -  if (mapping(string:mixed) ret = -  write_access(new_uri, 0, id) || -  write_access(oldf, 0, id)) { +  mapping(string:mixed) ret = +  write_access(({ combine_path(f, "../"), f, new_uri }), 0, id); +  if (ret) {    TRACE_LEAVE("MOVE: Locked");    return ret;    }       size = _file_size(moveto,id);       SETUID_TRACE("Moving file", 0);       if (size != -1) {    // Destination exists.
Roxen.git/server/modules/filesystems/filesystem.pike:1407:    set_status_for_path (new_uri, res->error, res->rettext);    return ([]);    }    } else {    privs = 0;    TRACE_LEAVE("MOVE: Cannot overwrite directory");    return Roxen.http_status(412);    }    }    -  code = mv(f, decode_path(moveto)); +  code = mv(norm_f, moveto);    int err_code = errno();    privs = 0;       TRACE_ENTER("MOVE: Accepted", 0);       moves++;       /* Clear the stat-cache for this file */    if (stat_cache) { -  cache_set("stat_cache", moveto, 0); +  cache_set("stat_cache", new_uri, 0);    cache_set("stat_cache", f, 0);    }       if(!code)    {    SIMPLE_TRACE_LEAVE("MOVE: Move failed (%s)", strerror (err_code));    return errno_to_status (err_code, 1, id);    }    TRACE_LEAVE("MOVE: Success");    TRACE_LEAVE("Success");
Roxen.git/server/modules/filesystems/filesystem.pike:1451:    TRACE_LEAVE("DELETE: Disabled");    return 0;    }       if (FILTER_INTERNAL_FILE (f, id)) {    id->misc->error_code = 405;    TRACE_LEAVE("DELETE of internal file is disallowed");    return 0;    }    -  if (query("no_symlinks") && (contains_symlinks(path, oldf))) { +  if (query("no_symlinks") && (contains_symlinks(path, f))) {    errors++;    report_error(LOCALE(48,"Deletion of %s failed. Permission denied.\n"),f);    TRACE_LEAVE("DELETE: Contains symlinks");    return Roxen.http_status(403, "Permission denied.");    }       if ((size < 0) &&    (String.trim_whites(id->request_headers->depth||"infinity") !=    "infinity")) {    // RFC 2518 8.6.2:    // The DELETE method on a collection MUST act as if a "Depth: infinity"    // header was used on it.    TRACE_LEAVE(sprintf("DELETE: Bad depth header: %O.",    id->request_headers->depth));    return Roxen.http_status(400, "Unsupported depth.");    }       if (size < 0) { -  mapping|int(0..1) res; -  if (mappingp(res = write_access(combine_path(oldf, "../"), 1, id)) || -  (res && mappingp(res = write_access(oldf, 1, id)))) { +  mapping|int(0..1) res = +  write_access(({ combine_path(f, "../"), f }), 1, id); +  if (mappingp(res)) {    SIMPLE_TRACE_LEAVE("DELETE: Recursive write access denied.");    return res;    }   #if 0    report_notice(LOCALE(64,"DELETING the directory %s.\n"), f);   #endif       accesses++;       SETUID_TRACE("Deleting directory", 0);       int start_ms_size = id->multi_status_size(); -  recursive_rm(f, query_location() + oldf, res, id); +  recursive_rm(norm_f, query_location() + f, res, id);    -  if (!rm(f) && errno() != System.ENOENT) { +  if (!rm(norm_f) && errno() != System.ENOENT) {    if (id->multi_status_size() > start_ms_size) {    if (errno() != System.EEXIST   #if constant (System.ENOTEMPTY)    && errno() != System.ENOTEMPTY   #endif    )    {    return errno_to_status (errno(), 0, id);    }    } else {    return errno_to_status (errno(), 0, id);    }       if (id->multi_status_size() > start_ms_size) {    TRACE_LEAVE("DELETE: Partial failure.");    return ([]);    }    }    } else { -  mapping|int(0..1) res; -  if ((res = write_access(combine_path(oldf, "../"), 0, id)) || -  (res = write_access(oldf, 0, id))) { +  mapping|int(0..1) res = +  write_access(({ combine_path(f, "../"), f }), 0, id); +  if (res) {    SIMPLE_TRACE_LEAVE("DELETE: Write access denied.");    return res;    }      #if 0    report_notice(LOCALE(49,"DELETING the file %s.\n"),f);   #endif       accesses++;       /* Clear the stat-cache for this file */    if (stat_cache) {    cache_set("stat_cache", f, 0);    }       SETUID_TRACE("Deleting file", 0);    -  if(!rm(f)) +  if(!rm(norm_f))    {    privs = 0;    id->misc->error_code = 405;    TRACE_LEAVE("DELETE: Failed");    return 0;    }    privs = 0;    deletes++;       if (id->misc->quota_obj && (size > 0)) { -  id->misc->quota_obj->deallocate(oldf, size); +  id->misc->quota_obj->deallocate(URI, size);    }    }    TRACE_LEAVE("DELETE: Success"); -  return Roxen.http_status(204,(f+" DELETED from the server")); +  return Roxen.http_status(204,(norm_f+" DELETED from the server"));       default:    id->misc->error_code = 501;    SIMPLE_TRACE_LEAVE("%s: Not supported", id->method);    return 0;    }    TRACE_LEAVE("Not reached");    return 0;   }   
Roxen.git/server/modules/filesystems/filesystem.pike:1566:    SIMPLE_TRACE_ENTER(this, "COPY: Copy %O to %O.", source, dest);    Stat source_st = stat_file(source, id);    if (!source_st) {    TRACE_LEAVE("COPY: Source doesn't exist.");    return Roxen.http_status(404, "File not found.");    }    if (!query("put")) {    TRACE_LEAVE("COPY: Put not allowed.");    return Roxen.http_status(405, "Not allowed.");    } -  mapping|int(0..1) res = write_access(dest, 0, id) || -  write_access(combine_path(dest, "../"), 0, id); +  mapping|int(0..1) res = +  write_access(({ dest, combine_path(dest, "../")}) , 0, id);    if (mappingp(res)) return res; -  string dest_path = path + dest; -  catch { dest_path = decode_path(dest_path); }; +  string dest_path = path + encode_path(dest);    dest_path = NORMALIZE_PATH (dest_path); -  if (query("no_symlinks") && (contains_symlinks(path, dest_path))) { +  if (query("no_symlinks") && (contains_symlinks(path, dest))) {    errors++;    report_error(LOCALE(57,"Copy to %O failed. Permission denied.\n"),    dest);    TRACE_LEAVE("COPY: Contains symlinks. Permission denied");    return Roxen.http_status(403, "Permission denied.");    }    Stat dest_st = stat_file(dest, id);    if (dest_st) {    SIMPLE_TRACE_ENTER (this, "COPY: Destination exists");    switch(overwrite) {
Roxen.git/server/modules/filesystems/filesystem.pike:1682:    if (msg) {    TRACE_LEAVE(sprintf("Chmod %O failed: %s", dest_path, msg));    } else {    TRACE_LEAVE("Success");    }    return Roxen.http_status(dest_st?204:201, "Created");    } else {    return errno_to_status (err_code, 1, id);    }    } else { -  string source_path = path + source; -  catch { source_path = decode_path(source_path); }; +  string source_path = path + encode_path(source);    source_path = NORMALIZE_PATH (source_path); -  if (query("no_symlinks") && (contains_symlinks(path, source_path))) { +  if (query("no_symlinks") && (contains_symlinks(path, source))) {    errors++;    report_error(LOCALE(57,"Copy to %O failed. Permission denied.\n"),    dest);    TRACE_LEAVE("COPY: Contains symlinks. Permission denied");    return Roxen.http_status(403, "Permission denied.");    }    puts++;       QUOTA_WERR("Checking quota.\n");    if (id->misc->quota_obj && (id->misc->len > 0) &&    !id->misc->quota_obj->check_quota(mountpoint + dest,    source_st->size)) {    errors++;    report_warning(LOCALE(47,"Creation of %O failed. Out of quota.\n"),    dest_path);    TRACE_LEAVE("PUT: Out of quota.");    return Roxen.http_status(507, "Out of disk quota.");    } -  object source_file = open(source_path, "r"); -  if (!source_file) { +  object source_file = Stdio.File(); +  if (!source_file->open(source_path, "r")) {    TRACE_LEAVE("Failed to open source file.");    return Roxen.http_status(404);    }    // Workaround for Linux, Tru64 and FreeBSD.    if (has_suffix(dest_path, "/")) {    dest_path = dest_path[..sizeof(dest_path)-2];    }    object privs;    SETUID_TRACE("COPY: Copying file.", 0); -  object dest_file = open(dest_path, "cwt"); +  object dest_file = Stdio.File(); +  if (!dest_file->open(dest_path, "cwt")) {    privs = 0; -  if (!dest_file) { +     return errno_to_status (errno(), 1, id);    } -  +  privs = 0;    int len = source_st->size;    while (len > 0) {    string buf = source_file->read((len > 4096)?4096:len);    if (buf && sizeof(buf)) {    int sub_len;    len -= (sub_len = sizeof(buf));    while (sub_len > 0) {    int written = dest_file->write(buf);    if ((sub_len -= written) > 0) {    if (!written) {