【问题标题】:Open File with Non ASCII Characters使用非 ASCII 字符打开文件
【发布时间】:2015-05-26 09:39:24
【问题描述】:

我正在尝试计算文件的 SHA-256。我有以下代码在路径有效时给出正确的校验和值,即。它是 ASCII。我有以下代码:

#include <openssl\evp.h>
#include <sys\stat.h>
#include <iostream>
#include <string>
#include <fstream>
#include <cstdio>
const int MAX_BUFFER_SIZE = 1024;
std::string FileChecksum(std::string, std::string);

long long int GetFileSize(std::string filename)
{
    struct _stat64 stat_buf;
    int rc = _stat64(filename.c_str(), &stat_buf);
    return rc == 0 ? stat_buf.st_size : -1;
}

std::string fname = "D:\\Private\\Test\\asdf.txt"; // Need to support this D:\\Private\\Test\\सर्वज्ञ पन्त.txt

int main()
{
    std::string checksum = FileChecksum(fname , "sha256");
    std::cout << checksum << std::endl;
    return 0;
}


static std::string FileChecksum(std::string file_path, std::string algorithm="sha256")
{
    EVP_MD_CTX *mdctx;
    const EVP_MD *md;
    unsigned char md_value[EVP_MAX_MD_SIZE];
    int i;
    unsigned int md_len;

    OpenSSL_add_all_digests();
    md = EVP_get_digestbyname(algorithm.c_str());

    if(!md) {
        printf("Unknown message digest %s\n",algorithm);
        return "";
    }

    mdctx = EVP_MD_CTX_create();
    std::ifstream readfile(file_path,std::ifstream::binary);
    if(!readfile.is_open())
    {
        std::cout << "COuldnot open file\n";
        return "";
    }
    readfile.seekg(0, std::ios::end);
    long long filelen = readfile.tellg();
    std::cout << "LEN IS " << filelen << std::endl;
    readfile.seekg(0, std::ios::beg);
    if(filelen == -1)
    {
        std::cout << "Return Null \n";
        return "";
    }

    EVP_DigestInit_ex(mdctx, md, NULL);
    long long temp_fil = filelen;
    while(!readfile.eof() && readfile.is_open() && temp_fil>0)
    {

        int bufferS = (temp_fil < MAX_BUFFER_SIZE) ? temp_fil : MAX_BUFFER_SIZE;
        char *buffer = new char[bufferS+1];
        buffer[bufferS] = 0;
        readfile.read(buffer, bufferS);
        EVP_DigestUpdate(mdctx, buffer, bufferS);
        temp_fil -= bufferS;
        delete[] buffer;
    }
    EVP_DigestFinal_ex(mdctx, md_value, &md_len);
    EVP_MD_CTX_destroy(mdctx);
    char str[128] = { 0 };
    char *ptr = str;
    std::string ret;
    for(i = 0; i < md_len; i++)
    {
    //_snprintf(checksum_msg+cx,md_len-cx,"%02x",md_value[i]);
        sprintf(ptr,"%02x", md_value[i]);
        ptr += 2;
    }

    ret = str;
    /* Call this once before exit. */
    EVP_cleanup();
    return ret;
}

代码将给出正确的文件校验和有效的名称。但是一旦给出非 ascii 字符文件,程序就会失败。我使用 std::wstring 似乎解决了这个问题,但网站 here 不鼓励使用 std::wstring不要在接受 UTF-16 的 API 的相邻点以外的任何地方使用 wchar_t 或 std::wstring。 如果我要遵循这一点,如何使此代码适用于所有类型的路径。我正在使用 VS2010。

【问题讨论】:

  • ifstream 不需要 in。它已经“进入”了。
  • 是的。这是一个愚蠢的错误。
  • 这似乎是一个“与接受 UTF-16 的 API 相邻的点”,所以 std::wstring 听起来不错
  • 什么情况下我应该更喜欢 std::wstring 而不是 std::string
  • Nitpick:不要通过字符串参数选择算法,使用枚举或类似的东西。

标签: c++ visual-studio-2010 unicode


【解决方案1】:

wchar_t 不能跨多个平台移植,因为它在某些平台 (Windows) 上是 2 字节 (UTF-16),但在其他平台 (Linux 等) 上是 4 字节 (UTF-32)。这就是site 向您发出的警告。

在您的特定情况下,您只关注 Windows,因此 std::wstring 非常适合使用,因为它使用 UTF-16,这与 Win32 API 在所有地方使用的编码相同。您正在寻找的是 Microsoft 的 _wstat64() 函数,以及接受 wchar_t* 文件名的 Microsoft 的非标准 std::ifstream 构造函数:

