/* * virbuffer.c: buffers for libvirt * * Copyright (C) 2005-2008, 2010-2015 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 "virbuffer.h" #include "virstring.h" #include "viralloc.h" #define VIR_FROM_THIS VIR_FROM_NONE /** * virBufferAdjustIndent: * @buf: the buffer * @indent: adjustment to make * * Alter the auto-indent value by adding indent (positive to increase, * negative to decrease). Automatic indentation is performed by all * additive functions when the existing buffer is empty or ends with a * newline (however, note that no indentation is added after newlines * embedded in an appended string). If @indent would cause overflow, the * indentation level is truncated. */ void virBufferAdjustIndent(virBufferPtr buf, int indent) { if (!buf) return; if (indent > 0) { if (INT_MAX - indent < buf->indent) { buf->indent = INT_MAX; return; } } else { if (buf->indent < -indent) { buf->indent = 0; return; } } buf->indent += indent; } /** * virBufferSetIndent: * @buf: the buffer * @indent: new indentation size. * * Set the auto-indent value to @indent. See virBufferAdjustIndent on how auto * indentation is applied. */ void virBufferSetIndent(virBufferPtr buf, int indent) { if (!buf) return; buf->indent = indent; } /** * virBufferGetIndent: * @buf: the buffer * * Return the current auto-indent setting of @buf. */ size_t virBufferGetIndent(const virBuffer *buf) { return buf->indent; } /** * virBufferGetEffectiveIndent: * @buf: the buffer * * Returns the number of spaces that need to be appended to @buf to honour * auto-indentation. */ size_t virBufferGetEffectiveIndent(const virBuffer *buf) { if (buf->str && buf->str->len && buf->str->str[buf->str->len - 1] != '\n') return 0; return buf->indent; } /** * virBufferInitialize * @buf: the buffer * * Ensures that the internal GString container is allocated. */ static void virBufferInitialize(virBufferPtr buf) { if (!buf->str) buf->str = g_string_new(NULL); } static void virBufferApplyIndent(virBufferPtr buf) { const char space[] = " "; size_t spacesz = sizeof(space) - 1; size_t toindent = virBufferGetEffectiveIndent(buf); if (toindent == 0) return; while (toindent > spacesz) { g_string_append_len(buf->str, space, spacesz); toindent -= spacesz; } g_string_append_len(buf->str, space, toindent); } /** * virBufferAdd: * @buf: the buffer to append to * @str: the string * @len: the number of bytes to add, or -1 * * Add a string range to an XML buffer. If @len == -1, the length of * str is recomputed to the full string. Auto indentation may be applied. * */ void virBufferAdd(virBufferPtr buf, const char *str, int len) { if (!str || !buf) return; virBufferInitialize(buf); virBufferApplyIndent(buf); if (len < 0) g_string_append(buf->str, str); else g_string_append_len(buf->str, str, len); } /** * virBufferAddBuffer: * @buf: the buffer to append to * @toadd: the buffer to append * * Add a buffer into another buffer without need to go through: * virBufferContentAndReset(), virBufferAdd(). Auto indentation * is (intentionally) NOT applied! * * The @toadd virBuffer is consumed and cleared. */ void virBufferAddBuffer(virBufferPtr buf, virBufferPtr toadd) { if (!toadd || !toadd->str) return; if (!buf) goto cleanup; virBufferInitialize(buf); g_string_append_len(buf->str, toadd->str->str, toadd->str->len); cleanup: virBufferFreeAndReset(toadd); } /** * virBufferAddChar: * @buf: the buffer to append to * @c: the character to add * * Add a single character 'c' to a buffer. Auto indentation may be applied. * */ void virBufferAddChar(virBufferPtr buf, char c) { virBufferAdd(buf, &c, 1); } /** * virBufferCurrentContent: * @buf: Buffer * * Get the current content from the buffer. The content is only valid * until the next operation on @buf, and an empty string is returned if * no content is present yet. * * Returns the buffer content or NULL in case of error. */ const char * virBufferCurrentContent(virBufferPtr buf) { if (!buf) return NULL; if (!buf->str || buf->str->len == 0) return ""; return buf->str->str; } /** * virBufferContentAndReset: * @buf: Buffer * * Get the content from the buffer and free (only) the buffer structure. * The caller owns the returned string & should free it when no longer * required. The buffer object is reset to its initial state. This * interface intentionally returns NULL instead of an empty string if * there is no content. * * Returns the buffer content or NULL in case of error. */ char * virBufferContentAndReset(virBufferPtr buf) { char *str = NULL; if (!buf) return NULL; if (buf->str) str = g_string_free(buf->str, false); memset(buf, 0, sizeof(*buf)); return str; } /** * virBufferFreeAndReset: * @buf: the buffer to free and reset * * Frees the buffer content and resets the buffer structure. */ void virBufferFreeAndReset(virBufferPtr buf) { if (!buf) return; if (buf->str) g_string_free(buf->str, true); memset(buf, 0, sizeof(*buf)); } /** * virBufferUse: * @buf: the usage of the string in the buffer * * Return the string usage in bytes */ size_t virBufferUse(const virBuffer *buf) { if (!buf || !buf->str) return 0; return buf->str->len; } /** * virBufferAsprintf: * @buf: the buffer to append to * @format: the format * @...: the variable list of arguments * * Do a formatted print to an XML buffer. Auto indentation may be applied. */ void virBufferAsprintf(virBufferPtr buf, const char *format, ...) { va_list argptr; va_start(argptr, format); virBufferVasprintf(buf, format, argptr); va_end(argptr); } /** * virBufferVasprintf: * @buf: the buffer to append to * @format: the format * @argptr: the variable list of arguments * * Do a formatted print to an XML buffer. Auto indentation may be applied. */ void virBufferVasprintf(virBufferPtr buf, const char *format, va_list argptr) { if ((format == NULL) || (buf == NULL)) return; virBufferInitialize(buf); virBufferApplyIndent(buf); g_string_append_vprintf(buf->str, format, argptr); } /** * virBufferEscapeString: * @buf: the buffer to append to * @format: a printf like format string but with only one %s parameter * @str: the string argument which needs to be escaped * * Do a formatted print with a single string to an XML buffer. The * string is escaped for use in XML. If @str is NULL, nothing is * added (not even the rest of @format). Auto indentation may be * applied. */ void virBufferEscapeString(virBufferPtr buf, const char *format, const char *str) { int len; g_autofree char *escaped = NULL; char *out; const char *cur; const char forbidden_characters[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, /*\t*/ /*\n*/ 0x0B, 0x0C, /*\r*/ 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, '"', '&', '\'', '<', '>', '\0' }; if ((format == NULL) || (buf == NULL) || (str == NULL)) return; len = strlen(str); if (strcspn(str, forbidden_characters) == len) { virBufferAsprintf(buf, format, str); return; } escaped = g_malloc0_n(len + 1, 6); cur = str; out = escaped; while (*cur != 0) { if (*cur == '<') { *out++ = '&'; *out++ = 'l'; *out++ = 't'; *out++ = ';'; } else if (*cur == '>') { *out++ = '&'; *out++ = 'g'; *out++ = 't'; *out++ = ';'; } else if (*cur == '&') { *out++ = '&'; *out++ = 'a'; *out++ = 'm'; *out++ = 'p'; *out++ = ';'; } else if (*cur == '"') { *out++ = '&'; *out++ = 'q'; *out++ = 'u'; *out++ = 'o'; *out++ = 't'; *out++ = ';'; } else if (*cur == '\'') { *out++ = '&'; *out++ = 'a'; *out++ = 'p'; *out++ = 'o'; *out++ = 's'; *out++ = ';'; } else if (!strchr(forbidden_characters, *cur)) { /* * default case, just copy ! * Note that character over 0x80 are likely to give problem * with UTF-8 XML, but since our string don't have an encoding * it's hard to handle properly we have to assume it's UTF-8 too */ *out++ = *cur; } else { /* silently ignore control characters */ } cur++; } *out = 0; virBufferAsprintf(buf, format, escaped); } /** * virBufferEscapeSexpr: * @buf: the buffer to append to * @format: a printf like format string but with only one %s parameter * @str: the string argument which needs to be escaped * * Do a formatted print with a single string to an sexpr buffer. The * string is escaped to avoid generating a sexpr that xen will choke * on. This doesn't fully escape the sexpr, just enough for our code * to work. Auto indentation may be applied. */ void virBufferEscapeSexpr(virBufferPtr buf, const char *format, const char *str) { virBufferEscape(buf, '\\', "\\'", format, str); } /** * virBufferEscapeRegex: * @buf: the buffer to append to * @format: a printf like format string but with only one %s parameter * @str: the string argument which needs to be escaped * * Do a formatted print with a single string to a buffer. The @str is * escaped to avoid using POSIX extended regular expression meta-characters. * Escaping is not applied to characters specified in @format. Auto * indentation may be applied. */ void virBufferEscapeRegex(virBufferPtr buf, const char *format, const char *str) { virBufferEscape(buf, '\\', "^$.|?*+()[]{}\\", format, str); } /** * virBufferEscapeSQL: * @buf: the buffer to append to * @format: a printf like format string but with only one %s parameter * @str: the string argument which needs to be escaped * * Do a formatted print with a single string to a buffer. The @str is * escaped to prevent SQL injection (format is expected to contain \"%s\"). * Auto indentation may be applied. */ void virBufferEscapeSQL(virBufferPtr buf, const char *format, const char *str) { virBufferEscape(buf, '\\', "'\"\\", format, str); } /** * virBufferEscape: * @buf: the buffer to append to * @escape: the escape character to inject * @toescape: NUL-terminated list of characters to escape * @format: a printf like format string but with only one %s parameter * @str: the string argument which needs to be escaped * * Do a formatted print with a single string to a buffer. Any characters * in the provided list that are contained in @str are escaped with the * given escape. Escaping is not applied to characters specified in @format. * Auto indentation may be applied. */ void virBufferEscape(virBufferPtr buf, char escape, const char *toescape, const char *format, const char *str) { int len; g_autofree char *escaped = NULL; char *out; const char *cur; if ((format == NULL) || (buf == NULL) || (str == NULL)) return; len = strlen(str); if (strcspn(str, toescape) == len) { virBufferAsprintf(buf, format, str); return; } escaped = g_malloc0_n(len + 1, 2); cur = str; out = escaped; while (*cur != 0) { if (strchr(toescape, *cur)) *out++ = escape; *out++ = *cur; cur++; } *out = 0; virBufferAsprintf(buf, format, escaped); } /** * virBufferURIEncodeString: * @buf: the buffer to append to * @str: the string argument which will be URI-encoded * * Append the string to the buffer. The string will be URI-encoded * during the append (ie any non alphanumeric characters are replaced * with '%xx' hex sequences). Auto indentation may be applied. */ void virBufferURIEncodeString(virBufferPtr buf, const char *str) { if ((buf == NULL) || (str == NULL)) return; virBufferInitialize(buf); virBufferApplyIndent(buf); g_string_append_uri_escaped(buf->str, str, NULL, false); } /** * virBufferEscapeShell: * @buf: the buffer to append to * @str: an unquoted string * * Quotes a string so that the shell (/bin/sh) will interpret the * quoted string to mean str. Auto indentation may be applied. */ void virBufferEscapeShell(virBufferPtr buf, const char *str) { int len; g_autofree char *escaped = NULL; char *out; const char *cur; if ((buf == NULL) || (str == NULL)) return; /* Only quote if str includes shell metacharacters. */ if (*str && !strpbrk(str, "\r\t\n !\"#$&'()*;<>?[\\]^`{|}~")) { virBufferAdd(buf, str, -1); return; } if (*str) { len = strlen(str); escaped = g_malloc0_n(len + 1, 4); } else { virBufferAddLit(buf, "''"); return; } cur = str; out = escaped; *out++ = '\''; while (*cur != 0) { if (*cur == '\'') { *out++ = '\''; /* Replace literal ' with a close ', a \', and a open ' */ *out++ = '\\'; *out++ = '\''; } *out++ = *cur++; } *out++ = '\''; *out = 0; virBufferAdd(buf, escaped, -1); } /** * virBufferStrcatVArgs: * @buf: the buffer to append to * @ap: variable argument structure * * See virBufferStrcat. */ void virBufferStrcatVArgs(virBufferPtr buf, va_list ap) { char *str; while ((str = va_arg(ap, char *)) != NULL) virBufferAdd(buf, str, -1); } /** * virBufferStrcat: * @buf: the buffer to append to * @...: the variable list of strings, the last argument must be NULL * * Concatenate strings to an XML buffer. Auto indentation may be applied * after each string argument. */ void virBufferStrcat(virBufferPtr buf, ...) { va_list ap; if (!buf) return; va_start(ap, buf); virBufferStrcatVArgs(buf, ap); va_end(ap); } /** * virBufferTrim: * @buf: the buffer to trim * @str: the string to be trimmed from the tail * * Trim the supplied string from the tail of the buffer. */ void virBufferTrim(virBufferPtr buf, const char *str) { size_t len = 0; if (!buf || !buf->str) return; if (!str) return; len = strlen(str); if (len > buf->str->len || memcmp(&buf->str->str[buf->str->len - len], str, len) != 0) return; g_string_truncate(buf->str, buf->str->len - len); } /** * virBufferTrimChars: * @buf: the buffer to trim * @trim: the characters to be trimmed * * Trim the tail of the buffer. The longest string that can be formed with * the characters from @trim is trimmed. */ void virBufferTrimChars(virBufferPtr buf, const char *trim) { ssize_t i; if (!buf || !buf->str) return; if (!trim) return; for (i = buf->str->len - 1; i > 0; i--) { if (!strchr(trim, buf->str->str[i])) break; } g_string_truncate(buf->str, i + 1); } /** * virBufferTrimLen: * @buf: the buffer to trim * @len: the number of bytes to trim * * Trim the tail of a buffer. */ void virBufferTrimLen(virBufferPtr buf, int len) { if (!buf || !buf->str) return; if (len > buf->str->len) return; g_string_truncate(buf->str, buf->str->len - len); } /** * virBufferAddStr: * @buf: the buffer to append to * @str: string to append * * Appends @str to @buffer. Applies autoindentation on the separate lines of * @str. */ void virBufferAddStr(virBufferPtr buf, const char *str) { const char *end; if (!buf || !str) return; while (*str) { if ((end = strchr(str, '\n'))) { virBufferAdd(buf, str, (end - str) + 1); str = end + 1; } else { virBufferAdd(buf, str, -1); break; } } }