Oracle PLSQL equivalent of ASCIISTR(N'str') - oracle

My database has NLS_LANGUAGE:AMERICAN / NLS_CHARACTERSET:WE8ISO8859P15 / NLS_NCHAR_CHARACTERSET:AL16UTF16; NLS_LANG is set to AMERICAN_AMERICA.WE8MSWIN1252 in my Windows> properties> advanced system settings> advanced> environment variables - hope it applies to my PLSQL Dev.
I use ASCIISTR to get a unicode encoded value for exotic chars like this:
SELECT ASCIISTR(N'κόσμε') FROM DUAL;
Results in
ASCIISTR(UNISTR('\03BA\1F79\03...
---------------------------------
\03BA\1F79\03C3\03BC\03B5
It looks like the 'N' means the string is unicode, because if I don't specify it I get it wrong encoded.
SELECT ASCIISTR('κόσμε') FROM DUAL;
Results in
ASCIISTR('??SµE')
--------------------
??s\00B5e
What does this 'N' stands for? How do I invoke it in PLSQL?
I intend to use it on a pl/sql variable to encode exotice characters like this:
DECLARE
l_in VARCHAR2(2000);
l_ec VARCHAR2(2000);
l_dc VARCHAR2(2000);
BEGIN
l_in := 'κόσμε';
execute immediate 'select ASCIISTR(N'''||l_in||''') from dual' into l_ec;
DBMS_OUTPUT.PUT_LINE(l_ec);
select unistr(l_ec) into l_dc from dual;
DBMS_OUTPUT.PUT_LINE (l_dc);
END;
But I get
??s\00B5e
??sµe
As if I were in the second case above, without the 'N'

N'κόσμε' is (more or less) equivalent to CAST('κόσμε' AS NVARCHAR2(..))
With N'κόσμε' you say "treat the string as NVARCHAR". If you write just 'κόσμε' then the string is treated as VARCHAR. However, your NLS_CHARACTERSET is WE8ISO8859P15 which does not support Greek characters. Thus you get ? as placeholder.
You didn't tell us your NLS_NCHARACTERSET setting, most likely this supports Unicode.
btw, you don't have to select ... from dual, simply write like
l_ec := ASCIISTR('κόσμε');
in PL/SQL.
What is your local NLS_LANG value, i.e. at your client side? Most likely it does not match the character encoding of your SQL*Plus. See this answer for more details: OdbcConnection returning Chinese Characters as "?"

I (sadly) discovered in PLSQL decode NVARCHAR2 from BASE64 to UTF-8 that DBMS_OUTPUT doesn't support NVARCHAR2 datatype. I thusly can't use it to debug.
Then I can do the following to test:
-- encoding
CREATE OR REPLACE FUNCTION my_ec(l_in nvarchar2) RETURN varchar2 is
l_out varchar2(32000);
BEGIN
l_out := asciistr(l_in);
return l_out;
END;
/
-- decoding
CREATE OR REPLACE FUNCTION my_dc(l_in varchar2) RETURN nvarchar2 is
l_out nvarchar2(32000);
BEGIN
l_out := unistr(l_in);
return l_out;
END;
/
with expected result!
select my_ec(N'κόσμε') from dual;
--'\03BA\1F79\03C3\03BC\03B5'
select my_dc('\03BA\1F79\03C3\03BC\03B5') from dual;
--'κόσμε'
select my_dc(my_ec(N'κόσμε')) from dual;
--'κόσμε'

Related

Input sanitization - Numeric values

I've been asked to do input validation in order to prevent sql injection. I've been using dbms assert package functions to do the sanitization. However, when I try to sanitize a number(I'm getting it in varchar2(12 byte)) error is thrown. It's the same case with alphanumeric characters starting with number.
I tried various functions of dbms assert. Nothing seems to work except noop. But, noop is of no use since it does not do any validation.
create or replace procedure employee
(
v_emp_id IN varchar2(12 byte)
)
AS
lv_query CLOB;
BEGIN
if v_emp_id is NOT NULL THEN
lv_query := 'select * from employee where emp_id=''' || dbms_assert.enquote_name(v_emp_id) || '''';
--I also tried below:
-- lv_query := 'select * from employee where emp_id=''' || dbms_assert.simple_sql_name(v_emp_id) || '''';
end if;
END
No source gives more detailed input on dbms_assert package. Please help me in
Whether dbms_assert package can be used to sanitize numeric values(stored in VARCHAR2 variables). If yes, how?
Other ways of sanitizing input. (other than using bind variables)
Thanks.
Oracle 12.2 and higher
If you are on Oracle 12.2 or higher, you can use the VALIDATE_CONVERSION function which would be the simplest solution. Your code could potentially look something like this:
CREATE OR REPLACE PROCEDURE employee (v_emp_id IN VARCHAR2)
AS
lv_query CLOB;
BEGIN
IF v_emp_id IS NOT NULL AND validate_conversion (v_emp_id AS NUMBER) = 1
THEN
lv_query := 'select * from employee where emp_id = ' || v_emp_id;
ELSE
--do something here with an invalid number
null;
END IF;
END;
/
Earlier than Oracle 12.2
If you are not on Oracle 12.2 or higher, you can write your own small function to validate that the value is a number. Using a method similar to what Belayer suggested, just attempt to convert the value to a number using the TO_NUMBER function and if it fails, then you know it's not a number. In my example, I have it as a small anonymous block within the code but you can also make it a standalone function if you wish.
CREATE OR REPLACE PROCEDURE employee (v_emp_id IN VARCHAR2)
AS
lv_query CLOB;
l_is_number BOOLEAN;
BEGIN
--Verify that the parameter is a number
DECLARE
l_test_num NUMBER;
BEGIN
l_test_num := TO_NUMBER (v_emp_id);
l_is_number := TRUE;
EXCEPTION
WHEN VALUE_ERROR
THEN
l_is_number := FALSE;
END;
--Finished verifying if the parameter is a number
IF v_emp_id IS NOT NULL AND l_is_number
THEN
lv_query := 'select * from employee where emp_id = ' || v_emp_id;
ELSE
--do something here with an invalid number
null;
END IF;
END;
/
Well if you cannot change the procedure it means you have no test as that procedure will not compile, so it cannot be executed. However that may be a moot point. You need to define exactly what you mean by "sanitize numeric values". Do you mean validate a string contains a numeric value. If so DBMS_ASSERT will not do that. (Note: The function chooses ENQUOTE_NAME will uppercase the string and put double quotes (") around it thus making it a valid object name.) Further your particular validation may require you define a valid numeric value, is it: an integer, a floating point, is scientific nation permitted, is there a required precision and scale that must be satisfied, etc. As a brute force validation you can simulate the assertion by just convert to number. The following will do that. Like dbms_assert if the assertion is successful it returns the input string. Unlike dbms_assert, however, when the assertion fails it just returns null instead of raising an exception. See fiddle.
create or replace
function assert_is_numeric(value_in varchar2)
return varchar2
is
not_numeric exception;
pragma exception_init (not_numeric,-06502);
l_numeric number;
begin
l_numeric := to_number(value_in);
return value_in;
exception
when not_numeric then
return null;
end assert_is_numeric;

One or more UTF8 fields contain non-UTF 8 data, editing might give unexpected results

I have text file(iso-8859-1) located on Oracle Linux 7.2, which i'm trying to load in table on my Oracle DB 12.1c(AL32UTF8).
declare
f Utl_File.File_Type;
v_Buffer varchar2(1000);
v_Table Parse.Varchar2_Table;
v_Nfields integer;
begin
f := Utl_File.Fopen('SA', '1.txt', 'R');
if Utl_File.Is_Open(f) then
loop
begin
Utl_File.Get_Line(f, v_Line, 1000);
if v_Line is null then
exit;
end if;
Parse.Delimstring_To_Table(v_Line, v_Table, v_Nfields, Chr(9));
--insert into ...
end if;
exception
when No_Data_Found then
exit;
end;
end loop;
end if;
Utl_File.Fclose(f);
end;
With using this to parse
I have beautiful(correct) output in pl/sql developer with message "One or more UTF8 fields contain non-UTF 8 data, editing might give unexpected results"
and uncorrect output in Apex 5.
Can I do something with this? I'm trieng convert? translate in oracle and more...
UPDATE 1
select *
from nls_database_parameters
where parameter like '%CHARACTERSET%';
PARAMETER VALUE
NLS_NCHAR_CHARACTERSET AL16UTF16
NLS_CHARACTERSET AL32UTF8
UTL_FILE documentation says
UTL_FILE expects that files opened by UTL_FILE.FOPEN in text mode are encoded in the database character set.
Obviously this is not the case.
Use DBMS_LOB.OPEN() to open a BFILE (see BFILENAME) as RAW value and convert it with UTL_I18N.RAW_TO_CHAR() function to VARCHAR2.
Then you can use your Parse.Delimstring_To_Table function to parse lines.
Consider to use EXTERNAL TABLE or SQL*Loader, perhaps they are easier to use.

PLSQL decode NVARCHAR2 from BASE64 to UTF-8

I have a database which stores usernames only in English at the moment.
I would like to incorporate BASE64 & UTF-8 in order to store in other languages as well; I want to store it in a column of type NVARCHAR2.
The database procedure receives the name in BASE64, I'm decoding it via UTL_ENCODE.BASE64_DECODE & converting the string to VARCHAR2 using UTL_RAW.CAST_TO_VARCHAR2. But I get gibberish back and not the actual word.
For example I get 'алекс' as the name in BASE64. I'm able to decode it but the cast to VARCHAR2/NVARCHAR2 does not return the value: I get only gibberish.
I'm running on Oracle 12c using NLS_CHARACTERSET WE8ISO8859P1
Here is the code I use to decode:
DECLARE
lv_OrgUserName VARCHAR2(2000);
lv_encodedUserName VARCHAR2(2000);
lv_UserName VARCHAR2(2000);
BEGIN
lv_OrgUserName := 'алекс';
lv_encodedUserName := UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(UTL_RAW.CAST_TO_RAW(lv_OrgUserName)));
DBMS_OUTPUT.PUT_LINE (lv_encodedUserName);
lv_UserName := UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_DECODE(UTL_RAW.CAST_TO_RAW (lv_encodedUserName)));
DBMS_OUTPUT.PUT_LINE (lv_UserName);
END;
How can I overcome this?
First and foremost WE8ISO8859P1 (Western European 8-bit ISO 8859 Part 1, or - ISO8859 Part 1) does not support cyryllic characters:
see this link: https://en.wikipedia.org/wiki/ISO/IEC_8859-1
Therefore if you try to store a string like алекс into VARCHAR2 variable/column, you will always get a???? as an outcome.
Probably during the database installation someone has not considered cyryllic characters and has choosen a bad codepage.
A better option is ISO/IEC 8859-5 (part 5), see this link: https://en.wikipedia.org/wiki/ISO/IEC_8859-5
One option is to change this encoding - but this is not easy and it is beyound of this question.
What you can do is to strictly use NVARCHAR2 datatype instead of VARCHAR2 datatype in all places of your application that must support cyrillic characters.
There are still some pitfalls though you need to be aware of:
You cannot use DBMS_OUTPUT package to debug your code, because this package support only VARCHAR2 datatype, it doesn't support NVARCHAR
you must use N'some string' literals (with N prefix) in all literals --> 'алекс' is of VARCHAR2 datatype and it is always automatically converted to 'a????' in your encoding, while n'алекс' is of NVARCHAR2 datatype and such conversion doesn't occur.
The below code is tested on version 12c, I am using EE8MSWIN1250 code page (it also desn't support cyrillic characters):
select * from nls_database_parameters
where parameter like '%CHARACTERSET%';
PARAMETER VALUE
----------------------- ------------
NLS_NCHAR_CHARACTERSET AL16UTF16
NLS_CHARACTERSET EE8MSWIN1250
please give it a try:
CREATE OR REPLACE PACKAGE my_base64 AS
FUNCTION BASE64_ENCODE( str nvarchar2 ) RETURN varchar2;
FUNCTION BASE64_DECODE( str varchar2 ) RETURN nvarchar2;
END;
/
CREATE OR REPLACE PACKAGE BODY my_base64 AS
FUNCTION BASE64_ENCODE( str nvarchar2 ) RETURN varchar2
IS
lv_encodedUserName VARCHAR2(2000);
BEGIN
lv_encodedUserName := UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(UTL_RAW.CAST_TO_RAW(str)));
RETURN lv_encodedUserName;
END;
FUNCTION BASE64_DECODE( str varchar2 ) RETURN nvarchar2
IS
lv_UserName nVARCHAR2(2000);
BEGIN
lv_UserName := UTL_RAW.CAST_TO_nVARCHAR2(UTL_ENCODE.BASE64_DECODE(UTL_RAW.CAST_TO_RAW (str)));
RETURN lv_UserName;
END;
END;
/
A few examples:
select 'aлекс' As A, n'aлекс' As B from dual;
A B
----- -----
a???? aлекс
select my_base64.BASE64_ENCODE( n'аaaлекс' ) As aleks from dual;
ALEKS
--------------------------------------------------------------------------------
BDAAYQBhBDsENQQ6BEE=
select my_base64.BASE64_DECODE( 'BDAAYQBhBDsENQQ6BEE=' ) as aleks from dual;
ALEKS
--------------------------------------------------------------------------------
аaaлекс
select my_base64.BASE64_DECODE( my_base64.BASE64_ENCODE( n'аaaлекс' ) ) as Aleks from dual;
ALEKS
--------------------------------------------------------------------------------
аaaлекс

Not able to pass CLOB in Oracle function

I'm trying to pass CLOB as input parameter in oracle function. The function is created successfully, but when I try to pass a lengthy string, it gives
ora-01704 string literal too long
error.
Below is my function:
CREATE OR REPLACE FUNCTION mySchema.TESTFUNCTION(myData IN CLOB)
RETURN INT
AS
BEGIN
DBMS_OUTPUT.PUT_LINE(myData);
RETURN 1;
END;
When I try to call this function by passing lengthy string more than 5000 characters, it gives the error.
Can anybody help please
yes, I pass as a string only. Eg: select TESTFUNCTION('more than 5000 chars') from dual;
No, it cannot be done like that. That error is the expected one, simply because SQL(Oracle versions prior to 12c) cannot handle character literals that are more than 4000 bytes in length.
If you need to test your function use PL/SQL, where character literal can be up to 32767 characters(single byte character set) in length:
Here is our function:
Note: Starting from Oracle 10g R2 dbms_output.put_line() line limit is 32767 bytes, in versions prior to 10g R2 the line limit is 255 bytes.
create or replace function f1(
p_clob in clob
) return number is
begin
dbms_output.put_line(p_clob);
return 1;
end;
Here is our anonymous PL/SQL block to test that function:
clear screen;
set serveroutput on;
declare
l_var clob;
l_res number;
begin
l_var := 'aaaaaaaaaaaaaaaaaa.. more than 5000 characters';
l_res := f1(l_var);
end;
Result:
anonymous block completed
aaaaaaaaaaaaaaaaaa.. more than 5000 characters

Making a sha1-hash of a row in Oracle

I'm having a problem with making a sha1-hash of a row in a select on an Oracle database. I've done it in MSSQL as follows:
SELECT *,HASHBYTES('SHA1',CAST(ID as varchar(10)+
TextEntry1+TextEntry2+CAST(Timestamp as varchar(10)) as Hash
FROM dbo.ExampleTable
WHERE ID = [foo]
However, I can't seem to find a similar function to use when working with Oracle.
As far as my googling has brought me, I'm guessing dbms_crypto.hash_sh1 has something to do with it, but I haven't been able to wrap my brain around it yet...
Any pointers would be greatly appreciated.
The package DBMS_CRYPTO is the correct package to generate hashes. It is not granted to PUBLIC by default, you will have to grant it specifically (GRANT EXECUTE ON SYS.DBMS_CRYPTO TO user1).
The result of this function is of datatype RAW. You can store it in a RAW column or convert it to VARCHAR2 using the RAWTOHEX or UTL_ENCODE.BASE64_ENCODE functions.
The HASH function is overloaded to accept three datatypes as input: RAW, CLOB and BLOB. Due to the rules of implicit conversion, if you use a VARCHAR2 as input, Oracle will try to convert it to RAW and will most likely fail since this conversion only works with hexadecimal strings.
If you use VARCHAR2 then, you need to convert the input to a binary datatype or a CLOB, for instance :
DECLARE
x RAW(20);
BEGIN
SELECT sys.dbms_crypto.hash(utl_raw.cast_to_raw(col1||col2||to_char(col3)),
sys.dbms_crypto.hash_sh1)
INTO x
FROM t;
END;
you will find additional information in the documentation of DBMS_CRYPTO.hash
The DBMS_crypto package does not support varchar2. It works with raw type so if you need a varchar2 you have to convert it. Here is a sample function showing how to do this :
declare
p_string varchar2(2000) := 'Hello world !';
lv_hash_value_md5 raw (100);
lv_hash_value_sh1 raw (100);
lv_varchar_key_md5 varchar2 (32);
lv_varchar_key_sh1 varchar2 (40);
begin
lv_hash_value_md5 :=
dbms_crypto.hash (src => utl_raw.cast_to_raw (p_string),
typ => dbms_crypto.hash_md5);
-- convert into varchar2
select lower (to_char (rawtohex (lv_hash_value_md5)))
into lv_varchar_key_md5
from dual;
lv_hash_value_sh1 :=
dbms_crypto.hash (src => utl_raw.cast_to_raw (p_string),
typ => dbms_crypto.hash_sh1);
-- convert into varchar2
select lower (to_char (rawtohex (lv_hash_value_sh1)))
into lv_varchar_key_sh1
from dual;
--
dbms_output.put_line('String to encrypt : '||p_string);
dbms_output.put_line('MD5 encryption : '||lv_varchar_key_md5);
dbms_output.put_line('SHA1 encryption : '||lv_varchar_key_sh1);
end;
Just to put it here, if someone will search for.
In Oracle 12 you can use standard_hash(<your_value>, <algorythm>) function.
With no parameter <algorythm> defined, it will generate SHA-1 hash (output datatype raw(20))
You can define this function in your favorite package, I defined in utils_pkg.
FUNCTION SHA1(STRING_TO_ENCRIPT VARCHAR2) RETURN VARCHAR2 AS
BEGIN
RETURN LOWER(TO_CHAR(RAWTOHEX(SYS.DBMS_CRYPTO.HASH(UTL_RAW.CAST_TO_RAW(STRING_TO_ENCRIPT), SYS.DBMS_CRYPTO.HASH_SH1))));
END SHA1;
Now to call it
SELECT UTILS_PKG.SHA1('My Text') AS SHA1 FROM DUAL;
The response is
SHA1
--------------------------------------------
5411d08baddc1ad09fa3329f9920814c33ea10c0
You can select a column from some table:
SELECT UTILS_PKG.SHA1(myTextColumn) FROM myTable;
Enjoy!
Oracle 19c:
select LOWER(standard_hash('1234')) from dual;
which is equivalent to
select LOWER(standard_hash('1234','SHA1')) from dual;
will return an SHA1 hash.
For alternative algorithms see: https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/STANDARD_HASH.html

Resources