Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ set(SRC_FILES
"src/address.cpp"
"src/mailer.cpp"
"src/email.cpp"
"src/chunkstreambuf.cpp")
"src/chunkstreambuf.cpp"
"src/wstringconvert.cpp")
set(TESTS
"protocol"
"http"
Expand Down
25 changes: 25 additions & 0 deletions include/fastcgi++/wstringconvert.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*!
* @file wstringconvert.hpp
* @brief Replacement for std::wstring_convert
* @date June 6, 2026
*/

#pragma once

#include <string>

//! Topmost namespace for the fastcgi++ library
namespace Fastcgipp
{
/*!
* This class is a simplified proxy to the now obsolete std::wstring_convert.
* Throws std::range_error on conversion or range errors.
*/
class WstringConvert
{
public:
std::wstring from_bytes(const char *first, const char *last); // [first, last)
std::wstring from_bytes(const std::string &str);
std::string to_bytes(const std::wstring &wstr);
};
}
7 changes: 4 additions & 3 deletions src/curl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include "fastcgi++/curl.hpp"
#include "fastcgi++/log.hpp"
#include "fastcgi++/wstringconvert.hpp"

#include <curl/curl.h>
#include <codecvt>
Expand Down Expand Up @@ -126,7 +127,7 @@ void Fastcgipp::Curl_base::verifySSL(bool verify)

void Fastcgipp::Curl_base::setUrl(const std::wstring& url)
{
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
WstringConvert converter;
try
{
setUrl(converter.to_bytes(url));
Expand All @@ -145,7 +146,7 @@ void Fastcgipp::Curl_base::addHeader(const char* const header)

void Fastcgipp::Curl_base::addHeader(const std::wstring& header)
{
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
WstringConvert converter;
try
{
addHeader(converter.to_bytes(header));
Expand Down Expand Up @@ -404,7 +405,7 @@ void Fastcgipp::Curl_base::StreamBuf<wchar_t>::injectHeader(
const char* valueStart,
const char* valueEnd)
{
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
WstringConvert converter;
try
{
std::wstring key = converter.from_bytes(keyStart, keyEnd);
Expand Down
5 changes: 3 additions & 2 deletions src/email.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include "fastcgi++/email.hpp"
#include "fastcgi++/log.hpp"
#include "fastcgi++/wstringconvert.hpp"

#include <codecvt>
#include <locale>
Expand All @@ -35,7 +36,7 @@
template <>
void Fastcgipp::Mail::Email<wchar_t>::to(const std::wstring& address)
{
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
WstringConvert converter;
try
{
m_data.to = converter.to_bytes(address);
Expand All @@ -49,7 +50,7 @@ void Fastcgipp::Mail::Email<wchar_t>::to(const std::wstring& address)
template <>
void Fastcgipp::Mail::Email<wchar_t>::from(const std::wstring& address)
{
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
WstringConvert converter;
try
{
m_data.from = converter.to_bytes(address);
Expand Down
4 changes: 2 additions & 2 deletions src/http.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@

#include "fastcgi++/log.hpp"
#include "fastcgi++/http.hpp"

#include "fastcgi++/wstringconvert.hpp"

void Fastcgipp::Http::vecToString(
const char* start,
const char* end,
std::wstring& string)
{
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
WstringConvert converter;
try
{
string = converter.from_bytes(&*start, &*end);
Expand Down
5 changes: 3 additions & 2 deletions src/log.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
*******************************************************************************/

#include "fastcgi++/log.hpp"
#include "fastcgi++/wstringconvert.hpp"

#include <iomanip>
#include <iostream>
Expand All @@ -50,7 +51,7 @@ namespace Fastcgipp
{
char buffer[HOST_NAME_MAX+2];
gethostname(buffer, sizeof(buffer));
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
WstringConvert converter;
try
{
return(converter.from_bytes(
Expand All @@ -67,7 +68,7 @@ namespace Fastcgipp

std::wstring getProgram()
{
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
WstringConvert converter;
std::wostringstream ss;
try
{
Expand Down
7 changes: 4 additions & 3 deletions src/parameters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "sqlTraits.hpp"
#include "fastcgi++/sql/parameters.hpp"
#include "fastcgi++/log.hpp"
#include "fastcgi++/wstringconvert.hpp"

#include <locale>
#include <codecvt>
Expand All @@ -37,7 +38,7 @@ using namespace Fastcgipp::SQL;

TEXT Parameter<WTEXT>::convert(const WTEXT& x)
{
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
WstringConvert converter;
try
{
return converter.to_bytes(x);
Expand Down Expand Up @@ -174,7 +175,7 @@ ARRAY<TEXT> Parameter<ARRAY<WTEXT>>::convert(const ARRAY<WTEXT>& x)
ARRAY<TEXT> result;
result.reserve(x.size());

std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
WstringConvert converter;
try
{
for(const auto& string: x)
Expand All @@ -189,7 +190,7 @@ ARRAY<TEXT> Parameter<ARRAY<WTEXT>>::convert(const ARRAY<WTEXT>& x)

WTEXT Parameter<ARRAY<WTEXT>>::convert(const TEXT& x)
{
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
WstringConvert converter;
try
{
return converter.from_bytes(x);
Expand Down
5 changes: 3 additions & 2 deletions src/results.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#include "fastcgi++/sql/results.hpp"
#include "fastcgi++/log.hpp"
#include "fastcgi++/wstringconvert.hpp"
#include "sqlTraits.hpp"

#include <locale>
Expand Down Expand Up @@ -125,7 +126,7 @@ template<> void Results_base::field<WTEXT>(
int column,
WTEXT& value) const
{
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
WstringConvert converter;
const char* const start = PQgetvalue(
reinterpret_cast<const PGresult*>(m_res),
row,
Expand Down Expand Up @@ -407,7 +408,7 @@ void Results_base::field<WTEXT>(

value.clear();
value.reserve(size);
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
WstringConvert converter;
try
{
for(int i=0; i<size; ++i)
Expand Down
85 changes: 85 additions & 0 deletions src/wstringconvert.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*!
* @file wstringconvert.cpp
* @brief Replacement for std::wstring_convert
* @date June 6, 2026
*/

#include "fastcgi++/wstringconvert.hpp"

#include <cerrno>
#include <iconv.h>
#include <limits>
#include <optional>
#include <stdexcept>
#include <vector>

//! Topmost namespace for the fastcgi++ library
namespace Fastcgipp
{
template <typename ToChar, typename FromChar>
std::optional<std::basic_string<ToChar>>
iconv_convert(const std::basic_string<FromChar> & input,
const char * to_encoding,
const char * from_encoding)
{
if (input.empty())
return std::basic_string<ToChar>();
//
if (input.size() >= std::numeric_limits<size_t>::max() / (4 * sizeof(ToChar))) // out_bytes_max below would overflow
return std::nullopt;
//
size_t in_bytes_left = input.size() * sizeof(FromChar);
size_t out_bytes_max = input.size() * (4 * sizeof(ToChar)); // worst case
size_t out_bytes_left = out_bytes_max;
//
std::vector<char> output(out_bytes_max); // allocate outpout buffer
//
iconv_t cd = iconv_open(to_encoding, from_encoding);
if (cd == (iconv_t)-1)
return std::nullopt;
//
char * input_reader = const_cast<char*>(reinterpret_cast<const char*>(input.data()));
char * output_writer = output.data();
//
size_t res = iconv(cd, &input_reader, &in_bytes_left, &output_writer, &out_bytes_left);
iconv_close(cd);
//
if (res == (size_t)-1)
return std::nullopt;
//
size_t bytes_written = out_bytes_max - out_bytes_left;
size_t chars_written = bytes_written / sizeof(ToChar);
//
return std::basic_string<ToChar>(reinterpret_cast<const ToChar*>(output.data()), chars_written); // resized to actual output size
}

std::wstring WstringConvert::from_bytes(const char *first, const char *last)
{
if (last < first)
throw std::range_error("from_bytes invalid range");
//
auto ws = iconv_convert<wchar_t, char>(std::string(first, last), "WCHAR_T", "UTF-8"); // std::string uses [first, last) too
if (ws.has_value())
return ws.value();
else
throw std::range_error("from_bytes conversion failed");
}

std::wstring WstringConvert::from_bytes(const std::string &str)
{
auto ws = iconv_convert<wchar_t, char>(str, "WCHAR_T", "UTF-8");
if (ws.has_value())
return ws.value();
else
throw std::range_error("from_bytes conversion failed");
}

std::string WstringConvert::to_bytes(const std::wstring &wstr)
{
auto s = iconv_convert<char, wchar_t>(wstr, "UTF-8", "WCHAR_T");
if (s.has_value())
return s.value();
else
throw std::range_error("to_bytes conversion failed");
}
}
48 changes: 43 additions & 5 deletions tests/http.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "fastcgi++/log.hpp"
#include "fastcgi++/http.hpp"
#include "fastcgi++/wstringconvert.hpp"

#include <list>
#include <array>
Expand Down Expand Up @@ -407,6 +408,43 @@ int main()
FAIL_LOG("Fastcgipp::Http::percentEscapedToRealBytes()")
}

// Test Fastcgipp::WstringConvert
{
static const std::vector<std::wstring> texts =
{
L"ASCII test 123",
L"проверка тест - 中文 - Français - Jürgen - σκύλος",
L"",
L"a",
L"Σὲ γνωρίζω ἀπὸ τὴν κόψη"
"τοῦ σπαθιοῦ τὴν τρομερή,"
"σὲ γνωρίζω ἀπὸ τὴν ὄψη"
"ποὺ μὲ βία μετράει τὴ γῆ."
"᾿Απ᾿ τὰ κόκκαλα βγαλμένη"
"τῶν ῾Ελλήνων τὰ ἱερά"
"καὶ σὰν πρῶτα ἀνδρειωμένη"
"χαῖρε, ὦ χαῖρε, ᾿Ελευθεριά!"
};
//
for (const std::wstring &src : texts)
{
Fastcgipp::WstringConvert converter;
auto s = converter.to_bytes(src);
auto ws = converter.from_bytes(s.c_str(), s.c_str() + s.length());
if (src != ws)
FAIL_LOG("Fastcgipp::WstringConvert from_bytes(ptr,ptr)");
}
//
for (const std::wstring &src : texts)
{
Fastcgipp::WstringConvert converter;
auto s = converter.to_bytes(src);
auto ws = converter.from_bytes(s);
if (src != ws)
FAIL_LOG("Fastcgipp::WstringConvert from_bytes(s)");
}
}

// Testing Fastcgipp::Http::decodeUrlEncoded() #1
{
const char input[] =
Expand Down Expand Up @@ -528,7 +566,7 @@ int main()
Fastcgipp::Http::Environment<wchar_t> environment;
{
{
const unsigned char parms[] =
const unsigned char parms[] =
#include "multipartParam.hpp"
environment.fill(
reinterpret_cast<const char*>(parms),
Expand Down Expand Up @@ -583,7 +621,7 @@ int main()

// Checking posts
{
static const unsigned char data[] =
static const unsigned char data[] =
#include "multipartPost.hpp"
static const char* const dataStart =
reinterpret_cast<const char*>(data);
Expand All @@ -605,7 +643,7 @@ int main()

// Checking files
{
static const unsigned char gnu_png[] =
static const unsigned char gnu_png[] =
#include "gnu.png.hpp"
if(
environment.files.size() != 1 ||
Expand Down Expand Up @@ -633,7 +671,7 @@ int main()
Fastcgipp::Http::Environment<wchar_t> environment;
{
{
static const unsigned char data[] =
static const unsigned char data[] =
#include "urlencodedParam.hpp"
static const char* const dataStart =
reinterpret_cast<const char*>(data);
Expand Down Expand Up @@ -701,7 +739,7 @@ int main()

// Checking posts
{
const unsigned char data[] =
const unsigned char data[] =
#include "urlencodedPost.hpp"
static const char* const dataStart =
reinterpret_cast<const char*>(data);
Expand Down