Error when decoding base 64 to blob - oracle

I am using the following function to convert a large base64 encoded file(image or voice) into a blob file and store it in the Oracle database (Oracle Database 11g Enterprise Edition Release 11.1.0.7.0 - Production).
I am able to store it and retrieve it but the image is getting corrupted. Only a potion of the image is getting retrieved. I tried using small images(11KB size) and it is working fine. But for larger images(88KB to 700KB) only a portion of the image is retrieved.
The problem is with the base-64 decoding. Earlier I was not able to get even the smaller image due to corruption, but when I increased the buffer size, it came fine. Now the buffer size is at its maximum at 32767 as its the maximum for varchar2 and raw.
Can anyone provide a suitable workaround or solution.
function decode_base64(p_clob_in in clob) return blob is
v_blob blob;
v_result blob;
v_offset integer;
v_buffer_size binary_integer := 32767; -- 24, 48, 3072
v_buffer_varchar varchar2(32767);
v_buffer_raw raw(32767);
begin
if p_clob_in is null then
return null;
end if;
dbms_lob.createtemporary(v_blob, true);
v_offset := 1;
for i in 1 .. ceil(dbms_lob.getlength(p_clob_in) / v_buffer_size)
loop
dbms_lob.read(p_clob_in, v_buffer_size, v_offset, v_buffer_varchar);
v_buffer_raw := utl_raw.cast_to_raw(v_buffer_varchar);
v_buffer_raw := utl_encode.base64_decode(v_buffer_raw);
dbms_lob.writeappend(v_blob, utl_raw.length(v_buffer_raw), v_buffer_raw);
v_offset := v_offset + v_buffer_size;
end loop;
v_result := v_blob;
dbms_lob.freetemporary(v_blob);
return v_result;
end decode_base64;
The code that i use to call the function and insert the blob into the table is given below...
PROCEDURE create_notes (
p_task_id IN NUMBER
,p_note_title IN VARCHAR2
,p_note_detail IN VARCHAR2
,p_attach_name IN VARCHAR2
,p_attachment IN CLOB
,p_attach_type IN VARCHAR2
,x_return_code OUT VARCHAR2
,x_return_message OUT VARCHAR2
)
IS
l_blob_data BLOB;
BEGIN
.
.
.
IF p_attachment IS NOT NULL THEN
SELECT incident_id INTO l_pk1_value FROM csf_ct_tasks where task_id = p_task_id;
l_blob_data := xx_utl_base64.decode_base64(p_attachment);
INSERT INTO fnd_lobs
(file_id, file_name, file_content_type, upload_date,
expiration_date, program_name, program_tag, file_data,
LANGUAGE, oracle_charset, file_format
)
VALUES (l_media_id, p_attach_name,p_attach_type, -- 'audio/mpeg','application/pdf','image/jpeg'
SYSDATE,
NULL, 'FNDATTCH', NULL, l_blob_data, --l_blob_data,EMPTY_BLOB ()
'US', 'UTF8', 'binary'
)
RETURNING file_data
INTO x_blob;
COMMIT;
END IF:
Attaching the original picture and its decoded version, below.

I got the below code from net. It worked like a charm. Dont know whats the problem with my old code though.
FUNCTION base64decode(p_clob CLOB)
RETURN BLOB
IS
l_blob BLOB;
l_raw RAW(32767);
l_amt NUMBER := 7700;
l_offset NUMBER := 1;
l_temp VARCHAR2(32767);
BEGIN
BEGIN
DBMS_LOB.createtemporary (l_blob, FALSE, DBMS_LOB.CALL);
LOOP
DBMS_LOB.read(p_clob, l_amt, l_offset, l_temp);
l_offset := l_offset + l_amt;
l_raw := UTL_ENCODE.base64_decode(UTL_RAW.cast_to_raw(l_temp));
DBMS_LOB.append (l_blob, TO_BLOB(l_raw));
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL;
END;
RETURN l_blob;
END;

