Символы UrlUnescape() и unicode


Я пытаюсь написать программу на C++, которая будет декодировать строку, закодированную URL, содержащую некоторые символы юникода, закодированные URL.

#include <windows.h>
#include <string>
#include <shlwapi.h>
#pragma comment(lib, "Shlwapi.lib")

int _tmain(int argc, _TCHAR* argv[])
{
    std::wstring test = L"bla+%D0%B1%D0%BB%D0%BE%D1%84+%E6%97%A5%E6%9C%AC%E8%AA%9E";
    PWSTR urlencodedStr = const_cast<WCHAR*>(test.c_str());
    WCHAR decodedStr[1025];
    DWORD size = 1024;
    HRESULT hres = UrlUnescape(urlencodedStr, decodedStr, &size, NULL);

    if (hres == S_OK)
        MessageBox(NULL, decodedStr, L"decoded string", MB_OK);

    return 0;
}

Я ожидаю, чтобы получить L"бла блоф 日本語" в decodedStr. Но вместо этого я получаю L"bla+бÐ"оÑ"+æ-¥æèèžž". Я использую кодировку unicode в своей сборке. Что я делаю не так?

2 2

2 ответа:

UrlUnescape преобразует URL-декодированные байты %xx в символы, используя кодовую страницу по умолчанию (ANSI) по умолчанию. Это почти никогда не то, чего вы хотите.

Начиная с Windows 8, вы можете передать флаг UNESCAPE_AS_UTF8, чтобы заставить его работать. Если вы не можете зависеть от Win8, вам придется использовать/написать другой вызов библиотеки декодирования URL, который не страдает от этой проблемы.

Также существует проблема +: в простой кодировке URL (например, для использования в части пути) это означает, что плюс, но в кодировке form-url (например, в параметре запроса), которая, похоже, у вас здесь есть, это означает пробел. Хороший декодер URL-адресов даст вам возможность сказать, какой из них вы имеете в виду; UrlUnescape этого не делает. Альтернативой является ручная замена + пробелом на входе перед декодированием URL-адреса; это один частный случай, и никакие другие символы не затрагиваются подобным образом.

Хорошо. Поэтому я написал свою собственную функцию для декодирования URL-кодированных строк с помощью символов unicode. Вот оно:

#include <windows.h>
#include <string>
#include <shlwapi.h>
#include <sstream>
#include <iostream>
#include <wininet.h> // For INTERNET_MAX_URL_LENGTH

#pragma comment(lib, "Shlwapi.lib")

bool IsHexChar(const WCHAR _char)
{
    return ((_char == L'A') || 
            (_char == L'B') ||
            (_char == L'C') ||
            (_char == L'D') ||
            (_char == L'E') ||
            (_char == L'F') || 
            iswalnum(_char));
}

std::wstring UrlDecode(const std::wstring& _encodedStr)
{
    std::string charStr;

    for (size_t i = 0; i < _encodedStr.length(); ++i)
    {
        if ((_encodedStr[i] == L'%') && (IsHexChar(_encodedStr[i+1])) && (IsHexChar(_encodedStr[i+2])))
        {
            std::wstring hexCodeStr = L"0x";
            hexCodeStr += _encodedStr[i+1];
            hexCodeStr += _encodedStr[i+2];

            unsigned int hexCharCode;   
            std::wstringstream ss;
            ss << std::hex << hexCodeStr;
            ss >> hexCharCode;

            charStr += static_cast<char>(hexCharCode);

            i += 2;
        }
        else if (_encodedStr[i] == L'+')
            charStr += L' ';
        else
            charStr += _encodedStr[i];
    }

    WCHAR decodedStr[INTERNET_MAX_URL_LENGTH];
    MultiByteToWideChar(CP_UTF8, 0, charStr.c_str(), -1, decodedStr, sizeof(decodedStr));

    return decodedStr;
}

Используйте так:

std::wstring encodedStr = L"bla+%D0%B1%D0%BB%D0%BE%D1%84+%E6%97%A5%E6%9C%AC%E8%AA%9E";
std::wstring decodedStr = UrlDecode(encodedStr);