musl/src/time/strftime.c
Rich Felker f63b8c8c45 fix off-by-one length failure in strftime/wcsftime and improve error behavior
these functions were spuriously failing in the case where the buffer
size was exactly the number of bytes/characters to be written,
including null termination. since these functions do not have defined
error conditions other than buffer size, a reasonable application may
fail to check the return value when the format string and buffer size
are known to be valid; such an application could then attempt to use a
non-terminated buffer.

in addition to fixing the bug, I have changed the error handling
behavior so that these functions always null-terminate the output
except in the case where the buffer size is zero, and so that they
always write as many characters as possible before failing, rather
than dropping whole fields that do not fit. this actually simplifies
the logic somewhat anyway.
2013-11-26 20:01:21 -05:00

269 lines
5.4 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <langinfo.h>
#include <locale.h>
#include <time.h>
#include <limits.h>
#include "libc.h"
#include "time_impl.h"
const char *__nl_langinfo_l(nl_item, locale_t);
static int is_leap(int y)
{
/* Avoid overflow */
if (y>INT_MAX-1900) y -= 2000;
y += 1900;
return !(y%4) && ((y%100) || !(y%400));
}
static int week_num(const struct tm *tm)
{
int val = (tm->tm_yday + 7 - (tm->tm_wday+6)%7) / 7;
/* If 1 Jan is just 1-3 days past Monday,
* the previous week is also in this year. */
if ((tm->tm_wday - tm->tm_yday - 2 + 371) % 7 <= 2)
val++;
if (!val) {
val = 52;
/* If 31 December of prev year a Thursday,
* or Friday of a leap year, then the
* prev year has 53 weeks. */
int dec31 = (tm->tm_wday - tm->tm_yday - 1 + 7) % 7;
if (dec31 == 4 || (dec31 == 5 && is_leap(tm->tm_year%400-1)))
val++;
} else if (val == 53) {
/* If 1 January is not a Thursday, and not
* a Wednesday of a leap year, then this
* year has only 52 weeks. */
int jan1 = (tm->tm_wday - tm->tm_yday + 371) % 7;
if (jan1 != 4 && (jan1 != 3 || !is_leap(tm->tm_year)))
val = 1;
}
return val;
}
const char *__tm_to_tzname(const struct tm *);
size_t __strftime_l(char *restrict, size_t, const char *restrict, const struct tm *restrict, locale_t);
const char *__strftime_fmt_1(char (*s)[100], size_t *l, int f, const struct tm *tm, locale_t loc)
{
nl_item item;
long long val;
const char *fmt;
int width = 2;
switch (f) {
case 'a':
item = ABDAY_1 + tm->tm_wday;
goto nl_strcat;
case 'A':
item = DAY_1 + tm->tm_wday;
goto nl_strcat;
case 'h':
case 'b':
item = ABMON_1 + tm->tm_mon;
goto nl_strcat;
case 'B':
item = MON_1 + tm->tm_mon;
goto nl_strcat;
case 'c':
item = D_T_FMT;
goto nl_strftime;
case 'C':
val = (1900LL+tm->tm_year) / 100;
goto number;
case 'd':
val = tm->tm_mday;
goto number;
case 'D':
fmt = "%m/%d/%y";
goto recu_strftime;
case 'e':
*l = snprintf(*s, sizeof *s, "%2d", tm->tm_mday);
return *s;
case 'F':
fmt = "%Y-%m-%d";
goto recu_strftime;
case 'g':
case 'G':
val = tm->tm_year + 1900LL;
if (tm->tm_yday < 3 && week_num(tm) != 1) val--;
else if (tm->tm_yday > 360 && week_num(tm) == 1) val++;
if (f=='g') val %= 100;
else width = 4;
goto number;
case 'H':
val = tm->tm_hour;
goto number;
case 'I':
val = tm->tm_hour;
if (!val) val = 12;
else if (val > 12) val -= 12;
goto number;
case 'j':
val = tm->tm_yday+1;
width = 3;
goto number;
case 'm':
val = tm->tm_mon+1;
goto number;
case 'M':
val = tm->tm_min;
goto number;
case 'n':
*l = 1;
return "\n";
case 'p':
item = tm->tm_hour >= 12 ? PM_STR : AM_STR;
goto nl_strcat;
case 'r':
item = T_FMT_AMPM;
goto nl_strftime;
case 'R':
fmt = "%H:%M";
goto recu_strftime;
case 's':
val = __tm_to_secs(tm) + tm->__tm_gmtoff;
goto number;
case 'S':
val = tm->tm_sec;
goto number;
case 't':
*l = 1;
return "\t";
case 'T':
fmt = "%H:%M:%S";
goto recu_strftime;
case 'u':
val = tm->tm_wday ? tm->tm_wday : 7;
width = 1;
goto number;
case 'U':
val = (tm->tm_yday + 7 - tm->tm_wday) / 7;
goto number;
case 'W':
val = (tm->tm_yday + 7 - (tm->tm_wday+6)%7) / 7;
goto number;
case 'V':
val = week_num(tm);
goto number;
case 'w':
val = tm->tm_wday;
width = 1;
goto number;
case 'x':
item = D_FMT;
goto nl_strftime;
case 'X':
item = T_FMT;
goto nl_strftime;
case 'y':
val = tm->tm_year % 100;
goto number;
case 'Y':
val = tm->tm_year + 1900;
if (val >= 10000) {
*l = snprintf(*s, sizeof *s, "+%lld", val);
return *s;
}
width = 4;
goto number;
case 'z':
if (tm->tm_isdst < 0) {
*l = 0;
return "";
}
*l = snprintf(*s, sizeof *s, "%+.2d%.2d",
(-tm->__tm_gmtoff)/3600,
abs(tm->__tm_gmtoff%3600)/60);
return *s;
case 'Z':
if (tm->tm_isdst < 0) {
*l = 0;
return "";
}
fmt = __tm_to_tzname(tm);
goto string;
case '%':
*l = 1;
return "%";
default:
return 0;
}
number:
*l = snprintf(*s, sizeof *s, "%0*lld", width, val);
return *s;
nl_strcat:
fmt = __nl_langinfo_l(item, loc);
string:
*l = strlen(fmt);
return fmt;
nl_strftime:
fmt = __nl_langinfo_l(item, loc);
recu_strftime:
*l = __strftime_l(*s, sizeof *s, fmt, tm, loc);
if (!*l) return 0;
return *s;
}
size_t __strftime_l(char *restrict s, size_t n, const char *restrict f, const struct tm *restrict tm, locale_t loc)
{
size_t l, k;
char buf[100];
char *p;
const char *t;
int plus;
unsigned long width;
for (l=0; l<n; f++) {
if (!*f) {
s[l] = 0;
return l;
}
if (*f != '%') {
s[l++] = *f;
continue;
}
f++;
if ((plus = (*f == '+'))) f++;
width = strtoul(f, &p, 10);
if (*p == 'C' || *p == 'F' || *p == 'G' || *p == 'Y') {
if (!width && p!=f) width = 1;
} else {
width = 0;
}
f = p;
if (*f == 'E' || *f == 'O') f++;
t = __strftime_fmt_1(&buf, &k, *f, tm, loc);
if (!t) break;
if (width) {
for (; *t=='+' || *t=='-' || (*t=='0'&&t[1]); t++, k--);
width--;
if (plus && tm->tm_year >= 10000-1900)
s[l++] = '+';
else if (tm->tm_year < -1900)
s[l++] = '-';
else
width++;
for (; width > k && l < n; width--)
s[l++] = '0';
}
if (k > n-l) k = n-l;
memcpy(s+l, t, k);
l += k;
}
if (n) {
if (l==n) l=n-1;
s[l] = 0;
}
return 0;
}
size_t strftime(char *restrict s, size_t n, const char *restrict f, const struct tm *restrict tm)
{
return __strftime_l(s, n, f, tm, 0);
}
weak_alias(__strftime_l, strftime_l);