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