-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathcs_parser.cpp
149 lines (135 loc) · 3.99 KB
/
cs_parser.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#include "cs_parser.h"
#include <cstdint>
#if __has_include(<charconv>)
#include <charconv>
#endif
namespace tnt
{
using namespace std;
// TODO https://github.com/tarantool/tarantool-c/issues/120
// > Support user:pass@unix/:/path/to/socket
cs_parts parse_cs(string_view connection_string) noexcept
{
if (connection_string.empty())
return {};
cs_parts res;
string_view tail = connection_string;
auto get_chunk = [&tail](const char *sep = ":/@[") noexcept
{
auto tmp = tail;
size_t pos = tail.find_first_of(sep);
if (pos == string_view::npos)
{
tail.remove_prefix(tail.size());
return tmp;
}
tail.remove_prefix(pos);
return string_view{tmp.data(), pos};
};
auto chunk2port = [&res](const string_view &chunk) noexcept
{
if (chunk.size() > 5)
return false;
// ensure the chunk contains valid port number
#if __has_include(<charconv>)
uint16_t tmp_port;
if (auto [ptr, err] = from_chars(chunk.data(), chunk.data() + chunk.size(), tmp_port);
err == std::errc() && static_cast<size_t>(ptr - chunk.data()) == chunk.size())
res.port = chunk;
#else
// to guarantee terminating null character
char tmp_chunk[6] = {0};
copy(chunk.begin(), chunk.end(), begin(tmp_chunk));
char *parse_end;
auto tmp_port = strtoul(tmp_chunk, &parse_end, 10);
if (!*parse_end && tmp_port && tmp_port <= 0xffff)
res.port = chunk;
#endif
return !res.port.empty();
};
string_view chunk;
while (res.port.empty())
{
chunk = get_chunk();
if (tail.empty()) // port only?
{
// not first chunk or incorrect port
if (chunk.data() != connection_string.data() || !chunk2port(chunk))
return {};
break;
}
switch (tail.front())
{
case ':':
{
tail.remove_prefix(1);
if (tail.front() == '/')
{
res.unix_socket_path = tail;
return res;
}
string_view chunk2 = get_chunk();
if (tail.empty())
{
// chunk2 is port?
if (!chunk2port(chunk2))
return {};
res.host = chunk;
break;
}
if (tail.front() == '@')
{
// chunk2 is password
res.user = chunk;
res.password = chunk2;
tail.remove_prefix(1);
break;
}
return {};
}
case '/': // unix socket
if (tail[1] == ':')
{
tail.remove_prefix(2);
if (chunk == "env")
{
string var{tail};
const char *cs = std::getenv(var.c_str());
if (!cs)
return {};
return parse_cs(cs);
}
if (chunk != "unix")
return {};
}
if (!res.user.empty())
return {};
res.unix_socket_path = tail;
return res;
case '[': // ipv6 address
if (!chunk.empty())
return {};
tail.remove_prefix(1);
chunk = get_chunk("]");
if (chunk.empty() || tail.front() != ']')
return {};
tail.remove_prefix(1);
if (tail.empty() || tail.front() != ':')
return {};
tail.remove_prefix(1);
if (!chunk2port(tail))
return {};
res.host = chunk;
break;
default: // @
res.user = chunk;
tail.remove_prefix(1);
}
}
if (res.user.empty())
res.user = "guest";
if (res.host.empty())
res.host = "localhost";
return res;
}
} // namespace tnt