/* * viruri.c: URI parsing wrappers for libxml2 functions * * Copyright (C) 2012-2014 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . */ #include #include #include "viruri.h" #include "viralloc.h" #include "virerror.h" #include "virbuffer.h" #include "virlog.h" #include "virstring.h" #define VIR_FROM_THIS VIR_FROM_URI VIR_LOG_INIT("util.uri"); static int virURIParamAppend(virURIPtr uri, const char *name, const char *value) { char *pname = NULL; char *pvalue = NULL; pname = g_strdup(name); pvalue = g_strdup(value); if (VIR_RESIZE_N(uri->params, uri->paramsAlloc, uri->paramsCount, 1) < 0) goto error; uri->params[uri->paramsCount].name = pname; uri->params[uri->paramsCount].value = pvalue; uri->params[uri->paramsCount].ignore = false; uri->paramsCount++; return 0; error: VIR_FREE(pname); VIR_FREE(pvalue); return -1; } static int virURIParseParams(virURIPtr uri) { const char *end, *eq; const char *query = uri->query; if (!query || query[0] == '\0') return 0; while (*query) { g_autofree char *name = NULL; g_autofree char *value = NULL; /* Find the next separator, or end of the string. */ end = strchr(query, '&'); if (!end) end = strchr(query, ';'); if (!end) end = query + strlen(query); /* Find the first '=' character between here and end. */ eq = strchr(query, '='); if (eq && eq >= end) eq = NULL; if (end == query) { /* Empty section (eg. "&&"). */ goto next; } else if (!eq) { /* If there is no '=' character, then we have just "name" * and consistent with CGI.pm we assume value is "". */ name = xmlURIUnescapeString(query, end - query, NULL); if (!name) return -1; } else if (eq+1 == end) { /* Or if we have "name=" here (works around annoying * problem when calling xmlURIUnescapeString with len = 0). */ name = xmlURIUnescapeString(query, eq - query, NULL); if (!name) return -1; } else if (query == eq) { /* If the '=' character is at the beginning then we have * "=value" and consistent with CGI.pm we _ignore_ this. */ goto next; } else { /* Otherwise it's "name=value". */ name = xmlURIUnescapeString(query, eq - query, NULL); if (!name) return -1; value = xmlURIUnescapeString(eq+1, end - (eq+1), NULL); if (!value) return -1; } /* Append to the parameter set. */ if (virURIParamAppend(uri, name, NULLSTR_EMPTY(value)) < 0) return -1; next: query = end; if (*query) query ++; /* skip '&' separator */ } return 0; } /** * virURIParse: * @uri: URI to parse * * Wrapper for xmlParseURI * * Unfortunately there are few things that should be managed after * parsing the URI. Fortunately there is only one thing now and its * removing of square brackets around IPv6 addresses. * * @returns the parsed uri object with some fixes */ virURIPtr virURIParse(const char *uri) { xmlURIPtr xmluri; virURIPtr ret = NULL; xmluri = xmlParseURI(uri); if (!xmluri) { /* libxml2 does not tell us what failed. Grr :-( */ virReportError(VIR_ERR_INTERNAL_ERROR, _("Unable to parse URI %s"), uri); return NULL; } ret = g_new0(virURI, 1); ret->scheme = g_strdup(xmluri->scheme); ret->server = g_strdup(xmluri->server); /* xmluri->port value is not defined if server was * not given. Modern versions libxml2 fill port * differently to old versions in this case, so * don't rely on it. eg libxml2 git commit: * beb7281055dbf0ed4d041022a67c6c5cfd126f25 */ if (!ret->server || STREQ(ret->server, "")) ret->port = 0; else ret->port = xmluri->port; ret->path = g_strdup(xmluri->path); ret->query = g_strdup(xmluri->query_raw); ret->fragment = g_strdup(xmluri->fragment); ret->user = g_strdup(xmluri->user); /* Strip square bracket from an IPv6 address. * The function modifies the string in-place. Even after such * modification, it is OK to free the URI with xmlFreeURI. */ virStringStripIPv6Brackets(ret->server); if (virURIParseParams(ret) < 0) goto error; xmlFreeURI(xmluri); return ret; error: xmlFreeURI(xmluri); virURIFree(ret); return NULL; } /** * virURIFormat: * @uri: URI to format * * Wrapper for xmlSaveUri * * This function constructs back everything that @ref virURIParse * changes after parsing * * @returns the constructed uri as a string */ char * virURIFormat(virURIPtr uri) { xmlURI xmluri; g_autofree char *tmpserver = NULL; char *ret; memset(&xmluri, 0, sizeof(xmluri)); xmluri.scheme = uri->scheme; xmluri.server = uri->server; xmluri.port = uri->port; xmluri.path = uri->path; xmluri.query_raw = uri->query; xmluri.fragment = uri->fragment; xmluri.user = uri->user; /* First check: does it make sense to do anything */ if (xmluri.server != NULL && strchr(xmluri.server, ':') != NULL) { tmpserver = g_strdup_printf("[%s]", xmluri.server); xmluri.server = tmpserver; } /* * This helps libxml2 deal with the difference * between uri:/absolute/path and uri:///absolute/path. */ if (!xmluri.server && !xmluri.port) xmluri.port = -1; ret = (char *)xmlSaveUri(&xmluri); if (!ret) { virReportOOMError(); return NULL; } return ret; } char *virURIFormatParams(virURIPtr uri) { g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; size_t i; bool amp = false; for (i = 0; i < uri->paramsCount; ++i) { if (!uri->params[i].ignore) { if (amp) virBufferAddChar(&buf, '&'); virBufferStrcat(&buf, uri->params[i].name, "=", NULL); virBufferURIEncodeString(&buf, uri->params[i].value); amp = true; } } return virBufferContentAndReset(&buf); } /** * virURIFree: * @uri: uri to free * * Frees the URI */ void virURIFree(virURIPtr uri) { size_t i; if (!uri) return; g_free(uri->scheme); g_free(uri->server); g_free(uri->user); g_free(uri->path); g_free(uri->query); g_free(uri->fragment); for (i = 0; i < uri->paramsCount; i++) { g_free(uri->params[i].name); g_free(uri->params[i].value); } g_free(uri->params); g_free(uri); } #define URI_ALIAS_CHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-" static int virURIFindAliasMatch(char *const*aliases, const char *alias, char **uri) { size_t alias_len; alias_len = strlen(alias); while (*aliases) { char *offset; size_t safe; if (!(offset = strchr(*aliases, '='))) { virReportError(VIR_ERR_CONF_SYNTAX, _("Malformed 'uri_aliases' config entry '%s', " "expected 'alias=uri://host/path'"), *aliases); return -1; } safe = strspn(*aliases, URI_ALIAS_CHARS); if (safe < (offset - *aliases)) { virReportError(VIR_ERR_CONF_SYNTAX, _("Malformed 'uri_aliases' config entry '%s', " "aliases may only contain 'a-Z, 0-9, _, -'"), *aliases); return -1; } if (alias_len == (offset - *aliases) && STREQLEN(*aliases, alias, alias_len)) { VIR_DEBUG("Resolved alias '%s' to '%s'", alias, offset+1); *uri = g_strdup(offset + 1); return 0; } aliases++; } VIR_DEBUG("No alias found for '%s', continuing...", alias); return 0; } /** * virURIResolveAlias: * @conf: configuration file handler * @alias: URI alias to be resolved * @uri: URI object reference where the resolved URI should be stored * * Resolves @alias to a canonical URI according to our configuration * file. * * Returns 0 on success, -1 on error. */ int virURIResolveAlias(virConfPtr conf, const char *alias, char **uri) { int ret = -1; char **aliases = NULL; *uri = NULL; if (virConfGetValueStringList(conf, "uri_aliases", false, &aliases) < 0) return -1; if (aliases && *aliases) { ret = virURIFindAliasMatch(aliases, alias, uri); g_strfreev(aliases); } else { ret = 0; } return ret; } const char * virURIGetParam(virURIPtr uri, const char *name) { size_t i; for (i = 0; i < uri->paramsCount; i++) { if (STREQ(uri->params[i].name, name)) return uri->params[i].value; } virReportError(VIR_ERR_INVALID_ARG, _("Missing URI parameter '%s'"), name); return NULL; } /** * virURICheckUnixSocket: * @uri: URI to check * * Check if the URI looks like it refers to a non-standard socket path. In such * scenario the socket might be proxied to a remote server even though the URI * looks like it is only local. * * Returns: true if the URI might be proxied to a remote server */ bool virURICheckUnixSocket(virURIPtr uri) { size_t i = 0; if (!uri->scheme) return false; if (STRNEQ_NULLABLE(strchr(uri->scheme, '+'), "+unix")) return false; for (i = 0; i < uri->paramsCount; i++) { if (STREQ(uri->params[i].name, "socket")) return true; } return false; }