prince
prince

Reputation: 924

Error when decoding base 64 to blob

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.Original picture

Decoded image

Upvotes: 1

Views: 2917

Answers (3)

Andrey Zotov
Andrey Zotov

Reputation: 1

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;

Upvotes: 0

prince
prince

Reputation: 924

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;

Upvotes: 2

furman87
furman87

Reputation: 968

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.

Upvotes: 1

Related Questions