std::string 中的 GSOAP 格式错误的 utf-8

GSOAP malforms utf-8 in std::string

我有使用 GSOAP 的 C++ 服务器。其中一个 API 接受字符串。

<message name="concatRequest">
  <part name="a" type="ns:password"/><!-- ns__concat::a -->
  <part name="b" type="xsd:string"/><!-- ns__concat::b -->
</message>

int billon__concat( struct soap *soap, std::string a, std::string b, std::string &result )
{
//    std::cout <<"PACZPAN A:"<<a<<" B:"<<b <<std::endl;
    std::cout <<"PACZPAN B[0..3]: " << (int)b[0] << " " << (int)b[1] << " " << (int)b[2] << " " <<(int)b[3] << std::endl;
    std::cout <<"PACZPAN B[0..3]: " << (char)b[0] << " " << (char)b[1] << " " << (char)b[2] << " " <<(char)b[3] << std::endl;
    result = a + b;
  //  std::cout <<"PACZPAN res:"<<result <<std::endl;
    return SOAP_OK;
}

ns::password 也只是一个字符串。

现在我通过两种不同的方式发送带有参数 B='PŁOCK' 的请求,在 wireshark 中显示为 'PŁOCK' 或 P&#x141;OCK,所以我认为两者都是正确的。 还记录 gsoap 打印:

POST / HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: text/xml;charset=UTF-8
SOAPAction: ""
Content-Length: 471
Host: localhost:8080
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:calc">
   <soapenv:Header/>
   <soapenv:Body>
      <urn:concat soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
         <a xsi:type="urn:password">      </a>
         <b xsi:type="xsd:string">PŁOCK</b>
      </urn:concat>
   </soapenv:Body>
</soapenv:Envelope>

当服务器收到它时,它变成PAOCK。 ASCII 之外没有坏字节,只是不同的字母。

PACZPAN B[0..3]: 80 65 79 67
PACZPAN B[0..3]: P A O C

我不在乎 std::string 不能很好地处理 unicode。我希望它按原样处理发送的字节。

我可以在 typemap.dat 中添加映射:xsd__string = | std::wstring,但我不想使用 std::wstring - 它无论如何都不是 utf-8。

GSOAP 默认不处理拉丁语集之外的字符:doc. It can be changed during initialization of soap context with a flag:

struct soap *soap = soap_new1( SOAP_C_UTFSTRING );

GSOAP 的 UTF-8 处理的主要问题是,它将每个 UTF-8 字符转换为 Latin1 字符并且不关心对话是否可能。

GSOAP 假装通过接受和生成 来正确处理 UTF-8,但事实并非如此。 在 stdsoap2.cpp 中的 GSOAP 方法 soap_pututf8() 和 soap_getutf8() 中,无法进行预期的错误字符转换,因此 所有内容 都将被转换. 因此,UTF-8 字符“Ł”无声无息地变成了绝对错误的字符 'A'。在我看来,这是国家语言支持的噩梦。我已经告诉 GSOAP 的作者,但他认为这个习惯没有问题。

所以在我看来,小型界面的最佳解决方案是使用您提到的 SOAP_C_UTFSTRING 并自行为每个字符串调用 iconv() 。如果结果为 ((size_t) -1) 你的 GSOAP-Methode 可以 return soap_sender_fault(soap, strerror(errno), NULL); (参见 man 3 iconv)

否则,在您的 GSOAP 应用程序之前执行字符集对话的 GSOAP 插件或 Web 服务器过滤器将是一个机会。

现在您有机会正确处理您想要的任何字符集。如果要处理 ISO-8859-16(因为字母“Ł”),请参见以下示例。 https://de.wikipedia.org/wiki/ISO_8859

#include <iconv.h>
#include <stdexcept>    // std::invalid_argument
#include <string.h>     // strerror
#include <system_error>

class Iconv {
    const std::string m_to, m_from; // remember for error messages
    const iconv_t m_cd; // the encapsulated conversion descriptor
    const size_t m_multiplier; // one UTF-8 character can need up to 4 bytes
public:
    Iconv(const char *to, const char *from, size_t multiplier)
        : m_to(to), m_from(from), m_cd(iconv_open(to, from)), m_multiplier(multiplier) {
        if (m_cd == ((iconv_t) -1))
            throw std::system_error(errno, std::system_category(), m_from + " to " + m_to);
    }
    ~Iconv() { iconv_close(m_cd); }

    std::string operator()(std::string in) const {
        size_t inbytesleft = in.length();
        char *inbuf = &in[0];
        size_t outbytesleft = m_multiplier * inbytesleft + 1;
        std::string out;
        out.resize(outbytesleft);
        char *outbuf = &out[0];
        if (iconv(m_cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft) == ((size_t) -1)) {
            if (errno == EILSEQ || errno == EINVAL)
                throw std::invalid_argument(m_from + " to " + m_to + ": " + strerror(errno));
            else
                throw std::system_error(errno, std::system_category(), m_from + " to " + m_to);
        }
        out.resize(out.length() - outbytesleft);
        return out;
    }
};

int billon__concat(struct soap *soap, std::string a, std::string b, std::string &result) {
    try {
        static const Iconv utf8_to_iso885916("ISO-8859-16", "UTF-8", 1);
        a = utf8_to_iso885916(a);
        b = utf8_to_iso885916(b);

        // do your fancy stuff with ISO-8859-16 strings ...
        result = a + b;

        static const Iconv iso885916_to_utf8("UTF-8", "ISO-8859-16", 4);
        result = iso885916_to_utf8(result);
        return SOAP_OK;
    } catch (const std::invalid_argument& ex) {
        return soap_sender_fault(soap, ex.what(), NULL);
    } catch (const std::exception& ex) {
        return soap_receiver_fault(soap, ex.what(), NULL);
    }
}