Repository: nushell/nushell
File: crates/nu-engine/src/glob_from.rs (Rust)

Code before:

        // Now expand `p` to get full prefix
        let path = expand_path_with(p, cwd, pattern.item.is_expand());
        let escaped_prefix = PathBuf::from(nu_glob::Pattern::escape(&path.to_string_lossy()));

        (Some(path), escaped_prefix.join(just_pattern))
    } else {
        let path = PathBuf::from(&pattern.item.as_ref());
        let path = expand_path_with(path, cwd, pattern.item.is_expand());
        let is_symlink = match fs::symlink_metadata(&path) {
            Ok(attr) => attr.file_type().is_symlink(),
            Err(_) => false,
        };

        if is_symlink {
            (path.parent().map(|parent| parent.to_path_buf()), path)
        } else {
            let path = match canonicalize_with(path.clone(), cwd) {
                Ok(p) if p.to_string_lossy().contains(GLOB_CHARS) => {
                    // our path might contains GLOB_CHARS too
                    // in such case, we need to escape our path to make
                    // glob work successfully
                    PathBuf::from(nu_glob::Pattern::escape(&p.to_string_lossy()))
                }
                Ok(p) => p,
                Err(err) => {
                    return match err.kind() {
                        ErrorKind::PermissionDenied => Err(ShellError::GenericError {
                            error: "Permission denied".into(),
                            msg: err.to_string(),
                            span: None,
                            help: None,
                            inner: vec![],
                        }),
                        // Previously, all these errors were treated as "directory not found."
                        // Now, permission denied errors are handled separately.
                        // TODO: Refine handling of I/O errors for more precise responses.
                        _ => Err(ShellError::DirectoryNotFound {
                            dir: path.to_string_lossy().to_string(),
                            span: pattern.span,
                        }),
                    };
                }
            };
            (path.parent().map(|parent| parent.to_path_buf()), path)
        }
    };

    let pattern = pattern.to_string_lossy().to_string();
    let glob_options = options.unwrap_or_default();


Review comment (security):
I think this should be a `ShellError::IOError` instead of a `ShellError::GenericError`; `cd` uses `IOError` when you attempt to `cd` into a directory without permission.

Code after:
        let path = expand_path_with(p, cwd, pattern.item.is_expand());
        let escaped_prefix = PathBuf::from(nu_glob::Pattern::escape(&path.to_string_lossy()));

        (Some(path), escaped_prefix.join(just_pattern))
    } else {
        let path = PathBuf::from(&pattern.item.as_ref());
        let path = expand_path_with(path, cwd, pattern.item.is_expand());
        let is_symlink = match fs::symlink_metadata(&path) {
            Ok(attr) => attr.file_type().is_symlink(),
            Err(_) => false,
        };

        if is_symlink {
            (path.parent().map(|parent| parent.to_path_buf()), path)
        } else {
            let path = match canonicalize_with(path.clone(), cwd) {
                Ok(p) if nu_glob::is_glob(p.to_string_lossy().as_ref()) => {
                    // our path might contain glob metacharacters too.
                    // in such case, we need to escape our path to make
                    // glob work successfully
                    PathBuf::from(nu_glob::Pattern::escape(&p.to_string_lossy()))
                }
                Ok(p) => p,
                Err(err) => {
                    return match err.kind() {
                        ErrorKind::PermissionDenied => Err(ShellError::GenericError {
                            error: "Permission denied".into(),
                            msg: err.to_string(),
                            span: None,
                            help: None,
                            inner: vec![],
                        }),
                        // Previously, all these errors were treated as "directory not found."
                        // Now, permission denied errors are handled separately.
                        // TODO: Refine handling of I/O errors for more precise responses.
                        _ => Err(ShellError::DirectoryNotFound {
                            dir: path.to_string_lossy().to_string(),
                            span: pattern.span,
                        }),
                    };
                }
            };
            (path.parent().map(|parent| parent.to_path_buf()), path)
        }
    };

    let pattern = pattern.to_string_lossy().to_string();
    let glob_options = options.unwrap_or_default();

    let glob = nu_glob::glob_with(&pattern, glob_options).map_err(|e| {
        nu_protocol::ShellError::GenericError {