From 01210e8a420858a72f108dc0b838324ee5d633f5 Mon Sep 17 00:00:00 2001 From: Chunting Gu Date: Mon, 19 Apr 2021 14:01:05 +0800 Subject: [PATCH] simplify string utilities --- autotest/client_autotest/client_autotest.cc | 11 +-- examples/book_client/book_client.cc | 5 +- unittest/string_unittest.cc | 74 +++++++---------- webcc/common.cc | 18 +++-- webcc/globals.cc | 4 +- webcc/message.cc | 5 +- webcc/parser.cc | 19 ++--- webcc/request_builder.cc | 2 +- webcc/request_parser.cc | 27 ++++--- webcc/response_parser.cc | 8 +- webcc/router.cc | 7 +- webcc/string.cc | 45 ++++++++--- webcc/string.h | 88 +++------------------ webcc/url.cc | 14 +++- webcc/url.h | 4 +- webcc/utility.cc | 10 +-- 16 files changed, 150 insertions(+), 191 deletions(-) diff --git a/autotest/client_autotest/client_autotest.cc b/autotest/client_autotest/client_autotest.cc index 2df578d..923a066 100644 --- a/autotest/client_autotest/client_autotest.cc +++ b/autotest/client_autotest/client_autotest.cc @@ -1,6 +1,7 @@ #include #include +#include "boost/algorithm/string.hpp" #include "boost/filesystem/fstream.hpp" #include "boost/filesystem/operations.hpp" @@ -299,7 +300,7 @@ TEST(ClientTest, Post) { static bfs::path GenerateTempFile(const std::string& data) { try { - bfs::path path = bfs::temp_directory_path() / webcc::random_string(10); + bfs::path path = bfs::temp_directory_path() / webcc::RandomString(10); bfs::ofstream ofs; ofs.open(path, std::ios::binary); @@ -419,24 +420,24 @@ TEST(ClientTest, KeepAlive) { // Keep-Alive by default. auto r = session.Send(webcc::RequestBuilder{}.Get(url)()); - EXPECT_TRUE(webcc::iequals(r->GetHeader("Connection"), "Keep-alive")); + EXPECT_TRUE(boost::iequals(r->GetHeader("Connection"), "Keep-alive")); // Close by setting Connection header directly. r = session.Send(webcc::RequestBuilder{}.Get(url). Header("Connection", "Close") ()); - EXPECT_TRUE(webcc::iequals(r->GetHeader("Connection"), "Close")); + EXPECT_TRUE(boost::iequals(r->GetHeader("Connection"), "Close")); // Close by using request builder. r = session.Send(webcc::RequestBuilder{}.Get(url).KeepAlive(false)()); - EXPECT_TRUE(webcc::iequals(r->GetHeader("Connection"), "Close")); + EXPECT_TRUE(boost::iequals(r->GetHeader("Connection"), "Close")); // Keep-Alive explicitly by using request builder. r = session.Send(webcc::RequestBuilder{}.Get(url).KeepAlive(true)()); - EXPECT_TRUE(webcc::iequals(r->GetHeader("Connection"), "Keep-alive")); + EXPECT_TRUE(boost::iequals(r->GetHeader("Connection"), "Keep-alive")); } catch (const webcc::Error& error) { std::cerr << error << std::endl; diff --git a/examples/book_client/book_client.cc b/examples/book_client/book_client.cc index 069fcc0..c286235 100644 --- a/examples/book_client/book_client.cc +++ b/examples/book_client/book_client.cc @@ -2,12 +2,11 @@ #include +#include "boost/algorithm/string.hpp" #include "boost/filesystem/operations.hpp" #include "json/json.h" -#include "webcc/string.h" - #include "book_json.h" namespace bfs = boost::filesystem; @@ -180,7 +179,7 @@ bool BookClient::CheckPhoto(const bfs::path& photo) { } auto ext = photo.extension().string(); - if (!webcc::iequals(ext, ".jpg") && !webcc::iequals(ext, ".jpeg")) { + if (!boost::iequals(ext, ".jpg") && !boost::iequals(ext, ".jpeg")) { return false; } diff --git a/unittest/string_unittest.cc b/unittest/string_unittest.cc index 429958b..f54ca5a 100644 --- a/unittest/string_unittest.cc +++ b/unittest/string_unittest.cc @@ -2,31 +2,13 @@ #include -#include "webcc/string.h" - -TEST(StringTest, iequals) { - EXPECT_TRUE(webcc::iequals("", "")); - EXPECT_TRUE(webcc::iequals("abc", "abc")); - EXPECT_TRUE(webcc::iequals("ABC", "abc")); - EXPECT_TRUE(webcc::iequals("123", "123")); - - EXPECT_FALSE(webcc::iequals("abc", "def")); - EXPECT_FALSE(webcc::iequals("abc", "abcdef")); -} +#include "boost/algorithm/string.hpp" -TEST(StringTest, starts_with) { - EXPECT_TRUE(webcc::starts_with("123", "1")); - EXPECT_TRUE(webcc::starts_with("123", "12")); - EXPECT_TRUE(webcc::starts_with("123", "123")); - EXPECT_TRUE(webcc::starts_with(" 123", " ")); - - EXPECT_FALSE(webcc::starts_with("123", "")); - EXPECT_FALSE(webcc::starts_with("123", "2")); -} +#include "webcc/string.h" -TEST(StringTest, split) { - std::vector parts; - webcc::split(parts, "GET /path/to HTTP/1.1"); +TEST(StringTest, Split) { + std::vector parts; + webcc::Split("GET /path/to HTTP/1.1", ' ', false, &parts); EXPECT_EQ(3, parts.size()); EXPECT_EQ("GET", parts[0]); @@ -34,15 +16,15 @@ TEST(StringTest, split) { EXPECT_EQ("HTTP/1.1", parts[2]); } -TEST(StringTest, split_token_compress_off) { +TEST(StringTest, Split_TokenCompressOff) { std::string str = "one,two,,three,,"; - std::vector parts; + std::vector parts; // Same as: // boost::split(parts, str, boost::is_any_of(","), // boost::token_compress_off); - webcc::split(parts, str, ',', false); + webcc::Split(str, ',', false, &parts); EXPECT_EQ(6, parts.size()); EXPECT_EQ("one", parts[0]); @@ -53,14 +35,14 @@ TEST(StringTest, split_token_compress_off) { EXPECT_EQ("", parts[5]); } -TEST(StringTest, split_token_compress_on) { +TEST(StringTest, Split_TokenCompressOn) { std::string str = "one,two,,three,,"; - std::vector parts; + std::vector parts; // Same as: // boost::split(parts, str, boost::is_any_of(","), // boost::token_compress_on); - webcc::split(parts, str, ',', true); + webcc::Split(str, ',', true, &parts); EXPECT_EQ(4, parts.size()); EXPECT_EQ("one", parts[0]); @@ -69,9 +51,9 @@ TEST(StringTest, split_token_compress_on) { EXPECT_EQ("", parts[3]); } -TEST(StringTest, split_new_line) { - std::vector lines; - webcc::split(lines, "line one\nline two\nline 3", '\n'); +TEST(StringTest, Split_NewLine) { + std::vector lines; + webcc::Split("line one\nline two\nline 3", '\n', false, &lines); EXPECT_EQ(3, lines.size()); EXPECT_EQ("line one", lines[0]); @@ -79,24 +61,24 @@ TEST(StringTest, split_new_line) { EXPECT_EQ("line 3", lines[2]); } -TEST(UtilityTest, split_kv) { +TEST(StringTest, SplitKV) { const std::string str = "key=value"; std::string key; std::string value; - bool ok = webcc::split_kv(key, value, str, '='); + bool ok = webcc::SplitKV(str, '=', true, &key, &value); EXPECT_EQ(true, ok); EXPECT_EQ("key", key); EXPECT_EQ("value", value); } -TEST(UtilityTest, split_kv_other_delim) { +TEST(StringTest, SplitKV_OtherDelim) { const std::string str = "key:value"; std::string key; std::string value; - bool ok = webcc::split_kv(key, value, str, ':'); + bool ok = webcc::SplitKV(str, ':', true, &key, &value); EXPECT_TRUE(ok); @@ -104,12 +86,12 @@ TEST(UtilityTest, split_kv_other_delim) { EXPECT_EQ("value", value); } -TEST(UtilityTest, split_kv_spaces) { +TEST(StringTest, SplitKV_Spaces) { const std::string str = " key = value "; std::string key; std::string value; - bool ok = webcc::split_kv(key, value, str, '='); + bool ok = webcc::SplitKV(str, '=', true, &key, &value); EXPECT_TRUE(ok); @@ -117,12 +99,12 @@ TEST(UtilityTest, split_kv_spaces) { EXPECT_EQ("value", value); } -TEST(UtilityTest, split_kv_spaces_no_trim) { +TEST(StringTest, SplitKV_SpacesNoTrim) { const std::string str = " key = value "; std::string key; std::string value; - bool ok = webcc::split_kv(key, value, str, '=', false); + bool ok = webcc::SplitKV(str, '=', false, &key, &value); EXPECT_TRUE(ok); @@ -130,12 +112,12 @@ TEST(UtilityTest, split_kv_spaces_no_trim) { EXPECT_EQ(" value ", value); } -TEST(UtilityTest, split_kv_no_key) { +TEST(StringTest, SplitKV_NoKey) { const std::string str = "=value"; std::string key; std::string value; - bool ok = webcc::split_kv(key, value, str, '='); + bool ok = webcc::SplitKV(str, '=', true, &key, &value); EXPECT_TRUE(ok); @@ -143,12 +125,12 @@ TEST(UtilityTest, split_kv_no_key) { EXPECT_EQ("value", value); } -TEST(UtilityTest, split_kv_no_value) { +TEST(StringTest, SplitKV_NoValue) { const std::string str = "key="; std::string key; std::string value; - bool ok = webcc::split_kv(key, value, str, '='); + bool ok = webcc::SplitKV(str, '=', true, &key, &value); EXPECT_TRUE(ok); @@ -156,12 +138,12 @@ TEST(UtilityTest, split_kv_no_value) { EXPECT_EQ("", value); } -TEST(UtilityTest, split_kv_no_key_no_value) { +TEST(StringTest, SplitKV_NoKeyNoValue) { const std::string str = "="; std::string key; std::string value; - bool ok = webcc::split_kv(key, value, str, '='); + bool ok = webcc::SplitKV(str, '=', true, &key, &value); EXPECT_TRUE(ok); diff --git a/webcc/common.cc b/webcc/common.cc index 2860278..527d487 100644 --- a/webcc/common.cc +++ b/webcc/common.cc @@ -2,6 +2,8 @@ #include +#include "boost/algorithm/string.hpp" + #include "webcc/logger.h" #include "webcc/string.h" #include "webcc/utility.h" @@ -64,7 +66,7 @@ const std::string& Headers::Get(const std::string& key, bool* existed) const { std::vector
::iterator Headers::Find(const std::string& key) { auto it = headers_.begin(); for (; it != headers_.end(); ++it) { - if (iequals(it->first, key)) { + if (boost::iequals(it->first, key)) { break; } } @@ -76,7 +78,7 @@ std::vector
::iterator Headers::Find(const std::string& key) { static bool ParseValue(const std::string& str, const std::string& expected_key, std::string* value) { std::string key; - if (!split_kv(key, *value, str, '=')) { + if (!SplitKV(str, '=', true, &key, value)) { return false; } @@ -125,8 +127,8 @@ void ContentType::Init(const std::string& str) { other = str.substr(pos + 1); } - trim(media_type_); - trim(other); + boost::trim(media_type_); + boost::trim(other); if (media_type_ == "multipart/form-data") { multipart_ = true; @@ -145,12 +147,12 @@ void ContentType::Init(const std::string& str) { // ----------------------------------------------------------------------------- static inline void Unquote(std::string& str) { - trim(str, "\""); + boost::trim_if(str, boost::is_any_of("\"")); } bool ContentDisposition::Init(const std::string& str) { - std::vector parts; - split(parts, str, ';'); + std::vector parts; + Split(str, ';', false, &parts); if (parts.empty()) { return false; @@ -163,7 +165,7 @@ bool ContentDisposition::Init(const std::string& str) { std::string key; std::string value; for (std::size_t i = 1; i < parts.size(); ++i) { - if (!split_kv(key, value, parts[i], '=')) { + if (!SplitKV(parts[i].to_string(), '=', true, &key, &value)) { return false; } diff --git a/webcc/globals.cc b/webcc/globals.cc index a5ada98..b9f7731 100644 --- a/webcc/globals.cc +++ b/webcc/globals.cc @@ -2,7 +2,7 @@ #include -#include "webcc/string.h" +#include "boost/algorithm/string/case_conv.hpp" namespace webcc { @@ -21,7 +21,7 @@ const char DOUBLE_DASHES[2] = { '-', '-' }; namespace media_types { std::string FromExtension(const std::string& ext) { - std::string lext = tolower(ext); + std::string lext = boost::to_lower_copy(ext); if (lext == ".htm") { return "text/html"; } if (lext == ".html") { return "text/html"; } diff --git a/webcc/message.cc b/webcc/message.cc index d940ba0..031782f 100644 --- a/webcc/message.cc +++ b/webcc/message.cc @@ -2,8 +2,9 @@ #include +#include "boost/algorithm/string.hpp" + #include "webcc/logger.h" -#include "webcc/string.h" #include "webcc/utility.h" namespace webcc { @@ -52,7 +53,7 @@ bool Message::IsConnectionKeepAlive() const { return true; } - if (iequals(connection, "Keep-Alive")) { + if (boost::iequals(connection, "Keep-Alive")) { return true; } diff --git a/webcc/parser.cc b/webcc/parser.cc index dbbdab5..ec4b6b5 100644 --- a/webcc/parser.cc +++ b/webcc/parser.cc @@ -1,5 +1,6 @@ #include "webcc/parser.h" +#include "boost/algorithm/string.hpp" #include "boost/filesystem/operations.hpp" #include "webcc/logger.h" @@ -73,7 +74,7 @@ bool FileBodyHandler::OpenFile() { // Generate a random string as file name. // A replacement of boost::filesystem::unique_path(). - temp_path_ /= random_string(10); + temp_path_ /= RandomString(10); LOG_VERB("Generate a temp path for streaming: %s", temp_path_.string().c_str()); @@ -259,30 +260,30 @@ bool Parser::GetNextLine(std::size_t off, std::string* line, bool erase) { bool Parser::ParseHeaderLine(const std::string& line) { Header header; - if (!split_kv(header.first, header.second, line, ':')) { + if (!SplitKV(line, ':', true, &header.first, &header.second)) { LOG_ERRO("Invalid header: %s", line.c_str()); return false; } - if (iequals(header.first, headers::kContentLength)) { + if (boost::iequals(header.first, headers::kContentLength)) { content_length_parsed_ = true; std::size_t content_length = kInvalidLength; - if (!to_size_t(header.second, 10, &content_length)) { - LOG_ERRO("Invalid content length: %s.", header.second.c_str()); + if (!ToSizeT(header.second, 10, &content_length)) { + LOG_ERRO("Invalid content length: %s", header.second.c_str()); return false; } - LOG_INFO("Content length: %u.", content_length); + LOG_INFO("Content length: %u", content_length); content_length_ = content_length; - } else if (iequals(header.first, headers::kContentType)) { + } else if (boost::iequals(header.first, headers::kContentType)) { content_type_.Parse(header.second); if (!content_type_.Valid()) { LOG_ERRO("Invalid content-type header: %s", header.second.c_str()); return false; } - } else if (iequals(header.first, headers::kTransferEncoding)) { + } else if (boost::iequals(header.first, headers::kTransferEncoding)) { if (header.second == "chunked") { // The content is chunked. chunked_ = true; @@ -401,7 +402,7 @@ bool Parser::ParseChunkSize() { hex_str = line; } - if (!to_size_t(hex_str, 16, &chunk_size_)) { + if (!ToSizeT(hex_str, 16, &chunk_size_)) { LOG_ERRO("Invalid chunk-size: %s", hex_str.c_str()); return false; } diff --git a/webcc/request_builder.cc b/webcc/request_builder.cc index 2d4096f..b091ef3 100644 --- a/webcc/request_builder.cc +++ b/webcc/request_builder.cc @@ -40,7 +40,7 @@ RequestPtr RequestBuilder::operator()() { } else if (!form_parts_.empty()) { // Another choice to generate the boundary is like what Apache does. // See: https://stackoverflow.com/a/5686863 - auto boundary = random_string(30); + auto boundary = RandomString(30); request->SetContentType("multipart/form-data; boundary=" + boundary); diff --git a/webcc/request_parser.cc b/webcc/request_parser.cc index 9bf30d4..d72f081 100644 --- a/webcc/request_parser.cc +++ b/webcc/request_parser.cc @@ -2,6 +2,8 @@ #include +#include "boost/algorithm/string.hpp" + #include "webcc/logger.h" #include "webcc/request.h" #include "webcc/string.h" @@ -34,15 +36,15 @@ bool RequestParser::OnHeadersEnd() { } bool RequestParser::ParseStartLine(const std::string& line) { - std::vector strs; - split(strs, line, ' ', true); + std::vector parts; + Split(line, ' ', true, &parts); - if (strs.size() != 3) { + if (parts.size() != 3) { return false; } - request_->set_method(std::move(strs[0])); - request_->set_url(Url(strs[1])); + request_->set_method(std::move(parts[0].to_string())); + request_->set_url(Url{ parts[1].to_string() }); // HTTP version is ignored. @@ -95,7 +97,7 @@ bool RequestParser::ParseMultipartContent(const char* data, if (ParsePartHeaders(&need_more_data)) { // Go to next step. step_ = Step::kHeadersParsed; - LOG_INFO("Part headers just ended."); + LOG_INFO("Part headers just ended"); continue; } else { if (need_more_data) { @@ -129,7 +131,7 @@ bool RequestParser::ParseMultipartContent(const char* data, // +2 for including the CRLF after the boundary. pending_data_.erase(0, off + count + 2); } else { - LOG_ERRO("Invalid part data. off=%u", off); + LOG_ERRO("Invalid part data, off=%u", off); return false; } @@ -152,7 +154,7 @@ bool RequestParser::ParseMultipartContent(const char* data, } if (step_ == Step::kEnded) { - LOG_INFO("Multipart data has ended."); + LOG_INFO("Multipart data has ended"); // Create a body and set to the request. @@ -186,16 +188,16 @@ bool RequestParser::ParsePartHeaders(bool* need_more_data) { } Header header; - if (!split_kv(header.first, header.second, line, ':')) { + if (!SplitKV(line, ':', true, &header.first, &header.second)) { LOG_ERRO("Invalid part header line: %s", line.c_str()); return false; } - LOG_INFO("Part header (%s: %s).", header.first.c_str(), + LOG_INFO("Part header (%s: %s)", header.first.c_str(), header.second.c_str()); // Parse Content-Disposition. - if (iequals(header.first, headers::kContentDisposition)) { + if (boost::iequals(header.first, headers::kContentDisposition)) { ContentDisposition content_disposition(header.second); if (!content_disposition.valid()) { LOG_ERRO("Invalid content-disposition header: %s", @@ -217,8 +219,7 @@ bool RequestParser::ParsePartHeaders(bool* need_more_data) { return true; } -bool RequestParser::GetNextBoundaryLine(std::size_t* b_off, - std::size_t* b_len, +bool RequestParser::GetNextBoundaryLine(std::size_t* b_off, std::size_t* b_len, bool* ended) { std::size_t off = 0; diff --git a/webcc/response_parser.cc b/webcc/response_parser.cc index 0f9bbf9..a317d3a 100644 --- a/webcc/response_parser.cc +++ b/webcc/response_parser.cc @@ -1,8 +1,9 @@ #include "webcc/response_parser.h" +#include "boost/algorithm/string.hpp" + #include "webcc/logger.h" #include "webcc/response.h" -#include "webcc/string.h" namespace webcc { @@ -25,7 +26,8 @@ void SplitStartLine(const std::string& line, std::vector* parts) { parts->push_back(line.substr(off, pos - off)); off = pos + 1; - for (; off < line.size() && line[off] == SPACE; ++off) {} + for (; off < line.size() && line[off] == SPACE; ++off) { + } } if (off < line.size()) { @@ -53,7 +55,7 @@ bool ResponseParser::ParseStartLine(const std::string& line) { return false; } - if (!starts_with(parts[0], "HTTP/1.")) { + if (!boost::starts_with(parts[0], "HTTP/1.")) { LOG_ERRO("Invalid HTTP version: %s", parts[0].c_str()); return false; } diff --git a/webcc/router.cc b/webcc/router.cc index f728716..d16caea 100644 --- a/webcc/router.cc +++ b/webcc/router.cc @@ -2,8 +2,9 @@ #include +#include "boost/algorithm/string.hpp" + #include "webcc/logger.h" -#include "webcc/string.h" namespace webcc { @@ -59,7 +60,7 @@ ViewPtr Router::FindView(const std::string& method, const std::string& url, return route.view; } } else { - if (iequals(route.url, url)) { + if (boost::iequals(route.url, url)) { return route.view; } } @@ -87,7 +88,7 @@ bool Router::MatchView(const std::string& method, const std::string& url, return true; } } else { - if (iequals(route.url, url)) { + if (boost::iequals(route.url, url)) { *stream = route.view->Stream(method); return true; } diff --git a/webcc/string.cc b/webcc/string.cc index cac769a..265fea9 100644 --- a/webcc/string.cc +++ b/webcc/string.cc @@ -2,10 +2,12 @@ #include +#include "boost/algorithm/string/trim.hpp" + namespace webcc { // Ref: https://stackoverflow.com/a/24586587 -std::string random_string(std::size_t length) { +std::string RandomString(std::size_t length) { static const char chrs[] = "0123456789" "abcdefghijklmnopqrstuvwxyz" @@ -13,7 +15,7 @@ std::string random_string(std::size_t length) { thread_local static std::mt19937 rg{ std::random_device{}() }; thread_local static std::uniform_int_distribution - pick(0, sizeof(chrs) - 2); + pick{ 0, sizeof(chrs) - 2 }; std::string s; s.reserve(length); @@ -25,7 +27,7 @@ std::string random_string(std::size_t length) { return s; } -bool to_size_t(const std::string& str, int base, std::size_t* size) { +bool ToSizeT(const std::string& str, int base, std::size_t* size) { try { *size = static_cast(std::stoul(str, 0, base)); } catch (const std::exception&) { @@ -34,19 +36,42 @@ bool to_size_t(const std::string& str, int base, std::size_t* size) { return true; } -bool split_kv(std::string& key, std::string& value, const std::string& str, - char delim, bool trim_spaces) { - std::size_t pos = str.find(delim); +void Split(boost::string_view input, char delim, bool compress_token, + std::vector* output) { + std::size_t i = 0; + std::size_t p = 0; + + i = input.find(delim); + + while (i != boost::string_view::npos) { + output->emplace_back(input.substr(p, i - p)); + p = i + 1; + + if (compress_token) { + while (input[p] == delim) { + ++p; + } + } + + i = input.find(delim, p); + } + + output->emplace_back(input.substr(p, i - p)); +} + +bool SplitKV(const std::string& input, char delim, bool trim_spaces, + std::string* key, std::string* value) { + std::size_t pos = input.find(delim); if (pos == std::string::npos) { return false; } - key = str.substr(0, pos); - value = str.substr(pos + 1); + *key = input.substr(0, pos); + *value = input.substr(pos + 1); if (trim_spaces) { - trim(key); - trim(value); + boost::trim(*key); + boost::trim(*value); } return true; diff --git a/webcc/string.h b/webcc/string.h index b78d176..880c628 100644 --- a/webcc/string.h +++ b/webcc/string.h @@ -1,92 +1,30 @@ #ifndef WEBCC_STRING_H_ #define WEBCC_STRING_H_ -#include -#include -#include #include +#include + +#include "boost/utility/string_view.hpp" namespace webcc { // Get a randomly generated string with the given length. -std::string random_string(std::size_t length); +std::string RandomString(std::size_t length); // Convert string to size_t. -bool to_size_t(const std::string& str, int base, std::size_t* size); - -inline std::string toupper(std::string s) { - std::transform(s.begin(), s.end(), s.begin(), - [](unsigned char c) { return std::toupper(c); }); - return s; -} - -inline std::string tolower(std::string s) { - std::transform(s.begin(), s.end(), s.begin(), - [](unsigned char c) { return std::tolower(c); }); - return s; -} - -inline bool iequals(const std::string& str1, const std::string& str2) { - if (str1.size() != str2.size()) { - return false; - } - - return std::equal(str1.begin(), str1.end(), str2.begin(), [](int c1, int c2) { - return std::toupper(c1) == std::toupper(c2); - }); -} - -inline bool starts_with(const std::string& str, const std::string& sub) { - if (sub.empty()) { - return false; - } - return str.find(sub) == 0; -} - -inline std::string& ltrim(std::string& str, const std::string& chars = "\t ") { - str.erase(0, str.find_first_not_of(chars)); - return str; -} - -inline std::string& rtrim(std::string& str, const std::string& chars = "\t ") { - str.erase(str.find_last_not_of(chars) + 1); - return str; -} - -inline std::string& trim(std::string& str, const std::string& chars = "\t ") { - return ltrim(rtrim(str, chars), chars); -} - -// \param compress_token Same as boost::token_compress_on, especially useful -// when the delimeter is space. -template -void split(Container& cont, const std::string& str, char delim = ' ', - bool compress_token = false) { - std::size_t i = 0; - std::size_t p = 0; - - i = str.find(delim); - - while (i != std::string::npos) { - cont.push_back(str.substr(p, i - p)); - p = i + 1; - - if (compress_token) { - while (str[p] == delim) { - ++p; - } - } - - i = str.find(delim, p); - } +// Just a wrapper of std::stoul. +bool ToSizeT(const std::string& str, int base, std::size_t* size); - cont.push_back(str.substr(p, i - p)); -} +// Split string without copy. +// |compress_token| is the same as boost::token_compress_on for boost::split. +void Split(boost::string_view input, char delim, bool compress_token, + std::vector* output); // Split a key-value string. // E.g., split "Connection: Keep-Alive". -bool split_kv(std::string& key, std::string& value, const std::string& str, - char delim, bool trim_spaces = true); +// TODO: Use string_view (blocked by trim) +bool SplitKV(const std::string& input, char delim, bool trim_spaces, + std::string* key, std::string* value); } // namespace webcc diff --git a/webcc/url.cc b/webcc/url.cc index 3837bb1..86b5c8c 100644 --- a/webcc/url.cc +++ b/webcc/url.cc @@ -4,6 +4,8 @@ #include #include +#include "boost/algorithm/string/trim.hpp" + #include "webcc/string.h" #include "webcc/utility.h" @@ -238,7 +240,7 @@ void Url::AppendQuery(const std::string& key, const std::string& value, void Url::Parse(const std::string& str) { std::string tmp = str; - ltrim(tmp); + boost::trim_left(tmp); std::size_t p = std::string::npos; @@ -314,7 +316,7 @@ UrlQuery::UrlQuery(const std::string& encoded_str) { std::string key; std::string value; - if (split_kv(key, value, kv, '=', false)) { + if (SplitKV(kv, '=', false, &key, &value)) { parameters_.push_back({ DecodeUnsafe(key), DecodeUnsafe(value) }); } } @@ -349,7 +351,7 @@ void UrlQuery::Remove(const std::string& key) { } } -std::string UrlQuery::ToString() const { +std::string UrlQuery::ToString(bool encode) const { if (parameters_.empty()) { return ""; } @@ -363,7 +365,11 @@ std::string UrlQuery::ToString() const { str += parameters_[i].first + "=" + parameters_[i].second; } - return Url::EncodeQuery(str); + if (encode) { + return Url::EncodeQuery(str); + } else { + return str; + } } UrlQuery::ConstIterator UrlQuery::Find(const std::string& key) const { diff --git a/webcc/url.h b/webcc/url.h index 3b70fc2..4c907fe 100644 --- a/webcc/url.h +++ b/webcc/url.h @@ -109,9 +109,9 @@ public: void Remove(const std::string& key); - // Return encoded query string joined with '&'. + // Return query string, encoded or not, joined with '&'. // E.g., "item=12731&color=blue&size=large". - std::string ToString() const; + std::string ToString(bool encode = true) const; private: using ConstIterator = std::vector::const_iterator; diff --git a/webcc/utility.cc b/webcc/utility.cc index c2d0a78..6890548 100644 --- a/webcc/utility.cc +++ b/webcc/utility.cc @@ -32,7 +32,7 @@ std::string HttpDate() { // std::stringstream ss; // ss << std::put_time(gmt, "%a, %d %b %Y %H:%M:%S") << " GMT"; // return ss.str(); - // + // // char buf[26]; // std::strftime(buf, 26, "%a, %d %b %Y %H:%M:%S", gmt); @@ -80,16 +80,16 @@ bool ReadFile(const bfs::path& path, std::string* output) { void DumpByLine(const std::string& data, std::ostream& os, const std::string& prefix) { - std::vector lines; - split(lines, data, '\n'); + std::vector lines; + Split(data, '\n', false, &lines); std::size_t size = 0; - for (const std::string& line : lines) { + for (const auto& line : lines) { os << prefix; if (line.size() + size > kMaxDumpSize) { - os.write(line.c_str(), kMaxDumpSize - size); + os.write(line.data(), kMaxDumpSize - size); os << "..." << std::endl; break; } else {