bool sqlsrv_param::send_data_packet()

in source/shared/core_stmt.cpp [2520:2602]


bool sqlsrv_param::send_data_packet(_Inout_ sqlsrv_stmt* stmt)
{
    // Check EOF first
    if (php_stream_eof(param_stream)) {
        // But return to the very beginning of param_stream since SQLParamData() may ask for the same data again
        int ret = php_stream_seek(param_stream, 0, SEEK_SET);
        if (ret != 0) {
            LOG(SEV_ERROR, "PHP stream: stream seek failed.");
            throw core::CoreException();
        }
        // Reset num_bytes_read
        num_bytes_read = 0;

        return false;
    } else {
        // Read the data from the stream, send it via SQLPutData and track how much is already sent.
        char buffer[PHP_STREAM_BUFFER_SIZE + 1] = { '\0' };
        std::size_t buffer_size = sizeof(buffer) - 3;   // -3 to preserve enough space for a cut off UTF-8 character
        std::size_t read = php_stream_read(param_stream, buffer, buffer_size);

        if (read > UINT_MAX) {
            LOG(SEV_ERROR, "PHP stream: buffer length exceeded.");
            throw core::CoreException();
        }

        num_bytes_read += read;
        if (read == 0) {
            // Send an empty string, which is what a 0 length does.
            char buff[1];       // Temp storage to hand to SQLPutData
            core::SQLPutData(stmt, buff, 0);
        } else if (read > 0) {
            // If this is a UTF-8 stream, then we will use the UTF-8 encoding to determine if we're in the middle of a character
            // then read in the appropriate number more bytes and then retest the string.  This way we try at most to convert it
            // twice.
            // If we support other encondings in the future, we'll simply need to read a single byte and then retry the conversion
            // since all other MBCS supported by SQL Server are 2 byte maximum size.

            if (encoding == CP_UTF8) {
                // The size of wbuffer is set for the worst case of UTF-8 to UTF-16 conversion, which is an
                // expansion of 2x the UTF-8 size.
                SQLWCHAR wbuffer[PHP_STREAM_BUFFER_SIZE + 1] = { L'\0' };
                int wbuffer_size = static_cast<int>(sizeof(wbuffer) / sizeof(SQLWCHAR));
                DWORD last_error_code = ERROR_SUCCESS;
                    
                // The buffer_size is the # of wchars.  Set to buffer_size / 2
#ifndef _WIN32
                int wsize = SystemLocale::ToUtf16Strict(encoding, buffer, static_cast<int>(read), wbuffer, wbuffer_size, &last_error_code);
#else
                int wsize = MultiByteToWideChar(encoding, MB_ERR_INVALID_CHARS, buffer, static_cast<int>(read), wbuffer, wbuffer_size);
                last_error_code = GetLastError();
#endif // !_WIN32

                if (wsize == 0 && last_error_code == ERROR_NO_UNICODE_TRANSLATION) {
                    // This will calculate how many bytes were cut off from the last UTF-8 character and read that many more
                    // in, then reattempt the conversion.  If it fails the second time, then an error is returned.
                    size_t need_to_read = calc_utf8_missing(stmt, buffer, read);
                    // read the missing bytes
                    size_t new_read = php_stream_read(param_stream, static_cast<char*>(buffer) + read, need_to_read);
                    // if the bytes couldn't be read, then we return an error
                    CHECK_CUSTOM_ERROR(new_read != need_to_read, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, get_last_error_message(ERROR_NO_UNICODE_TRANSLATION)) {
                        throw core::CoreException();
                    }

                    // Try the conversion again with the complete character
#ifndef _WIN32
                    wsize = SystemLocale::ToUtf16Strict(encoding, buffer, static_cast<int>(read + new_read), wbuffer, static_cast<int>(sizeof(wbuffer) / sizeof(SQLWCHAR)));
#else
                    wsize = MultiByteToWideChar(encoding, MB_ERR_INVALID_CHARS, buffer, static_cast<int>(read + new_read), wbuffer, static_cast<int>(sizeof(wbuffer) / sizeof(wchar_t)));
#endif //!_WIN32
                    // something else must be wrong if it failed
                    CHECK_CUSTOM_ERROR(wsize == 0, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, get_last_error_message(ERROR_NO_UNICODE_TRANSLATION)) {
                        throw core::CoreException();
                    }
                }
                core::SQLPutData(stmt, wbuffer, wsize * sizeof(SQLWCHAR));
            }
            else {
                core::SQLPutData(stmt, buffer, read);
            } // NOT UTF8
        } // read > 0
        return true;
    } // NOT EOF
}