@@ -309,7 +309,6 @@ using socket_t = int;
309309#include <array>
310310#include <atomic>
311311#include <cassert>
312- #include <cctype>
313312#include <chrono>
314313#include <climits>
315314#include <condition_variable>
@@ -540,6 +539,21 @@ make_unique(std::size_t n) {
540539 return std::unique_ptr<T>(new RT[n]);
541540}
542541
542+ // Locale-independent ASCII character classification. The <cctype>
543+ // counterparts (std::isalnum, std::isdigit, ...) consult the global C locale,
544+ // so e.g. std::isalnum(0xC5) can return true once an embedder calls
545+ // setlocale(). HTTP grammars are defined over ASCII, so raw bytes must be
546+ // classified without regard to the locale.
547+ inline bool is_ascii_digit(char c) { return '0' <= c && c <= '9'; }
548+
549+ inline bool is_ascii_alpha(char c) {
550+ return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
551+ }
552+
553+ inline bool is_ascii_alnum(char c) {
554+ return is_ascii_digit(c) || is_ascii_alpha(c);
555+ }
556+
543557namespace case_ignore {
544558
545559inline unsigned char to_lower(int c) {
@@ -661,7 +675,7 @@ inline from_chars_result<T> from_chars(const char *first, const char *last,
661675 for (; p != last; ++p) {
662676 char c = *p;
663677 int digit = -1;
664- if ('0' <= c && c <= '9' ) {
678+ if (is_ascii_digit(c) ) {
665679 digit = c - '0';
666680 } else if ('a' <= c && c <= 'z') {
667681 digit = c - 'a' + 10;
@@ -733,14 +747,14 @@ inline from_chars_result<double> from_chars(const char *first, const char *last,
733747 return false;
734748 };
735749
736- for (; p != last && '0' <= *p && *p <= '9' ; ++p) {
750+ for (; p != last && is_ascii_digit(*p) ; ++p) {
737751 seen_digit = true;
738752 accumulate(*p);
739753 }
740754
741755 if (p != last && *p == '.') {
742756 ++p;
743- for (; p != last && '0' <= *p && *p <= '9' ; ++p) {
757+ for (; p != last && is_ascii_digit(*p) ; ++p) {
744758 seen_digit = true;
745759 if (frac_digits < max_frac_digits && accumulate(*p)) { ++frac_digits; }
746760 }
@@ -803,8 +817,8 @@ inline bool parse_url(const std::string &url, UrlComponents &uc) {
803817 // IPv6 host must be [a-fA-F0-9:]+ only
804818 if (uc.host.empty()) { return false; }
805819 for (auto c : uc.host) {
806- if (!((c >= 'a' && c <= 'f' ) || (c >= 'A ' && c <= 'F ') ||
807- (c >= '0 ' && c <= '9 ') || c == ':')) {
820+ if (!(is_ascii_digit(c ) || (c >= 'a ' && c <= 'f ') ||
821+ (c >= 'A ' && c <= 'F ') || c == ':')) {
808822 return false;
809823 }
810824 }
@@ -2929,9 +2943,7 @@ template <size_t N> inline constexpr size_t str_len(const char (&)[N]) {
29292943}
29302944
29312945inline bool is_numeric(const std::string &str) {
2932- return !str.empty() &&
2933- std::all_of(str.cbegin(), str.cend(),
2934- [](unsigned char c) { return std::isdigit(c); });
2946+ return !str.empty() && std::all_of(str.cbegin(), str.cend(), is_ascii_digit);
29352947}
29362948
29372949inline size_t get_header_value_u64(const Headers &headers,
@@ -4480,7 +4492,7 @@ inline bool set_socket_opt_time(socket_t sock, int level, int optname,
44804492}
44814493
44824494inline bool is_hex(char c, int &v) {
4483- if (isdigit(static_cast<unsigned char>(c) )) {
4495+ if (is_ascii_digit(c )) {
44844496 v = c - '0';
44854497 return true;
44864498 } else if ('A' <= c && c <= 'F') {
@@ -7893,8 +7905,7 @@ inline bool parse_range_header(const std::string &s, Ranges &ranges) {
78937905inline bool parse_range_header(const std::string &s, Ranges &ranges) try {
78947906#endif
78957907 auto is_valid = [](const std::string &str) {
7896- return std::all_of(str.cbegin(), str.cend(),
7897- [](unsigned char c) { return std::isdigit(c); });
7908+ return std::all_of(str.cbegin(), str.cend(), is_ascii_digit);
78987909 };
78997910
79007911 if (s.size() > 7 && s.compare(0, 6, "bytes=") == 0) {
@@ -8342,7 +8353,7 @@ inline bool is_multipart_boundary_chars_valid(const std::string &boundary) {
83428353 auto valid = true;
83438354 for (size_t i = 0; i < boundary.size(); i++) {
83448355 auto c = boundary[i];
8345- if (!std::isalnum(static_cast<unsigned char>(c) ) && c != '-' && c != '_') {
8356+ if (!is_ascii_alnum(c ) && c != '-' && c != '_') {
83468357 valid = false;
83478358 break;
83488359 }
@@ -8856,10 +8867,9 @@ class ContentProviderAdapter {
88568867namespace fields {
88578868
88588869inline bool is_token_char(char c) {
8859- return std::isalnum(static_cast<unsigned char>(c)) || c == '!' || c == '#' ||
8860- c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' ||
8861- c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' ||
8862- c == '|' || c == '~';
8870+ return is_ascii_alnum(c) || c == '!' || c == '#' || c == '$' || c == '%' ||
8871+ c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' ||
8872+ c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
88638873}
88648874
88658875inline bool is_token(const std::string &s) {
@@ -9635,9 +9645,8 @@ inline std::string encode_uri_component(const std::string &value) {
96359645 escaped << std::hex;
96369646
96379647 for (auto c : value) {
9638- if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' ||
9639- c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' ||
9640- c == ')') {
9648+ if (detail::is_ascii_alnum(c) || c == '-' || c == '_' || c == '.' ||
9649+ c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || c == ')') {
96419650 escaped << c;
96429651 } else {
96439652 escaped << std::uppercase;
@@ -9656,10 +9665,10 @@ inline std::string encode_uri(const std::string &value) {
96569665 escaped << std::hex;
96579666
96589667 for (auto c : value) {
9659- if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' ||
9660- c == '. ' || c == '! ' || c == '~ ' || c == '*' || c == '\'' || c == '( ' ||
9661- c == ') ' || c == '; ' || c == '/ ' || c == '? ' || c == ': ' || c == '@ ' ||
9662- c == '&' || c == ' =' || c == '+' || c == '$' || c == ',' || c == '#') {
9668+ if (detail::is_ascii_alnum(c) || c == '-' || c == '_' || c == '. ' ||
9669+ c == '! ' || c == '~ ' || c == '* ' || c == '\'' || c == '(' || c == ') ' ||
9670+ c == '; ' || c == '/ ' || c == '? ' || c == ': ' || c == '@ ' || c == '& ' ||
9671+ c == '=' || c == '+' || c == '$' || c == ',' || c == '#') {
96639672 escaped << c;
96649673 } else {
96659674 escaped << std::uppercase;
@@ -9720,7 +9729,8 @@ inline std::string encode_path_component(const std::string &component) {
97209729 auto c = static_cast<unsigned char>(component[i]);
97219730
97229731 // Unreserved characters per RFC 3986: ALPHA / DIGIT / "-" / "." / "_" / "~"
9723- if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
9732+ if (detail::is_ascii_alnum(static_cast<char>(c)) || c == '-' || c == '.' ||
9733+ c == '_' || c == '~') {
97249734 result += static_cast<char>(c);
97259735 }
97269736 // Path-safe sub-delimiters: "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" /
@@ -9793,7 +9803,8 @@ inline std::string encode_query_component(const std::string &component,
97939803 auto c = static_cast<unsigned char>(component[i]);
97949804
97959805 // Unreserved characters per RFC 3986
9796- if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
9806+ if (detail::is_ascii_alnum(static_cast<char>(c)) || c == '-' || c == '.' ||
9807+ c == '_' || c == '~') {
97979808 result += static_cast<char>(c);
97989809 }
97999810 // Space handling
@@ -16607,7 +16618,7 @@ inline bool is_ipv4_address(const std::string &str) {
1660716618 for (char c : str) {
1660816619 if (c == '.') {
1660916620 dots++;
16610- } else if (!isdigit(static_cast<unsigned char>(c) )) {
16621+ } else if (!detail::is_ascii_digit(c )) {
1661116622 return false;
1661216623 }
1661316624 }
@@ -16624,7 +16635,7 @@ inline bool parse_ipv4(const std::string &str, unsigned char *out) {
1662416635 }
1662516636 int val = 0;
1662616637 int digits = 0;
16627- while (*p >= '0' && *p <= '9' ) {
16638+ while (detail::is_ascii_digit(*p) ) {
1662816639 val = val * 10 + (*p - '0');
1662916640 if (val > 255) { return false; }
1663016641 p++;
0 commit comments