I tried your function with a v_buffer_size of 8192 and it worked fine. I've tried several numbers smaller than 32767 and they all worked fine, so try something less than that.

For those who's still looking for a correct solution - you need to decode input data in multiples of 4. In case input contains non-base64 symbols (which are ignored by built-in function utl_encode.base64_decode), it might lead to incorrect results on large files.
I've found a lot of samples on the web which do not correctly decode, posting my code below
FUNCTION base64_decode(p_content CLOB) RETURN BLOB
IS
C_CHUNK_SIZE CONSTANT INTEGER := 12000; -- should be a multiple of 4
C_NON_BASE64_SYM_PATTERN CONSTANT VARCHAR2(20) := '[^A-Za-z0-9+/]';
l_chunk_buf VARCHAR2(12000);
l_chunk_b64_buf RAW(9000);
l_chunk_offset INTEGER := 1;
l_chunk_size INTEGER;
l_res BLOB;
FUNCTION get_next_full_base64_chunk(l_data CLOB, p_cur_pos IN OUT INTEGER, p_desired_size INTEGER, p_cur_size IN OUT INTEGER) RETURN VARCHAR2 IS
l_res VARCHAR2(12000);
l_tail_desired_size INTEGER;
BEGIN
l_res := dbms_lob.substr(l_data, p_desired_size, p_cur_pos);
p_cur_pos := p_cur_pos + p_desired_size;
IF l_res IS NULL THEN
RETURN NULL;
END IF;
l_res := regexp_replace(l_res, C_NON_BASE64_SYM_PATTERN, '');
p_cur_size := p_cur_size + length(l_res);
l_tail_desired_size := 4 - mod(p_cur_size, 4);
IF l_tail_desired_size = 4 THEN
RETURN l_res;
ELSE
RETURN l_res || get_next_full_base64_chunk(l_data, p_cur_pos, l_tail_desired_size, p_cur_size);
END IF;
END;
BEGIN
dbms_lob.createtemporary(l_res, false);
WHILE true
LOOP
l_chunk_size := 0;
l_chunk_buf := get_next_full_base64_chunk(p_content, l_chunk_offset, C_CHUNK_SIZE, l_chunk_size);
EXIT WHEN l_chunk_buf IS NULL;
l_chunk_b64_buf := utl_encode.base64_decode(utl_raw.cast_to_raw(l_chunk_buf));
dbms_lob.writeappend(l_res, utl_raw.length(l_chunk_b64_buf), l_chunk_b64_buf);
END LOOP;
RETURN l_res;
END;

Related

Returning a single large clob from a restful service in Oracle APEX

Is there any way to return a clob as text (or file) without splitting into smaller pieces first?
I tried creating a GET handler with PL/SQL source that would just do this:
declare
txt clob;
begin
...-- set clob
htp.p(txt);
end;
But then I was getting the error ORA-06502: PL/SQL: numeric or value error.
I then tried cutting the clob in smaller segments and calling htp.p multiple times, which worked, but I was wondering if there was a way to send the whole thing in one go.
The doc states that htp.p and htp.prn take only VARCHAR2 so you're limited by the max size of a varchar2 and if the clob length exceeds that it will throw an error. This is what you can do:
Loop through the clob in 4k chunks and output using htp.prn. Avoid using htp.p in a loop because that generates a newline characters which could mess up the output, for example if you're generating json. It's also good practice to let the browser know what he's getting by setting the mime header.
DECLARE
l_clob CLOB;
l_amt INTEGER := 4000;
l_pos INTEGER := 1;
l_buf VARCHAR2(4000);
BEGIN
owa_util.mime_header ('text/html', true);
l_clob := '....';
LOOP
BEGIN
dbms_lob.read(
l_clob,
l_amt,
l_pos,
l_buf
);
l_pos := l_pos + l_amt;
-- need htp.prn since htp.p generates a newline char at the end.
htp.prn(l_buf);
EXCEPTION
WHEN no_data_found THEN
EXIT;
END;
END LOOP;
END;

