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

version» Context lines:

Roxen.git/server/modules/filesystems/filesystem.pike:255:   {    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 = 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 constant(System.normalize_path)    if (catch {    if ((<'/','\\'>)[path[-1]]) {    normalized_path = System.normalize_path(path + ".");    } else {    normalized_path = System.normalize_path(path);    }
Roxen.git/server/modules/filesystems/filesystem.pike:375:    privs=Privs(X, uid->uid(), uid->gid() ); \    }      mixed stat_file( string f, RequestID id )   {    Stat fs;       FILESYSTEM_WERR("stat_file for \""+f+"\"" +    (id->misc->internal_get ? " (internal)" : ""));    -  f = path+f; +  f = path + encode_path(f);       if (FILTER_INTERNAL_FILE (f, id))    return 0;       if(stat_cache && !id->pragma["no-cache"] &&    (fs=cache_lookup("stat_cache",f)))    return fs[0];    object privs;    SETUID_NT("Statting file");       /* No security currently in this function */ -  fs = file_stat(encode_path(f)); +  fs = file_stat(f);    privs = 0;    if(!stat_cache) return fs;    cache_set("stat_cache", f, ({fs}));    return fs;   }      //! Convert to filesystem encoding.   //!   //! @note   //! Note that the @expr{"iso-8859-1"@} encoding will perform
Roxen.git/server/modules/filesystems/filesystem.pike:436:    // the path as appropriate.    return Unicode.normalize(utf8_to_string(p), "NFC");    default:    return Charset.decoder(path_encoding)->feed(p)->drain();    }   #endif /* !__NT__ */   }      string real_path(string f, RequestID id)   { -  f = normalized_path + f; +  f = normalized_path + encode_path(f);    if (FILTER_INTERNAL_FILE(f, id)) return 0;    catch { -  f = NORMALIZE_PATH(encode_path(f)); +  f = NORMALIZE_PATH(f);    if (has_prefix(f, normalized_path) ||   #ifdef __NT__    (f+"\\" == normalized_path)   #else /* !__NT__ */    (f+"/" == normalized_path)   #endif /* __NT__ */    ) {    return f;    }    };
Roxen.git/server/modules/filesystems/filesystem.pike:531:   {    array dir;       FILESYSTEM_WERR("find_dir for \""+f+"\"" +    (id->misc->internal_get ? " (internal)" : ""));       object privs;    SETUID_NT("Read dir");       if (catch { -  f = NORMALIZE_PATH(encode_path(path + f)); +  f = NORMALIZE_PATH(path + encode_path(f));    } || !(dir = get_dir(f))) {    privs = 0;    return 0;    }    privs = 0;       if(!query("dir"))    // Access to this dir is allowed.    if(! has_value(dir, ".www_browsable"))    {
Roxen.git/server/modules/filesystems/filesystem.pike:717:    if(putting[from] <= 0) {    putting[from] = 0; // Paranoia    done_with_put( id_arr );    }    }   }      int _file_size(string X, RequestID id)   {    Stat fs; +  X = path + encode_path(X);    if( stat_cache )    {    array(Stat) cached_fs;    if(!id->pragma["no-cache"] &&    (cached_fs = cache_lookup("stat_cache", X)))    {    id->misc->stat = cached_fs[0];    return cached_fs[0] ? cached_fs[0][ST_SIZE] : -1;    }    } -  if(fs = file_stat(encode_path(X))) +  if(fs = file_stat(X))    {    id->misc->stat = fs;    if( stat_cache ) cache_set("stat_cache",(X),({fs}));    return fs[ST_SIZE];    } else if( stat_cache )    cache_set("stat_cache",(X),({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(encode_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:780:    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;    }
Roxen.git/server/modules/filesystems/filesystem.pike:858:    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;       catch {    /* NOTE: NORMALIZE_PATH() may throw errors. */ -  f = norm_f = NORMALIZE_PATH(f = encode_path(path + f)); +  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__ */    ) {    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("");    TRACE_LEAVE("Permission denied.");    return Roxen.http_status(403, "File exists, but 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 += "/"; +  // 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 += "/";    } -  +  +  /* Adjust not_query */ +  id->not_query = mountpoint + f;   #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.
Roxen.git/server/modules/filesystems/filesystem.pike:950:    }       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:998:    // 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:1025:    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);    TRACE_LEAVE(sprintf("%s: Contains symlinks. Permission denied",    id->method));    return Roxen.http_status(403, "Permission denied.");    }       code = mkdir(f);
Roxen.git/server/modules/filesystems/filesystem.pike:1082:    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 = 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->open(f, "wct", 0666)) +  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;       putting[id->my_fd] = id->misc->len;    if(id->data && strlen(id->data))    {    // FIXME: What if sizeof(id->data) > id->misc->len ?    if (id->misc->len > 0) {    putting[id->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[id->my_fd]) {    TRACE_LEAVE("PUT: Just a string");    TRACE_LEAVE("Put: Success");    if (size < 0) {
Roxen.git/server/modules/filesystems/filesystem.pike:1211:    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:1292:    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) || +  if (mapping(string:mixed) ret = write_access(f, 0, id) ||    write_access(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:1365:    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)) { +  write_access(f, 0, id)) {    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:1435:    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, encode_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:1479:    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)))) { +  if (mappingp(res = write_access(combine_path(f, "../"), 1, id)) || +  (res && mappingp(res = write_access(f, 1, id)))) {    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))) { +  if ((res = write_access(combine_path(f, "../"), 0, id)) || +  (res = write_access(f, 0, id))) {    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:1597:    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);    if (mappingp(res)) return res; -  string dest_path = path + dest; -  catch { dest_path = encode_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:1710:    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 = encode_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) &&