rust 如何从OsStr和str组件构建URL?

moiiocjp  于 2023-05-18  发布在  其他
关注(0)|答案(2)|浏览(236)

这段代码遍历一个文件夹,并查找以.txt结尾的文件。接下来,我想获取std::ffi::os_str::OsStr类型的entry.file_name(),并将其与BASE_URL连接以形成URL。

const FOLDER_NAME: &str = "/tmp";
const PATTERN: &str = ".txt";
const BASE_URL: &str = "http://192.168.1.100:3310/";

use std::error::Error;
use std::ffi::OsStr;
use walkdir::WalkDir;

fn main() -> Result<(), Box<dyn Error>> {
    println!("Walking folder {}", FOLDER_NAME);

    let valid_entries = WalkDir::new(FOLDER_NAME)
        .into_iter()
        .flat_map(|e| e)
        .flat_map(|e| {
            let name = e.file_name().to_str()?;
            if name.contains(PATTERN) {
                Some(e)
            } else {
                None
            }
        });

    print_type_of(&valid_entries);

    for entry in valid_entries {
        println!("This file matches: {:?}", entry);
        let metadata = entry.metadata()?;
        let size = metadata.len();
        let name = entry.file_name();
        let fullpath = entry.path().display();
        println!("path: {}, filename: {:?}, Len: {:?}", fullpath, name, size);
        print_type_of(&name);
        //let url = format!("{}{}",BASE_URL, name); <--- this, how do I do this?
    }
    Ok(())
}

fn print_type_of<T>(_: &T) {
    println!("{}", std::any::type_name::<T>())
}

blowup的示例输出:

error[E0277]: `std::ffi::OsStr` doesn't implement `std::fmt::Display`
--> src/main.rs:34:38
   |
34 |         let url = format!("{}{}",BASE_URL, name);
   |                                            ^^^^ `std::ffi::OsStr` cannot be formatted with the default formatter
   |
   = help: the trait `std::fmt::Display` is not implemented for `std::ffi::OsStr`
   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
   = note: required because of the requirements on the impl of `std::fmt::Display` for `&std::ffi::OsStr`
   = note: required by `std::fmt::Display::fmt`

在Go中,我这样做:

video_url := baseURL + *video.Name
req, err := http.NewRequest("HEAD", video_url, nil)
if err != nil {
    log.Println("Err", err)
}

在Rust中正确的方法是什么?
我得到的最接近的是:

let url = format!("{}{:?}", BASE_URL, name);
println!("url {}", url);

其给出:

This file matches: DirEntry("/tmp/a.txt")
path: /tmp/a.txt, filename: "a.txt", Len: 820805
&std::ffi::os_str::OsStr
&str
url http://192.168.1.100:3310/"a.txt"  <- malformed
This file matches: DirEntry("/tmp/somefile.txt")
path: /tmp/somefile.txt, filename: "somefile.txt", Len: 13
&std::ffi::os_str::OsStr
&str
url http://192.168.1.100:3310/"somefile.txt" <- malformed

有了这个,我必须弄清楚如何删除" s。当然感觉有一个更好的方式,我不知道..
既然正确的方法使用了match表达式,那么我如何用它来构建URL呢?

let name = entry.file_name();
let name_to_str = entry.file_name().to_str();
let url = match name_to_str {
        Some(name) =>  format!("{}{:?}",BASE_URL, name_to_str).replace("\"", ""),
        _ => None
};
hvvq6cgz

hvvq6cgz1#

您可以使用OsStr中的to_str方法来获取Option<&str>。它返回Option,因为如果字符串不包含有效的Unicode,转换可能会失败。如果你绝对确定它将永远是有效的Unicode,你可以只unwrapOption。因此,您可以按如下方式创建您的URL:

let url = format!("{}{}", BASE_URL, name.to_str().unwrap());

编辑1
{:?}用于调试打印。它使用Debug trait。如链接所示,Debug
应该在面向程序员的调试上下文中格式化输出
在你的例子中,这恰好是你想要的字符串,但这不是它的预期行为。我仍然推荐使用为您的用例显式提供的方法to_str

yeotifhr

yeotifhr2#

我怀疑URL原始数据的正确类型不是真正的str
完全编码的URL,使用percent-encoding编码特殊字符,是纯ASCII。当你解码它时,你会得到URL的许多部分(协议,域名,'/''?''&'等之间的各个部分)作为未解释的二进制数据(即[u8]),不像str/String强制的那样有效的UTF-8。
为了更健壮,您可能希望直接从原始数据中对url的各个部分进行百分比编码。特别是,文件名应该直接从&OsStr/OsString进行URL编码。这样你就不会冒着让非UTF-8文件名破坏你的代码的风险,甚至更糟的是,文件名中有潜在的危险字符,如?=&,成为攻击向量。
实际上,使用crate urlencoding,它会是这样的:

let name = urlencoding::encode_binary(entry.file_name().as_bytes());

这将完全按照您的操作系统中的表示对文件名进行编码。也就是说,如果您的操作系统支持as_bytes()。不幸的是,并非所有操作系统都是如此。
这是可以理解的:我不知道如何将Windows 16位wchar编码为大于255的URL。在这种情况下,惯例似乎是在URL编码之前将整个字符串转换为UTF-8(使用.to_str(),如另一个答案所建议的),但然后文件名可能是无效的UTF-16,在这种情况下转换将失败,我猜在URL中表示这样的文件名确实是不可能的,并且你应该在你的程序中返回一个错误(见问号操作符)。

相关问题