Oracle PL/SQL utl_url.escape clob

I have a large BLOB object which I need to convert to CLOB in base64 with encoded characters, I have tried to use utl_encode.base64_encode but it does not escape special characters like ={}+#~ etc. does anyone know how to escape those special characters with utl_url.escape? my code that I have:
PROCEDURE base64encode ( i_blob in blob, io_clob in out nocopy clob) IS
l_step pls_integer := 22500;
l_converted VARCHAR2(32767);
l_buffer_size_approx pls_integer := 1048576;
l_buffer CLOB;
BEGIN
dbms_lob.createtemporary(l_buffer, TRUE, dbms_lob.call);
FOR i IN 0 .. trunc((dbms_lob.getlength(i_blob) - 1 )/l_step)
LOOP
l_converted := utl_raw.cast_to_varchar2(utl_encode.base64_encode(dbms_lob.substr(i_blob, l_step, i * l_step + 1)));
dbms_lob.writeappend(l_buffer, length(l_converted), l_converted);
IF dbms_lob.getlength(l_buffer) >= l_buffer_size_approx THEN
dbms_lob.append(io_clob, l_buffer);
dbms_lob.trim(l_buffer, 0);
END IF;
END LOOP;
dbms_lob.append(io_clob, l_buffer);
dbms_lob.freetemporary(l_buffer);
END base64encode;
and if I try to convert BLOB to base64 it gives me output like:
8J5G7Ty8t3Pn7T+9ce/+w/c//nn/Jd1VDTKhadyLoNKDDx/Cl+4/SHN7jFjSVFrj <- not full base64 just single line,
But I need the output like this, with escaped characters:
8J5G7Ty8t3Pn7T%2B9ce%2F%2Bw%2Fc%2F%2Fnn%2FJd1VDTKhadyLoNKDDx%2FCl%2B4%2FSHN7jFjSVFrj
the result is large CLOB text, it does not fit in varchar2 variable.
APEX_UTIL.URL_ENCODE might do the trick.
SELECT APEX_UTIL.URL_ENCODE('8J5G7Ty8t3Pn7T+9ce/+w/c//nn/Jd1VDTKhadyLoNKDDx/Cl+4/SHN7jFjSVFrj')
FROM dual;
Output:
8J5G7Ty8t3Pn7T%2B9ce%2F%2Bw%2Fc%2F%2Fnn%2FJd1VDTKhadyLoNKDDx%2FCl%2B4%2FSHN7jFjSVFrj
So I have came up with a working solution, this code decodes base64 clob and escapes reserved chars. It is a bit slow, but it does the work. first, you need to call base64encode function from my main question, then pass the clob to this function. I hope it will help others too.
FUNCTION encode64_clob(in_clob CLOB) RETURN CLOB IS
temp_chunk VARCHAR(4000);
l_enter NUMBER := 1;
l_old_ent NUMBER := 1;
l_ent_cnt NUMBER := 1;
l_out CLOB;
BEGIN
LOOP
l_enter := instr(in_clob, chr(10), 1, l_ent_cnt);
IF l_enter = 0 THEN
temp_chunk := regexp_replace(substr(in_clob, l_old_ent, length(in_clob)), '[[:space:]]+', '');
l_out := l_out || temp_chunk;
EXIT;
END IF;
temp_chunk := regexp_replace(substr(in_clob, l_old_ent, l_enter - l_old_ent), '[[:space:]]+', '');
temp_chunk := utl_url.escape(url => temp_chunk, escape_reserved_chars => TRUE, url_charset => 'UTF-8');
l_out := l_out || temp_chunk;
l_ent_cnt := l_ent_cnt + 1;
l_old_ent := l_enter;
END LOOP;
RETURN l_out;
END;

How to SHA-256 hmac in Oracle PL/SQL?

