1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2
3 #include "base/array.hpp"
4 #include "base/utility.hpp"
5 #include "base/objectlock.hpp"
6 #include "remote/url.hpp"
7 #include "remote/url-characters.hpp"
8 #include <boost/tokenizer.hpp>
9
10 using namespace icinga;
11
Url(const String & base_url)12 Url::Url(const String& base_url)
13 {
14 String url = base_url;
15
16 if (url.GetLength() == 0)
17 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid URL Empty URL."));
18
19 size_t pHelper = String::NPos;
20 if (url[0] != '/')
21 pHelper = url.Find(":");
22
23 if (pHelper != String::NPos) {
24 if (!ParseScheme(url.SubStr(0, pHelper)))
25 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid URL Scheme."));
26 url = url.SubStr(pHelper + 1);
27 }
28
29 if (*url.Begin() != '/')
30 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid URL: '/' expected after scheme."));
31
32 if (url.GetLength() == 1) {
33 return;
34 }
35
36 if (*(url.Begin() + 1) == '/') {
37 pHelper = url.Find("/", 2);
38
39 if (pHelper == String::NPos)
40 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid URL: Missing '/' after authority."));
41
42 if (!ParseAuthority(url.SubStr(0, pHelper)))
43 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid URL Authority"));
44
45 url = url.SubStr(pHelper);
46 }
47
48 if (*url.Begin() == '/') {
49 pHelper = url.FindFirstOf("#?");
50 if (!ParsePath(url.SubStr(1, pHelper - 1)))
51 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid URL Path"));
52
53 if (pHelper != String::NPos)
54 url = url.SubStr(pHelper);
55 } else
56 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid URL: Missing path."));
57
58 if (*url.Begin() == '?') {
59 pHelper = url.Find("#");
60 if (!ParseQuery(url.SubStr(1, pHelper - 1)))
61 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid URL Query"));
62
63 if (pHelper != String::NPos)
64 url = url.SubStr(pHelper);
65 }
66
67 if (*url.Begin() == '#') {
68 if (!ParseFragment(url.SubStr(1)))
69 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid URL Fragment"));
70 }
71 }
72
GetScheme() const73 String Url::GetScheme() const
74 {
75 return m_Scheme;
76 }
77
GetAuthority() const78 String Url::GetAuthority() const
79 {
80 if (m_Host.IsEmpty())
81 return "";
82
83 String auth;
84 if (!m_Username.IsEmpty()) {
85 auth = m_Username;
86 if (!m_Password.IsEmpty())
87 auth += ":" + m_Password;
88 auth += "@";
89 }
90
91 auth += m_Host;
92
93 if (!m_Port.IsEmpty())
94 auth += ":" + m_Port;
95
96 return auth;
97 }
98
GetUsername() const99 String Url::GetUsername() const
100 {
101 return m_Username;
102 }
103
GetPassword() const104 String Url::GetPassword() const
105 {
106 return m_Password;
107 }
108
GetHost() const109 String Url::GetHost() const
110 {
111 return m_Host;
112 }
113
GetPort() const114 String Url::GetPort() const
115 {
116 return m_Port;
117 }
118
GetPath() const119 const std::vector<String>& Url::GetPath() const
120 {
121 return m_Path;
122 }
123
GetQuery() const124 const std::vector<std::pair<String, String>>& Url::GetQuery() const
125 {
126 return m_Query;
127 }
128
GetFragment() const129 String Url::GetFragment() const
130 {
131 return m_Fragment;
132 }
133
SetScheme(const String & scheme)134 void Url::SetScheme(const String& scheme)
135 {
136 m_Scheme = scheme;
137 }
138
SetUsername(const String & username)139 void Url::SetUsername(const String& username)
140 {
141 m_Username = username;
142 }
143
SetPassword(const String & password)144 void Url::SetPassword(const String& password)
145 {
146 m_Password = password;
147 }
148
SetHost(const String & host)149 void Url::SetHost(const String& host)
150 {
151 m_Host = host;
152 }
153
SetPort(const String & port)154 void Url::SetPort(const String& port)
155 {
156 m_Port = port;
157 }
158
SetPath(const std::vector<String> & path)159 void Url::SetPath(const std::vector<String>& path)
160 {
161 m_Path = path;
162 }
163
SetQuery(const std::vector<std::pair<String,String>> & query)164 void Url::SetQuery(const std::vector<std::pair<String, String>>& query)
165 {
166 m_Query = query;
167 }
168
SetArrayFormatUseBrackets(bool useBrackets)169 void Url::SetArrayFormatUseBrackets(bool useBrackets)
170 {
171 m_ArrayFormatUseBrackets = useBrackets;
172 }
173
AddQueryElement(const String & name,const String & value)174 void Url::AddQueryElement(const String& name, const String& value)
175 {
176 m_Query.emplace_back(name, value);
177 }
178
SetFragment(const String & fragment)179 void Url::SetFragment(const String& fragment) {
180 m_Fragment = fragment;
181 }
182
Format(bool onlyPathAndQuery,bool printCredentials) const183 String Url::Format(bool onlyPathAndQuery, bool printCredentials) const
184 {
185 String url;
186
187 if (!onlyPathAndQuery) {
188 if (!m_Scheme.IsEmpty())
189 url += m_Scheme + ":";
190
191 if (printCredentials && !GetAuthority().IsEmpty())
192 url += "//" + GetAuthority();
193 else if (!GetHost().IsEmpty())
194 url += "//" + GetHost() + (!GetPort().IsEmpty() ? ":" + GetPort() : "");
195 }
196
197 if (m_Path.empty())
198 url += "/";
199 else {
200 for (const String& segment : m_Path) {
201 url += "/";
202 url += Utility::EscapeString(segment, ACPATHSEGMENT_ENCODE, false);
203 }
204 }
205
206 String param;
207 if (!m_Query.empty()) {
208 typedef std::pair<String, std::vector<String> > kv_pair;
209
210 for (const auto& kv : m_Query) {
211 String key = Utility::EscapeString(kv.first, ACQUERY_ENCODE, false);
212 if (param.IsEmpty())
213 param = "?";
214 else
215 param += "&";
216
217 param += key;
218 param += kv.second.IsEmpty() ?
219 String() : "=" + Utility::EscapeString(kv.second, ACQUERY_ENCODE, false);
220 }
221 }
222
223 url += param;
224
225 if (!m_Fragment.IsEmpty())
226 url += "#" + Utility::EscapeString(m_Fragment, ACFRAGMENT_ENCODE, false);
227
228 return url;
229 }
230
ParseScheme(const String & scheme)231 bool Url::ParseScheme(const String& scheme)
232 {
233 m_Scheme = scheme;
234
235 if (scheme.FindFirstOf(ALPHA) != 0)
236 return false;
237
238 return (ValidateToken(scheme, ACSCHEME));
239 }
240
ParseAuthority(const String & authority)241 bool Url::ParseAuthority(const String& authority)
242 {
243 String auth = authority.SubStr(2);
244 size_t pos = auth.Find("@");
245 if (pos != String::NPos && pos != 0) {
246 if (!Url::ParseUserinfo(auth.SubStr(0, pos)))
247 return false;
248 auth = auth.SubStr(pos+1);
249 }
250
251 pos = auth.Find(":");
252 if (pos != String::NPos) {
253 if (pos == 0 || pos == auth.GetLength() - 1 || !Url::ParsePort(auth.SubStr(pos+1)))
254 return false;
255 }
256
257 m_Host = auth.SubStr(0, pos);
258 return ValidateToken(m_Host, ACHOST);
259 }
260
ParseUserinfo(const String & userinfo)261 bool Url::ParseUserinfo(const String& userinfo)
262 {
263 size_t pos = userinfo.Find(":");
264 m_Username = userinfo.SubStr(0, pos);
265 if (!ValidateToken(m_Username, ACUSERINFO))
266 return false;
267 m_Username = Utility::UnescapeString(m_Username);
268 if (pos != String::NPos && pos != userinfo.GetLength() - 1) {
269 m_Password = userinfo.SubStr(pos+1);
270 if (!ValidateToken(m_Username, ACUSERINFO))
271 return false;
272 m_Password = Utility::UnescapeString(m_Password);
273 } else
274 m_Password = "";
275
276 return true;
277 }
278
ParsePort(const String & port)279 bool Url::ParsePort(const String& port)
280 {
281 m_Port = Utility::UnescapeString(port);
282 if (!ValidateToken(m_Port, ACPORT))
283 return false;
284 return true;
285 }
286
ParsePath(const String & path)287 bool Url::ParsePath(const String& path)
288 {
289 const std::string& pathStr = path;
290 boost::char_separator<char> sep("/");
291 boost::tokenizer<boost::char_separator<char> > tokens(pathStr, sep);
292
293 for (const String& token : tokens) {
294 if (token.IsEmpty())
295 continue;
296
297 if (!ValidateToken(token, ACPATHSEGMENT))
298 return false;
299
300 m_Path.emplace_back(Utility::UnescapeString(token));
301 }
302
303 return true;
304 }
305
ParseQuery(const String & query)306 bool Url::ParseQuery(const String& query)
307 {
308 /* Tokenizer does not like String AT ALL */
309 const std::string& queryStr = query;
310 boost::char_separator<char> sep("&");
311 boost::tokenizer<boost::char_separator<char> > tokens(queryStr, sep);
312
313 for (const String& token : tokens) {
314 size_t pHelper = token.Find("=");
315
316 if (pHelper == 0)
317 // /?foo=bar&=bar == invalid
318 return false;
319
320 String key = token.SubStr(0, pHelper);
321 String value = Empty;
322
323 if (pHelper != String::NPos && pHelper != token.GetLength() - 1)
324 value = token.SubStr(pHelper+1);
325
326 if (!ValidateToken(value, ACQUERY))
327 return false;
328
329 value = Utility::UnescapeString(value);
330
331 pHelper = key.Find("[]");
332
333 if (pHelper == 0 || (pHelper != String::NPos && pHelper != key.GetLength()-2))
334 return false;
335
336 key = key.SubStr(0, pHelper);
337
338 if (!ValidateToken(key, ACQUERY))
339 return false;
340
341 m_Query.emplace_back(Utility::UnescapeString(key), std::move(value));
342 }
343
344 return true;
345 }
346
ParseFragment(const String & fragment)347 bool Url::ParseFragment(const String& fragment)
348 {
349 m_Fragment = Utility::UnescapeString(fragment);
350
351 return ValidateToken(fragment, ACFRAGMENT);
352 }
353
ValidateToken(const String & token,const String & symbols)354 bool Url::ValidateToken(const String& token, const String& symbols)
355 {
356 for (const char ch : token) {
357 if (symbols.FindFirstOf(ch) == String::NPos)
358 return false;
359 }
360
361 return true;
362 }
363
364