Audit string comparisons (#4197)

* Audit string comparisons

* Mac fix

* !isxdigit()

* Rewrite condition + comment

* <3

* Clang-format

* 255

* Allow uppercase

* stoi() radix

* ShouldEncode()

Co-authored-by: Anton Kolesnyk <antkmsft@users.noreply.github.com>
This commit is contained in:
Anton Kolesnyk 2023-01-03 11:16:54 -08:00 committed by GitHub
parent 8891d30f4e
commit a70be339e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 254 additions and 512 deletions

View File

@ -3,7 +3,7 @@
#include "get_env.hpp"
#include <cctype>
#include <locale>
#if defined(WINAPI_PARTITION_DESKTOP) && !WINAPI_PARTITION_DESKTOP
@ -20,7 +20,7 @@ char* std::getenv(const char* name)
}
// We're still trying to match the name.
if (std::toupper(*buf) == std::toupper(name[i]))
if (std::toupper(*buf, std::locale::classic()) == std::toupper(name[i], std::locale::classic()))
{
// Matching so far, keep matching name and buffer, char by char, case insensitive.
++i;

View File

@ -1,6 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#pragma once
#include <azure/core/internal/strings.hpp>
#include <algorithm>
#include <cstring>
#include <memory>
@ -193,14 +196,9 @@ public:
}
hashedThumbprint.resize(hashLength);
auto hexThumbprint(BinaryToHexString(hashedThumbprint));
// HexString uses an "a"-"f" alphabet, but the CLR hex encoder uses an "A"-"F" alphabet,
// convert between them.
std::transform(
hexThumbprint.begin(), hexThumbprint.end(), hexThumbprint.begin(), [](char ch) {
return static_cast<char>(std::toupper(ch));
});
return hexThumbprint;
// so we need to uppercase them.
return Azure::Core::_internal::StringExtensions::ToUpper(BinaryToHexString(hashedThumbprint));
}
static std::unique_ptr<Cryptography::X509Certificate> Import(

View File

@ -186,8 +186,7 @@ namespace Azure { namespace Core { namespace Test {
// creating
std::string GetTestNameLowerCase(bool sanitize = true)
{
std::string testName(GetTestName(sanitize));
return Azure::Core::_internal::StringExtensions::ToLower(testName);
return Azure::Core::_internal::StringExtensions::ToLower(GetTestName(sanitize));
}
/**

View File

@ -147,7 +147,6 @@ set(
src/operation_status.cpp
src/private/environment_log_level_listener.hpp
src/private/package_version.hpp
src/strings.cpp
src/tracing/tracing.cpp
src/uuid.cpp
)

View File

@ -363,7 +363,7 @@ namespace Azure { namespace Core { namespace Http {
}
// Always toLower() headers
auto headerName
auto const headerName
= Azure::Core::_internal::StringExtensions::ToLower(std::string(start, end));
start = end + 1; // start value
while (start < last && (*start == ' ' || *start == '\t'))

View File

@ -9,6 +9,7 @@
#pragma once
#include <algorithm>
#include <cstring>
#include <string>
namespace Azure { namespace Core { namespace _internal {
@ -19,24 +20,61 @@ namespace Azure { namespace Core { namespace _internal {
*/
struct StringExtensions final
{
static constexpr char ToUpper(char c) noexcept
{
return (c < 'a' || c > 'z') ? c : c - ('a' - 'A');
}
static constexpr char ToLower(char c) noexcept
{
return (c < 'A' || c > 'Z') ? c : c + ('a' - 'A');
}
struct CaseInsensitiveComparator final
{
bool operator()(const std::string& lhs, const std::string& rhs) const
bool operator()(std::string const& lhs, std::string const& rhs) const
{
return std::lexicographical_compare(
lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), [](char c1, char c2) {
return ToLower(c1) < ToLower(c2);
lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), [](auto l, auto r) {
return ToLower(l) < ToLower(r);
});
}
};
static bool LocaleInvariantCaseInsensitiveEqual(
const std::string& lhs,
const std::string& rhs) noexcept;
static std::string const ToLower(std::string const& src) noexcept;
static unsigned char ToLower(unsigned char const src) noexcept;
static std::string const ToUpper(std::string const& src) noexcept;
static unsigned char ToUpper(unsigned char const src) noexcept;
std::string const& lhs,
std::string const& rhs) noexcept
{
auto const rhsSize = rhs.size();
if (lhs.size() != rhsSize)
{
return false;
}
auto const lhsData = lhs.c_str();
auto const rhsData = rhs.c_str();
for (size_t i = 0; i < rhsSize; ++i)
{
if (ToLower(lhsData[i]) != ToLower(rhsData[i]))
{
return false;
}
}
return true;
}
static std::string ToLower(std::string src)
{
std::transform(src.begin(), src.end(), src.begin(), [](auto c) { return ToLower(c); });
return src;
}
static std::string ToUpper(std::string src)
{
std::transform(src.begin(), src.end(), src.begin(), [](auto c) { return ToUpper(c); });
return src;
}
};
}}} // namespace Azure::Core::_internal

