Avoiding round-tripping via mbcs on windows in strftime()

This commit is contained in:
Kovid Goyal 2018-02-14 03:14:50 +05:30
parent 1335ca8b54
commit ae4cd7c902
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 52 additions and 70 deletions

View File

@ -543,9 +543,9 @@ def strftime(fmt, t=None):
t[0] = replacement t[0] = replacement
ans = None ans = None
if iswindows: if iswindows:
if isinstance(fmt, unicode): if isinstance(fmt, bytes):
fmt = fmt.encode('mbcs', 'replace') fmt = fmt.decode('mbcs', 'replace')
fmt = fmt.replace(b'%e', b'%#d') fmt = fmt.replace(u'%e', u'%#d')
ans = plugins['winutil'][0].strftime(fmt, t) ans = plugins['winutil'][0].strftime(fmt, t)
else: else:
ans = time.strftime(fmt, t).decode(preferred_encoding, 'replace') ans = time.strftime(fmt, t).decode(preferred_encoding, 'replace')

View File

@ -300,84 +300,69 @@ winutil_strftime(PyObject *self, PyObject *args)
{ {
PyObject *tup = NULL; PyObject *tup = NULL;
struct tm buf; struct tm buf;
const char *_fmt; size_t buflen;
size_t fmtlen, buflen; wchar_t *outbuf = NULL;
wchar_t *outbuf = NULL, *fmt = NULL; Py_UNICODE *fmt = NULL;
int fmtlen;
size_t i; size_t i;
memset((void *) &buf, '\0', sizeof(buf)); memset((void *) &buf, 0, sizeof(buf));
if (!PyArg_ParseTuple(args, "s|O:strftime", &_fmt, &tup)) if (!PyArg_ParseTuple(args, "u#|O:strftime", &fmt, &fmtlen, &tup)) return NULL;
return NULL;
if (mbstowcs_s(&fmtlen, NULL, 0, _fmt, strlen(_fmt)) != 0) {
PyErr_SetString(PyExc_ValueError, "Failed to convert fmt to wchar");
return NULL;
}
fmt = (wchar_t *)PyMem_Malloc((fmtlen+2)*sizeof(wchar_t));
if (fmt == NULL) return PyErr_NoMemory();
if (mbstowcs_s(&fmtlen, fmt, fmtlen+2, _fmt, strlen(_fmt)) != 0) {
PyErr_SetString(PyExc_ValueError, "Failed to convert fmt to wchar");
goto end;
}
if (tup == NULL) { if (tup == NULL) {
time_t tt = time(NULL); time_t tt = time(NULL);
if(localtime_s(&buf, &tt) != 0) { if(localtime_s(&buf, &tt) != 0) {
PyErr_SetString(PyExc_ValueError, "Failed to get localtime()"); PyErr_SetString(PyExc_ValueError, "Failed to get localtime()");
goto end; return NULL;
} }
} else if (!gettmarg(tup, &buf)) } else if (!gettmarg(tup, &buf)) return NULL;
goto end;
if (buf.tm_mon == -1) if (buf.tm_mon == -1) buf.tm_mon = 0;
buf.tm_mon = 0;
else if (buf.tm_mon < 0 || buf.tm_mon > 11) { else if (buf.tm_mon < 0 || buf.tm_mon > 11) {
PyErr_SetString(PyExc_ValueError, "month out of range"); PyErr_SetString(PyExc_ValueError, "month out of range");
goto end; return NULL;
} }
if (buf.tm_mday == 0) if (buf.tm_mday == 0) buf.tm_mday = 1;
buf.tm_mday = 1;
else if (buf.tm_mday < 0 || buf.tm_mday > 31) { else if (buf.tm_mday < 0 || buf.tm_mday > 31) {
PyErr_SetString(PyExc_ValueError, "day of month out of range"); PyErr_SetString(PyExc_ValueError, "day of month out of range");
goto end; return NULL;
} }
if (buf.tm_hour < 0 || buf.tm_hour > 23) { if (buf.tm_hour < 0 || buf.tm_hour > 23) {
PyErr_SetString(PyExc_ValueError, "hour out of range"); PyErr_SetString(PyExc_ValueError, "hour out of range");
goto end; return NULL;
} }
if (buf.tm_min < 0 || buf.tm_min > 59) { if (buf.tm_min < 0 || buf.tm_min > 59) {
PyErr_SetString(PyExc_ValueError, "minute out of range"); PyErr_SetString(PyExc_ValueError, "minute out of range");
goto end; return NULL;
} }
if (buf.tm_sec < 0 || buf.tm_sec > 61) { if (buf.tm_sec < 0 || buf.tm_sec > 61) {
PyErr_SetString(PyExc_ValueError, "seconds out of range"); PyErr_SetString(PyExc_ValueError, "seconds out of range");
goto end; return NULL;
} }
/* tm_wday does not need checking of its upper-bound since taking /* tm_wday does not need checking of its upper-bound since taking
``% 7`` in gettmarg() automatically restricts the range. */ ``% 7`` in gettmarg() automatically restricts the range. */
if (buf.tm_wday < 0) { if (buf.tm_wday < 0) {
PyErr_SetString(PyExc_ValueError, "day of week out of range"); PyErr_SetString(PyExc_ValueError, "day of week out of range");
goto end; return NULL;
} }
if (buf.tm_yday == -1) if (buf.tm_yday == -1) buf.tm_yday = 0;
buf.tm_yday = 0;
else if (buf.tm_yday < 0 || buf.tm_yday > 365) { else if (buf.tm_yday < 0 || buf.tm_yday > 365) {
PyErr_SetString(PyExc_ValueError, "day of year out of range"); PyErr_SetString(PyExc_ValueError, "day of year out of range");
goto end; return NULL;
} }
if (buf.tm_isdst < -1 || buf.tm_isdst > 1) { if (buf.tm_isdst < -1 || buf.tm_isdst > 1) {
PyErr_SetString(PyExc_ValueError, PyErr_SetString(PyExc_ValueError,
"daylight savings flag out of range"); "daylight savings flag out of range");
goto end; return NULL;
} }
for (i = 5*fmtlen; ; i += i) { for (i = 5*(unsigned int)fmtlen; ; i += i) {
outbuf = (wchar_t *)PyMem_Malloc(i*sizeof(wchar_t)); outbuf = (wchar_t *)PyMem_Malloc(i*sizeof(wchar_t));
if (outbuf == NULL) { if (outbuf == NULL) {
PyErr_NoMemory(); goto end; PyErr_NoMemory(); return NULL;
} }
buflen = wcsftime(outbuf, i, fmt, &buf); buflen = wcsftime(outbuf, i, fmt, &buf);
if (buflen > 0 || i >= 256 * fmtlen) { if (buflen > 0 || i >= 256 * (unsigned int)fmtlen) {
/* If the buffer is 256 times as long as the format, /* If the buffer is 256 times as long as the format,
it's probably not failing for lack of room! it's probably not failing for lack of room!
More likely, the format yields an empty result, More likely, the format yields an empty result,
@ -385,20 +370,17 @@ winutil_strftime(PyObject *self, PyObject *args)
is unknown. */ is unknown. */
PyObject *ret; PyObject *ret;
ret = PyUnicode_FromWideChar(outbuf, buflen); ret = PyUnicode_FromWideChar(outbuf, buflen);
PyMem_Free(outbuf); PyMem_Free(fmt); PyMem_Free(outbuf);
return ret; return ret;
} }
PyMem_Free(outbuf); PyMem_Free(outbuf);
#if defined _MSC_VER && _MSC_VER >= 1400 && defined(__STDC_SECURE_LIB__)
/* VisualStudio .NET 2005 does this properly */ /* VisualStudio .NET 2005 does this properly */
if (buflen == 0 && errno == EINVAL) { if (buflen == 0 && errno == EINVAL) {
PyErr_SetString(PyExc_ValueError, "Invalid format string"); PyErr_SetString(PyExc_ValueError, "Invalid format string");
goto end; return NULL;
} }
#endif
} }
end: return NULL;
PyMem_Free(fmt); return NULL;
} }