Is there any way to do SHA-256 hmac encryption in Oracle PL/SQL ?
I have Oracle 11.2 and the function is not available in Crypto package.
Thank you in advance.
Function hmac_sha256
(
ptext Varchar2,
pkey Varchar2
) Return Varchar2
Is
-- pad const
c_opad Raw(1) := '5c';
c_ipad Raw(1) := '36';
c_kpad Raw(1) := '00';
--SHA256 block size 512 bit
c_blocksize Integer := 64;
--local var, length equals to blocksize
l_opad Raw(64);
l_ipad Raw(64);
l_key Raw(64);
Begin
l_opad := utl_raw.copies(c_opad, c_blocksize);
l_ipad := utl_raw.copies(c_ipad, c_blocksize);
If utl_raw.length(utl_raw.cast_to_raw(pkey)) > c_blocksize Then
l_key := utl_raw.cast_to_raw(sha256.encrypt(pkey));
Else
l_key := utl_raw.cast_to_raw(pkey);
End If;
l_key := l_key ||
utl_raw.copies(c_kpad, c_blocksize - utl_raw.length(l_key));
Return sha256.encrypt_raw(utl_raw.bit_xor(l_key, l_opad) ||
sha256.encrypt_raw(utl_raw.bit_xor(l_key, l_ipad) || utl_raw.cast_to_raw(ptext))
);
End;

How to call SOAP WS from Oracle DB 11.2 with payload more than 32000 chars?

