1 /*
2 * viruri.c: URI parsing wrappers for libxml2 functions
3 *
4 * Copyright (C) 2012-2014 Red Hat, Inc.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library. If not, see
18 * <http://www.gnu.org/licenses/>.
19 */
20
21 #include <config.h>
22
23 #include <libxml/uri.h>
24
25 #include "viruri.h"
26
27 #include "viralloc.h"
28 #include "virerror.h"
29 #include "virbuffer.h"
30 #include "virlog.h"
31 #include "virstring.h"
32
33 #define VIR_FROM_THIS VIR_FROM_URI
34
35 VIR_LOG_INIT("util.uri");
36
37 static int
virURIParamAppend(virURI * uri,const char * name,const char * value)38 virURIParamAppend(virURI *uri,
39 const char *name,
40 const char *value)
41 {
42 char *pname = NULL;
43 char *pvalue = NULL;
44
45 pname = g_strdup(name);
46 pvalue = g_strdup(value);
47
48 VIR_RESIZE_N(uri->params, uri->paramsAlloc, uri->paramsCount, 1);
49
50 uri->params[uri->paramsCount].name = pname;
51 uri->params[uri->paramsCount].value = pvalue;
52 uri->params[uri->paramsCount].ignore = false;
53 uri->paramsCount++;
54
55 return 0;
56 }
57
58
59 static int
virURIParseParams(virURI * uri)60 virURIParseParams(virURI *uri)
61 {
62 const char *end, *eq;
63 const char *query = uri->query;
64
65 if (!query || query[0] == '\0')
66 return 0;
67
68 while (*query) {
69 g_autofree char *name = NULL;
70 g_autofree char *value = NULL;
71
72 /* Find the next separator, or end of the string. */
73 end = strchr(query, '&');
74 if (!end)
75 end = strchr(query, ';');
76 if (!end)
77 end = query + strlen(query);
78
79 /* Find the first '=' character between here and end. */
80 eq = strchr(query, '=');
81 if (eq && eq >= end) eq = NULL;
82
83 if (end == query) {
84 /* Empty section (eg. "&&"). */
85 goto next;
86 } else if (!eq) {
87 /* If there is no '=' character, then we have just "name"
88 * and consistent with CGI.pm we assume value is "".
89 */
90 name = xmlURIUnescapeString(query, end - query, NULL);
91 if (!name)
92 return -1;
93 } else if (eq+1 == end) {
94 /* Or if we have "name=" here (works around annoying
95 * problem when calling xmlURIUnescapeString with len = 0).
96 */
97 name = xmlURIUnescapeString(query, eq - query, NULL);
98 if (!name)
99 return -1;
100 } else if (query == eq) {
101 /* If the '=' character is at the beginning then we have
102 * "=value" and consistent with CGI.pm we _ignore_ this.
103 */
104 goto next;
105 } else {
106 /* Otherwise it's "name=value". */
107 name = xmlURIUnescapeString(query, eq - query, NULL);
108 if (!name)
109 return -1;
110 value = xmlURIUnescapeString(eq+1, end - (eq+1), NULL);
111 if (!value)
112 return -1;
113 }
114
115 /* Append to the parameter set. */
116 if (virURIParamAppend(uri, name, NULLSTR_EMPTY(value)) < 0)
117 return -1;
118
119 next:
120 query = end;
121 if (*query) query ++; /* skip '&' separator */
122 }
123
124 return 0;
125 }
126
127 /**
128 * virURIParse:
129 * @uri: URI to parse
130 *
131 * Wrapper for xmlParseURI
132 *
133 * Unfortunately there are few things that should be managed after
134 * parsing the URI. Fortunately there is only one thing now and its
135 * removing of square brackets around IPv6 addresses.
136 *
137 * @returns the parsed uri object with some fixes
138 */
139 virURI *
virURIParse(const char * uri)140 virURIParse(const char *uri)
141 {
142 xmlURIPtr xmluri;
143 virURI *ret = NULL;
144
145 xmluri = xmlParseURI(uri);
146
147 if (!xmluri) {
148 /* libxml2 does not tell us what failed. Grr :-( */
149 virReportError(VIR_ERR_INTERNAL_ERROR,
150 _("Unable to parse URI %s"), uri);
151 return NULL;
152 }
153
154 ret = g_new0(virURI, 1);
155
156 ret->scheme = g_strdup(xmluri->scheme);
157 ret->server = g_strdup(xmluri->server);
158 /* xmluri->port value is not defined if server was
159 * not given. Modern versions libxml2 fill port
160 * differently to old versions in this case, so
161 * don't rely on it. eg libxml2 git commit:
162 * beb7281055dbf0ed4d041022a67c6c5cfd126f25
163 */
164 if (!ret->server || STREQ(ret->server, ""))
165 ret->port = 0;
166 else
167 ret->port = xmluri->port;
168 ret->path = g_strdup(xmluri->path);
169 ret->query = g_strdup(xmluri->query_raw);
170 ret->fragment = g_strdup(xmluri->fragment);
171 ret->user = g_strdup(xmluri->user);
172
173 /* Strip square bracket from an IPv6 address.
174 * The function modifies the string in-place. Even after such
175 * modification, it is OK to free the URI with xmlFreeURI. */
176 virStringStripIPv6Brackets(ret->server);
177
178 if (virURIParseParams(ret) < 0)
179 goto error;
180
181 xmlFreeURI(xmluri);
182
183 return ret;
184
185 error:
186 xmlFreeURI(xmluri);
187 virURIFree(ret);
188 return NULL;
189 }
190
191 /**
192 * virURIFormat:
193 * @uri: URI to format
194 *
195 * Wrapper for xmlSaveUri
196 *
197 * This function constructs back everything that @ref virURIParse
198 * changes after parsing
199 *
200 * @returns the constructed uri as a string
201 */
202 char *
virURIFormat(virURI * uri)203 virURIFormat(virURI *uri)
204 {
205 xmlURI xmluri;
206 g_autofree char *tmpserver = NULL;
207 char *ret;
208
209 memset(&xmluri, 0, sizeof(xmluri));
210
211 xmluri.scheme = uri->scheme;
212 xmluri.server = uri->server;
213 xmluri.port = uri->port;
214 xmluri.path = uri->path;
215 xmluri.query_raw = uri->query;
216 xmluri.fragment = uri->fragment;
217 xmluri.user = uri->user;
218
219 /* First check: does it make sense to do anything */
220 if (xmluri.server != NULL &&
221 strchr(xmluri.server, ':') != NULL) {
222
223 tmpserver = g_strdup_printf("[%s]", xmluri.server);
224
225 xmluri.server = tmpserver;
226 }
227
228 /*
229 * This helps libxml2 deal with the difference
230 * between uri:/absolute/path and uri:///absolute/path.
231 */
232 if (!xmluri.server && !xmluri.port)
233 xmluri.port = -1;
234
235 /* xmlSaveUri can fail only on OOM condition if argument is non-NULL */
236 if (!(ret = (char *)xmlSaveUri(&xmluri)))
237 abort();
238
239 return ret;
240 }
241
242
virURIFormatParams(virURI * uri)243 char *virURIFormatParams(virURI *uri)
244 {
245 g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
246 size_t i;
247 bool amp = false;
248
249 for (i = 0; i < uri->paramsCount; ++i) {
250 if (!uri->params[i].ignore) {
251 if (amp) virBufferAddChar(&buf, '&');
252 virBufferStrcat(&buf, uri->params[i].name, "=", NULL);
253 virBufferURIEncodeString(&buf, uri->params[i].value);
254 amp = true;
255 }
256 }
257
258 return virBufferContentAndReset(&buf);
259 }
260
261 /**
262 * virURIFree:
263 * @uri: uri to free
264 *
265 * Frees the URI
266 */
virURIFree(virURI * uri)267 void virURIFree(virURI *uri)
268 {
269 size_t i;
270
271 if (!uri)
272 return;
273
274 g_free(uri->scheme);
275 g_free(uri->server);
276 g_free(uri->user);
277 g_free(uri->path);
278 g_free(uri->query);
279 g_free(uri->fragment);
280
281 for (i = 0; i < uri->paramsCount; i++) {
282 g_free(uri->params[i].name);
283 g_free(uri->params[i].value);
284 }
285 g_free(uri->params);
286
287 g_free(uri);
288 }
289
290
291 #define URI_ALIAS_CHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"
292
293 static int
virURIFindAliasMatch(char * const * aliases,const char * alias,char ** uri)294 virURIFindAliasMatch(char *const*aliases, const char *alias,
295 char **uri)
296 {
297 size_t alias_len;
298
299 alias_len = strlen(alias);
300 while (*aliases) {
301 char *offset;
302 size_t safe;
303
304 if (!(offset = strchr(*aliases, '='))) {
305 virReportError(VIR_ERR_CONF_SYNTAX,
306 _("Malformed 'uri_aliases' config entry '%s', "
307 "expected 'alias=uri://host/path'"), *aliases);
308 return -1;
309 }
310
311 safe = strspn(*aliases, URI_ALIAS_CHARS);
312 if (safe < (offset - *aliases)) {
313 virReportError(VIR_ERR_CONF_SYNTAX,
314 _("Malformed 'uri_aliases' config entry '%s', "
315 "aliases may only contain 'a-Z, 0-9, _, -'"),
316 *aliases);
317 return -1;
318 }
319
320 if (alias_len == (offset - *aliases) &&
321 STREQLEN(*aliases, alias, alias_len)) {
322 VIR_DEBUG("Resolved alias '%s' to '%s'",
323 alias, offset+1);
324 *uri = g_strdup(offset + 1);
325 return 0;
326 }
327
328 aliases++;
329 }
330
331 VIR_DEBUG("No alias found for '%s', continuing...",
332 alias);
333 return 0;
334 }
335
336
337 /**
338 * virURIResolveAlias:
339 * @conf: configuration file handler
340 * @alias: URI alias to be resolved
341 * @uri: URI object reference where the resolved URI should be stored
342 *
343 * Resolves @alias to a canonical URI according to our configuration
344 * file.
345 *
346 * Returns 0 on success, -1 on error.
347 */
348 int
virURIResolveAlias(virConf * conf,const char * alias,char ** uri)349 virURIResolveAlias(virConf *conf, const char *alias, char **uri)
350 {
351 int ret = -1;
352 g_auto(GStrv) aliases = NULL;
353
354 *uri = NULL;
355
356 if (virConfGetValueStringList(conf, "uri_aliases", false, &aliases) < 0)
357 return -1;
358
359 if (aliases && *aliases) {
360 ret = virURIFindAliasMatch(aliases, alias, uri);
361 } else {
362 ret = 0;
363 }
364
365 return ret;
366 }
367
368
369 const char *
virURIGetParam(virURI * uri,const char * name)370 virURIGetParam(virURI *uri, const char *name)
371 {
372 size_t i;
373
374 for (i = 0; i < uri->paramsCount; i++) {
375 if (STREQ(uri->params[i].name, name))
376 return uri->params[i].value;
377 }
378
379 virReportError(VIR_ERR_INVALID_ARG,
380 _("Missing URI parameter '%s'"), name);
381 return NULL;
382 }
383
384
385 /**
386 * virURICheckUnixSocket:
387 * @uri: URI to check
388 *
389 * Check if the URI looks like it refers to a non-standard socket path. In such
390 * scenario the socket might be proxied to a remote server even though the URI
391 * looks like it is only local.
392 *
393 * Returns: true if the URI might be proxied to a remote server
394 */
395 bool
virURICheckUnixSocket(virURI * uri)396 virURICheckUnixSocket(virURI *uri)
397 {
398 size_t i = 0;
399
400 if (!uri->scheme)
401 return false;
402
403 if (STRNEQ_NULLABLE(strchr(uri->scheme, '+'), "+unix"))
404 return false;
405
406 for (i = 0; i < uri->paramsCount; i++) {
407 if (STREQ(uri->params[i].name, "socket"))
408 return true;
409 }
410
411 return false;
412 }
413