@@ -7829,14 +7829,34 @@ PyUnicode_DecodeMBCS(const char *s,
78297829 return PyUnicode_DecodeMBCSStateful (s , size , errors , NULL );
78307830}
78317831
7832+ /* WideCharToMultiByte() only supports some code pages with flags=0 and
7833+ lpUsedDefaultChar=NULL, see its documentation. Unencodable characters
7834+ are silently replaced then, so they are detected by decoding the result
7835+ back (see encode_code_page_lossy()). */
7836+ static int
7837+ encode_code_page_zero_flags (UINT code_page )
7838+ {
7839+ switch (code_page ) {
7840+ case 42 : /* Symbol */
7841+ case 50220 : case 50221 : case 50222 : /* ISO-2022-JP */
7842+ case 50225 : /* ISO-2022-KR */
7843+ case 50227 : case 50229 : /* ISO-2022-CN */
7844+ case 52936 : /* HZ-GB2312 */
7845+ case 54936 : /* GB18030 */
7846+ case CP_UTF7 :
7847+ return 1 ;
7848+ default :
7849+ return 57002 <= code_page && code_page <= 57011 ; /* ISCII */
7850+ }
7851+ }
7852+
78327853static DWORD
78337854encode_code_page_flags (UINT code_page , const char * errors )
78347855{
78357856 if (code_page == CP_UTF8 ) {
78367857 return WC_ERR_INVALID_CHARS ;
78377858 }
7838- else if (code_page == CP_UTF7 ) {
7839- /* CP_UTF7 only supports flags=0 */
7859+ else if (encode_code_page_zero_flags (code_page )) {
78407860 return 0 ;
78417861 }
78427862 else {
@@ -7847,6 +7867,56 @@ encode_code_page_flags(UINT code_page, const char *errors)
78477867 }
78487868}
78497869
7870+ /* Code pages encoded without WC_NO_BEST_FIT_CHARS and lpUsedDefaultChar
7871+ need the result to be decoded back to detect replaced characters.
7872+ UTF-7 encodes every character, so it is exempted. */
7873+ static int
7874+ encode_code_page_check_lossy (UINT code_page )
7875+ {
7876+ return code_page != CP_UTF7 && encode_code_page_zero_flags (code_page );
7877+ }
7878+
7879+ /*
7880+ * Check whether some characters were replaced when encoding, i.e. whether
7881+ * the encoded string is not decoded back into the original string.
7882+ *
7883+ * Returns 1 if some characters were replaced, 0 if not, or raise an OSError
7884+ * and returns -1 on error.
7885+ */
7886+ static int
7887+ encode_code_page_lossy (UINT code_page ,
7888+ const wchar_t * p , int size ,
7889+ const char * out , int outsize )
7890+ {
7891+ wchar_t * decoded ;
7892+ int n , lossy ;
7893+
7894+ n = MultiByteToWideChar (code_page , 0 , out , outsize , NULL , 0 );
7895+ if (n <= 0 ) {
7896+ PyErr_SetFromWindowsErr (0 );
7897+ return -1 ;
7898+ }
7899+ if (n != size ) {
7900+ return 1 ;
7901+ }
7902+
7903+ decoded = PyMem_New (wchar_t , n );
7904+ if (decoded == NULL ) {
7905+ PyErr_NoMemory ();
7906+ return -1 ;
7907+ }
7908+ n = MultiByteToWideChar (code_page , 0 , out , outsize , decoded , n );
7909+ if (n <= 0 ) {
7910+ PyErr_SetFromWindowsErr (0 );
7911+ lossy = -1 ;
7912+ }
7913+ else {
7914+ lossy = (memcmp (decoded , p , (size_t )size * sizeof (wchar_t )) != 0 );
7915+ }
7916+ PyMem_Free (decoded );
7917+ return lossy ;
7918+ }
7919+
78507920/*
78517921 * Encode a Unicode string to a Windows code page into a byte string in strict
78527922 * mode.
@@ -7866,14 +7936,15 @@ encode_code_page_strict(UINT code_page, PyBytesWriter **writer,
78667936 Py_ssize_t size ;
78677937 const DWORD flags = encode_code_page_flags (code_page , NULL );
78687938 char * out ;
7939+ Py_ssize_t start ;
78697940 /* Create a substring so that we can get the UTF-16 representation
78707941 of just the slice under consideration. */
78717942 PyObject * substring ;
78727943 int ret = -1 ;
78737944
78747945 assert (len > 0 );
78757946
7876- if (code_page != CP_UTF8 && code_page != CP_UTF7 )
7947+ if (code_page != CP_UTF8 && ! encode_code_page_zero_flags ( code_page ) )
78777948 pusedDefaultChar = & usedDefaultChar ;
78787949 else
78797950 pusedDefaultChar = NULL ;
@@ -7903,6 +7974,7 @@ encode_code_page_strict(UINT code_page, PyBytesWriter **writer,
79037974
79047975 if (* writer == NULL ) {
79057976 /* Create string object */
7977+ start = 0 ;
79067978 * writer = PyBytesWriter_Create (outsize );
79077979 if (* writer == NULL ) {
79087980 goto done ;
@@ -7911,11 +7983,11 @@ encode_code_page_strict(UINT code_page, PyBytesWriter **writer,
79117983 }
79127984 else {
79137985 /* Extend string object */
7914- Py_ssize_t n = PyBytesWriter_GetSize (* writer );
7986+ start = PyBytesWriter_GetSize (* writer );
79157987 if (PyBytesWriter_Grow (* writer , outsize ) < 0 ) {
79167988 goto done ;
79177989 }
7918- out = (char * )PyBytesWriter_GetData (* writer ) + n ;
7990+ out = (char * )PyBytesWriter_GetData (* writer ) + start ;
79197991 }
79207992
79217993 /* Do the conversion */
@@ -7929,6 +8001,22 @@ encode_code_page_strict(UINT code_page, PyBytesWriter **writer,
79298001 ret = -2 ;
79308002 goto done ;
79318003 }
8004+ if (encode_code_page_check_lossy (code_page )) {
8005+ int lossy = encode_code_page_lossy (code_page , p , (int )size ,
8006+ out , outsize );
8007+ if (lossy < 0 ) {
8008+ goto done ;
8009+ }
8010+ if (lossy ) {
8011+ /* Drop the lossy result, it will be re-encoded with an error
8012+ handler. */
8013+ if (PyBytesWriter_Resize (* writer , start ) < 0 ) {
8014+ goto done ;
8015+ }
8016+ ret = -2 ;
8017+ goto done ;
8018+ }
8019+ }
79328020 ret = 0 ;
79338021
79348022done :
@@ -7962,8 +8050,10 @@ encode_code_page_errors(UINT code_page, PyBytesWriter **writer,
79628050 /* Ideally, we should get reason from FormatMessage. This is the Windows
79638051 2000 English version of the message. */
79648052 const char * reason = "invalid character" ;
7965- /* 4=maximum length of a UTF-8 sequence */
7966- char buffer [4 ];
8053+ /* 4=maximum length of a UTF-8 sequence, 16 is enough for a character
8054+ encoded together with escape sequences (e.g. in ISO-2022) */
8055+ char buffer [16 ];
8056+ int maxcharsize ;
79678057 BOOL usedDefaultChar = FALSE, * pusedDefaultChar ;
79688058 Py_ssize_t outsize ;
79698059 char * out ;
@@ -7993,16 +8083,21 @@ encode_code_page_errors(UINT code_page, PyBytesWriter **writer,
79938083 return -1 ;
79948084 }
79958085
7996- if (code_page != CP_UTF8 && code_page != CP_UTF7 )
8086+ if (code_page != CP_UTF8 && ! encode_code_page_zero_flags ( code_page ) )
79978087 pusedDefaultChar = & usedDefaultChar ;
79988088 else
79998089 pusedDefaultChar = NULL ;
80008090
8001- if (Py_ARRAY_LENGTH (buffer ) > PY_SSIZE_T_MAX / insize ) {
8091+ /* Only code pages encoded with flags=0 can need more than 4 bytes
8092+ per character. */
8093+ maxcharsize = encode_code_page_zero_flags (code_page )
8094+ ? (int )Py_ARRAY_LENGTH (buffer ) : 4 ;
8095+
8096+ if (maxcharsize > PY_SSIZE_T_MAX / insize ) {
80028097 PyErr_NoMemory ();
80038098 goto error ;
80048099 }
8005- outsize = insize * Py_ARRAY_LENGTH ( buffer ) ;
8100+ outsize = insize * maxcharsize ;
80068101
80078102 if (* writer == NULL ) {
80088103 /* Create string object */
@@ -8039,10 +8134,18 @@ encode_code_page_errors(UINT code_page, PyBytesWriter **writer,
80398134
80408135 outsize = WideCharToMultiByte (code_page , flags ,
80418136 chars , charsize ,
8042- buffer , Py_ARRAY_LENGTH ( buffer ) ,
8137+ buffer , maxcharsize ,
80438138 NULL , pusedDefaultChar );
80448139 if (outsize > 0 ) {
8045- if (pusedDefaultChar == NULL || !(* pusedDefaultChar ))
8140+ int lossy = (pusedDefaultChar != NULL && * pusedDefaultChar );
8141+ if (!lossy && encode_code_page_check_lossy (code_page )) {
8142+ lossy = encode_code_page_lossy (code_page , chars , charsize ,
8143+ buffer , (int )outsize );
8144+ if (lossy < 0 ) {
8145+ goto error ;
8146+ }
8147+ }
8148+ if (!lossy )
80468149 {
80478150 pos ++ ;
80488151 memcpy (out , buffer , outsize );
0 commit comments