int pdo_sqlsrv_dbh_quote()

in source/pdo_sqlsrv/pdo_dbh.cpp [1652:1841]


int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const char* unquoted, _In_ size_t unquoted_len, _Outptr_result_buffer_(*quoted_len) char **quoted, _Out_ size_t* quoted_len,
                          enum pdo_param_type paramtype )
#else
zend_string* pdo_sqlsrv_dbh_quote(_Inout_ pdo_dbh_t* dbh, _In_ const zend_string *unquoted, _In_ enum pdo_param_type paramtype)
#endif
{
    PDO_RESET_DBH_ERROR;
    PDO_VALIDATE_CONN;
    PDO_LOG_DBH_ENTRY;

    SQLSRV_ENCODING encoding = SQLSRV_ENCODING_CHAR;
    bool use_national_char_set = false;

    pdo_sqlsrv_dbh* driver_dbh = static_cast<pdo_sqlsrv_dbh*>(dbh->driver_data);
    SQLSRV_ASSERT(driver_dbh != NULL, "pdo_sqlsrv_dbh_quote: driver_data object was NULL.");

    // get the current object in PHP; this distinguishes pdo_sqlsrv_dbh_quote being called from:
    // 1. PDO::quote() - object name is PDO
    // 2. PDOStatement::execute() - object name is PDOStatement
    zend_execute_data* execute_data = EG( current_execute_data );
    zval *object = getThis();

    // iterate through parents to find "PDOStatement"
    bool is_statement = false;
    if ( object ) {
        zend_class_entry* curr_class = ( Z_OBJ_P( object ))->ce;
        while ( curr_class != NULL ) {
            if ( strcmp( reinterpret_cast<const char*>( curr_class->name->val ), "PDOStatement" ) == 0 ) {
                is_statement = true;
                break;
            }
            curr_class = curr_class->parent;
        }
    }
    // only change the encoding if quote is called from the statement level (which should only be called when a statement
    // is prepared with emulate prepared on)
    if (is_statement) {
        pdo_stmt_t *stmt = Z_PDO_STMT_P(object);
        SQLSRV_ASSERT(stmt != NULL, "pdo_sqlsrv_dbh_quote: stmt object was null");
        // set the encoding to be the encoding of the statement otherwise set to be the encoding of the dbh

        pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>(stmt->driver_data);
        SQLSRV_ASSERT(driver_stmt != NULL, "pdo_sqlsrv_dbh_quote: driver_data object was null");

        encoding = driver_stmt->encoding();
        if (encoding == SQLSRV_ENCODING_INVALID || encoding == SQLSRV_ENCODING_DEFAULT) {
            pdo_sqlsrv_dbh* stmt_driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>(stmt->driver_data);
            encoding = stmt_driver_dbh->encoding();
        }

        // get the placeholder at the current position in driver_stmt->placeholders ht
        // Normally it's not a good idea to alter the internal pointer in a hashed array 
        // (see pull request 634 on GitHub) but in this case this is for internal use only
        zval* placeholder = NULL;
        if ((placeholder = zend_hash_get_current_data(driver_stmt->placeholders)) != NULL && zend_hash_move_forward(driver_stmt->placeholders) == SUCCESS && stmt->bound_params != NULL) {
            pdo_bound_param_data* param = NULL;
            if (Z_TYPE_P(placeholder) == IS_STRING) {
                param = reinterpret_cast<pdo_bound_param_data*>(zend_hash_find_ptr(stmt->bound_params, Z_STR_P(placeholder)));
            }
            else if (Z_TYPE_P(placeholder) == IS_LONG) {
                param = reinterpret_cast<pdo_bound_param_data*>(zend_hash_index_find_ptr(stmt->bound_params, Z_LVAL_P(placeholder)));
            }
            if (NULL != param) {
                SQLSRV_ENCODING param_encoding = static_cast<SQLSRV_ENCODING>(Z_LVAL(param->driver_params));
                if (param_encoding != SQLSRV_ENCODING_INVALID) {
                    encoding = param_encoding;
                }
            }
        }
    }

    use_national_char_set = (driver_dbh->use_national_characters == 1 || encoding == SQLSRV_ENCODING_UTF8);
#if PHP_VERSION_ID >= 70200
    if ((paramtype & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) {
        use_national_char_set = true;
    }
    if ((paramtype & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) {
        use_national_char_set = false;
    }
#endif

    if (encoding == SQLSRV_ENCODING_BINARY) {
#if PHP_VERSION_ID < 80100
        *quoted_len = (unquoted_len * 2) + 2;   // each character will be converted to 2 hex digits and prepend '0x' to the result
        *quoted = reinterpret_cast<char*>(sqlsrv_malloc(*quoted_len, sizeof(char), 1));     // include space for null terminator
        memset(*quoted, '\0', *quoted_len + 1);

        unsigned int pos = 0;
        (*quoted)[pos++] = '0';
        (*quoted)[pos++] = 'x';

        for (size_t index = 0; index < unquoted_len && unquoted[index] != '\0'; ++index) {
            // On success, snprintf returns the total number of characters written
            // On failure, a negative number is returned
            // The generated string has a length of at most len - 1, so 
            // len is 3 (2 hex digits + 1)
            // Requires "& 0x000000FF", or snprintf will translate "0x90" to "0xFFFFFF90"
            int n = snprintf((char*)(*quoted + pos), 3, "%02X", unquoted[index] & 0x000000FF);
            if (n < 0) {
                // Something went wrong, simply return 0 (failure)
                return 0;
            }
            pos += 2;
        }

        return 1;
#else
        size_t unquoted_len = ZSTR_LEN(unquoted);
        const char *unquoted_str = ZSTR_VAL(unquoted);

        sqlsrv_malloc_auto_ptr<char> quoted;
        size_t quoted_len = (unquoted_len * 2) + 2;   // each character will be converted to 2 hex digits and prepend '0x' to the result
        quoted = reinterpret_cast<char*>(sqlsrv_malloc(quoted_len, sizeof(char), 1));     // include space for null terminator
        memset(quoted, '\0', quoted_len + 1);

        unsigned int pos = 0;
        quoted[pos++] = '0';
        quoted[pos++] = 'x';

        char *p = quoted;
        for (size_t index = 0; index < unquoted_len && unquoted_str[index] != '\0'; ++index) {
            // On success, snprintf returns the total number of characters written
            // On failure, a negative number is returned
            // The generated string has a length of at most len - 1, so 
            // len is 3 (2 hex digits + 1)
            // Requires "& 0x000000FF", or snprintf will translate "0x90" to "0xFFFFFF90"
            int n = snprintf((char*)(p + pos), 3, "%02X", unquoted_str[index] & 0x000000FF);
            if (n < 0) {
                // Something went wrong, simply return NULL (failure)
                return NULL;
            }
            pos += 2;
        }

        zend_string* zstr = zend_string_init(quoted, quoted_len, 0);
        return zstr;
#endif
    }
    else {
        // The minimum number of single quotes needed is 2 -- the initial start and end quotes
        // Add the letter N before the initial quote if the encoding is UTF8
        int quotes_needed = (use_national_char_set) ? 3 : 2;
        char c = '\'';

#if PHP_VERSION_ID < 80100
        std::string tmp_str(unquoted, unquoted_len);    // Copy all unquoted_len characters from unquoted
#else
        size_t unquoted_len = ZSTR_LEN(unquoted);
        const char *unquoted_str = ZSTR_VAL(unquoted);
        std::string tmp_str(unquoted_str, unquoted_len);    // Copy all unquoted_len characters from unquoted
#endif

        std::size_t found = tmp_str.find(c);            // Find the first single quote
        while (found != std::string::npos) {
            tmp_str.insert(found + 1, 1, c);            // Insert an additional single quote
            found = tmp_str.find(c, found + 2);         // Find the next single quote
        }
        size_t len = tmp_str.length();

#if PHP_VERSION_ID < 80100
        *quoted_len = quotes_needed + len;              // The new length should be number of quotes plus the length of tmp_str
        *quoted = reinterpret_cast<char*>(sqlsrv_malloc(*quoted_len, sizeof(char), 1));     // include space for null terminator
        memset(*quoted, '\0', *quoted_len + 1);

        char *p = *quoted;
#else
        sqlsrv_malloc_auto_ptr<char> quoted;
        size_t quoted_len = quotes_needed + len;  // length returned to the caller should not account for null terminator
        quoted = reinterpret_cast<char*>(sqlsrv_malloc(quoted_len, sizeof(char), 1));     // include space for null terminator
        memset(quoted, '\0', quoted_len + 1);

        char *p = quoted;
#endif
        size_t pos = 0;
        if (use_national_char_set) {                    // Insert the letter N if the encoding is UTF8
            *(p + (pos++)) = 'N';
        }
        *(p + (pos++)) = c;                             // Add the initial quote
        tmp_str.copy(p + pos, len, 0);                  // Copy tmp_str to *quoted
        pos += len;
        *(p + pos) = c;                                 // Add the end quote

#if PHP_VERSION_ID < 80100
        return 1;
#else
        zend_string* zstr = zend_string_init(quoted, quoted_len, 0);
        return zstr;
#endif
    }
}