Colleagues, hello. I need to call SOAP web service from Oracle DB where payload consists of file in base64 encoding. The problem is that i have limitation in payload size - 32 000 chars (varchar type). Examples, here and here. So it means that i can not call web service where payload is quite huge. Have anybody examples where payload increases the limitation of varchar2 type. Than you.
To go over the 32k characters limitation, you can try to use a CLOB with utl_http. Here is a procedure to do it :
PROCEDURE write_clob_to_http_request(pc_clob IN OUT NOCOPY CLOB)
IS
ln_offset NUMBER := 1;
ln_i NUMBER := 1;
ln_amount NUMBER := 32767;
ln_clob_length NUMBER := dbms_lob.getlength(pc_clob);
lv_buffer VARCHAR2(32767);
luh_http_request utl_http.req;
BEGIN
luh_http_request := utl_http.begin_request('http://SoapServiceLocation' , 'POST', 'HTTP/1.1');
utl_http.set_header(luh_http_request, 'Content-Type', lv_http_content_type);
utl_http.set_header(luh_http_request, 'Content-Length', LENGTH(pc_content));
utl_http.set_header(luh_http_request, 'SOAPAction', 'http://SoapServiceLocation');
IF dbms_lob.isopen(pc_clob) != 1 THEN
dbms_lob.open(pc_clob, 0);
END IF;
WHILE ( ln_offset < ln_clob_length )
LOOP
dbms_lob.read(pc_clob, ln_amount, ln_offset, lv_buffer);
UTL_HTTP.write_text(luh_http_request, lv_buffer);
ln_offset := ln_offset + ln_amount;
ln_i := ln_i + 1;
END LOOP;
IF dbms_lob.isopen(pc_clob) = 1 THEN
dbms_lob.close(pc_clob);
END IF;
luh_http_response := utl_http.get_response(luh_http_request);
utl_http.read_text(luh_http_response, pc_content);
utl_http.end_response(luh_http_response);
END write_clob_to_http_request;
You can easily create a CLOB by concatenating VARCHAR2 variables:
pc_content CLOB := TO_CLOB('a string') || TO_CLOB('another string)...;

Loading an Oracle table with an encrypted LOB file and then decrypting it

I am being given a large encrypted file containing 10 million rows of data.
I need to load this into an oracle database (in it's encrypted form) and then decrypt it in the database using pl/sql and the oracle built-in dbms_crypto.
I then need to process the unencrypted LOB to seperate out the 10 Million rows.
I will have the public key for the file. The file will be about 5GB in size.
Is this possible using just pl/sql? (and the oracle built-ins)
Has anyone had any experience of doing this sort of thing? - any pointers will be most welcome.
Thanks
this works for me.
SYS has to:
GRANT EXECUTE ON DBMS_CRYPTO TO <user>
in user :
CREATE OR REPLACE DIRECTORY
CRYPTDIR AS
'<crypted files directory>';
CREATE TABLE TESTCRYPT (ID INTEGER, E BLOB, D BLOB);
CREATE OR REPLACE FUNCTION load_Blob_FromFile(p_file_name VARCHAR2)
RETURN BLOB
AS
dest_loc BLOB := empty_blob();
src_loc BFILE := BFILENAME('CRYPTDIR', p_file_name);
BEGIN
-- Open source binary file from OS
DBMS_LOB.OPEN(src_loc, DBMS_LOB.LOB_READONLY);
-- Create temporary LOB object
DBMS_LOB.CREATETEMPORARY(
lob_loc => dest_loc
, cache => true
, dur => dbms_lob.session
);
-- Open temporary lob
DBMS_LOB.OPEN(dest_loc, DBMS_LOB.LOB_READWRITE);
-- Load binary file into temporary LOB
DBMS_LOB.LOADFROMFILE(
dest_lob => dest_loc
, src_lob => src_loc
, amount => DBMS_LOB.getLength(src_loc));
-- Close lob objects
DBMS_LOB.CLOSE(dest_loc);
DBMS_LOB.CLOSE(src_loc);
-- Return temporary LOB object
RETURN dest_loc;
END;
/
INSERT INTO TESTCRYPT
(ID, E)
SELECT 1, LOAD_BLOB_FROMFILE('EncryptedFile') FROM DUAL;
CREATE OR REPLACE FUNCTION DCRYPT2(TO_DECRYPT IN BLOB) RETURN BLOB
IS
DECRYPTED BLOB;
v_key PLS_INTEGER :=
DBMS_CRYPTO.ENCRYPT_AES128 +
DBMS_CRYPTO.CHAIN_ECB +
DBMS_CRYPTO.PAD_PKCS5;
BEGIN
dbms_lob.createtemporary(DECRYPTED,true);
DBMS_CRYPTO.DECRYPT(DECRYPTED,
TO_DECRYPT,
v_key,
'<Hex-Key>'
);
RETURN DECRYPTED;
END;
UPDATE TESTCRYPT SET D = DCRYPT2(E) WHERE ID=1;
CREATE OR REPLACE FUNCTION PADIS_MASTER.blob2clob (p_in blob) RETURN clob IS
v_clob clob;
v_varchar VARCHAR2(32767);
v_start PLS_INTEGER := 1;
v_buffer PLS_INTEGER := 32767;
BEGIN
dbms_lob.createtemporary(v_clob, TRUE);
FOR i IN 1..CEIL(dbms_lob.getlength(p_in) / v_buffer)
LOOP
v_varchar := utl_raw.cast_to_varchar2(dbms_lob.SUBSTR(p_in, v_buffer, v_start));
dbms_lob.writeappend(v_clob, LENGTH(v_varchar), v_varchar);
v_start := v_start + v_buffer;
END LOOP;
RETURN v_clob;
END;
/
SELECT BLOB2CLOB(D) FROM TESTCRYPT WHERE ID = 1;
for example Oracle 11 compiles with this key: 'b1b7adc285e82db81ea17f7be706e4f7'
at last the encryption function:
CREATE OR REPLACE FUNCTION ECRYPT(TO_CRYPT IN BLOB) RETURN BLOB
IS
CRYPTED BLOB;
v_key PLS_INTEGER := DBMS_CRYPTO.ENCRYPT_AES128
+ DBMS_CRYPTO.CHAIN_ECB
+ DBMS_CRYPTO.PAD_PKCS5;
BEGIN
dbms_lob.createtemporary(AUSGABE,true);
DBMS_CRYPTO.ENCRYPT(CRYPTED,
TO_CRYPT,
v_key,
'b1b7adc285e82db81ea17f7be706e4f7'
);
RETURN CRYPTED;
END;

Resources