From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

/*
* Copyright 2013 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <limits.h>
#include <stdarg.h>
#include <string.h>
#include "bson-compat.h"
#include "bson-config.h"
#include "bson-string.h"
#include "bson-memory.h"
#include "bson-utf8.h"
/*
*--------------------------------------------------------------------------
*
* bson_string_new --
*
* Create a new bson_string_t.
*
* bson_string_t is a power-of-2 allocation growing string. Every
* time data is appended the next power of two size is chosen for
* the allocation. Pretty standard stuff.
*
* It is UTF-8 aware through the use of bson_string_append_unichar().
* The proper UTF-8 character sequence will be used.
*
* Parameters:
* @str: a string to copy or NULL.
*
* Returns:
* A newly allocated bson_string_t that should be freed with
* bson_string_free().
*
* Side effects:
* None.
*
*--------------------------------------------------------------------------
*/
bson_string_t *
bson_string_new (const char *str) /* IN */
{
bson_string_t *ret;
ret = bson_malloc0 (sizeof *ret);
ret->len = str ? (int)strlen (str) : 0;
ret->alloc = ret->len + 1;
if (!bson_is_power_of_two (ret->alloc)) {
ret->alloc = (uint32_t)bson_next_power_of_two ((size_t)ret->alloc);
}
BSON_ASSERT (ret->alloc >= 1);
ret->str = bson_malloc (ret->alloc);
if (str) {
memcpy (ret->str, str, ret->len);
}
ret->str [ret->len] = '\0';
ret->str [ret->len] = '\0';
return ret;
}
/*
*--------------------------------------------------------------------------
*
* bson_string_free --
*
* Free the bson_string_t @string and related allocations.
*
* If @free_segment is false, then the strings buffer will be
* returned and is not freed. Otherwise, NULL is returned.
*
* Returns:
* The string->str if free_segment is false.
* Otherwise NULL.
*
* Side effects:
* None.
*
*--------------------------------------------------------------------------
*/
char *
bson_string_free (bson_string_t *string, /* IN */
bool free_segment) /* IN */
{
char *ret = NULL;
bson_return_val_if_fail (string, NULL);
if (!free_segment) {
ret = string->str;
} else {
bson_free (string->str);
}
bson_free (string);
return ret;
}
/*
*--------------------------------------------------------------------------
*
* bson_string_append --
*
* Append the UTF-8 string @str to @string.
*
* Returns:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------------------
*/
void
bson_string_append (bson_string_t *string, /* IN */
const char *str) /* IN */
{
uint32_t len;
bson_return_if_fail (string);
bson_return_if_fail (str);
len = (uint32_t)strlen (str);
if ((string->alloc - string->len - 1) < len) {
string->alloc += len;
if (!bson_is_power_of_two (string->alloc)) {
string->alloc = (uint32_t)bson_next_power_of_two ((size_t)string->alloc);
}
string->str = bson_realloc (string->str, string->alloc);
}
memcpy (string->str + string->len, str, len);
string->len += len;
string->str [string->len] = '\0';
}
/*
*--------------------------------------------------------------------------
*
* bson_string_append_c --
*
* Append the ASCII character @c to @string.
*
* Do not use this if you are working with UTF-8 sequences,
* use bson_string_append_unichar().
*
* Returns:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------------------
*/
void
bson_string_append_c (bson_string_t *string, /* IN */
char c) /* IN */
{
char cc[2];
BSON_ASSERT (string);
if (BSON_UNLIKELY (string->alloc == (string->len + 1))) {
cc [0] = c;
cc [1] = '\0';
bson_string_append (string, cc);
return;
}
string->str [string->len++] = c;
string->str [string->len] = '\0';
}
/*
*--------------------------------------------------------------------------
*
* bson_string_append_unichar --
*
* Append the bson_unichar_t @unichar to the string @string.
*
* Returns:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------------------
*/
void
bson_string_append_unichar (bson_string_t *string, /* IN */
bson_unichar_t unichar) /* IN */
{
uint32_t len;
char str [8];
BSON_ASSERT (string);
BSON_ASSERT (unichar);
bson_utf8_from_unichar (unichar, str, &len);
if (len <= 6) {
str [len] = '\0';
bson_string_append (string, str);
}
}
/*
*--------------------------------------------------------------------------
*
* bson_string_append_printf --
*
* Format a string according to @format and append it to @string.
*
* Returns:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------------------
*/
void
bson_string_append_printf (bson_string_t *string,
const char *format,
...)
{
va_list args;
char *ret;
BSON_ASSERT (string);
BSON_ASSERT (format);
va_start (args, format);
ret = bson_strdupv_printf (format, args);
va_end (args);
bson_string_append (string, ret);
bson_free (ret);
}
/*
*--------------------------------------------------------------------------
*
* bson_string_truncate --
*
* Truncate the string @string to @len bytes.
*
* The underlying memory will be released via realloc() down to
* the minimum required size specified by @len.
*
* Returns:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------------------
*/
void
bson_string_truncate (bson_string_t *string, /* IN */
uint32_t len) /* IN */
{
uint32_t alloc;
bson_return_if_fail (string);
bson_return_if_fail (len < INT_MAX);
alloc = len + 1;
if (alloc < 16) {
alloc = 16;
}
if (!bson_is_power_of_two (alloc)) {
alloc = (uint32_t)bson_next_power_of_two ((size_t)alloc);
}
string->str = bson_realloc (string->str, alloc);
string->alloc = alloc;
string->len = len;
string->str [string->len] = '\0';
}
/*
*--------------------------------------------------------------------------
*
* bson_strdup --
*
* Portable strdup().
*
* Returns:
* A newly allocated string that should be freed with bson_free().
*
* Side effects:
* None.
*
*--------------------------------------------------------------------------
*/
char *
bson_strdup (const char *str) /* IN */
{
long len;
char *out;
if (!str) {
return NULL;
}
len = (long)strlen (str);
out = bson_malloc (len + 1);
if (!out) {
return NULL;
}
memcpy (out, str, len + 1);
return out;
}
/*
*--------------------------------------------------------------------------
*
* bson_strdupv_printf --
*
* Like bson_strdup_printf() but takes a va_list.
*
* Returns:
* A newly allocated string that should be freed with bson_free().
*
* Side effects:
* None.
*
*--------------------------------------------------------------------------
*/
char *
bson_strdupv_printf (const char *format, /* IN */
va_list args) /* IN */
{
va_list my_args;
char *buf;
int len = 32;
int n;
bson_return_val_if_fail (format, NULL);
buf = bson_malloc0 (len);
while (true) {
va_copy (my_args, args);
n = bson_vsnprintf (buf, len, format, my_args);
va_end (my_args);
if (n > -1 && n < len) {
return buf;
}
if (n > -1) {
len = n + 1;
} else {
len *= 2;
}
buf = bson_realloc (buf, len);
}
}
/*
*--------------------------------------------------------------------------
*
* bson_strdup_printf --
*
* Convenience function that formats a string according to @format
* and returns a copy of it.
*
* Returns:
* A newly created string that should be freed with bson_free().
*
* Side effects:
* None.
*
*--------------------------------------------------------------------------
*/
char *
bson_strdup_printf (const char *format, /* IN */
...) /* IN */
{
va_list args;
char *ret;
bson_return_val_if_fail (format, NULL);
va_start (args, format);
ret = bson_strdupv_printf (format, args);
va_end (args);
return ret;
}
/*
*--------------------------------------------------------------------------
*
* bson_strndup --
*
* A portable strndup().
*
* Returns:
* A newly allocated string that should be freed with bson_free().
*
* Side effects:
* None.
*
*--------------------------------------------------------------------------
*/
char *
bson_strndup (const char *str, /* IN */
size_t n_bytes) /* IN */
{
char *ret;
bson_return_val_if_fail (str, NULL);
ret = bson_malloc (n_bytes + 1);
memcpy (ret, str, n_bytes);
ret[n_bytes] = '\0';
return ret;
}
/*
*--------------------------------------------------------------------------
*
* bson_strfreev --
*
* Frees each string in a NULL terminated array of strings.
* This also frees the underlying array.
*
* Returns:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------------------
*/
void
bson_strfreev (char **str) /* IN */
{
int i;
if (str) {
for (i = 0; str [i]; i++)
bson_free (str [i]);
bson_free (str);
}
}
/*
*--------------------------------------------------------------------------
*
* bson_strnlen --
*
* A portable strnlen().
*
* Returns:
* The length of @s up to @maxlen.
*
* Side effects:
* None.
*
*--------------------------------------------------------------------------
*/
size_t
bson_strnlen (const char *s, /* IN */
size_t maxlen) /* IN */
{
#ifdef HAVE_STRNLEN
return strnlen (s, maxlen);
#else
size_t i;
for (i = 0; i < maxlen; i++) {
if (s [i] == '\0') {
return i + 1;
}
}
return maxlen;
#endif
}
/*
*--------------------------------------------------------------------------
*
* bson_strncpy --
*
* A portable strncpy.
*
* Copies @src into @dst, which must be @size bytes or larger.
* The result is guaranteed to be \0 terminated.
*
* Returns:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------------------
*/
void
bson_strncpy (char *dst, /* IN */
const char *src, /* IN */
size_t size) /* IN */
{
#ifdef _MSC_VER
strcpy_s (dst, size, src);
#else
strncpy (dst, src, size);
dst[size - 1] = '\0';
#endif
}
/*
*--------------------------------------------------------------------------
*
* bson_vsnprintf --
*
* A portable vsnprintf.
*
* If more than @size bytes are required (exluding the null byte),
* then @size bytes will be written to @string and the return value
* is the number of bytes required.
*
* This function will always return a NULL terminated string.
*
* Returns:
* The number of bytes required for @format excluding the null byte.
*
* Side effects:
* @str is initialized with the formatted string.
*
*--------------------------------------------------------------------------
*/
int
bson_vsnprintf (char *str, /* IN */
size_t size, /* IN */
const char *format, /* IN */
va_list ap) /* IN */
{
#ifdef BSON_OS_WIN32
int r = -1;
BSON_ASSERT (str);
if (size != 0) {
r = _vsnprintf_s (str, size, _TRUNCATE, format, ap);
}
if (r == -1) {
r = _vscprintf (format, ap);
}
str [size - 1] = '\0';
return r;
#else
int r;
r = vsnprintf (str, size, format, ap);
str [size - 1] = '\0';
return r;
#endif
}
/*
*--------------------------------------------------------------------------
*
* bson_snprintf --
*
* A portable snprintf.
*
* If @format requires more than @size bytes, then @size bytes are
* written and the result is the number of bytes required (excluding
* the null byte).
*
* This function will always return a NULL terminated string.
*
* Returns:
* The number of bytes required for @format.
*
* Side effects:
* @str is initialized.
*
*--------------------------------------------------------------------------
*/
int
bson_snprintf (char *str, /* IN */
size_t size, /* IN */
const char *format, /* IN */
...)
{
int r;
va_list ap;
BSON_ASSERT (str);
va_start (ap, format);
r = bson_vsnprintf (str, size, format, ap);
va_end (ap);
return r;
}
int64_t
bson_ascii_strtoll (const char *s,
char **e,
int base)
{
char *tok = (char *)s;
char c;
int64_t number = 0;
int64_t sign = 1;
if (!s) {
errno = EINVAL;
return 0;
}
c = *tok;
while (isspace (c)) {
c = *++tok;
}
if (!isdigit (c) && (c != '+') && (c != '-')) {
*e = tok - 1;
errno = EINVAL;
return 0;
}
if (c == '-') {
sign = -1;
c = *++tok;
}
if (c == '+') {
c = *++tok;
}
if (c == '0' && tok[1] != '\0') {
/* Hex, octal or binary -- maybe. */
c = *++tok;
if (c == 'x' || c == 'X') { /* Hex */
if (base != 16) {
*e = (char *)(s);
errno = EINVAL;
return 0;
}
c = *++tok;
if (!isxdigit (c)) {
*e = tok;
errno = EINVAL;
return 0;
}
do {
number = (number << 4) + (c - '0');
c = *(++tok);
} while (isxdigit (c));
}
else { /* Octal */
if (base != 8) {
*e = (char *)(s);
errno = EINVAL;
return 0;
}
if (c < '0' || c >= '8') {
*e = tok;
errno = EINVAL;
return 0;
}
do {
number = (number << 3) + (c - '0');
c = *(++tok);
} while (('0' <= c) && (c < '8'));
}
while (c == 'l' || c == 'L' || c == 'u' || c == 'U') {
c = *++tok;
}
}
else {
/* Decimal */
if (base != 10) {
*e = (char *)(s);
errno = EINVAL;
return 0;
}
do {
number = (number * 10) + (c - '0');
c = *(++tok);
} while (isdigit (c));
while (c == 'l' || c == 'L' || c == 'u' || c == 'U') {
c = *(++tok);
}
}
*e = tok;
errno = 0;
return (sign * number);
}