| /* | |
| * Copyright (c) 2012 Nicolas George | |
| * | |
| * This file is part of FFmpeg. | |
| * | |
| * FFmpeg 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. | |
| * | |
| * FFmpeg 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 FFmpeg; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| */ | |
| #include <limits.h> | |
| #include <stdarg.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <time.h> | |
| #include "avstring.h" | |
| #include "bprint.h" | |
| #include "compat/va_copy.h" | |
| #include "error.h" | |
| #include "macros.h" | |
| #include "mem.h" | |
| #define av_bprint_room(buf) ((buf)->size - FFMIN((buf)->len, (buf)->size)) | |
| #define av_bprint_is_allocated(buf) ((buf)->str != (buf)->reserved_internal_buffer) | |
| static int av_bprint_alloc(AVBPrint *buf, unsigned room) | |
| { | |
| char *old_str, *new_str; | |
| unsigned min_size, new_size; | |
| if (buf->size == buf->size_max) | |
| return AVERROR(EIO); | |
| if (!av_bprint_is_complete(buf)) | |
| return AVERROR_INVALIDDATA; /* it is already truncated anyway */ | |
| min_size = buf->len + 1 + FFMIN(UINT_MAX - buf->len - 1, room); | |
| new_size = buf->size > buf->size_max / 2 ? buf->size_max : buf->size * 2; | |
| if (new_size < min_size) | |
| new_size = FFMIN(buf->size_max, min_size); | |
| old_str = av_bprint_is_allocated(buf) ? buf->str : NULL; | |
| new_str = av_realloc(old_str, new_size); | |
| if (!new_str) | |
| return AVERROR(ENOMEM); | |
| if (!old_str) | |
| memcpy(new_str, buf->str, buf->len + 1); | |
| buf->str = new_str; | |
| buf->size = new_size; | |
| return 0; | |
| } | |
| static void av_bprint_grow(AVBPrint *buf, unsigned extra_len) | |
| { | |
| /* arbitrary margin to avoid small overflows */ | |
| extra_len = FFMIN(extra_len, UINT_MAX - 5 - buf->len); | |
| buf->len += extra_len; | |
| if (buf->size) | |
| buf->str[FFMIN(buf->len, buf->size - 1)] = 0; | |
| } | |
| void av_bprint_init(AVBPrint *buf, unsigned size_init, unsigned size_max) | |
| { | |
| unsigned size_auto = (char *)buf + sizeof(*buf) - | |
| buf->reserved_internal_buffer; | |
| if (size_max == AV_BPRINT_SIZE_AUTOMATIC) | |
| size_max = size_auto; | |
| buf->str = buf->reserved_internal_buffer; | |
| buf->len = 0; | |
| buf->size = FFMIN(size_auto, size_max); | |
| buf->size_max = size_max; | |
| *buf->str = 0; | |
| if (size_init > buf->size) | |
| av_bprint_alloc(buf, size_init - 1); | |
| } | |
| void av_bprint_init_for_buffer(AVBPrint *buf, char *buffer, unsigned size) | |
| { | |
| if (size == 0) { | |
| av_bprint_init(buf, 0, AV_BPRINT_SIZE_COUNT_ONLY); | |
| return; | |
| } | |
| buf->str = buffer; | |
| buf->len = 0; | |
| buf->size = size; | |
| buf->size_max = size; | |
| *buf->str = 0; | |
| } | |
| void av_bprintf(AVBPrint *buf, const char *fmt, ...) | |
| { | |
| unsigned room; | |
| char *dst; | |
| va_list vl; | |
| int extra_len; | |
| while (1) { | |
| room = av_bprint_room(buf); | |
| dst = room ? buf->str + buf->len : NULL; | |
| va_start(vl, fmt); | |
| extra_len = vsnprintf(dst, room, fmt, vl); | |
| va_end(vl); | |
| if (extra_len <= 0) | |
| return; | |
| if (extra_len < room) | |
| break; | |
| if (av_bprint_alloc(buf, extra_len)) | |
| break; | |
| } | |
| av_bprint_grow(buf, extra_len); | |
| } | |
| void av_vbprintf(AVBPrint *buf, const char *fmt, va_list vl_arg) | |
| { | |
| unsigned room; | |
| char *dst; | |
| int extra_len; | |
| va_list vl; | |
| while (1) { | |
| room = av_bprint_room(buf); | |
| dst = room ? buf->str + buf->len : NULL; | |
| va_copy(vl, vl_arg); | |
| extra_len = vsnprintf(dst, room, fmt, vl); | |
| va_end(vl); | |
| if (extra_len <= 0) | |
| return; | |
| if (extra_len < room) | |
| break; | |
| if (av_bprint_alloc(buf, extra_len)) | |
| break; | |
| } | |
| av_bprint_grow(buf, extra_len); | |
| } | |
| void av_bprint_chars(AVBPrint *buf, char c, unsigned n) | |
| { | |
| unsigned room, real_n; | |
| while (1) { | |
| room = av_bprint_room(buf); | |
| if (n < room) | |
| break; | |
| if (av_bprint_alloc(buf, n)) | |
| break; | |
| } | |
| if (room) { | |
| real_n = FFMIN(n, room - 1); | |
| memset(buf->str + buf->len, c, real_n); | |
| } | |
| av_bprint_grow(buf, n); | |
| } | |
| void av_bprint_append_data(AVBPrint *buf, const char *data, unsigned size) | |
| { | |
| unsigned room, real_n; | |
| while (1) { | |
| room = av_bprint_room(buf); | |
| if (size < room) | |
| break; | |
| if (av_bprint_alloc(buf, size)) | |
| break; | |
| } | |
| if (room) { | |
| real_n = FFMIN(size, room - 1); | |
| memcpy(buf->str + buf->len, data, real_n); | |
| } | |
| av_bprint_grow(buf, size); | |
| } | |
| void av_bprint_strftime(AVBPrint *buf, const char *fmt, const struct tm *tm) | |
| { | |
| unsigned room; | |
| size_t l; | |
| if (!*fmt) | |
| return; | |
| while (1) { | |
| room = av_bprint_room(buf); | |
| if (room && (l = strftime(buf->str + buf->len, room, fmt, tm))) | |
| break; | |
| /* strftime does not tell us how much room it would need: let us | |
| retry with twice as much until the buffer is large enough */ | |
| room = !room ? strlen(fmt) + 1 : | |
| room <= INT_MAX / 2 ? room * 2 : INT_MAX; | |
| if (av_bprint_alloc(buf, room)) { | |
| /* impossible to grow, try to manage something useful anyway */ | |
| room = av_bprint_room(buf); | |
| if (room < 1024) { | |
| /* if strftime fails because the buffer has (almost) reached | |
| its maximum size, let us try in a local buffer; 1k should | |
| be enough to format any real date+time string */ | |
| char buf2[1024]; | |
| if ((l = strftime(buf2, sizeof(buf2), fmt, tm))) { | |
| av_bprintf(buf, "%s", buf2); | |
| return; | |
| } | |
| } | |
| if (room) { | |
| /* if anything else failed and the buffer is not already | |
| truncated, let us add a stock string and force truncation */ | |
| static const char txt[] = "[truncated strftime output]"; | |
| memset(buf->str + buf->len, '!', room); | |
| memcpy(buf->str + buf->len, txt, FFMIN(sizeof(txt) - 1, room)); | |
| av_bprint_grow(buf, room); /* force truncation */ | |
| } | |
| return; | |
| } | |
| } | |
| av_bprint_grow(buf, l); | |
| } | |
| void av_bprint_get_buffer(AVBPrint *buf, unsigned size, | |
| unsigned char **mem, unsigned *actual_size) | |
| { | |
| if (size > av_bprint_room(buf)) | |
| av_bprint_alloc(buf, size); | |
| *actual_size = av_bprint_room(buf); | |
| *mem = *actual_size ? buf->str + buf->len : NULL; | |
| } | |
| void av_bprint_clear(AVBPrint *buf) | |
| { | |
| if (buf->len) { | |
| *buf->str = 0; | |
| buf->len = 0; | |
| } | |
| } | |
| int av_bprint_finalize(AVBPrint *buf, char **ret_str) | |
| { | |
| unsigned real_size = FFMIN(buf->len + 1, buf->size); | |
| char *str; | |
| int ret = 0; | |
| if (ret_str) { | |
| if (av_bprint_is_allocated(buf)) { | |
| str = av_realloc(buf->str, real_size); | |
| if (!str) | |
| str = buf->str; | |
| buf->str = NULL; | |
| } else { | |
| str = av_memdup(buf->str, real_size); | |
| if (!str) | |
| ret = AVERROR(ENOMEM); | |
| } | |
| *ret_str = str; | |
| } else { | |
| if (av_bprint_is_allocated(buf)) | |
| av_freep(&buf->str); | |
| } | |
| buf->size = real_size; | |
| return ret; | |
| } | |
| #define WHITESPACES " \n\t\r" | |
| void av_bprint_escape(AVBPrint *dstbuf, const char *src, const char *special_chars, | |
| enum AVEscapeMode mode, int flags) | |
| { | |
| const char *src0 = src; | |
| if (mode == AV_ESCAPE_MODE_AUTO) | |
| mode = AV_ESCAPE_MODE_BACKSLASH; /* TODO: implement a heuristic */ | |
| switch (mode) { | |
| case AV_ESCAPE_MODE_QUOTE: | |
| /* enclose the string between '' */ | |
| av_bprint_chars(dstbuf, '\'', 1); | |
| for (; *src; src++) { | |
| if (*src == '\'') | |
| av_bprintf(dstbuf, "'\\''"); | |
| else | |
| av_bprint_chars(dstbuf, *src, 1); | |
| } | |
| av_bprint_chars(dstbuf, '\'', 1); | |
| break; | |
| case AV_ESCAPE_MODE_XML: | |
| /* escape XML non-markup character data as per 2.4 by default: */ | |
| /* [^<&]* - ([^<&]* ']]>' [^<&]*) */ | |
| /* additionally, given one of the AV_ESCAPE_FLAG_XML_* flags, */ | |
| /* escape those specific characters as required. */ | |
| for (; *src; src++) { | |
| switch (*src) { | |
| case '&' : av_bprintf(dstbuf, "%s", "&"); break; | |
| case '<' : av_bprintf(dstbuf, "%s", "<"); break; | |
| case '>' : av_bprintf(dstbuf, "%s", ">"); break; | |
| case '\'': | |
| if (!(flags & AV_ESCAPE_FLAG_XML_SINGLE_QUOTES)) | |
| goto XML_DEFAULT_HANDLING; | |
| av_bprintf(dstbuf, "%s", "'"); | |
| break; | |
| case '"' : | |
| if (!(flags & AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES)) | |
| goto XML_DEFAULT_HANDLING; | |
| av_bprintf(dstbuf, "%s", """); | |
| break; | |
| XML_DEFAULT_HANDLING: | |
| default: av_bprint_chars(dstbuf, *src, 1); | |
| } | |
| } | |
| break; | |
| /* case AV_ESCAPE_MODE_BACKSLASH or unknown mode */ | |
| default: | |
| /* \-escape characters */ | |
| for (; *src; src++) { | |
| int is_first_last = src == src0 || !*(src+1); | |
| int is_ws = !!strchr(WHITESPACES, *src); | |
| int is_strictly_special = special_chars && strchr(special_chars, *src); | |
| int is_special = | |
| is_strictly_special || strchr("'\\", *src) || | |
| (is_ws && (flags & AV_ESCAPE_FLAG_WHITESPACE)); | |
| if (is_strictly_special || | |
| (!(flags & AV_ESCAPE_FLAG_STRICT) && | |
| (is_special || (is_ws && is_first_last)))) | |
| av_bprint_chars(dstbuf, '\\', 1); | |
| av_bprint_chars(dstbuf, *src, 1); | |
| } | |
| break; | |
| } | |
| } |