long long int GetFileSize(std::wstring filename)
{
    struct _stat64 stat_buf;
    int rc = _wstat64(filename.c_str(), &stat_buf);
    return rc == 0 ? stat_buf.st_size : -1;
}

std::wstring file_path = L"D:\\Private\\Test\\सर्वज्ञ पन्त.txt";

...

static std::string FileChecksum(std::wstring file_path, std::string algorithm="sha256")
{
    ...
    std::ifstream readfile(file_path.c_str(), std::ifstream::binary);
    ...
}

话虽如此,你的FileChecksum() 函数比它需要的复杂得多,如果发生错误,它不会正确清理,它不能验证std::ifstream::read() 实际上正在读取你请求的字节数(它可能读得更少),而且它在滥用std::ifstream::eof()

试试类似的方法:

#include <openssl\evp.h>
#include <sys\stat.h>
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <iomanip>

const int MAX_BUFFER_SIZE = 1024;
std::string FileChecksum(std::wstring file_path, std::string algorithm = "sha256");

std::wstring fname = L"D:\\Private\\Test\\सर्वज्ञ पन्त.txt";

int main()
{
    std::string checksum = FileChecksum(fname, "sha256");
    std::cout << checksum << std::endl;
    return 0;
}

std::string FileChecksum(std::wstring file_path, std::string algorithm)
{
    EVP_MD_CTX *mdctx = NULL;
    const EVP_MD *md;
    unsigned char md_value[EVP_MAX_MD_SIZE];
    char buffer[MAX_BUFFER_SIZE];
    unsigned int md_len;
    std::ostringstream oss;
    std::string ret;

    std::ifstream readfile(file_path.c_str(), std::ifstream::binary);
    if (readfile.fail())
    {
        std::cout << "Could not open file\n";
        goto finished;
    }

    OpenSSL_add_all_digests();

    md = EVP_get_digestbyname(algorithm.c_str());    
    if (!md) {
        std::cout << "Unknown message digest " << algorithm << "\n";
        goto cleanup;
    }

    mdctx = EVP_MD_CTX_create();
    if (!mdctx) {
        std::cout << "Could not create context for message digest " << algorithm << "\n";
        goto cleanup;
    }

    EVP_DigestInit_ex(mdctx, md, NULL);

    do
    {
        readfile.read(buffer, sizeof(buffer));
        if ((readfile.fail()) && (!readfile.eof()))
        {
            std::cout << "Could not read from file\n";
            goto cleanup;
        }

        EVP_DigestUpdate(mdctx, buffer, readfile.gcount());
    }
    while (!readfile.eof());

    EVP_DigestFinal_ex(mdctx, md_value, &md_len);

    for(unsigned int i = 0; i < md_len; i++)
        oss << std::hex << std::setw(2) << std::setfill('0') << (int) md_value[i];
    ret = oss.str();

cleanup:
    if (mdctx) EVP_MD_CTX_destroy(mdctx);
    EVP_cleanup();

finished:
    return ret;
}

【讨论】:

  • 不是必须遵循一段时间。 似乎不见了。我必须让这段代码跨平台。代码应该在 windows 和 linux 中获取文件的校验和。
  • 是的,它缺少while,我现在已经修复了。不幸的是,不同的平台处理文件路径的方式不同,编码不同(Windows 使用 UTF-16,*Nix 使用 UTF-8 等),不同的格式等。所以你必须用一些特定于平台的代码来解释这一点包裹在 #ifdef 语句中,除非您使用已经为您工作的第三方库。
  • 代码无法计算正确的校验和。在调试器下运行时,代码将文件数据读入缓冲区,如果内容小于缓冲区大小,则缓冲区中有残留。例如喜欢sdasÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌ,因为没有终止字符。
  • 再次检查.. gcount() 通过read() 返回缓冲区中的实际字节数。没有“缓冲区中的残留物”被传递给EVP_DigestUpdate()。至于! 返回true,我忘记了read() 在达到EOF 时同时设置了failbiteofbit。我已经更新了代码来解决这个问题。
  • 代码现在似乎可以工作,但现在计算出错误的校验和。它确实支持非 ascii 路径,但计算出的 SHA-256 是错误的。
猜你喜欢
  • 2011-04-04
  • 1970-01-01
  • 2021-11-29
  • 2021-04-09
  • 2011-06-08
  • 2018-04-12
  • 1970-01-01
  • 1970-01-01
  • 2016-07-30
相关资源
最近更新 更多