View File

@ -14,7 +14,6 @@
#include <map>
#include <memory>
#include <string>
#include <unordered_set>
namespace Azure { namespace Core {
namespace _detail {
@ -53,10 +52,6 @@ namespace Azure { namespace Core {
// query parameters are all encoded
std::map<std::string, std::string> m_encodedQueryParameters;
// List of default non-URL-encode chars. While URL encoding a string, do not escape any chars in
// this set.
const static std::unordered_set<unsigned char> defaultNonUrlEncodeChars;
std::string GetUrlWithoutQuery(bool relative) const;
/**

View File

@ -5,10 +5,10 @@
#include "azure/core/platform.hpp"
#include <algorithm>
#include <cctype>
#include <ctime>
#include <iomanip>
#include <limits>
#include <locale>
#include <sstream>
#include <stdexcept>
@ -319,10 +319,10 @@ T ParseNumber(
auto i = 0;
for (; i < MaxChars; ++i)
{
int const ch = str[*cursor + i];
if (std::isdigit(ch))
auto const ch = str[*cursor + i];
if (std::isdigit(ch, std::locale::classic()))
{
value = (value * 10) + (static_cast<decltype(value)>(ch) - '0');
value = (value * 10) + (static_cast<decltype(value)>(static_cast<unsigned char>(ch)) - '0');
continue;
}
@ -654,10 +654,10 @@ DateTime DateTime::Parse(std::string const& dateTime, DateFormat format)
minDateTimeLength += charsRead;
if (charsRead == 7 && (DateTimeLength - cursor) > 0)
{
int const ch = dateTime[cursor];
if (std::isdigit(ch))
auto const ch = dateTime[cursor];
if (std::isdigit(ch, std::locale::classic()))
{
auto const num = static_cast<int>(ch - '0');
auto const num = static_cast<int>(static_cast<unsigned char>(ch) - '0');
if (num > 4)
{
if (fracSec < 9999999)
@ -677,7 +677,7 @@ DateTime DateTime::Parse(std::string const& dateTime, DateFormat format)
for (auto i = DateTimeLength - cursor; i > 0; --i)
{
if (std::isdigit(static_cast<int>(dateTime[cursor])))
if (std::isdigit(dateTime[cursor], std::locale::classic()))
{
++minDateTimeLength;
++cursor;

View File

@ -25,33 +25,42 @@ Logger::Level const* GetEnvironmentLogLevel()
{
EnvironmentLogLevelListener::SetInitialized(true);
auto const envVar = Environment::GetVariable("AZURE_LOG_LEVEL");
if (!envVar.empty())
auto const logLevelStr = Environment::GetVariable("AZURE_LOG_LEVEL");
if (!logLevelStr.empty())
{
auto const logLevelStr = Azure::Core::_internal::StringExtensions::ToLower(envVar);
// See https://github.com/Azure/azure-sdk-for-java/wiki/Logging-with-Azure-SDK
// And
// https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/core/azure-core/src/main/java/com/azure/core/util/logging/LogLevel.java
using Azure::Core::_internal::StringExtensions;
static Logger::Level envLogLevel = {};
envLogLevelPtr = &envLogLevel;
if (logLevelStr == "error" || logLevelStr == "err" || logLevelStr == "4")
if (logLevelStr == "4"
|| StringExtensions::LocaleInvariantCaseInsensitiveEqual(logLevelStr, "error")
|| StringExtensions::LocaleInvariantCaseInsensitiveEqual(logLevelStr, "err"))
{
envLogLevel = Logger::Level::Error;
}
else if (logLevelStr == "warning" || logLevelStr == "warn" || logLevelStr == "3")
else if (
logLevelStr == "3"
|| StringExtensions::LocaleInvariantCaseInsensitiveEqual(logLevelStr, "warning")
|| StringExtensions::LocaleInvariantCaseInsensitiveEqual(logLevelStr, "warn"))
{
envLogLevel = Logger::Level::Warning;
}
else if (
logLevelStr == "informational" || logLevelStr == "info" || logLevelStr == "information"
|| logLevelStr == "2")
logLevelStr == "2"
|| StringExtensions::LocaleInvariantCaseInsensitiveEqual(logLevelStr, "informational")
|| StringExtensions::LocaleInvariantCaseInsensitiveEqual(logLevelStr, "information")
|| StringExtensions::LocaleInvariantCaseInsensitiveEqual(logLevelStr, "info"))
{
envLogLevel = Logger::Level::Informational;
}
else if (logLevelStr == "verbose" || logLevelStr == "debug" || logLevelStr == "1")
else if (
logLevelStr == "1"
|| StringExtensions::LocaleInvariantCaseInsensitiveEqual(logLevelStr, "verbose")
|| StringExtensions::LocaleInvariantCaseInsensitiveEqual(logLevelStr, "debug"))
{
envLogLevel = Logger::Level::Verbose;
}

View File

@ -70,6 +70,7 @@
#include <algorithm>
#include <chrono>
#include <iomanip>
#include <locale>
#include <sstream>
#include <string>
#include <thread>
@ -1315,9 +1316,10 @@ void DumpCurlInfoToLog(std::string const& text, uint8_t* ptr, size_t size)
{
// Log the contents of the buffer as text, if it's printable, print the character, otherwise
// print '.'
if (isprint(ptr[i + c]))
auto const ch = static_cast<char>(ptr[i + c]);
if (std::isprint(ch, std::locale::classic()))
{
ss << ptr[i + c];
ss << ch;
}
else
{

View File

@ -6,6 +6,9 @@
#include "azure/core/internal/io/null_body_stream.hpp"
#include "azure/core/url.hpp"
#include <algorithm>
#include <locale>
#include <unordered_set>
#include <utility>
using namespace Azure::Core;
@ -25,153 +28,30 @@ const HttpMethod HttpMethod::Put("PUT");
const HttpMethod HttpMethod::Delete("DELETE");
const HttpMethod HttpMethod::Patch("PATCH");
namespace {
bool IsInvalidHeaderNameChar(char c)
{
static std::unordered_set<char> const HeaderNameExtraValidChars
= {' ', '!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~'};
return !std::isalnum(c, std::locale::classic())
&& HeaderNameExtraValidChars.find(c) == HeaderNameExtraValidChars.end();
}
} // namespace
void Azure::Core::Http::_detail::RawResponseHelpers::InsertHeaderWithValidation(
Azure::Core::CaseInsensitiveMap& headers,
std::string const& headerName,
std::string const& headerValue)
{
// Static table for validating header names. It is created just once for the program and reused
// each time SetHeader is called
static const uint8_t validChars[256] = {
0, /* 0 - null */
0, /* 1 - start of heading */
0, /* 2 - start of text */
0, /* 3 - end of text */
0, /* 4 - end of transmission */
0, /* 5 - enquiry */
0, /* 6 - acknowledge */
0, /* 7 - bell */
0, /* 8 - backspace */
0, /* 9 - horizontal tab */
0, /* 10 - new line */
0, /* 11 - vertical tab */
0, /* 12 - new page */
0, /* 13 - carriage return */
0, /* 14 - shift out */
0, /* 15 - shift in */
0, /* 16 - data link escape */
0, /* 17 - device control 1 */
0, /* 18 - device control 2 */
0, /* 19 - device control 3 */
0, /* 20 - device control 4 */
0, /* 21 - negative acknowledge */
0, /* 22 - synchronous idle */
0, /* 23 - end of trans. block */
0, /* 24 - cancel */
0, /* 25 - end of medium */
0, /* 26 - substitute */
0, /* 27 - escape */
0, /* 28 - file separator */
0, /* 29 - group separator */
0, /* 30 - record separator */
0, /* 31 - unit separator */
' ', /* 32 - space */
'!', /* 33 - ! */
0, /* 34 - " */
'#', /* 35 - # */
'$', /* 36 - $ */
'%', /* 37 - % */
'&', /* 38 - & */
'\'', /* 39 - ' */
0, /* 40 - ( */
0, /* 41 - ) */
'*', /* 42 - * */
'+', /* 43 - + */
0, /* 44 - , */
'-', /* 45 - - */
'.', /* 46 - . */
0, /* 47 - / */
'0', /* 48 - 0 */
'1', /* 49 - 1 */
'2', /* 50 - 2 */
'3', /* 51 - 3 */
'4', /* 52 - 4 */
'5', /* 53 - 5 */
'6', /* 54 - 6 */
'7', /* 55 - 7 */
'8', /* 56 - 8 */
'9', /* 57 - 9 */
0, /* 58 - : */
0, /* 59 - ; */
0, /* 60 - < */
0, /* 61 - = */
0, /* 62 - > */
0, /* 63 - ? */
0, /* 64 - @ */
'a', /* 65 - A */
'b', /* 66 - B */
'c', /* 67 - C */
'd', /* 68 - D */
'e', /* 69 - E */
'f', /* 70 - F */
'g', /* 71 - G */
'h', /* 72 - H */
'i', /* 73 - I */
'j', /* 74 - J */
'k', /* 75 - K */
'l', /* 76 - L */
'm', /* 77 - M */
'n', /* 78 - N */
'o', /* 79 - O */
'p', /* 80 - P */
'q', /* 81 - Q */
'r', /* 82 - R */
's', /* 83 - S */
't', /* 84 - T */
'u', /* 85 - U */
'v', /* 86 - V */
'w', /* 87 - W */
'x', /* 88 - X */
'y', /* 89 - Y */
'z', /* 90 - Z */
0, /* 91 - [ */
0, /* 92 - comment */
0, /* 93 - ] */
'^', /* 94 - ^ */
'_', /* 95 - _ */
'`', /* 96 - ` */
'a', /* 97 - a */
'b', /* 98 - b */
'c', /* 99 - c */
'd', /* 100 - d */
'e', /* 101 - e */
'f', /* 102 - f */
'g', /* 103 - g */
'h', /* 104 - h */
'i', /* 105 - i */
'j', /* 106 - j */
'k', /* 107 - k */
'l', /* 108 - l */
'm', /* 109 - m */
'n', /* 110 - n */
'o', /* 111 - o */
'p', /* 112 - p */
'q', /* 113 - q */
'r', /* 114 - r */
's', /* 115 - s */
't', /* 116 - t */
'u', /* 117 - u */
'v', /* 118 - v */
'w', /* 119 - w */
'x', /* 120 - x */
'y', /* 121 - y */
'z', /* 122 - z */
0, /* 123 - { */
'|', /* 124 - | */
0, /* 125 - } */
'~', /* 126 - ~ */
0 /* 127 - DEL */
// ...128-255 is all zeros (not valid) characters}
};
// Check all chars in name are valid
for (size_t index = 0; index < headerName.size(); index++)
if (std::find_if(headerName.begin(), headerName.end(), IsInvalidHeaderNameChar)
!= headerName.end())
{
if (validChars[static_cast<int>(headerName[index])] == 0)
{
throw std::invalid_argument("Invalid header: " + headerName);
}
throw std::invalid_argument("Invalid header name: " + headerName);
}
// insert (override if duplicated)
headers[headerName] = headerValue;
}

View File

@ -3,12 +3,6 @@
#include "azure/core/http/raw_response.hpp"
#include "azure/core/http/http.hpp"
#include "azure/core/internal/strings.hpp"
#include <cctype>
#include <map>
#include <string>
#include <vector>
using namespace Azure::Core::IO;
using namespace Azure::Core::Http;

View File

@ -24,29 +24,24 @@ static Azure::Core::CaseInsensitiveMap MergeMaps(
Azure::Nullable<std::string> Request::GetHeader(std::string const& name)
{
std::vector<std::string> returnedHeaders;
auto headerNameLowerCase = Azure::Core::_internal::StringExtensions::ToLower(name);
for (auto const& hdrs : {m_retryHeaders, m_headers})
{
auto const header = hdrs.find(name);
if (header != hdrs.end())
{
return header->second;
}
}
auto retryHeader = this->m_retryHeaders.find(headerNameLowerCase);
if (retryHeader != this->m_retryHeaders.end())
{
return retryHeader->second;
}
auto header = this->m_headers.find(headerNameLowerCase);
if (header != this->m_headers.end())
{
return header->second;
}
return Azure::Nullable<std::string>{};
return {};
}
void Request::SetHeader(std::string const& name, std::string const& value)
{
auto headerNameLowerCase = Azure::Core::_internal::StringExtensions::ToLower(name);
return this->m_retryModeEnabled ? _detail::RawResponseHelpers::InsertHeaderWithValidation(
this->m_retryHeaders, headerNameLowerCase, value)
: _detail::RawResponseHelpers::InsertHeaderWithValidation(
this->m_headers, headerNameLowerCase, value);
return _detail::RawResponseHelpers::InsertHeaderWithValidation(
m_retryModeEnabled ? m_retryHeaders : m_headers,
Azure::Core::_internal::StringExtensions::ToLower(name),
value);
}
void Request::RemoveHeader(std::string const& name)

View File

@ -5,145 +5,165 @@
#include "azure/core/internal/strings.hpp"
#include <algorithm>
#include <cctype>
#include <iterator>
#include <limits>
#include <locale>
#include <stdexcept>
#include <vector>
#include <unordered_set>
using namespace Azure::Core;
Url::Url(const std::string& url)
Url::Url(std::string const& url)
{
std::string::const_iterator pos = url.begin();
auto urlIter = url.cbegin();
const std::string schemeEnd = "://";
auto schemeIter = url.find(schemeEnd);
if (schemeIter != std::string::npos)
{
std::transform(url.begin(), url.begin() + schemeIter, std::back_inserter(m_scheme), [](char c) {
return static_cast<char>(
Azure::Core::_internal::StringExtensions::ToLower(static_cast<unsigned char>(c)));
});
std::string const SchemeEnd = "://";
auto const schemePos = url.find(SchemeEnd);
pos = url.begin() + schemeIter + schemeEnd.length();
if (schemePos != std::string::npos)
{
m_scheme = Azure::Core::_internal::StringExtensions::ToLower(url.substr(0, schemePos));
urlIter += schemePos + SchemeEnd.length();
}
}
auto hostIter
= std::find_if(pos, url.end(), [](char c) { return c == '/' || c == '?' || c == ':'; });
m_host = std::string(pos, hostIter);
pos = hostIter;
if (pos != url.end() && *pos == ':')
{
auto port_ite = std::find_if_not(
pos + 1, url.end(), [](char c) { return std::isdigit(static_cast<unsigned char>(c)); });
auto portNumber = std::stoi(std::string(pos + 1, port_ite));
auto const hostIter
= std::find_if(urlIter, url.end(), [](auto c) { return c == '/' || c == '?' || c == ':'; });
m_host = std::string(urlIter, hostIter);
urlIter = hostIter;
}
if (urlIter == url.end())
{
return;
}
if (*urlIter == ':')
{
++urlIter;
auto const portIter = std::find_if_not(
urlIter, url.end(), [](auto c) { return std::isdigit(c, std::locale::classic()); });
auto const portNumber = std::stoi(std::string(urlIter, portIter));
// stoi will throw out_of_range when `int` is overflow, but we need to throw if uint16 is
// overflow
auto maxPortNumberSupported = std::numeric_limits<uint16_t>::max();
if (portNumber > maxPortNumberSupported)
{
throw std::out_of_range(
"The port number is out of range. The max supported number is "
+ std::to_string(maxPortNumberSupported) + ".");
constexpr auto const MaxPortNumberSupported = std::numeric_limits<uint16_t>::max();
if (portNumber > MaxPortNumberSupported)
{
throw std::out_of_range(
"The port number is out of range. The max supported number is "
+ std::to_string(MaxPortNumberSupported) + ".");
}
}
// cast is safe because the overflow was detected before
m_port = static_cast<uint16_t>(portNumber);
pos = port_ite;
urlIter = portIter;
}
if (pos != url.end() && (*pos != '/') && (*pos != '?'))
if (urlIter == url.end())
{
// only char `\` or `?` is valid after the port (or the end of the URL). Any other char is an
return;
}
if (*urlIter != '/' && *urlIter != '?')
{
// only char '/' or '?' is valid after the port (or the end of the URL). Any other char is an
// invalid input
throw std::invalid_argument("The port number contains invalid characters.");
}
if (pos != url.end() && (*pos == '/'))
if (*urlIter == '/')
{
auto pathIter = std::find(pos + 1, url.end(), '?');
m_encodedPath = std::string(pos + 1, pathIter);
pos = pathIter;
++urlIter;
auto const pathIter = std::find(urlIter, url.end(), '?');
m_encodedPath = std::string(urlIter, pathIter);
urlIter = pathIter;
}
if (pos != url.end() && *pos == '?')
if (urlIter != url.end() && *urlIter == '?')
{
auto queryIter = std::find(pos + 1, url.end(), '#');
AppendQueryParameters(std::string(pos + 1, queryIter));
pos = queryIter;
++urlIter;
AppendQueryParameters(std::string(urlIter, std::find(urlIter, url.end(), '#')));
}
}
std::string Url::Decode(const std::string& value)
std::string Url::Decode(std::string const& value)
{
const static std::vector<int> hexTable = []() {
std::vector<int> t(256, -1);
for (int i = 0; i < 10; ++i)
{
t[static_cast<size_t>('0') + i] = i;
}
for (int i = 10; i < 16; ++i)
{
t[static_cast<size_t>('A') + i - 10] = i;
t[static_cast<size_t>('a') + i - 10] = i;
}
return t;
}();
std::string decodedValue;
for (size_t i = 0; i < value.size();)
auto const valueSize = value.size();
for (size_t i = 0; i < valueSize; ++i)
{
char c = value[i];
if (c == '+')
auto const c = value[i];
switch (c)
{
decodedValue += ' ';
++i;
}
else if (c == '%')
{
if (i + 2 >= value.size() || hexTable[value[i + 1]] < 0 || hexTable[value[i + 2]] < 0)
{
throw std::runtime_error("failed when decoding URL component");
}
int v = (hexTable[value[i + 1]] << 4) + hexTable[value[i + 2]];
decodedValue += static_cast<std::string::value_type>(v);
i += 3;
}
else
{
decodedValue += value[i];
++i;
case '%':
if ((valueSize - i) < 3 // need at least 3 characters: "%XY"
|| !std::isxdigit(value[i + 1], std::locale::classic())
|| !std::isxdigit(value[i + 2], std::locale::classic()))
{
throw std::runtime_error("failed when decoding URL component");
}
decodedValue += static_cast<char>(std::stoi(value.substr(i + 1, 2), nullptr, 16));
i += 2;
break;
case '+':
decodedValue += ' ';
break;
default:
decodedValue += c;
break;
}
}
return decodedValue;
}
namespace {
bool ShouldEncode(char c)
{
static std::unordered_set<char> const ExtraNonEncodableChars = {'-', '.', '_', '~'};
return !std::isalnum(c, std::locale::classic())
&& ExtraNonEncodableChars.find(c) == ExtraNonEncodableChars.end();
}
} // namespace
std::string Url::Encode(const std::string& value, const std::string& doNotEncodeSymbols)
{
const char* hex = "0123456789ABCDEF";
std::unordered_set<unsigned char> noEncodingSymbolsSet(
auto const Hex = "0123456789ABCDEF";
std::unordered_set<char> const doNotEncodeSymbolsSet(
doNotEncodeSymbols.begin(), doNotEncodeSymbols.end());
std::string encoded;
for (char c : value)
for (auto const c : value)
{
unsigned char uc = c;
// encode if char is not in the default non-encoding set AND if it is NOT in chars to ignore
// from user input
if (defaultNonUrlEncodeChars.find(uc) == defaultNonUrlEncodeChars.end()
&& noEncodingSymbolsSet.find(uc) == noEncodingSymbolsSet.end())
if (ShouldEncode(c) && doNotEncodeSymbolsSet.find(c) == doNotEncodeSymbolsSet.end())
{
auto const u8 = static_cast<uint8_t>(c);
encoded += '%';
encoded += hex[(uc >> 4) & 0x0f];
encoded += hex[uc & 0x0f];
encoded += Hex[(u8 >> 4) & 0x0f];
encoded += Hex[u8 & 0x0f];
}
else
{
encoded += c;
}
}
return encoded;
}
@ -219,9 +239,3 @@ std::string Url::GetAbsoluteUrl() const
return GetUrlWithoutQuery(false)
+ _detail::FormatEncodedUrlQueryParameters(m_encodedQueryParameters);
}
const std::unordered_set<unsigned char> Url::defaultNonUrlEncodeChars
= {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.', '_', '~'};

View File

@ -13,7 +13,7 @@
#include "azure/core/http/policies/policy.hpp"
#include "azure/core/internal/tracing/service_tracing.hpp"
#include "azure/core/platform.hpp"
#include <cctype>
#include <locale>
#include <sstream>
#if defined(AZ_PLATFORM_WINDOWS)
@ -133,10 +133,10 @@ std::string GetOSVersion()
std::string TrimString(std::string s)
{
auto const isSpace = [](int c) { return !std::isspace(c); };
auto const isNotSpace = [](char c) { return !std::isspace(c, std::locale::classic()); };
s.erase(s.begin(), std::find_if(s.begin(), s.end(), isSpace));
s.erase(std::find_if(s.rbegin(), s.rend(), isSpace).base(), s.end());
s.erase(s.begin(), std::find_if(s.begin(), s.end(), isNotSpace));
s.erase(std::find_if(s.rbegin(), s.rend(), isNotSpace).base(), s.end());
return s;
}

View File

@ -1,133 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "azure/core/internal/strings.hpp"
#include <algorithm>
namespace {
// The locale invariant case table is generated with the following program
// followed by clang-format.
#if 0
// generate with
// cl /EHsc /W4 /WX .\casetable.cpp && .\casetable.exe
#include <iterator>
#include <numeric>
#include <stdio.h>
const int *PrintUpToLine(const int *first, const int *const last) {
if (first != last) {
printf("0x%02X,", *first);
for (ptrdiff_t idx = 1; ++first != last && idx < 8; ++idx) {
printf(" 0x%02X,", *first);
}
puts("");
}
return first;
}
int main() {
using namespace std;
int characters[256];
iota(begin(characters), end(characters), 0);
for (ptrdiff_t idx = 'a'; idx <= 'z'; ++idx) {
characters[idx] = idx - ('a' - 'A');
}
const int *first = begin(characters);
const auto last = first + size(characters);
printf("{");
while (first != last) {
first = PrintUpToLine(first, last);
}
printf("};");
}
#endif
const unsigned char LocaleInvariantLowercaseTable[256] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
};
const unsigned char LocaleInvariantUppercaseTable[256] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
};
} // unnamed namespace
namespace Azure { namespace Core { namespace _internal {
unsigned char StringExtensions::ToLower(unsigned char const symbol) noexcept
{
return LocaleInvariantLowercaseTable[symbol];
}
std::string const StringExtensions::ToLower(const std::string& src) noexcept
{
auto result = std::string(src);
std::transform(result.begin(), result.end(), result.begin(), [](char const ch) {
return StringExtensions::ToLower(ch);
});
return result;
}
unsigned char StringExtensions::ToUpper(unsigned char const symbol) noexcept
{
return LocaleInvariantUppercaseTable[symbol];
}
std::string const StringExtensions::ToUpper(const std::string& src) noexcept
{
auto result = std::string(src);
std::transform(result.begin(), result.end(), result.begin(), [](char const ch) {
return StringExtensions::ToUpper(ch);
});
return result;
}
bool StringExtensions::LocaleInvariantCaseInsensitiveEqual(
const std::string& lhs,
const std::string& rhs) noexcept
{
return std::equal(
lhs.begin(),
lhs.end(),
rhs.begin(),
rhs.end(),
[](const char left, const char right) noexcept {
return LocaleInvariantLowercaseTable[static_cast<unsigned char>(left)]
== LocaleInvariantLowercaseTable[static_cast<unsigned char>(right)];
});
}
}}} // namespace Azure::Core::_internal

View File

@ -5,7 +5,6 @@
#include "azure/core/http/policies/policy.hpp"
#include "azure/core/internal/tracing/service_tracing.hpp"
#include "azure/core/internal/tracing/tracing_impl.hpp"
#include <cctype>
#include <sstream>
namespace Azure { namespace Core { namespace Tracing { namespace _internal {

View File

@ -3,6 +3,8 @@
#include <azure/core/internal/strings.hpp>
#include <gtest/gtest.h>
#include <locale>
#include <string>
TEST(String, invariantCompare)
@ -23,18 +25,20 @@ TEST(String, invariantCompare)
TEST(String, toLowerC)
{
using Azure::Core::_internal::StringExtensions;
for (unsigned char ch = 0; ch < 255; ch += 1)
for (unsigned i = 0; i <= 255; ++i)
{
EXPECT_TRUE(StringExtensions::ToLower(ch) == std::tolower(ch));
auto const c = static_cast<char>(static_cast<unsigned char>(i));
EXPECT_TRUE(StringExtensions::ToLower(c) == std::tolower(c, std::locale::classic()));
}
}
TEST(String, toUpperC)
{
using Azure::Core::_internal::StringExtensions;
for (unsigned char ch = 0; ch < 255; ch += 1)
for (unsigned i = 0; i <= 255; ++i)
{
EXPECT_TRUE(StringExtensions::ToUpper(ch) == std::toupper(ch));
auto const c = static_cast<char>(static_cast<unsigned char>(i));
EXPECT_TRUE(StringExtensions::ToUpper(c) == std::toupper(c, std::locale::classic()));
}
}

View File

@ -9,8 +9,8 @@
#include <azure/core/internal/unique_handle.hpp>
#include <azure/core/platform.hpp>
#include <cctype>
#include <cstdio>
#include <locale>
#include <stdexcept>
#include <thread>
#include <type_traits>
@ -63,7 +63,7 @@ void ThrowIfNotSafeCmdLineInput(std::string const& input, std::string const& des
break;
default:
if (!std::isalnum(c))
if (!std::isalnum(c, std::locale::classic()))
{
throw AuthenticationException(
"AzureCliCredential: Unsafe command line input found in " + description + ": "

View File

@ -62,17 +62,16 @@ KeyRotationPolicy _detail::KeyRotationPolicySerializer::KeyRotationPolicyDeseria
action[_detail::TriggerActionsValue],
_detail::TBEActionsValue);
auto actionType = action[_detail::ActionActionsValue][TypeActionsValue].get<std::string>();
actionType = Azure::Core::_internal::StringExtensions::ToLower(actionType);
auto const actionType
= action[_detail::ActionActionsValue][TypeActionsValue].get<std::string>();
if (actionType
== Azure::Core::_internal::StringExtensions::ToLower(_detail::RotateActionsValue))
if (Azure::Core::_internal::StringExtensions::LocaleInvariantCaseInsensitiveEqual(
actionType, _detail::RotateActionsValue))
{
currentAction.Action = LifetimeActionType::Rotate;
}
else if (
actionType
== Azure::Core::_internal::StringExtensions::ToLower(_detail::NotifyActionsValue))
else if (Azure::Core::_internal::StringExtensions::LocaleInvariantCaseInsensitiveEqual(
actionType, _detail::NotifyActionsValue))
{
currentAction.Action = LifetimeActionType::Notify;
}

View File

@ -13,12 +13,12 @@
#include <type_traits>
#include <vector>
#include <azure/core/case_insensitive_containers.hpp>
#include <azure/core/context.hpp>
#include <azure/core/datetime.hpp>
#include <azure/core/etag.hpp>
#include <azure/core/http/http.hpp>
#include <azure/core/internal/http/pipeline.hpp>
#include <azure/core/internal/strings.hpp>
#include <azure/core/io/body_stream.hpp>
#include <azure/core/nullable.hpp>
#include <azure/core/response.hpp>
@ -409,11 +409,7 @@ namespace Azure { namespace Storage { namespace Blobs {
/**
* A set of name-value pairs associated with this blob or blob container.
*/
std::map<
std::string,
std::string,
Core::_internal::StringExtensions::CaseInsensitiveComparator>
Metadata;
Core::CaseInsensitiveMap Metadata;
};
/**
* @brief An Azure Storage container.
@ -648,11 +644,7 @@ namespace Azure { namespace Storage { namespace Blobs {
/**
* A set of name-value pair associated with this blob container.
*/
std::map<
std::string,
std::string,
Core::_internal::StringExtensions::CaseInsensitiveComparator>
Metadata;
Core::CaseInsensitiveMap Metadata;
/**
* The ETag contains a value that you can use to perform operations conditionally. If the
* request version is 2011-08-18 or newer, the ETag value will be in quotes.
@ -1289,11 +1281,7 @@ namespace Azure { namespace Storage { namespace Blobs {
/**
* A set of name-value pairs associated with this blob or blob container.
*/
std::map<
std::string,
std::string,
Core::_internal::StringExtensions::CaseInsensitiveComparator>
Metadata;
Core::CaseInsensitiveMap Metadata;
/**
* User-defined tags for this blob.
*/
@ -1480,11 +1468,7 @@ namespace Azure { namespace Storage { namespace Blobs {
/**
* A set of name-value pairs associated with this blob or blob container.
*/
std::map<
std::string,
std::string,
Core::_internal::StringExtensions::CaseInsensitiveComparator>
Metadata;
Core::CaseInsensitiveMap Metadata;
/**
* The current sequence number for a page blob.
*/
@ -1653,11 +1637,7 @@ namespace Azure { namespace Storage { namespace Blobs {
/**
* A set of name-value pair associated with this blob.
*/
std::map<
std::string,
std::string,
Core::_internal::StringExtensions::CaseInsensitiveComparator>
Metadata;
Core::CaseInsensitiveMap Metadata;
/**
* Optional. Only valid when Object Replication is enabled for the storage container and on
* the destination blob of the replication.

View File

@ -4,7 +4,6 @@
#include "azure/storage/common/internal/shared_key_policy.hpp"
#include <algorithm>
#include <cctype>
#include <azure/core/http/http.hpp>
#include <azure/core/internal/strings.hpp>
@ -32,7 +31,7 @@ namespace Azure { namespace Storage { namespace _internal {
"If-Unmodified-Since",
"Range"})
{
auto ite = headers.find(Azure::Core::_internal::StringExtensions::ToLower(headerName));
auto ite = headers.find(headerName);
if (ite != headers.end())
{
if (headerName == "Content-Length" && ite->second == "0")

View File

@ -4,7 +4,6 @@
#include "test_base.hpp"
#include <algorithm>
#include <cctype>
#include <chrono>
#include <cstdio>
#include <cstdlib>

View File

@ -13,12 +13,12 @@
#include <type_traits>
#include <vector>
#include <azure/core/case_insensitive_containers.hpp>
#include <azure/core/context.hpp>
#include <azure/core/datetime.hpp>
#include <azure/core/etag.hpp>
#include <azure/core/http/http.hpp>
#include <azure/core/internal/http/pipeline.hpp>
#include <azure/core/internal/strings.hpp>
#include <azure/core/io/body_stream.hpp>
#include <azure/core/nullable.hpp>
#include <azure/core/response.hpp>
@ -316,11 +316,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares {
/**
* A set of name-value pairs associated with the share or file.
*/
std::map<
std::string,
std::string,
Core::_internal::StringExtensions::CaseInsensitiveComparator>
Metadata;
Core::CaseInsensitiveMap Metadata;
/**
* Properties of a share.
*/
@ -409,11 +405,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares {
/**
* A set of name-value pairs that contain the user-defined metadata of the share.
*/
std::map<
std::string,
std::string,
Core::_internal::StringExtensions::CaseInsensitiveComparator>
Metadata;
Core::CaseInsensitiveMap Metadata;
/**
* The ETag contains a value that you can use to perform operations conditionally, in quotes.
*/
@ -886,11 +878,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares {
/**
* A set of name-value pairs that contain metadata for the directory.
*/
std::map<
std::string,
std::string,
Core::_internal::StringExtensions::CaseInsensitiveComparator>
Metadata;
Core::CaseInsensitiveMap Metadata;
/**
* The ETag contains a value that you can use to perform operations conditionally, in quotes.
*/
@ -1290,11 +1278,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares {
/**
* A set of name-value pairs associated with the share or file.
*/
std::map<
std::string,
std::string,
Core::_internal::StringExtensions::CaseInsensitiveComparator>
Metadata;
Core::CaseInsensitiveMap Metadata;
/**
* String identifier for this copy operation. Use with Get File Properties to check the
* status of this copy operation, or pass to Abort Copy File to abort a pending copy.
@ -1404,11 +1388,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares {
/**
* A set of name-value pairs associated with this file as user-defined metadata.
*/
std::map<
std::string,
std::string,
Core::_internal::StringExtensions::CaseInsensitiveComparator>
Metadata;
Core::CaseInsensitiveMap Metadata;
/**
* The size of the file in bytes. This header returns the value of the 'x-ms-content-length'
* header that is stored with the file.

View File

@ -12,10 +12,10 @@
#include <type_traits>
#include <vector>
#include <azure/core/case_insensitive_containers.hpp>
#include <azure/core/context.hpp>
#include <azure/core/datetime.hpp>
#include <azure/core/internal/http/pipeline.hpp>
#include <azure/core/internal/strings.hpp>
#include <azure/core/nullable.hpp>
#include <azure/core/response.hpp>
#include <azure/core/url.hpp>
@ -211,11 +211,7 @@ namespace Azure { namespace Storage { namespace Queues {
/**
* A set of name-value pairs associated with this queue.
*/
std::map<
std::string,
std::string,
Core::_internal::StringExtensions::CaseInsensitiveComparator>
Metadata;
Core::CaseInsensitiveMap Metadata;
};
/**
* @brief Include this parameter to specify that the queues' metadata be returned as part of the
@ -290,11 +286,7 @@ namespace Azure { namespace Storage { namespace Queues {
*/
struct QueueProperties final
{
std::map<
std::string,
std::string,
Core::_internal::StringExtensions::CaseInsensitiveComparator>
Metadata;
Core::CaseInsensitiveMap Metadata;
/**
* The approximate number of messages in the queue. This number is not lower than the actual
* number of messages in the queue, but could be higher.