387 lines
9.1 KiB
C
387 lines
9.1 KiB
C
/*
|
|
String utility functions
|
|
Copyright (C) 1999-2004, Joe Orton <joe@manyfish.co.uk>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 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
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public
|
|
License along with this library; if not, write to the Free
|
|
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
|
MA 02111-1307, USA
|
|
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#ifdef HAVE_STDLIB_H
|
|
#include <stdlib.h>
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <ctype.h> /* for isprint() etc in ne_strclean() */
|
|
|
|
#include "ne_alloc.h"
|
|
#include "ne_string.h"
|
|
|
|
char *ne_token(char **str, char separator)
|
|
{
|
|
char *ret = *str, *pnt = strchr(*str, separator);
|
|
|
|
if (pnt) {
|
|
*pnt = '\0';
|
|
*str = pnt + 1;
|
|
} else {
|
|
/* no separator found: return end of string. */
|
|
*str = NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
char *ne_qtoken(char **str, char separator, const char *quotes)
|
|
{
|
|
char *pnt, *ret = NULL;
|
|
|
|
for (pnt = *str; *pnt != '\0'; pnt++) {
|
|
char *quot = strchr(quotes, *pnt);
|
|
|
|
if (quot) {
|
|
char *qclose = strchr(pnt+1, *quot);
|
|
|
|
if (!qclose) {
|
|
/* no closing quote: invalid string. */
|
|
return NULL;
|
|
}
|
|
|
|
pnt = qclose;
|
|
} else if (*pnt == separator) {
|
|
/* found end of token. */
|
|
*pnt = '\0';
|
|
ret = *str;
|
|
*str = pnt + 1;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* no separator found: return end of string. */
|
|
ret = *str;
|
|
*str = NULL;
|
|
return ret;
|
|
}
|
|
|
|
char *ne_shave(char *str, const char *whitespace)
|
|
{
|
|
char *pnt, *ret = str;
|
|
|
|
while (*ret != '\0' && strchr(whitespace, *ret) != NULL) {
|
|
ret++;
|
|
}
|
|
|
|
/* pnt points at the NUL terminator. */
|
|
pnt = &ret[strlen(ret)];
|
|
|
|
while (pnt > ret && strchr(whitespace, *(pnt-1)) != NULL) {
|
|
pnt--;
|
|
}
|
|
|
|
*pnt = '\0';
|
|
return ret;
|
|
}
|
|
|
|
void ne_buffer_clear(ne_buffer *buf)
|
|
{
|
|
memset(buf->data, 0, buf->length);
|
|
buf->used = 1;
|
|
}
|
|
|
|
/* Grows for given size, returns 0 on success, -1 on error. */
|
|
void ne_buffer_grow(ne_buffer *buf, size_t newsize)
|
|
{
|
|
#define NE_BUFFER_GROWTH 512
|
|
if (newsize > buf->length) {
|
|
/* If it's not big enough already... */
|
|
buf->length = ((newsize / NE_BUFFER_GROWTH) + 1) * NE_BUFFER_GROWTH;
|
|
|
|
/* Reallocate bigger buffer */
|
|
buf->data = ne_realloc(buf->data, buf->length);
|
|
}
|
|
}
|
|
|
|
static size_t count_concat(va_list *ap)
|
|
/*@*/
|
|
{
|
|
size_t total = 0;
|
|
char *next;
|
|
|
|
while ((next = va_arg(*ap, char *)) != NULL)
|
|
total += strlen(next);
|
|
|
|
return total;
|
|
}
|
|
|
|
static void do_concat(char *str, va_list *ap)
|
|
/*@modifies str @*/
|
|
{
|
|
char *next;
|
|
|
|
while ((next = va_arg(*ap, char *)) != NULL) {
|
|
#ifdef HAVE_STPCPY
|
|
str = stpcpy(str, next);
|
|
#else
|
|
size_t len = strlen(next);
|
|
memcpy(str, next, len);
|
|
str += len;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void ne_buffer_concat(ne_buffer *buf, ...)
|
|
{
|
|
va_list ap;
|
|
ssize_t total;
|
|
|
|
va_start(ap, buf);
|
|
total = buf->used + count_concat(&ap);
|
|
va_end(ap);
|
|
|
|
/* Grow the buffer */
|
|
ne_buffer_grow(buf, total);
|
|
|
|
va_start(ap, buf);
|
|
do_concat(buf->data + buf->used - 1, &ap);
|
|
va_end(ap);
|
|
|
|
buf->used = total;
|
|
buf->data[total - 1] = '\0';
|
|
}
|
|
|
|
char *ne_concat(const char *str, ...)
|
|
{
|
|
va_list ap;
|
|
size_t total, slen = strlen(str);
|
|
char *ret;
|
|
|
|
va_start(ap, str);
|
|
total = slen + count_concat(&ap);
|
|
va_end(ap);
|
|
|
|
ret = memcpy(ne_malloc(total + 1), str, slen);
|
|
|
|
va_start(ap, str);
|
|
do_concat(ret + slen, &ap);
|
|
va_end(ap);
|
|
|
|
ret[total] = '\0';
|
|
return ret;
|
|
}
|
|
|
|
/* Append zero-terminated string... returns 0 on success or -1 on
|
|
* realloc failure. */
|
|
void ne_buffer_zappend(ne_buffer *buf, const char *str)
|
|
{
|
|
ne_buffer_append(buf, str, strlen(str));
|
|
}
|
|
|
|
void ne_buffer_append(ne_buffer *buf, const char *data, size_t len)
|
|
{
|
|
ne_buffer_grow(buf, buf->used + len);
|
|
memcpy(buf->data + buf->used - 1, data, len);
|
|
buf->used += len;
|
|
buf->data[buf->used - 1] = '\0';
|
|
}
|
|
|
|
ne_buffer *ne_buffer_create(void)
|
|
{
|
|
return ne_buffer_ncreate(512);
|
|
}
|
|
|
|
ne_buffer *ne_buffer_ncreate(size_t s)
|
|
{
|
|
ne_buffer *buf = ne_malloc(sizeof(*buf));
|
|
buf->data = ne_malloc(s);
|
|
buf->data[0] = '\0';
|
|
buf->length = s;
|
|
buf->used = 1;
|
|
return buf;
|
|
}
|
|
|
|
void ne_buffer_destroy(ne_buffer *buf)
|
|
{
|
|
ne_free(buf->data);
|
|
ne_free(buf);
|
|
}
|
|
|
|
char *ne_buffer_finish(ne_buffer *buf)
|
|
{
|
|
char *ret = buf->data;
|
|
ne_free(buf);
|
|
return ret;
|
|
}
|
|
|
|
void ne_buffer_altered(ne_buffer *buf)
|
|
{
|
|
buf->used = strlen(buf->data) + 1;
|
|
}
|
|
|
|
/*@unchecked@*/ /*@observer@*/
|
|
static const char *b64_alphabet =
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
"abcdefghijklmnopqrstuvwxyz"
|
|
"0123456789+/=";
|
|
|
|
char *ne_base64(const unsigned char *text, size_t inlen)
|
|
{
|
|
/* The tricky thing about this is doing the padding at the end,
|
|
* doing the bit manipulation requires a bit of concentration only */
|
|
char *buffer, *point;
|
|
size_t outlen;
|
|
|
|
/* Use 'buffer' to store the output. Work out how big it should be...
|
|
* This must be a multiple of 4 bytes */
|
|
|
|
outlen = (inlen*4)/3;
|
|
if ((inlen % 3) > 0) /* got to pad */
|
|
outlen += 4 - (inlen % 3);
|
|
|
|
buffer = ne_malloc(outlen + 1); /* +1 for the \0 */
|
|
|
|
/* now do the main stage of conversion, 3 bytes at a time,
|
|
* leave the trailing bytes (if there are any) for later */
|
|
|
|
for (point=buffer; inlen>=3; inlen-=3, text+=3) {
|
|
*(point++) = b64_alphabet[ (*text)>>2 ];
|
|
*(point++) = b64_alphabet[ ((*text)<<4 & 0x30) | (*(text+1))>>4 ];
|
|
*(point++) = b64_alphabet[ ((*(text+1))<<2 & 0x3c) | (*(text+2))>>6 ];
|
|
*(point++) = b64_alphabet[ (*(text+2)) & 0x3f ];
|
|
}
|
|
|
|
/* Now deal with the trailing bytes */
|
|
if (inlen > 0) {
|
|
/* We always have one trailing byte */
|
|
*(point++) = b64_alphabet[ (*text)>>2 ];
|
|
*(point++) = b64_alphabet[ (((*text)<<4 & 0x30) |
|
|
(inlen==2?(*(text+1))>>4:0)) ];
|
|
*(point++) = (inlen==1?'=':b64_alphabet[ (*(text+1))<<2 & 0x3c ]);
|
|
*(point++) = '=';
|
|
}
|
|
|
|
/* Null-terminate */
|
|
*point = '\0';
|
|
|
|
return buffer;
|
|
}
|
|
|
|
/* VALID_B64: fail if 'ch' is not a valid base64 character */
|
|
#define VALID_B64(ch) (((ch) >= 'A' && (ch) <= 'Z') || \
|
|
((ch) >= 'a' && (ch) <= 'z') || \
|
|
((ch) >= '0' && (ch) <= '9') || \
|
|
(ch) == '/' || (ch) == '+' || (ch) == '=')
|
|
|
|
/* DECODE_B64: decodes a valid base64 character. */
|
|
#define DECODE_B64(ch) ((ch) >= 'a' ? ((ch) + 26 - 'a') : \
|
|
((ch) >= 'A' ? ((ch) - 'A') : \
|
|
((ch) >= '0' ? ((ch) + 52 - '0') : \
|
|
((ch) == '+' ? 62 : 63))))
|
|
|
|
size_t ne_unbase64(const char *data, unsigned char **out)
|
|
{
|
|
size_t inlen = strlen(data);
|
|
unsigned char *outp;
|
|
const unsigned char *in;
|
|
|
|
if (inlen == 0 || (inlen % 4) != 0) return 0;
|
|
|
|
outp = *out = ne_malloc(inlen * 3 / 4);
|
|
|
|
for (in = (const unsigned char *)data; *in; in += 4) {
|
|
unsigned int tmp;
|
|
if (!VALID_B64(in[0]) || !VALID_B64(in[1]) || !VALID_B64(in[2]) ||
|
|
!VALID_B64(in[3]) || in[0] == '=' || in[1] == '=' ||
|
|
(in[2] == '=' && in[3] != '=')) {
|
|
ne_free(*out);
|
|
return 0;
|
|
}
|
|
tmp = (DECODE_B64(in[0]) & 0x3f) << 18 |
|
|
(DECODE_B64(in[1]) & 0x3f) << 12;
|
|
*outp++ = (tmp >> 16) & 0xff;
|
|
if (in[2] != '=') {
|
|
tmp |= (DECODE_B64(in[2]) & 0x3f) << 6;
|
|
*outp++ = (tmp >> 8) & 0xff;
|
|
if (in[3] != '=') {
|
|
tmp |= DECODE_B64(in[3]) & 0x3f;
|
|
*outp++ = tmp & 0xff;
|
|
}
|
|
}
|
|
}
|
|
|
|
return outp - *out;
|
|
}
|
|
|
|
char *ne_strclean(char *str)
|
|
{
|
|
char *pnt;
|
|
for (pnt = str; *pnt; pnt++)
|
|
if (iscntrl(*pnt) || !isprint(*pnt)) *pnt = ' ';
|
|
return str;
|
|
}
|
|
|
|
char *ne_strerror(int errnum, char *buf, size_t buflen)
|
|
{
|
|
#ifdef HAVE_STRERROR_R
|
|
#ifdef STRERROR_R_CHAR_P
|
|
/* glibc-style strerror_r which may-or-may-not use provided buffer. */
|
|
char *ret = strerror_r(errnum, buf, buflen);
|
|
if (ret != buf)
|
|
ne_strnzcpy(buf, ret, buflen);
|
|
#else /* POSIX-style strerror_r: */
|
|
strerror_r(errnum, buf, buflen);
|
|
#endif
|
|
#else /* no strerror_r: */
|
|
ne_strnzcpy(buf, strerror(errnum), buflen);
|
|
#endif
|
|
return buf;
|
|
}
|
|
|
|
|
|
/* Wrapper for ne_snprintf. */
|
|
size_t ne_snprintf(char *str, size_t size, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
#ifdef HAVE_TRIO
|
|
trio_vsnprintf(str, size, fmt, ap);
|
|
#else
|
|
vsnprintf(str, size, fmt, ap);
|
|
#endif
|
|
va_end(ap);
|
|
str[size-1] = '\0';
|
|
return strlen(str);
|
|
}
|
|
|
|
/* Wrapper for ne_vsnprintf. */
|
|
size_t ne_vsnprintf(char *str, size_t size, const char *fmt, va_list ap)
|
|
{
|
|
#ifdef HAVE_TRIO
|
|
trio_vsnprintf(str, size, fmt, ap);
|
|
#else
|
|
vsnprintf(str, size, fmt, ap);
|
|
#endif
|
|
str[size-1] = '\0';
|
|
return strlen(str);
|
|
}
|