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