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

version» Context lines:

Roxen.git/server/modules/filesystems/filesystem.pike:319:    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 = roxen_path(encode_path(query("searchpath")));    mountpoint = query("mountpoint");    stat_cache = query("stat_cache");    internal_files = map(query("internal_files"), encode_path);    -  if (sizeof(path) && !has_suffix(path, "/")) path += "/"; +  // Expand cwd-relative paths, and ensure terminating slash. +  path = combine_path(getcwd(), path, "./");      #if constant(System.normalize_path)    if (catch {    normalized_path = System.normalize_path(path + ".");    }) {    report_error(LOCALE(1, "Path normalization of %s: %s failed.\n"),    path, mountpoint);   #ifdef __NT__    normalized_path = replace(path, "/", "\\");   #else
Roxen.git/server/modules/filesystems/filesystem.pike:449:       if(stat_cache && !id->pragma["no-cache"] &&    (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(norm_f);    privs = 0; +  if (fs && !(fs->ino)) { +  /* NB: NT does not have a way to get a valid ino field before +  * Windows Server 2012. Substitute with a hash of the +  * normalized path. +  * +  * NB: Use %+4c to avoid bignums (which are not supported by +  * the ino field). +  */ +  sscanf(Crypto.SHA256.hash(string_to_utf8(lower_case(f))), "%+4c", fs->ino); +  }    if(!stat_cache) return fs;    cache_set("stat_cache", norm_f, ({fs}));    return fs;   }    -  + //! Normalize DAVLock path identifier. + string resource_id(string path, RequestID|int(0..0) id) + { +  if ((< "Darwin", "Win32" >)[uname()->sysname]) { +  return ::resource_id(lower_case(path), id); +  } +  return ::resource_id(path, id); + } +    //! 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 = Charset.encoder( path_encoding )->feed( p )->drain(); - #ifndef __NT__ + #if !defined(__NT__) || constant(Stdio.__HAVE_UTF8_FS__)    if( String.width( p ) != 8 )    p = string_to_utf8( p ); - #else + #endif + #ifdef __NT__    // 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;   }      //! Convert from filesystem encoding.   string decode_path(string p)   { - #ifdef __NT__ + #if defined(__NT__) && !constant(Stdio.__HAVE_UTF8_FS__)    // 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__ */ + #endif /* !__NT__ || Stdio.__HAVE_UTF8_FS__ */   }      protected string low_real_path(string f, RequestID id)   {    string norm_f;    -  + #ifdef __NT__ +  // These characters are apparently invalid in NTFS filenames. +  if (f != replace(f, "*?|"/"", ({ "", "", "" }))) { +  return 0; +  } + #endif    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__ */    (norm_f+"/" != normalized_path)   #endif /* __NT__ */
Roxen.git/server/modules/filesystems/filesystem.pike:561:   {    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(encode_path(path), lock, id); +  register_lock(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(encode_path(path), lock, id); +  unregister_lock(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;   }   
Roxen.git/server/modules/filesystems/filesystem.pike:606:    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(encode_path(path), recursive, id); +  return ::write_access(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);
Roxen.git/server/modules/filesystems/filesystem.pike:677:      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 + "/" + decode_path(fname);       Stat stat = file_stat(real_fname); +  SIMPLE_TRACE_ENTER(this, "Deleting %s %O.", +  stat?(stat->isdir?"directory":"file"):"missing", +  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); +  if (stat->isdir) { +  virt_fname += "/"; +  }    int(0..1)|mapping sub_status; -  if (check_status_needed && -  mappingp(sub_status = write_access(virt_fname, 1, id))) { +  if (mappingp(sub_status = write_access(virt_fname, 1, id))) {    id->set_status_for_path(virt_fname, sub_status->error);    TRACE_LEAVE("Write access denied.");    continue;    }    if (stat->isdir) {    recursive_rm(real_fname, virt_fname, sub_status, id);    }       /* Clear the stat-cache for this file */    if (stat_cache) {
Roxen.git/server/modules/filesystems/filesystem.pike:716:    TRACE_LEAVE("Deletion failed.");    }   #if constant(System.EEXIST)    else {    TRACE_LEAVE("Directory not empty.");    }   #endif    } else {    deletes++;    +  unlock_path(virt_fname, id); +     if (id->misc->quota_obj && stat->isreg()) {    id->misc->quota_obj->deallocate(virt_fname,    stat->size());    }    TRACE_LEAVE("Ok.");    }    }    TRACE_LEAVE("Done.");   }   
Roxen.git/server/modules/filesystems/filesystem.pike:855:    if (rr[1] == -3) {    return(1);    }    } else {    return(0);    }    }    return(0);   }    + //! Return @expr{1@} if both arguments refer to the same inode. + int is_same_inode(Stdio.Stat a_st, Stdio.Stat b_st) + { +  if (a_st == b_st) return 1; +  if ((a_st->mode == b_st->mode) && +  (a_st->size == b_st->size) && +  (a_st->ino == b_st->ino) && +  (a_st->dev == b_st->dev)) { +  return 1; +  } +  return 0; + } +    //! @[chmod()] that doesn't throw errors.   string safe_chmod(string path, int mask)   {    return describe_error(catch {    chmod(path, mask);    return 0;    });   }      mapping make_collection(string coll, RequestID id)
Roxen.git/server/modules/filesystems/filesystem.pike:896:    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, combine_path(coll, "..")}), 0, id); +  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:967:    */   #define URI combine_path(mountpoint, f, ".")       string norm_f = real_path(f, id);       if (!norm_f) {    TRACE_LEAVE("Permission denied.");    return Roxen.http_status(403, "Access forbidden by user");    }    + #ifdef __NT__ +  foreach(f/"/", string segment) { +  if (has_suffix(segment, " ")) { +  // Path segments on NT may not end with space. +  return Roxen.http_status(405, "Invalid filesystem path."); +  } +  } + #endif +     // NOTE: Sets id->misc->stat.    size = _file_size( f, id );       FILESYSTEM_WERR(sprintf("_file_size(%O, %O) ==> %d\n", f, id, size));    -  +  if(!id->misc->internal_get) { +  if (!dotfiles && sizeof(filter(f/"/", has_prefix, "."))) { +  TRACE_LEAVE("Path contains .-file or .-directory."); +  return 0; +  } +  if (FILTER_INTERNAL_FILE (f, id)) { +  id->misc->error_code = 405; +  TRACE_LEAVE ("Is internal file"); +  return 0; +  } +  } +     /*    * FIXME: Should probably move path-info extraction here.    * /grubba 1998-08-26    */       switch(id->method)    {    case "GET":    case "HEAD":    case "POST":
Roxen.git/server/modules/filesystems/filesystem.pike:1001:    case 2:    TRACE_LEAVE("Is directory");    return -1; /* Is dir */       default:    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] == '.') -  { -  TRACE_LEAVE("Is .-file"); -  return 0; -  } -  if (FILTER_INTERNAL_FILE (f, id)) -  { -  TRACE_LEAVE ("Is internal file"); -  return 0; -  } -  } -  +     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, f))))    {
Roxen.git/server/modules/filesystems/filesystem.pike:1073:    // 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(f, id); +  mixed ret = make_collection(f, id); +  if (ret) return ret; +  if (id->misc->error_code) { +  return Roxen.http_status(id->misc->error_code); +  } +  return 0;   #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)) { -  id->misc->error_code = 405; -  TRACE_LEAVE(sprintf("%s disallowed (since the dir name matches internal file glob)", -  id->method)); -  return 0; -  } -  +     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(f, 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, f))) {    privs = 0;    errors++;    report_error(LOCALE(46,"Creation of %O failed. Permission denied.\n"),    f);    TRACE_LEAVE(sprintf("%s: Contains symlinks. Permission denied",    id->method));    return Roxen.http_status(403, "Permission denied.");    }    -  +  TRACE_ENTER(sprintf("%s: Accepted", id->method), 0); +     code = mkdir(f);    int err_code = errno();    -  TRACE_ENTER(sprintf("%s: Accepted", id->method), 0); -  +     if (code) {    string msg = safe_chmod(f, 0777 & ~(id->misc->umask || 022));    privs = 0;    if (msg) {    TRACE_LEAVE(sprintf("%s: chmod %O failed: %s", id->method, f, msg));    } else {    TRACE_LEAVE(sprintf("%s: Success", id->method));    }    TRACE_LEAVE("Success");    if (id->method == "MKCOL") {    return Roxen.http_status(201, "Created");    }    return Roxen.http_string_answer("Ok");    } else {    privs = 0; -  SIMPLE_TRACE_LEAVE("%s: Failed (errcode:%d)", id->method, errcode); +  SIMPLE_TRACE_LEAVE("%s: Failed (err: %d: %s)", +  id->method, err_code, strerror(err_code));    TRACE_LEAVE("Failure");    if (id->method == "MKCOL") {    if (err_code ==   #if constant(System.ENOENT)    System.ENOENT - #elif constant(System.ENOENT) -  System.ENOENT +    #else    2   #endif    ) {    return Roxen.http_status(409, "Missing intermediate.");    } else {    return Roxen.http_status(507, "Failed.");    }    }    return 0;
Roxen.git/server/modules/filesystems/filesystem.pike:1168:    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(f, 0, id)) {    TRACE_LEAVE("PUT: Locked");    return ret;    }    -  +  if (size == -2) { +  // RFC 4918 9.7.2: +  // A PUT request to an existing collection MAY be treated as an +  // error (405 Method Not Allowed). +  id->misc->error_code = 405; +  TRACE_LEAVE("PUT: Is directory."); +  return 0; +  } +     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, 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.");    }    -  +  TRACE_ENTER("PUT: Accepted", 0); +     SETUID_TRACE("Saving file", 0);    -  rm(norm_f); -  mkdirhier(norm_f); +  object to = Stdio.File(); +  if(!to->open(norm_f, "wct", 0666)) +  { +  int err = to->errno(); +  privs = 0; +  TRACE_LEAVE("PUT: Open failed"); +  mixed ret = errno_to_status (err, 1, id); +  if (ret) return ret; +  if (id->misc->error_code) { +  return Roxen.http_status(id->misc->error_code); +  } +  return 0; +  }       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 = Stdio.File(); -  -  TRACE_ENTER("PUT: Accepted", 0); -  +     /* Clear the stat-cache for this file */    if (stat_cache) {    cache_set("stat_cache", norm_f, 0);    }    -  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(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 ?
Roxen.git/server/modules/filesystems/filesystem.pike:1283:    // Change permission of a file.    // FIXME: !!       if(!query("put"))    {    id->misc->error_code = 405;    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(f, 0, id)) {    TRACE_LEAVE("CHMOD: Locked");    return ret;    }       SETUID_TRACE("CHMODing file", 0);       if (query("no_symlinks") && (contains_symlinks(path, f))) {    privs = 0;    errors++;
Roxen.git/server/modules/filesystems/filesystem.pike:1360:    string movefrom;    if(!id->misc->move_from ||    !has_prefix(id->misc->move_from, mountpoint) ||    !(movefrom = id->conf->real_file(id->misc->move_from, id))) {    id->misc->error_code = 405;    errors++;    TRACE_LEAVE("MV: No source file");    return 0;    }    + #ifdef __NT__ +  foreach(id->misc->move_from/"/", string segment) { +  if (has_suffix(segment, " ")) { +  // Path segments on NT may not end with space. +  return Roxen.http_status(405, "MV: Invalid filesystem path."); +  } +  } + #endif +     string relative_from = id->misc->move_from[sizeof(mountpoint)..];    -  if (FILTER_INTERNAL_FILE (movefrom, id) || -  FILTER_INTERNAL_FILE (f, id)) { +  if (!dotfiles && sizeof(filter(relative_from/"/", has_prefix, "."))) { +  TRACE_LEAVE("From-path contains .-file or .-directory."); +  return 0; +  } +  if (FILTER_INTERNAL_FILE(relative_from, id)) {    id->misc->error_code = 405; -  TRACE_LEAVE("MV to or from internal file is disallowed"); +  TRACE_LEAVE("MV from internal file is disallowed.");    return 0;    }       if (query("no_symlinks") &&    ((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? +  // NB: Consider the case of moving of directories containing locked files.    if (mapping(string:mixed) ret = -  write_access(({ f, relative_from }), 0, id)) { +  write_access(({ f, relative_from }), 1, id)) {    TRACE_LEAVE("MV: Locked");    return ret;    }       SETUID_TRACE("Moving file", 0);       code = mv(movefrom, norm_f);    int err_code = errno();    privs = 0;   
Roxen.git/server/modules/filesystems/filesystem.pike:1436:    }       string new_uri = id->misc["new-uri"] || "";    if (new_uri == "") {    id->misc->error_code = 405;    errors++;    TRACE_LEAVE("MOVE: No dest file");    return 0;    }    + #ifdef __NT__ +  foreach(new_uri/"/", string segment) { +  if (has_suffix(segment, " ")) { +  // Path segments on NT may not end with space. +  return Roxen.http_status(405, "MOVE: Invalid filesystem path."); +  } +  } + #endif +     // 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 + "/" + encode_path(new_uri); +  string moveto = real_path(new_uri, id);       // Workaround for Linux, Tru64 and FreeBSD.    if (has_suffix(moveto, "/")) {    moveto = moveto[..sizeof(moveto)-2]; -  + #if constant(System.normalize_path) +  } else { +  // normalize_path() may have adjusted the case of +  // the destination filename, so restore it. +  moveto = combine_path(moveto, "..", encode_path(basename(new_uri))); + #ifdef __NT__ +  moveto = replace(moveto, "/", "\\"); + #endif + #endif    }    -  if (FILTER_INTERNAL_FILE (f, id) || -  FILTER_INTERNAL_FILE (new_uri, id)) { +  if (!dotfiles && sizeof(filter(new_uri/"/", has_prefix, "."))) { +  TRACE_LEAVE("Path contains .-file or .-directory."); +  return 0; +  } +  if (FILTER_INTERNAL_FILE (new_uri, id)) {    id->misc->error_code = 405; -  TRACE_LEAVE("MOVE to or from internal file is disallowed"); +  TRACE_LEAVE("MOVE to internal file is disallowed");    return 0;    }       if (query("no_symlinks") &&    ((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.");    }    -  mapping(string:mixed) ret = -  write_access(({ combine_path(f, "../"), f, new_uri }), 0, id); +  // NB: Consider the case of moving of directories containing locked files. +  mapping(string:mixed) ret = write_access(({ f, new_uri }), 1, id);    if (ret) {    TRACE_LEAVE("MOVE: Locked");    return ret;    } -  +  ret = write_access(combine_path(f, "../"), 0, id); +  if (ret) { +  TRACE_LEAVE("MOVE: Parent directory locked"); +  return ret; +  }    -  size = _file_size(moveto,id); +  if (norm_f == moveto) { +  privs = 0; +  errors++; +  TRACE_LEAVE("MOVE: Source and destination are the same path."); +  return Roxen.http_status(403, "Permission denied."); +  }    -  +  size = _file_size(new_uri, id); +     SETUID_TRACE("Moving file", 0);       if (size != -1) {    // Destination exists.    -  +  TRACE_ENTER(sprintf("Destination exists: %d\n", size), 0);    int(0..1) overwrite =    !id->request_headers->overwrite ||    id->request_headers->overwrite == "T";    if (!overwrite) {    privs = 0; -  +  TRACE_LEAVE("");    TRACE_LEAVE("MOVE disallowed (overwrite header:F).");    return Roxen.http_status(412);    }    if(!query("delete"))    {    privs = 0;    id->misc->error_code = 405; -  +  TRACE_LEAVE("");    TRACE_LEAVE("MOVE disallowed (DELE disabled)");    return 0;    } -  +  TRACE_LEAVE("Overwrite allowed.");    if (overwrite || (size > -1)) { -  mapping(string:mixed) res = -  recurse_delete_files(new_uri, id); +  Stdio.Stat src_st = stat_file(f, id); +  Stdio.Stat dst_st = stat_file(new_uri, id); +  // Check that src and dst refers to different inodes. +  // Needed on case insensitive filesystems. +  if (!is_same_inode(src_st, dst_st)) { +  TRACE_ENTER(sprintf("Deleting destination: %O...\n", new_uri), 0); +  mapping(string:mixed) res = recurse_delete_files(new_uri, id);    if (res && (!sizeof (res) || res->error >= 300)) {    privs = 0; -  +  TRACE_LEAVE("");    TRACE_LEAVE("MOVE: Recursive delete failed.");    if (sizeof (res)) -  set_status_for_path (new_uri, res->error, res->rettext); +  set_status_for_path(new_uri, id, res->error, res->rettext);    return ([]);    } -  +  TRACE_LEAVE("Recursive delete ok."); +  }    } else {    privs = 0;    TRACE_LEAVE("MOVE: Cannot overwrite directory");    return Roxen.http_status(412);    }    }    -  +  TRACE_ENTER(sprintf("MOVE: mv(%O, %O)...\n", norm_f, moveto), 0);    code = mv(norm_f, moveto);    int err_code = errno();    privs = 0; -  +  TRACE_LEAVE(sprintf("==> %d (errno: %d: %s)\n", +  code, err_code, strerror(err_code)));       TRACE_ENTER("MOVE: Accepted", 0);       moves++;       /* Clear the stat-cache for this file */    if (stat_cache) {    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); +  mixed ret = errno_to_status (err_code, 1, id); +  if (ret) return ret; +  if (id->misc->error_code) { +  return Roxen.http_status(id->misc->error_code);    } -  +  return 0; +  }    TRACE_LEAVE("MOVE: Success");    TRACE_LEAVE("Success");    if (size != -1) return Roxen.http_status(204);    return Roxen.http_status(201);    }       case "DELETE":    if (size==-1) {    id->misc->error_code = 404;    TRACE_LEAVE("DELETE: Not found");    return 0;    }    if(!query("delete"))    {    id->misc->error_code = 405;    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, 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")) {
Roxen.git/server/modules/filesystems/filesystem.pike:1611:    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 { +  unlock_path(f, id);    }    } else {    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
Roxen.git/server/modules/filesystems/filesystem.pike:1643:    if(!rm(norm_f))    {    privs = 0;    id->misc->error_code = 405;    TRACE_LEAVE("DELETE: Failed");    return 0;    }    privs = 0;    deletes++;    +  unlock_path(f, id); +     if (id->misc->quota_obj && (size > 0)) {    id->misc->quota_obj->deallocate(URI, size);    }    }    TRACE_LEAVE("DELETE: Success");    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);
Roxen.git/server/modules/filesystems/filesystem.pike:1687:    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"); +  if (is_same_inode(source_st, dest_st)) { +  TRACE_LEAVE("Source and destination are the same inode."); +  TRACE_LEAVE(""); +  return Roxen.http_status(403, "Permission denied."); +  } +  if (has_prefix(source, dest)) { +  TRACE_LEAVE("Destination contains source."); +  TRACE_LEAVE(""); +  return Roxen.http_status(403, "Permission denied."); +  }    switch(overwrite) {    case NEVER_OVERWRITE:    TRACE_LEAVE("");    TRACE_LEAVE("");    return Roxen.http_status(412, "Destination already exists.");    case DO_OVERWRITE:    if (!query("delete")) {    TRACE_LEAVE("COPY: Deletion not allowed.");    TRACE_LEAVE("");    return Roxen.http_status(405, "Not allowed.");
Roxen.git/server/modules/filesystems/filesystem.pike:1728:    TRACE_LEAVE("");    return errno_to_status (errno(), 0, id);    }       if (id->multi_status_size() > start_ms_size) {    privs = 0;    TRACE_LEAVE("COPY: Partial failure in destination directory delete.");    TRACE_LEAVE("");    return ([]);    } +  } else { +  unlock_path(dest, id);    }    SIMPLE_TRACE_LEAVE("COPY: Delete ok.");    } else if (source_st->isdir) {    if (!rm(dest_path)) {    privs = 0;    if (errno() != System.ENOENT)    {    mapping(string:mixed) status = errno_to_status (errno(), 0, id);    if (!status) status = (["error": id->misc->error_code]);    id->set_status_for_path(mountpoint + dest,    status->error, status->rettext);    TRACE_LEAVE("");    return ([]);    }    SIMPLE_TRACE_LEAVE("COPY: File deletion failed (destination disappeared).");    } else {    SIMPLE_TRACE_LEAVE("COPY: File deletion ok."); -  +  +  unlock_path(dest, id);    }    } else {    SIMPLE_TRACE_LEAVE("COPY: No need to perform deletion.");    }    privs = 0;    break;    case MAYBE_OVERWRITE:    if ((source_st->isreg != dest_st->isreg) ||    (source_st->isdir != dest_st->isdir)) {    TRACE_LEAVE("COPY: Resource types for source and destination differ.");
Roxen.git/server/modules/filesystems/filesystem.pike:1767:    } else if (source_st->isdir) {    TRACE_LEAVE("Already done (both are directories).");    TRACE_LEAVE("");    return Roxen.http_status(204, "Destination already existed.");    }    break;    }    }       if (source_st->isdir) { +  if (has_prefix(dest, source)) { +  TRACE_LEAVE("Source contains destination."); +  return Roxen.http_status(403, "Permission denied."); +  } +     mkdirs++;    object privs;    SETUID_TRACE("Creating directory/collection", 0);       int code = mkdir(dest_path);    int err_code = errno();       if (code) {    string msg = safe_chmod(dest_path, 0777 & ~(id->misc->umask || 022));    privs = 0;