#include "includes.h"
#include <sys/types.h>
#ifdef HAVE_LANGINFO_H
# include <langinfo.h>
#endif
#include <limits.h>
#include <locale.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS)
# include <vis.h>
#endif
#ifdef HAVE_WCHAR_H
# include <wchar.h>
#endif
#include "utf8.h"
static int dangerous_locale(void);
static int grow_dst(char **, size_t *, size_t, char **, size_t);
static int
dangerous_locale(void) {
char *loc;
loc = nl_langinfo(CODESET);
return strcmp(loc, "UTF-8") != 0 &&
strcmp(loc, "US-ASCII") != 0 &&
strcmp(loc, "ANSI_X3.4-1968") != 0 &&
strcmp(loc, "ISO8859-1") != 0 &&
strcmp(loc, "646") != 0 &&
strcmp(loc, "") != 0;
}
static int
grow_dst(char **dst, size_t *sz, size_t maxsz, char **dp, size_t need)
{
char *tp;
size_t tsz;
if (*dp + need < *dst + *sz)
return 0;
tsz = *sz + 128;
if (tsz > maxsz)
tsz = maxsz;
if ((tp = recallocarray(*dst, *sz, tsz, 1)) == NULL)
return -1;
*dp = tp + (*dp - *dst);
*dst = tp;
*sz = tsz;
return 0;
}
int
vasnmprintf(char **str, size_t maxsz, int *wp, const char *fmt, va_list ap)
{
char *src;
char *sp;
char *dst;
char *dp;
char *tp;
size_t sz;
wchar_t wc;
int len;
int ret;
int width;
int total_width, max_width, print;
src = NULL;
if ((ret = vasprintf(&src, fmt, ap)) <= 0)
goto fail;
sz = strlen(src) + 1;
if ((dst = malloc(sz)) == NULL) {
free(src);
ret = -1;
goto fail;
}
if (maxsz > INT_MAX)
maxsz = INT_MAX;
sp = src;
dp = dst;
ret = 0;
print = 1;
total_width = 0;
max_width = wp == NULL ? INT_MAX : *wp;
while (*sp != '\0') {
if ((len = mbtowc(&wc, sp, MB_CUR_MAX)) == -1) {
(void)mbtowc(NULL, NULL, MB_CUR_MAX);
if (dangerous_locale()) {
ret = -1;
break;
}
len = 1;
width = -1;
} else if (wp == NULL &&
(wc == L'\n' || wc == L'\r' || wc == L'\t')) {
width = 0;
} else if ((width = wcwidth(wc)) == -1 &&
dangerous_locale()) {
ret = -1;
break;
}
if (width >= 0) {
if (print && (dp - dst >= (int)maxsz - len ||
total_width > max_width - width))
print = 0;
if (print) {
if (grow_dst(&dst, &sz, maxsz,
&dp, len) == -1) {
ret = -1;
break;
}
total_width += width;
memcpy(dp, sp, len);
dp += len;
}
sp += len;
if (ret >= 0)
ret += len;
continue;
}
while (len > 0) {
if (print && (dp - dst >= (int)maxsz - 4 ||
total_width > max_width - 4))
print = 0;
if (print) {
if (grow_dst(&dst, &sz, maxsz,
&dp, 4) == -1) {
ret = -1;
break;
}
tp = vis(dp, *sp, VIS_OCTAL | VIS_ALL, 0);
width = tp - dp;
total_width += width;
dp = tp;
} else
width = 4;
len--;
sp++;
if (ret >= 0)
ret += width;
}
if (len > 0)
break;
}
free(src);
*dp = '\0';
*str = dst;
if (wp != NULL)
*wp = total_width;
if (ret < (int)maxsz && !print)
ret = -1;
return ret;
fail:
if (wp != NULL)
*wp = 0;
if (ret == 0) {
*str = src;
return 0;
} else {
*str = NULL;
return -1;
}
}
int
snmprintf(char *str, size_t sz, int *wp, const char *fmt, ...)
{
va_list ap;
char *cp = NULL;
int ret;
va_start(ap, fmt);
ret = vasnmprintf(&cp, sz, wp, fmt, ap);
va_end(ap);
if (cp != NULL) {
(void)strlcpy(str, cp, sz);
free(cp);
} else
*str = '\0';
return ret;
}
int
asmprintf(char **outp, size_t sz, int *wp, const char *fmt, ...)
{
va_list ap;
int ret;
*outp = NULL;
va_start(ap, fmt);
ret = vasnmprintf(outp, sz, wp, fmt, ap);
va_end(ap);
return ret;
}
int
vfmprintf(FILE *stream, const char *fmt, va_list ap)
{
char *str = NULL;
int ret;
if ((ret = vasnmprintf(&str, INT_MAX, NULL, fmt, ap)) < 0) {
free(str);
return -1;
}
if (fputs(str, stream) == EOF)
ret = -1;
free(str);
return ret;
}
int
fmprintf(FILE *stream, const char *fmt, ...)
{
va_list ap;
int ret;
va_start(ap, fmt);
ret = vfmprintf(stream, fmt, ap);
va_end(ap);
return ret;
}
int
mprintf(const char *fmt, ...)
{
va_list ap;
int ret;
va_start(ap, fmt);
ret = vfmprintf(stdout, fmt, ap);
va_end(ap);
return ret;
}
void
msetlocale(void)
{
const char *vars[] = { "LC_ALL", "LC_CTYPE", "LANG", NULL };
char *cp;
int i;
for (i = 0; vars[i] != NULL; i++) {
if ((cp = getenv(vars[i])) == NULL)
continue;
if (strncasecmp(cp, "TR", 2) != 0)
break;
if ((strcasestr(cp, "UTF-8") != NULL ||
strcasestr(cp, "UTF8") != NULL) &&
(setlocale(LC_CTYPE, "C.UTF-8") != NULL ||
setlocale(LC_CTYPE, "POSIX.UTF-8") != NULL))
return;
setlocale(LC_CTYPE, "C");
return;
}
setlocale(LC_CTYPE, "");
}