#include "Python.h" #include "pycore_codecs.h" #include "pycore_global_strings.h" #include "pycore_runtime.h" #include "errcode.h" #include "reader_internal.h" #include "helpers.h" #include "../lexer/state.h" #define UNICODE_BOM 0xFEFF #define UTF16_LE_BOM "\xff\xfe" #define UTF16_BE_BOM "\xfe\xff" #define UTF16_BOM_LENGTH ((Py_ssize_t)(sizeof(UTF16_LE_BOM) - 1)) #define UTF8_BOM "\xef\xbb\xbf" #define UTF8_BOM_LENGTH ((Py_ssize_t)(sizeof(UTF8_BOM) - 1)) char * _PyTok_CopyBytes(const char *data, Py_ssize_t len) { if (len < 0 || len == PY_SSIZE_T_MAX) { PyErr_NoMemory(); return NULL; } char *copy = PyMem_Malloc((size_t)len + 1); if (copy == NULL) { PyErr_NoMemory(); return NULL; } memcpy(copy, data, (size_t)len); copy[len] = '\0'; return copy; } static void chunk_release_data(_PyTok_Chunk *chunk) { switch (chunk->ownership) { case _PYTOK_CHUNK_BORROWED: break; case _PYTOK_CHUNK_PYMEM: PyMem_Free(chunk->data); break; case _PYTOK_CHUNK_PYOBJECT: Py_DECREF(chunk->owner); break; } } void _PyTok_ChunkClear(_PyTok_Chunk *chunk) { chunk_release_data(chunk); *chunk = (_PyTok_Chunk){0}; } static int chunk_set_unicode(struct tok_state *tok, _PyTok_Chunk *chunk, PyObject *unicode, int strip_bom) { Py_ssize_t utf8_len; const char *utf8 = PyUnicode_AsUTF8AndSize(unicode, &utf8_len); if (utf8 == NULL) { Py_DECREF(unicode); tok->done = PyErr_ExceptionMatches(PyExc_MemoryError) ? E_NOMEM : E_DECODE; return -1; } if (strip_bom && PyUnicode_GET_LENGTH(unicode) > 0 && PyUnicode_ReadChar(unicode, 0) == UNICODE_BOM) { utf8 += UTF8_BOM_LENGTH; utf8_len -= UTF8_BOM_LENGTH; } chunk_release_data(chunk); chunk->owner = unicode; chunk->data = (char *)utf8; chunk->len = utf8_len; chunk->ownership = _PYTOK_CHUNK_PYOBJECT; return 0; } char * _PyTok_NormalizeNewlines(const char *data, Py_ssize_t len, Py_ssize_t *out_len) { if (len < 0 || len == PY_SSIZE_T_MAX) { PyErr_NoMemory(); return NULL; } char *result = PyMem_Malloc((size_t)len + 1); if (result == NULL) { PyErr_NoMemory(); return NULL; } Py_ssize_t write = 0; for (Py_ssize_t read = 0; read < len; read++) { char c = data[read]; if (c == '\r') { if (read + 1 < len && data[read + 1] == '\n') { read++; } c = '\n'; } result[write++] = c; } result[write] = '\0'; *out_len = write; return result; } int _PyTok_SetEncoding(struct tok_state *tok, const char *encoding) { char *copy = _PyTok_CopyBytes(encoding, strlen(encoding)); if (copy == NULL) { tok->done = E_NOMEM; return -1; } PyMem_Free(tok->reader->encoding); tok->reader->encoding = copy; return 0; } static int find_cookie(const char *line, Py_ssize_t len, char **encoding, int *scan_next) { Py_ssize_t i = 0; *encoding = NULL; *scan_next = 1; for (; i < len; i++) { if (line[i] == '#') { break; } if (line[i] == '\n' || line[i] == '\r') { return 0; } if (line[i] != ' ' && line[i] != '\t' && line[i] != '\f') { *scan_next = 0; return 0; } } const Py_ssize_t marker_len = sizeof("coding") - 1; for (; i < len - marker_len; i++) { if (memcmp(line + i, "coding", marker_len) != 0) { continue; } const char *cursor = line + i + marker_len; if (*cursor != ':' && *cursor != '=') { continue; } do { cursor++; } while (cursor < line + len && (*cursor == ' ' || *cursor == '\t')); const char *start = cursor; while (cursor < line + len && (Py_ISALNUM(*cursor) || *cursor == '-' || *cursor == '_' || *cursor == '.')) { cursor++; } if (cursor == start) { continue; } Py_ssize_t encoding_len = cursor - start; // Include the longest recognized prefix and its NUL terminator. char normalized[sizeof("iso-latin-1-")]; const Py_ssize_t prefix_len = Py_MIN( encoding_len, (Py_ssize_t)sizeof(normalized) - 1); Py_ssize_t n; for (n = 0; n < prefix_len; n++) { normalized[n] = start[n] == '_' ? '-' : Py_TOLOWER(start[n]); } normalized[n] = '\0'; const char *canonical = start; if (strcmp(normalized, "utf-8") == 0 || strncmp(normalized, "utf-8-", sizeof("utf-8-") - 1) == 0) { canonical = "utf-8"; } else if (strcmp(normalized, "latin-1") == 0 || strcmp(normalized, "iso-8859-1") == 0 || strcmp(normalized, "iso-latin-1") == 0 || strncmp(normalized, "latin-1-", sizeof("latin-1-") - 1) == 0 || strncmp(normalized, "iso-8859-1-", sizeof("iso-8859-1-") - 1) == 0 || strncmp(normalized, "iso-latin-1-", sizeof("iso-latin-1-") - 1) == 0) { canonical = "iso-8859-1"; } if (canonical != start) { encoding_len = strlen(canonical); } *encoding = _PyTok_CopyBytes(canonical, encoding_len); if (*encoding == NULL) { return -1; } *scan_next = 0; return 0; } return 0; } _PyTok_EncodingResult _PyTok_DetectEncoding(struct tok_state *tok, const _PyTok_Chunk *first, const _PyTok_Chunk *second, int final, Py_ssize_t *bom_len) { int bom = first->len >= UTF8_BOM_LENGTH && memcmp(first->data, UTF8_BOM, UTF8_BOM_LENGTH) == 0; *bom_len = bom ? UTF8_BOM_LENGTH : 0; char *cookie = NULL; int scan_next = 0; int cookie_line = 1; const char *first_data = first->data + *bom_len; Py_ssize_t first_len = first->len - *bom_len; if (find_cookie(first_data, first_len, &cookie, &scan_next) < 0) { return _PYTOK_ENCODING_ERROR; } if (cookie == NULL && scan_next && second != NULL) { if (find_cookie(second->data, second->len, &cookie, &scan_next) < 0) { return _PYTOK_ENCODING_ERROR; } cookie_line = 2; } else if (cookie == NULL && scan_next && !final) { return _PYTOK_ENCODING_NEED_SECOND_LINE; } if (cookie == NULL) { if (bom && _PyTok_SetEncoding(tok, "utf-8") < 0) { return _PYTOK_ENCODING_ERROR; } return _PYTOK_ENCODING_DONE; } if (bom && strcmp(cookie, "utf-8") != 0) { const _PyTok_Chunk *line = cookie_line == 2 ? second : first; const char *line_data = line->data + (cookie_line == 1 ? *bom_len : 0); Py_ssize_t line_len = line->len - (cookie_line == 1 ? *bom_len : 0); int end_col = (int)Py_MIN(line_len, INT_MAX); if (end_col > 0 && (line_data[end_col - 1] == '\n' || line_data[end_col - 1] == '\r')) { end_col--; } _PyTokenizer_syntaxerror_at( tok, line_data, 0, cookie_line, 0, end_col, "encoding problem: %s with BOM", cookie); PyMem_Free(cookie); return _PYTOK_ENCODING_ERROR; } PyMem_Free(tok->reader->encoding); tok->reader->encoding = cookie; return _PYTOK_ENCODING_DONE; } int _PyTok_DecodeOnce(struct tok_state *tok, _PyTok_Chunk *chunk, const char *encoding, const char *errors) { PyObject *unicode = PyUnicode_Decode( chunk->data, chunk->len, encoding, errors); if (unicode == NULL) { tok->done = PyErr_ExceptionMatches(PyExc_MemoryError) ? E_NOMEM : E_DECODE; return -1; } return chunk_set_unicode(tok, chunk, unicode, 0); } static Py_ssize_t raw_line_length(const char *data, Py_ssize_t len) { for (Py_ssize_t i = 0; i < len; i++) { if (data[i] == '\n') { return i + 1; } if (data[i] == '\r') { return i + 1 < len && data[i + 1] == '\n' ? i + 2 : i + 1; } } return len; } static int store_prepared_source(struct tok_state *tok, const char *data, Py_ssize_t len, int preserve_crlf, int add_final_newline) { Py_ssize_t pos = 0; while (pos < len) { Py_ssize_t raw_line_len; if (preserve_crlf) { const char *newline = memchr(data + pos, '\n', len - pos); raw_line_len = newline == NULL ? len - pos : newline - data - pos + 1; } else { raw_line_len = raw_line_length(data + pos, len - pos); } int terminated = preserve_crlf ? data[pos + raw_line_len - 1] == '\n' : data[pos + raw_line_len - 1] == '\n' || data[pos + raw_line_len - 1] == '\r'; int add_newline = add_final_newline && pos + raw_line_len == len && !terminated; const char *line = data + pos; // raw_line_length stops at the first CR or LF, so any CR is in // the line terminator. Py_ssize_t terminator_len = 0; if (!preserve_crlf) { if (line[raw_line_len - 1] == '\r') { terminator_len = 1; } else if (raw_line_len > 1 && line[raw_line_len - 2] == '\r') { terminator_len = 2; } } _PyTok_Off appended; if (terminator_len || add_newline) { appended = _PyTok_SourceAppendLineWithNewline( &tok->source, line, raw_line_len - terminator_len); } else { appended = _PyTok_SourceAppendLine(&tok->source, line, raw_line_len); } if (appended < 0) { tok->done = PyErr_ExceptionMatches(PyExc_MemoryError) ? E_NOMEM : E_ERROR; return -1; } tok->reader->prepared_final_newline = add_newline; pos += raw_line_len; } // Partial input includes the newline submitted by the next Enter key. // A final CR pairs with that newline instead of starting a blank line. if (add_final_newline == _PyTokenizer_APPEND_NEWLINE && !tok->reader->prepared_final_newline && (len == 0 || data[len - 1] != '\r')) { if (_PyTok_SourceAppendLine(&tok->source, "\n", 1) < 0) { return -1; } tok->reader->prepared_final_newline = _PyTokenizer_APPEND_NEWLINE; } return 0; } int _PyTok_PrepareString(struct tok_state *tok, const char *input, int utf8_only, int exec_input, int preserve_crlf) { Py_ssize_t raw_len = strlen(input); char *raw = (char *)input; if (utf8_only) { if (_PyTok_SetEncoding(tok, "utf-8") < 0) { return -1; } } else { Py_ssize_t first_original_len = raw_line_length(raw, raw_len); _PyTok_Chunk first = { .data = raw, .len = first_original_len, .ownership = _PYTOK_CHUNK_BORROWED, }; _PyTok_Chunk second = {0}; int have_second = first_original_len < raw_len; if (have_second) { second.data = raw + first_original_len; second.len = raw_line_length(second.data, raw_len - first_original_len); } Py_ssize_t bom_len; _PyTok_EncodingResult detection = _PyTok_DetectEncoding( tok, &first, have_second ? &second : NULL, 1, &bom_len); if (detection == _PYTOK_ENCODING_ERROR) { return -1; } raw += bom_len; raw_len -= bom_len; } _PyTok_Chunk decoded = { .data = raw, .len = raw_len, .ownership = _PYTOK_CHUNK_BORROWED, }; const char *encoding = tok->reader->encoding; if (encoding != NULL && strcmp(encoding, "utf-8") != 0) { if (_PyTok_DecodeOnce( tok, &decoded, encoding, NULL) < 0) { return -1; } } int stored = store_prepared_source( tok, decoded.data, decoded.len, preserve_crlf, exec_input); _PyTok_ChunkClear(&decoded); if (stored < 0) { return -1; } if (!utf8_only && (encoding == NULL || strcmp(encoding, "utf-8") == 0) && !_PyTokenizer_ensure_utf8(_PyTok_SourceData(&tok->source), tok, 1)) { return -1; } return 0; } int _PyTok_StartDecoder(struct tok_state *tok, const char *errors) { _PyTok_Reader *reader = tok->reader; if (reader->encoding == NULL || reader->decoder != NULL) { return 0; } if (reader->kind == _PYTOK_READER_FILE && strcmp(reader->encoding, "utf-8") == 0) { return 0; } PyObject *codec = _PyCodec_LookupTextEncoding(reader->encoding, NULL); if (codec != NULL) { reader->decoder = _PyCodecInfo_GetIncrementalDecoder(codec, errors); Py_DECREF(codec); } if (reader->decoder == NULL) { tok->done = PyErr_ExceptionMatches(PyExc_MemoryError) ? E_NOMEM : E_DECODE; if (reader->kind == _PYTOK_READER_FILE) { _PyTokenizer_raise_init_error( tok->filename != NULL ? tok->filename : Py_None); } return -1; } return 0; } int _PyTok_DecodeChunk(struct tok_state *tok, _PyTok_Chunk *chunk, int final) { _PyTok_Reader *reader = tok->reader; if (reader->decoder == NULL) { return 0; } int strip_bom = reader->kind == _PYTOK_READER_READLINE && chunk->len >= UTF16_BOM_LENGTH && (memcmp(chunk->data, UTF16_LE_BOM, UTF16_BOM_LENGTH) == 0 || memcmp(chunk->data, UTF16_BE_BOM, UTF16_BOM_LENGTH) == 0); PyObject *input; if (chunk->ownership == _PYTOK_CHUNK_PYOBJECT && PyBytes_Check(chunk->owner) && chunk->data == PyBytes_AS_STRING(chunk->owner)) { input = Py_NewRef(chunk->owner); } else { input = PyBytes_FromStringAndSize(chunk->data, chunk->len); } if (input == NULL) { tok->done = E_NOMEM; return -1; } PyObject *unicode = PyObject_CallMethodObjArgs( reader->decoder, &_Py_ID(decode), input, final ? Py_True : Py_False, NULL); Py_DECREF(input); if (unicode == NULL) { tok->done = PyErr_ExceptionMatches(PyExc_MemoryError) ? E_NOMEM : E_DECODE; if (reader->kind == _PYTOK_READER_FILE) { _PyTokenizer_raise_init_error( tok->filename != NULL ? tok->filename : Py_None); } return -1; } if (!PyUnicode_Check(unicode)) { PyErr_Format(PyExc_TypeError, "decoder should return a string result, not '%.200s'", Py_TYPE(unicode)->tp_name); Py_DECREF(unicode); tok->done = E_DECODE; return -1; } return chunk_set_unicode(tok, chunk, unicode, strip_bom); } int _PyTok_DecoderHasBufferedInput(struct tok_state *tok) { if (tok->reader->decoder == NULL) { return 0; } PyObject *state = PyObject_CallMethodNoArgs( tok->reader->decoder, &_Py_ID(getstate)); if (state == NULL) { tok->done = PyErr_ExceptionMatches(PyExc_MemoryError) ? E_NOMEM : E_DECODE; return -1; } if (!PyTuple_Check(state) || PyTuple_GET_SIZE(state) != 2 || !PyBytes_Check(PyTuple_GET_ITEM(state, 0)) || !PyLong_Check(PyTuple_GET_ITEM(state, 1))) { Py_DECREF(state); PyErr_SetString(PyExc_TypeError, "incremental decoder getstate() must return (bytes, int)"); tok->done = E_DECODE; return -1; } int pending = PyBytes_GET_SIZE(PyTuple_GET_ITEM(state, 0)) != 0; Py_DECREF(state); return pending; }