|
| 1 | +/** |
| 2 | + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| 3 | + * SPDX-License-Identifier: Apache-2.0. |
| 4 | + */ |
| 5 | +#include <aws/common/host_utils.h> |
| 6 | +#include <aws/common/string.h> |
| 7 | +#include <inttypes.h> |
| 8 | + |
| 9 | +#ifdef _MSC_VER /* Disable sscanf warnings on windows. */ |
| 10 | +# pragma warning(disable : 4204) |
| 11 | +# pragma warning(disable : 4706) |
| 12 | +# pragma warning(disable : 4996) |
| 13 | +#endif |
| 14 | + |
| 15 | +/* 4 octets of 3 chars max + 3 separators + null terminator */ |
| 16 | +#define AWS_IPV4_STR_LEN 16 |
| 17 | +#define IP_CHAR_FMT "%03" SCNu16 |
| 18 | + |
| 19 | +static bool s_is_ipv6_char(uint8_t value) { |
| 20 | + return aws_isxdigit(value) || value == ':'; |
| 21 | +} |
| 22 | + |
| 23 | +static bool s_starts_with(struct aws_byte_cursor cur, uint8_t ch) { |
| 24 | + return cur.len > 0 && cur.ptr[0] == ch; |
| 25 | +} |
| 26 | + |
| 27 | +static bool s_ends_with(struct aws_byte_cursor cur, uint8_t ch) { |
| 28 | + return cur.len > 0 && cur.ptr[cur.len - 1] == ch; |
| 29 | +} |
| 30 | + |
| 31 | +bool aws_host_utils_is_ipv4(struct aws_byte_cursor host) { |
| 32 | + if (host.len > AWS_IPV4_STR_LEN - 1) { |
| 33 | + return false; |
| 34 | + } |
| 35 | + |
| 36 | + char copy[AWS_IPV4_STR_LEN] = {0}; |
| 37 | + memcpy(copy, host.ptr, host.len); |
| 38 | + |
| 39 | + uint16_t octet[4] = {0}; |
| 40 | + char remainder[2] = {0}; |
| 41 | + if (4 != sscanf( |
| 42 | + copy, |
| 43 | + IP_CHAR_FMT "." IP_CHAR_FMT "." IP_CHAR_FMT "." IP_CHAR_FMT "%1s", |
| 44 | + &octet[0], |
| 45 | + &octet[1], |
| 46 | + &octet[2], |
| 47 | + &octet[3], |
| 48 | + remainder)) { |
| 49 | + return false; |
| 50 | + } |
| 51 | + |
| 52 | + for (size_t i = 0; i < 4; ++i) { |
| 53 | + if (octet[i] > 255) { |
| 54 | + return false; |
| 55 | + } |
| 56 | + } |
| 57 | + |
| 58 | + return true; |
| 59 | +} |
| 60 | + |
| 61 | +/* actual encoding is %25, but % is omitted for simplicity, since split removes it */ |
| 62 | +static struct aws_byte_cursor s_percent_uri_enc = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("25"); |
| 63 | +/* |
| 64 | + * IPv6 format: |
| 65 | + * 8 groups of 4 hex chars separated by colons (:) |
| 66 | + * leading 0s in each group can be skipped |
| 67 | + * 2 or more consecutive zero groups can be replaced by double colon (::), |
| 68 | + * but only once. |
| 69 | + * ipv6 literal can be scoped by to zone by appending % followed by zone name |
| 70 | + * ( does not look like there is length reqs on zone name length. this |
| 71 | + * implementation enforces that its > 1 ) |
| 72 | + * ipv6 can be embedded in url, in which case it must be wrapped inside [] |
| 73 | + * and % be uri encoded as %25. |
| 74 | + * Implementation is fairly trivial and just iterates through the string |
| 75 | + * keeping track of the spec above. |
| 76 | + */ |
| 77 | +bool aws_host_utils_is_ipv6(struct aws_byte_cursor host, bool is_uri_encoded) { |
| 78 | + if (host.len == 0) { |
| 79 | + return false; |
| 80 | + } |
| 81 | + |
| 82 | + if (is_uri_encoded) { |
| 83 | + if (!s_starts_with(host, '[') || !s_ends_with(host, ']')) { |
| 84 | + return false; |
| 85 | + } |
| 86 | + aws_byte_cursor_advance(&host, 1); |
| 87 | + --host.len; |
| 88 | + } |
| 89 | + |
| 90 | + struct aws_byte_cursor substr = {0}; |
| 91 | + /* first split is required ipv6 part */ |
| 92 | + bool is_split = aws_byte_cursor_next_split(&host, '%', &substr); |
| 93 | + AWS_ASSERT(is_split); /* function is guaranteed to return at least one split */ |
| 94 | + |
| 95 | + if (!is_split || substr.len == 0 || s_ends_with(substr, ':') || |
| 96 | + !aws_byte_cursor_satisfies_pred(&substr, s_is_ipv6_char)) { |
| 97 | + return false; |
| 98 | + } |
| 99 | + |
| 100 | + uint8_t group_count = 0; |
| 101 | + bool has_double_colon = false; |
| 102 | + struct aws_byte_cursor group = {0}; |
| 103 | + while (aws_byte_cursor_next_split(&substr, ':', &group)) { |
| 104 | + ++group_count; |
| 105 | + |
| 106 | + if (group_count > 8 || /* too many groups */ |
| 107 | + group.len > 4 || /* too many chars in group */ |
| 108 | + (has_double_colon && group.len == 0 && group_count > 2)) { /* only one double colon allowed */ |
| 109 | + return false; |
| 110 | + } |
| 111 | + |
| 112 | + has_double_colon = has_double_colon || group.len == 0; |
| 113 | + } |
| 114 | + |
| 115 | + /* second split is optional zone part */ |
| 116 | + if (aws_byte_cursor_next_split(&host, '%', &substr)) { |
| 117 | + if ((is_uri_encoded && |
| 118 | + (substr.len < 3 || |
| 119 | + !aws_byte_cursor_starts_with(&substr, &s_percent_uri_enc))) || /* encoding for % + 1 extra char */ |
| 120 | + (!is_uri_encoded && substr.len == 0) || /* at least 1 char */ |
| 121 | + !aws_byte_cursor_satisfies_pred(&substr, aws_isalnum)) { |
| 122 | + return false; |
| 123 | + } |
| 124 | + } |
| 125 | + |
| 126 | + return has_double_colon ? group_count < 7 : group_count == 8; |
| 127 | +} |
0 commit comments