diff --git a/include/boost/process/v2/detail/utf8.hpp b/include/boost/process/v2/detail/utf8.hpp index a31cdff4d..25414299e 100644 --- a/include/boost/process/v2/detail/utf8.hpp +++ b/include/boost/process/v2/detail/utf8.hpp @@ -44,9 +44,14 @@ std::basic_string conv_string( if (ec) detail::throw_error(ec, "size_as_utf8"); + std::basic_string res(allocator); res.resize(req_size); + if (req_size == 0) + return res; + + auto res_size = convert_to_utf8(data, size, &res.front(), req_size, ec); if (ec) detail::throw_error(ec, "convert_to_utf8"); @@ -70,6 +75,9 @@ std::basic_string conv_string( std::basic_string res(allocator); res.resize(req_size); + if (req_size == 0) + return res; + auto res_size = convert_to_wide(data, size, &res.front(), req_size, ec); if (ec) detail::throw_error(ec, "convert_to_wide"); diff --git a/include/boost/process/v2/windows/default_launcher.hpp b/include/boost/process/v2/windows/default_launcher.hpp index 52d863a25..be70fda41 100644 --- a/include/boost/process/v2/windows/default_launcher.hpp +++ b/include/boost/process/v2/windows/default_launcher.hpp @@ -352,7 +352,6 @@ struct default_launcher BOOST_PROCESS_V2_DECL static std::size_t escape_argv_string(wchar_t * itr, std::size_t max_size, basic_string_view ws); - @@ -395,6 +394,7 @@ struct default_launcher { return detail::conv_string(arg.data(), arg.size()); }); + return build_command_line_impl(pt, argw, L""); } @@ -406,10 +406,11 @@ struct default_launcher { std::wstring buffer; buffer.resize(escaped_argv_length(pt.native())); - escape_argv_string(&buffer.front(), buffer.size(), pt.native()); + + if (!buffer.empty()) + escape_argv_string(&buffer.front(), buffer.size(), pt.native()); return buffer; } - return build_command_line_impl(pt, args, *std::begin(args)); } @@ -438,4 +439,4 @@ BOOST_PROCESS_V2_END_NAMESPACE -#endif //BOOST_PROCESS_V2_WINDOWS_DEFAULT_LAUNCHER_HPP \ No newline at end of file +#endif //BOOST_PROCESS_V2_WINDOWS_DEFAULT_LAUNCHER_HPP diff --git a/src/windows/default_launcher.cpp b/src/windows/default_launcher.cpp index f52c86b9e..6df368fcd 100644 --- a/src/windows/default_launcher.cpp +++ b/src/windows/default_launcher.cpp @@ -24,54 +24,72 @@ namespace windows if (ws.empty()) return 2u; // just quotes - constexpr static auto space = L' '; constexpr static auto quote = L'"'; + const auto needs_quotes = ws.find_first_of(L" \t") != basic_string_view::npos; - const auto has_space = ws.find(space) != basic_string_view::npos; - const auto quoted = (ws.front() == quote) && (ws.back() == quote); - const auto needs_escape = has_space && !quoted ; - - if (!needs_escape) - return ws.size(); - else - return ws.size() + std::count(ws.begin(), ws.end(), quote) + 2u; + std::size_t needed_escapes = 0u; + for (auto itr = ws.begin(); itr != ws.end(); itr ++) + { + if (*itr == quote) + needed_escapes++; + else if (*itr == L'\\') + { + auto nx = std::next(itr); + if (nx != ws.end() && *nx == L'"') + needed_escapes ++; + else if (nx == ws.end()) + needed_escapes ++; + } + } + + return ws.size() + needed_escapes + (needs_quotes ? 2u : 0u); } std::size_t default_launcher::escape_argv_string(wchar_t * itr, std::size_t max_size, basic_string_view ws) { - const auto sz = escaped_argv_length(ws); + constexpr static auto quote = L'"'; + const auto needs_quotes = ws.find_first_of(L" \t") != basic_string_view::npos; + const auto needed_escapes = std::count(ws.begin(), ws.end(), quote); + + const auto sz = ws.size() + needed_escapes + (needs_quotes ? 2u : 0u); + if (sz > max_size) return 0u; + if (ws.empty()) { - itr[0] = L'"'; - itr[1] = L'"'; + itr[0] = quote; + itr[1] = quote; return 2u; } - const auto has_space = ws.find(L' ') != basic_string_view::npos; - const auto quoted = (ws.front() == L'"') && (ws.back() == L'"'); - const auto needs_escape = has_space && !quoted; - - if (!needs_escape) - return std::copy(ws.begin(), ws.end(), itr) - itr; - - if (sz < (2u + ws.size())) - return 0u; - const auto end = itr + sz; const auto begin = itr; - *(itr ++) = L'"'; - for (auto wc : ws) + if (needs_quotes) + *(itr++) = quote; + + for (auto it = ws.begin(); it != ws.end(); it ++) { - if (wc == L'"') + if (*it == quote) // makes it \" *(itr++) = L'\\'; - *(itr++) = wc; + + if (*it == L'\\') // \" needs to become \\\" + { + auto nx = std::next(it); + if (nx != ws.end() && *nx == L'"') + *(itr++) = L'\\'; + else if (nx == ws.end()) + *(itr++) = L'\\'; + + } + + *(itr++) = *it; } - *(itr ++) = L'"'; + if (needs_quotes) + *(itr++) = quote; return itr - begin; } @@ -108,13 +126,18 @@ namespace windows auto tl = get_thread_attribute_list(ec); if (ec) return; + + auto itr = std::unique(inherited_handles.begin(), inherited_handles.end()); + auto size = std::distance(inherited_handles.begin(), itr); + if (!::UpdateProcThreadAttribute( tl, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, - inherited_handles.data(), inherited_handles.size() * sizeof(HANDLE), nullptr, nullptr)) + inherited_handles.data(), size * sizeof(HANDLE), nullptr, nullptr)) BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec); } } BOOST_PROCESS_V2_END_NAMESPACE -#endif \ No newline at end of file +#endif + diff --git a/test/v2/process.cpp b/test/v2/process.cpp index 913422911..14d3ec801 100644 --- a/test/v2/process.cpp +++ b/test/v2/process.cpp @@ -256,8 +256,11 @@ BOOST_AUTO_TEST_CASE(print_args_spec_out) asio::writable_pipe wp{ctx}; asio::connect_pipe(rp, wp); - - bpv::process proc(ctx, pth, {"print-args", "&foo", "&", "|bar", "\"", "#foobar"}, bpv::process_stdio{/*in*/{},/*out*/wp, /*err*/ nullptr}); + fprintf(stderr, "print_args_spec_out\n"); + + bpv::process proc(ctx, pth, {"print-args", "&foo", "&", "", "\"\"", "\\\"", "|bar", "\"", "#foobar"}, + bpv::process_stdio{/*in*/{},/*out*/wp, /*err*/ nullptr}); + BOOST_CHECK(proc.running()); wp.close(); asio::streambuf st; @@ -288,6 +291,18 @@ BOOST_AUTO_TEST_CASE(print_args_spec_out) trim_end(line); BOOST_CHECK_EQUAL("&", line); + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("\"\"", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("\\\"", line); + BOOST_CHECK(std::getline(is, line)); trim_end(line); BOOST_CHECK_EQUAL("|bar", line); @@ -296,6 +311,7 @@ BOOST_AUTO_TEST_CASE(print_args_spec_out) trim_end(line); BOOST_CHECK_EQUAL("\"", line); + BOOST_CHECK(std::getline(is, line)); trim_end(line); BOOST_CHECK_EQUAL("#foobar", line); @@ -852,5 +868,70 @@ BOOST_AUTO_TEST_CASE(async_terminate_code) #endif +BOOST_AUTO_TEST_CASE(print_args_combined) +{ + using boost::unit_test::framework::master_test_suite; + const auto pth = master_test_suite().argv[1]; + + asio::io_context ctx; + + asio::readable_pipe rp{ctx}; + asio::writable_pipe wp{ctx}; + asio::connect_pipe(rp, wp); + + bpv::process proc(ctx, pth, {"print-args", "bar", "foo"}, bpv::process_stdio{/*in*/{}, /*.out= */ wp, /* .err=*/ wp}); + wp.close(); + + asio::streambuf st; + std::istream is{&st}; + bpv::error_code ec; + + auto sz = asio::read(rp, st, ec); + while (ec == asio::error::interrupted) + sz += asio::read(rp, st, ec); + + BOOST_CHECK_NE(sz , 0u); + BOOST_CHECK_MESSAGE((ec == asio::error::broken_pipe) || (ec == asio::error::eof), ec.message()); + + std::string line; + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL(pth, line ); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL(pth, line ); + + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("print-args", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("print-args", line); + + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("bar", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("bar", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("foo", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("foo", line); + + + proc.wait(); + BOOST_CHECK_EQUAL(proc.exit_code(), 0); +} + BOOST_AUTO_TEST_SUITE_END();