2016-04-12 23:43:46 +02:00
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_stream.cpp
//
// Contents: Implementation of PHP streams for reading SQL Server data
//
2021-09-08 02:38:17 +02:00
// Microsoft Drivers 5.10 for PHP for SQL Server
2016-04-12 23:43:46 +02:00
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""),
// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------
# include "core_sqlsrv.h"
namespace {
// close a stream and free the PHP resources used by it
2020-04-21 00:17:21 +02:00
int sqlsrv_stream_close ( _Inout_ php_stream * stream , int /*close_handle*/ )
2016-04-12 23:43:46 +02:00
{
sqlsrv_stream * ss = static_cast < sqlsrv_stream * > ( stream - > abstract ) ;
2018-06-06 22:18:58 +02:00
SQLSRV_ASSERT ( ss ! = NULL & & ss - > stmt ! = NULL , " sqlsrv_stream_close: sqlsrv_stream* ss was null. " ) ;
2016-04-12 23:43:46 +02:00
// free the stream resources in the Zend engine
php_stream_free ( stream , PHP_STREAM_FREE_RELEASE_STREAM ) ;
2016-06-13 23:44:53 +02:00
// UNDEF the stream zval and delete our reference count to it.
ZVAL_UNDEF ( & ( ss - > stmt - > active_stream ) ) ;
2016-04-12 23:43:46 +02:00
sqlsrv_free ( ss ) ;
stream - > abstract = NULL ;
return 0 ;
}
// read from a sqlsrv stream into the buffer provided by Zend. The parameters for binary vs. char are
// set when sqlsrv_get_field is called by the user specifying which field type they want.
2019-07-29 17:02:50 +02:00
# if PHP_VERSION_ID >= 70400
2020-04-21 00:17:21 +02:00
ssize_t sqlsrv_stream_read ( _Inout_ php_stream * stream , _Out_writes_bytes_ ( count ) char * buf , _Inout_ size_t count )
2019-07-29 17:02:50 +02:00
# else
2020-04-21 00:17:21 +02:00
size_t sqlsrv_stream_read ( _Inout_ php_stream * stream , _Out_writes_bytes_ ( count ) char * buf , _Inout_ size_t count )
2019-07-29 17:02:50 +02:00
# endif
2016-04-12 23:43:46 +02:00
{
SQLLEN read = 0 ;
SQLSMALLINT c_type = SQL_C_CHAR ;
char * get_data_buffer = buf ;
sqlsrv_malloc_auto_ptr < char > temp_buf ;
sqlsrv_stream * ss = static_cast < sqlsrv_stream * > ( stream - > abstract ) ;
2018-06-06 22:18:58 +02:00
SQLSRV_ASSERT ( ss ! = NULL & & ss - > stmt ! = NULL , " sqlsrv_stream_read: sqlsrv_stream* ss is NULL. " ) ;
2016-04-12 23:43:46 +02:00
try {
if ( stream - > eof ) {
return 0 ;
} ;
switch ( ss - > encoding ) {
case SQLSRV_ENCODING_CHAR :
c_type = SQL_C_CHAR ;
break ;
case SQLSRV_ENCODING_BINARY :
c_type = SQL_C_BINARY ;
break ;
case CP_UTF8 :
{
c_type = SQL_C_WCHAR ;
count / = 2 ; // divide the number of bytes we read by 2 since converting to UTF-8 can cause an increase in bytes
if ( count > PHP_STREAM_BUFFER_SIZE ) {
count = PHP_STREAM_BUFFER_SIZE ;
}
// use a temporary buffer to retrieve from SQLGetData since we need to translate it to UTF-8 from UTF-16
temp_buf = static_cast < char * > ( sqlsrv_malloc ( PHP_STREAM_BUFFER_SIZE ) ) ;
2017-01-25 02:18:20 +01:00
memset ( temp_buf , 0 , PHP_STREAM_BUFFER_SIZE ) ;
2016-04-12 23:43:46 +02:00
get_data_buffer = temp_buf ;
break ;
}
default :
DIE ( " Unknown encoding type when reading from a stream " ) ;
break ;
}
2019-03-14 19:17:14 +01:00
// Warnings will be handled below
2020-04-21 00:17:21 +02:00
SQLRETURN r = ss - > stmt - > current_results - > get_data ( ss - > field_index + 1 , c_type , get_data_buffer , count /*BufferLength*/ , & read , false /*handle_warning*/ ) ;
2016-04-12 23:43:46 +02:00
CHECK_SQL_ERROR ( r , ss - > stmt ) {
stream - > eof = 1 ;
throw core : : CoreException ( ) ;
}
2020-10-23 22:13:29 +02:00
// If the stream returns no data or NULL data, mark the "end of the stream" and return
if ( r = = SQL_NO_DATA | | read = = SQL_NULL_DATA ) {
stream - > eof = 1 ;
return 0 ;
}
// If the stream returns data less than the count requested then we are at the "end of the stream" but continue processing
if ( static_cast < size_t > ( read ) < = count & & read ! = SQL_NO_TOTAL ) {
2016-04-12 23:43:46 +02:00
stream - > eof = 1 ;
}
2020-10-23 22:13:29 +02:00
// If ODBC returns the 01004 (truncated string) warning, then we return the count minus the null terminator
2016-04-12 23:43:46 +02:00
// if it's not a binary encoded field
if ( r = = SQL_SUCCESS_WITH_INFO ) {
2018-07-31 23:58:21 +02:00
SQLCHAR state [ SQL_SQLSTATE_BUFSIZE ] = { L ' \0 ' } ;
2017-06-22 23:04:34 +02:00
SQLSMALLINT len = 0 ;
2016-04-12 23:43:46 +02:00
2020-04-21 00:17:21 +02:00
ss - > stmt - > current_results - > get_diag_field ( 1 , SQL_DIAG_SQLSTATE , state , SQL_SQLSTATE_BUFSIZE , & len ) ;
2016-04-12 23:43:46 +02:00
if ( read = = SQL_NO_TOTAL ) {
SQLSRV_ASSERT ( is_truncated_warning ( state ) , " sqlsrv_stream_read: truncation warning was expected but it "
" did not occur. " ) ;
}
2020-10-23 22:13:29 +02:00
// As per SQLGetData documentation, if the length of character data exceeds the BufferLength,
// SQLGetData truncates the data to BufferLength less the length of null-termination character.
// But when fetching binary fields as chars (wide chars), each byte is represented as 2 hex characters,
// each takes the size of a char (wide char). Note that BufferLength may not be multiples of 2 or 4.
bool is_binary = ( ss - > sql_type = = SQL_BINARY | | ss - > sql_type = = SQL_VARBINARY | | ss - > sql_type = = SQL_LONGVARBINARY ) ;
// With unixODBC connection pooling enabled the truncated state may not be returned so check the actual length read
// with buffer length.
2017-02-02 02:36:41 +01:00
# ifndef _WIN32
2017-02-01 03:10:37 +01:00
if ( is_truncated_warning ( state ) | | count < read ) {
# else
2016-04-12 23:43:46 +02:00
if ( is_truncated_warning ( state ) ) {
2017-02-02 02:36:41 +01:00
# endif // !_WIN32
2020-10-23 22:13:29 +02:00
size_t char_size = sizeof ( SQLCHAR ) ;
2016-04-12 23:43:46 +02:00
switch ( c_type ) {
case SQL_C_BINARY :
read = count ;
break ;
case SQL_C_WCHAR :
2020-10-23 22:13:29 +02:00
char_size = sizeof ( SQLWCHAR ) ;
if ( is_binary ) {
// Each binary byte read will be 2 hex wide chars in the buffer
SQLLEN num_bytes_read = static_cast < SQLLEN > ( floor ( ( count - char_size ) / ( 2 * char_size ) ) ) ;
read = num_bytes_read * char_size * 2 ;
} else {
read = ( count % 2 = = 0 ? count - 2 : count - 3 ) ;
}
2016-04-12 23:43:46 +02:00
break ;
case SQL_C_CHAR :
2020-10-23 22:13:29 +02:00
if ( is_binary ) {
read = ( ( count - char_size ) % 2 = = 0 ? count - char_size : count - char_size - 1 ) ;
} else {
read = count - 1 ;
}
2016-04-12 23:43:46 +02:00
break ;
default :
DIE ( " sqlsrv_stream_read: should have never reached in this switch case. " ) ;
break ;
}
}
else {
CHECK_SQL_WARNING ( r , ss - > stmt ) ;
}
}
2020-10-23 22:13:29 +02:00
// If the encoding is UTF-8
2017-01-27 00:05:30 +01:00
if ( c_type = = SQL_C_WCHAR ) {
count * = 2 ;
2020-10-23 22:13:29 +02:00
// Undo the shift to use the full buffer
2017-01-27 00:05:30 +01:00
// flags set to 0 by default, which means that any invalid characters are dropped rather than causing
// an error. This happens only on XP.
// convert to UTF-8
2017-01-27 02:06:17 +01:00
# ifdef _WIN32
2017-01-27 00:05:30 +01:00
DWORD flags = 0 ;
2017-06-29 21:23:46 +02:00
if ( isVistaOrGreater ) {
2017-01-27 00:05:30 +01:00
// Vista (and later) will detect invalid UTF-16 characters and raise an error.
flags = WC_ERR_INVALID_CHARS ;
}
2017-01-27 02:06:17 +01:00
# endif // _WIN32
2017-01-27 00:05:30 +01:00
if ( count > INT_MAX | | ( read > > 1 ) > INT_MAX ) {
LOG ( SEV_ERROR , " UTF-16 (wide character) string mapping: buffer length exceeded. " ) ;
throw core : : CoreException ( ) ;
}
2016-04-12 23:43:46 +02:00
2017-01-27 02:06:17 +01:00
# ifndef _WIN32
2017-01-25 02:18:20 +01:00
int enc_len = SystemLocale : : FromUtf16 ( ss - > encoding , reinterpret_cast < LPCWSTR > ( temp_buf . get ( ) ) ,
static_cast < int > ( read > > 1 ) , buf , static_cast < int > ( count ) , NULL , NULL ) ;
# else
2016-04-12 23:43:46 +02:00
int enc_len = WideCharToMultiByte ( ss - > encoding , flags , reinterpret_cast < LPCWSTR > ( temp_buf . get ( ) ) ,
2017-01-27 02:12:11 +01:00
static_cast < int > ( read > > 1 ) , buf , static_cast < int > ( count ) , NULL , NULL ) ;
2017-01-27 02:06:17 +01:00
# endif // !_WIN32
2016-04-12 23:43:46 +02:00
if ( enc_len = = 0 ) {
stream - > eof = 1 ;
THROW_CORE_ERROR ( ss - > stmt , SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE , get_last_error_message ( ) ) ;
}
read = enc_len ;
}
2017-06-29 19:51:58 +02:00
return static_cast < size_t > ( read ) ;
2016-04-12 23:43:46 +02:00
}
2019-07-29 17:02:50 +02:00
catch ( core : : CoreException & ) {
# if PHP_VERSION_ID >= 70400
return - 1 ;
# else
2016-04-12 23:43:46 +02:00
return 0 ;
2019-07-29 17:02:50 +02:00
# endif
2016-04-12 23:43:46 +02:00
}
2019-07-29 17:02:50 +02:00
catch ( . . . ) {
LOG ( SEV_ERROR , " sqlsrv_stream_read: Unknown exception caught. " ) ;
# if PHP_VERSION_ID >= 70400
return - 1 ;
# else
2016-04-12 23:43:46 +02:00
return 0 ;
2019-07-29 17:02:50 +02:00
# endif
2016-04-12 23:43:46 +02:00
}
}
// function table for stream operations. We only support reading and closing the stream
php_stream_ops sqlsrv_stream_ops = {
NULL ,
sqlsrv_stream_read ,
sqlsrv_stream_close ,
NULL ,
SQLSRV_STREAM ,
NULL ,
NULL ,
NULL ,
NULL
} ;
// open a stream and return the sqlsrv_stream_ops function table as part of the
// return value. There is only one valid way to open a stream, using sqlsrv_get_field on
// certain field types. A sqlsrv stream may only be opened in read mode.
2017-06-22 23:04:34 +02:00
static php_stream * sqlsrv_stream_opener ( _In_opt_ php_stream_wrapper * wrapper , _In_ const char * , _In_ const char * mode ,
2020-04-21 00:17:21 +02:00
_In_opt_ int options , _In_ zend_string * * , php_stream_context * STREAMS_DC )
2016-04-12 23:43:46 +02:00
{
# if ZEND_DEBUG
SQLSRV_UNUSED ( __zend_orig_lineno ) ;
SQLSRV_UNUSED ( __zend_orig_filename ) ;
SQLSRV_UNUSED ( __zend_lineno ) ;
SQLSRV_UNUSED ( __zend_filename ) ;
SQLSRV_UNUSED ( __php_stream_call_depth ) ;
# endif
sqlsrv_malloc_auto_ptr < sqlsrv_stream > ss ;
ss = static_cast < sqlsrv_stream * > ( sqlsrv_malloc ( sizeof ( sqlsrv_stream ) ) ) ;
memset ( ss , 0 , sizeof ( sqlsrv_stream ) ) ;
2020-09-30 02:53:58 +02:00
// The function core_get_field_common() is changed to pass REPORT_ERRORS for
// php_stream_open_wrapper(). Whether the error flag is toggled or cleared,
// the argument "options" will be zero.
// For details check this pull request: https://github.com/php/php-src/pull/6190
if ( options ! = 0 ) {
php_stream_wrapper_log_error ( wrapper , options , " Invalid option: no options except REPORT_ERRORS may be specified with a sqlsrv stream " ) ;
2016-04-12 23:43:46 +02:00
return NULL ;
}
// allocate the stream from PHP
php_stream * php_str = php_stream_alloc ( & sqlsrv_stream_ops , ss , 0 , mode ) ;
if ( php_str ! = NULL ) {
ss . transferred ( ) ;
}
return php_str ;
}
// information structure that contains PHP stream wrapper info. We supply the minimal
// possible, including the open function and the name only.
php_stream_wrapper_ops sqlsrv_stream_wrapper_ops = {
sqlsrv_stream_opener ,
NULL ,
NULL ,
NULL ,
NULL ,
SQLSRV_STREAM_WRAPPER ,
NULL ,
NULL ,
NULL ,
NULL
} ;
}
// structure used by PHP to get the function table for opening, closing, etc. of the stream
php_stream_wrapper g_sqlsrv_stream_wrapper = {
& sqlsrv_stream_wrapper_ops ,
NULL ,
0
} ;