Objective:
To
download blob files (ZIP Files) from table in APEX using Oracle PL/SQL.
Scenario:
Customer
has asked us to give the provision in APEX, which is used to download the blob
files from Oracle APEX application itself. 
Solution:
Step
1: Create
APEX process using below PL/SQL anonymous block.
<<
Anonymous Block <>
DECLARE
  v_mime      VARCHAR2(48);
  v_length    NUMBER(38);
  v_file_name VARCHAR2(38);
  p_src BLOB;
  p_pw VARCHAR2(200);
  v_return BLOB;
BEGIN
  p_pw := 'test';
  BEGIN
    SELECT
      'test' ,
      compressed_blob ,
     
'Redemption_payout.RAR' ,
     
dbms_lob.getlength(compressed_blob)
    INTO
      v_mime ,
      p_src ,
      v_file_name ,
      v_length
    FROM
      red_compressed_files;
  exception
  WHEN others THEN
    ROLLBACK;
  END;
  BEGIN
    v_return :=
zip_blobs.encrypt( p_pw => p_pw, p_src => p_src );
  END;
  owa_util.mime_header(
nvl(v_mime,'application/octet'), FALSE );
  htp.p('Content-length: '
|| v_length);
 
htp.p('Content-Disposition: 
attachment;filename="'||REPLACE(REPLACE(substr(
 
v_file_name,instr(v_file_name,'/')+1),chr(10),NULL),chr(13),NULL)||
'"' );
 
owa_util.http_header_close;
 
wpg_docload.download_file(v_return);
 
apex_application.stop_apex_engine;
END;
/

Step
2: Install
the Package zip_blobs.
<<
Package Specification <>
CREATE OR REPLACE PACKAGE
zip_blobs
AS
  PROCEDURE add1file(
     
p_zipped_blob IN out BLOB,
      p_name
       IN VARCHAR2,
     
p_content     IN BLOB);
  FUNCTION file2blob(
      p_dir
      VARCHAR2,
     
p_file_name VARCHAR2)
    RETURN BLOB;
  FUNCTION encrypt(
      p_pw
VARCHAR2,
      p_src
BLOB )
    RETURN BLOB;
  PROCEDURE add1file1(
     
p_zipped_blob IN out BLOB,
      p_name
       IN VARCHAR2,
     
p_content     IN BLOB,
     
p_password    IN VARCHAR2);
  PROCEDURE
finish_zip(
     
p_zipped_blob IN out BLOB);
  PROCEDURE
finish_zip1(
     
p_zipped_blob IN out BLOB );
  PROCEDURE save_zip(
     
p_zipped_blob IN BLOB,
      p_dir
        IN VARCHAR2,
     
p_filename    IN VARCHAR2);
END zip_blobs;
/
<<
Package Body <>
CREATE OR REPLACE PACKAGE
BODY zip_blobs
AS
  c_local_file_header
constant RAW(4) := hextoraw( '504B0304' ); -- Local file
  -- header signature
 
c_end_of_central_directory constant RAW(4) := hextoraw( '504B0506' ); -- End
  -- of central
directory signature
  --
FUNCTION little_endian(
    p_big  
IN NUMBER,
    p_bytes IN
pls_integer := 4 )
  RETURN RAW
IS
BEGIN
  RETURN utl_raw.substr(
utl_raw.cast_from_binary_integer( p_big,
 
utl_raw.little_endian ), 1, p_bytes );
END;
--
FUNCTION file2blob(
    p_dir
VARCHAR2
    ,
    p_file_name
VARCHAR2
  )
  RETURN BLOB
IS
  file_lob BFILE;
  file_blob BLOB;
BEGIN
  file_lob :=
bfilename( p_dir, p_file_name );
  dbms_lob.OPEN(
file_lob, dbms_lob.file_readonly );
 
dbms_lob.createtemporary( file_blob, TRUE );
 
dbms_lob.loadfromfile( file_blob, file_lob, dbms_lob.lobmaxsize );
  dbms_lob.CLOSE(
file_lob );
  RETURN file_blob;
exception
WHEN others THEN
  IF dbms_lob.isopen(
file_lob ) = 1
    THEN
   
dbms_lob.CLOSE( file_lob );
  END IF;
  IF
dbms_lob.istemporary( file_blob ) = 1
    THEN
   
dbms_lob.freetemporary( file_blob );
  END IF;
  raise;
END;
--
FUNCTION blob2num(
    p_blob BLOB,
    p_len
INTEGER,
    p_pos INTEGER
)
  RETURN NUMBER
IS
BEGIN
  RETURN
utl_raw.cast_to_binary_integer( dbms_lob.substr( p_blob, p_len, p_pos
  ),
utl_raw.little_endian );
END;
--
FUNCTION encrypt(
    p_pw
VARCHAR2,
    p_src BLOB )
  RETURN BLOB
IS
  t_salt RAW(16);
  t_key RAW(32);
  t_pw RAW(32767)
         := utl_raw.cast_to_raw( p_pw );
  t_key_bits
pls_integer   := 256;
  t_key_length
pls_integer := t_key_bits / 8 * 2 + 2;
  t_cnt pls_integer
       := 1000;
  t_keys RAW(32767);
  t_sum RAW(32767);
  t_mac RAW(20);
  t_iv RAW(16);
  t_block RAW(16);
  t_len pls_integer;
  t_rv BLOB;
  t_tmp BLOB;
BEGIN
  t_salt :=
dbms_crypto.randombytes( t_key_bits / 16 );
  FOR i    
                     
             IN 1 .. ceil( t_key_length / 20
  )
  loop
    t_mac :=
dbms_crypto.mac( utl_raw.concat( t_salt, to_char( i, 'fm0xxxxxxx'
    ) ),
dbms_crypto.hmac_sh1, t_pw );
    t_sum :=
t_mac;
    FOR j IN 1 ..
t_cnt - 1
    loop
      t_mac
:= dbms_crypto.mac( t_mac, dbms_crypto.hmac_sh1, t_pw );
      t_sum
:= utl_raw.bit_xor( t_mac, t_sum );
    END loop;
    t_keys :=
utl_raw.concat( t_keys, t_sum );
  END loop;
  t_keys :=
utl_raw.substr( t_keys, 1, t_key_length );
  t_key  :=
utl_raw.substr( t_keys, 1, t_key_bits           / 8 );
  t_rv   :=
utl_raw.concat( t_salt, utl_raw.substr( t_keys, -2, 2 ) );
  --
  FOR i IN 0 .. trunc(
( dbms_lob.getlength( p_src ) - 1 ) / 16 )
  loop
    t_block :=
dbms_lob.substr( p_src, 16, i * 16 + 1 );
    t_len  
:= utl_raw.LENGTH( t_block );
    IF t_len <
16
      THEN
     
t_block := utl_raw.concat( t_block, utl_raw.copies( '00', 16 - t_len ) );
    END IF;
    t_iv :=
utl_raw.REVERSE( to_char( i + 1,
   
'fm000000000000000000000000000000x' ) );
   
dbms_lob.writeappend( t_rv, t_len, dbms_crypto.encrypt( t_block,
   
dbms_crypto.encrypt_aes256 + dbms_crypto.chain_cfb + dbms_crypto.pad_none,
    t_key, t_iv )
);
  END loop;
  --
 
dbms_lob.createtemporary( t_tmp, TRUE );
  dbms_lob.copy(
t_tmp, t_rv, dbms_lob.getlength( p_src ), 1, t_key_bits / 16 +
  2    
                     
                     
                     + 1
);
  t_mac :=
dbms_crypto.mac( t_tmp, dbms_crypto.hmac_sh1, utl_raw.substr( t_keys
  , 1 + t_key_bits /
8, t_key_bits / 8 ) );
 
dbms_lob.writeappend( t_rv, 10, t_mac );
 
dbms_lob.freetemporary( t_tmp );
  RETURN t_rv;
END;
--
PROCEDURE add1file(
    p_zipped_blob
IN out BLOB
    ,
    p_name IN
VARCHAR2
    ,
    p_content IN
BLOB
  )
IS
  t_now DATE;
  t_blob BLOB;
  t_clen INTEGER;
BEGIN
  t_now := SYSDATE;
  BEGIN
   
dbms_lob.createtemporary( t_blob, TRUE );
    t_blob :=
utl_compress.lz_compress( p_content );
  exception
  WHEN others THEN
   
raise_application_error(-20001,'test');
  END;
  t_clen    
      := dbms_lob.getlength( t_blob );
  IF p_zipped_blob IS
NULL
    THEN
   
dbms_lob.createtemporary( p_zipped_blob, TRUE );
  END IF;
  dbms_lob.APPEND(
p_zipped_blob
  , utl_raw.concat(
hextoraw( '504B0304' ) -- Local file header signature
  , hextoraw( '1400' )
                    --
version 2.0
  , hextoraw( '0000' )
                    -- no
General purpose bits
  , hextoraw( '0800' )
                    --
deflate
  , little_endian(
to_number( to_char( t_now, 'ss' ) ) / 2
       
                     
                     
   + to_number( to_char(
  t_now, 'mi' ) )
                     
               * 32
       
                     
                     
   + to_number( to_char(
  t_now, 'hh24' ) )
                     
             * 2048
  , 2
  )
  -- File last
modification time
  , little_endian(
to_number( to_char( t_now, 'dd' ) )
  + to_number(
to_char( t_now, 'mm' ) ) * 32
  + ( to_number(
to_char( t_now, 'yyyy' ) ) - 1980 ) * 512
  , 2
  )    
                     
                     
 -- File last modification date
  , dbms_lob.substr(
t_blob, 4, t_clen - 7 )         -- CRC-32
  , little_endian(
t_clen              - 18 )    
   -- compressed size
  , little_endian(
dbms_lob.getlength( p_content ) ) -- uncompressed size
  , little_endian(
LENGTH( p_name ), 2 )             -- File name
length
  , hextoraw( '0000' )
                     
        -- Extra field length
  ,
utl_raw.cast_to_raw( p_name )              
     -- File name
  )
  );
  --dbms_lob.append(
p_zipped_blob, dbms_lob.substr( t_blob, t_clen - 18, 11 )
  -- );    
-- compressed content
  dbms_lob.copy(
p_zipped_blob, t_blob, t_clen - 18, dbms_lob.getlength(
  p_zipped_blob )
                     
       + 1, 11 );
 
dbms_lob.freetemporary( t_blob );
END;
--
PROCEDURE add1file1(
    p_zipped_blob
IN out BLOB
    ,
    p_name
VARCHAR2
    ,
    p_content
BLOB
    ,
    p_password
VARCHAR2
  )
IS
  t_now DATE;
  t_blob BLOB;
  t_len  INTEGER;
  t_clen INTEGER;
  t_crc32 RAW(4)
      := hextoraw( '00000000' );
  t_compressed boolean
:= FALSE;
  t_encrypted
 boolean := FALSE;
  t_name RAW(32767);
  t_extra RAW(11);
BEGIN
  t_now   :=
SYSDATE;
  t_len   := nvl(
dbms_lob.getlength( p_content ), 0 );
  IF t_len > 0
    THEN
   
dbms_lob.createtemporary( t_blob, TRUE );
   
dbms_lob.copy( t_blob, utl_compress.lz_compress( p_content ),
   
dbms_lob.lobmaxsize , 1, 11 );
    t_clen  
    := dbms_lob.getlength( t_blob ) - 8;
    t_compressed
:= t_clen < t_len;
    t_crc32
     := dbms_lob.substr( t_blob, 4, t_clen + 1 );
   
dbms_lob.trim( t_blob, t_clen );
  END IF;
  IF NOT t_compressed
    THEN
    t_clen :=
t_len;
    t_blob :=
p_content;
  END IF;
  --
  IF p_zipped_blob IS
NULL
    THEN
   
dbms_lob.createtemporary( p_zipped_blob, TRUE );
  END IF;
  --
  IF p_password IS NOT
NULL AND t_len > 0
    THEN
    t_encrypted
:= TRUE;
    t_crc32
    := hextoraw( '00000000' );
    t_extra
    := hextoraw( '019907000200414503' ||
    CASE
    WHEN
t_compressed
      THEN
      '0800'
-- deflate
    ELSE
      '0000'
-- stored
    END
    );
    t_blob :=
encrypt( p_password, t_blob );
    t_clen :=
dbms_lob.getlength( t_blob );
  END IF;
  t_name :=
utl_i18n.string_to_raw( p_name, 'AL32UTF8' );
  dbms_lob.APPEND(
p_zipped_blob
  , utl_raw.concat(
utl_raw.concat( c_local_file_header -- Local file header
  -- signature
  , hextoraw( '3300' )
-- version 5.1
  )
  ,
  CASE
  WHEN t_encrypted
    THEN
    hextoraw(
'01' ) -- encrypted
  ELSE
    hextoraw(
'00' )
  END
  ,
  CASE
  WHEN t_name =
utl_i18n.string_to_raw( p_name, 'US8PC437' )
    THEN
    hextoraw(
'00' )
  ELSE
    hextoraw(
'08' ) -- set Language encoding flag (EFS)
  END
  ,
  CASE
  WHEN t_encrypted
    THEN
    '6300'
  ELSE
    CASE
    WHEN
t_compressed
      THEN
     
hextoraw( '0800' ) -- deflate
    ELSE
     
hextoraw( '0000' ) -- stored
    END
  END
  , little_endian(
to_number( to_char( t_now, 'ss' ) ) / 2
       
                     
                     
   + to_number( to_char(
  t_now, 'mi' ) )
                     
               * 32
       
                     
                     
   + to_number( to_char(
  t_now, 'hh24' ) )
                     
             * 2048
  , 2
  ) -- File last
modification time
  , little_endian(
to_number( to_char( t_now, 'dd' ) )
  + to_number(
to_char( t_now, 'mm' ) ) * 32
  + ( to_number(
to_char( t_now, 'yyyy' ) ) - 1980 ) * 512
  , 2
  )    
                     
                     
        -- File last modification date
  , t_crc32  
                     
                     
  -- CRC-32
  , little_endian(
t_clen )                    
            -- compressed size
  , little_endian(
t_len )                    
             -- uncompressed size
  , little_endian(
utl_raw.LENGTH( t_name ), 2 )            -- File
name length
  , little_endian(
nvl( utl_raw.LENGTH( t_extra ), 0 ), 2 ) -- Extra field
  -- length
  , utl_raw.concat(
t_name -- File name
  , t_extra
  )
  )
  );
  IF t_len > 0
    THEN
   
dbms_lob.copy( p_zipped_blob, t_blob, t_clen, dbms_lob.getlength(
    p_zipped_blob
) + 1, 1 ); -- (compressed) content
  END IF;
 
dbms_lob.freetemporary( t_blob );
END;
--
PROCEDURE finish_zip(
    p_zipped_blob
IN out BLOB )
IS
  t_cnt pls_integer :=
0;
  t_offs    
       INTEGER;
  t_offs_dir_header
INTEGER;
  t_offs_end_header
INTEGER;
  t_comment RAW(32767)
:= utl_raw.cast_to_raw(
  'Implementation by
Anton Scheffer' );
BEGIN
  t_offs_dir_header :=
dbms_lob.getlength( p_zipped_blob );
  t_offs    
       := dbms_lob.instr( p_zipped_blob, hextoraw(
'504B0304' ), 1
  );
  while t_offs > 0
  loop
    t_cnt :=
t_cnt + 1;
   
dbms_lob.APPEND( p_zipped_blob
    ,
utl_raw.concat( hextoraw( '504B0102' ) -- Central directory file header
    -- signature
    , hextoraw(
'1400' ) -- version 2.0
    ,
dbms_lob.substr( p_zipped_blob, 26, t_offs + 4 )
    , hextoraw(
'0000' )          -- File comment length
    , hextoraw(
'0000' )          -- Disk number where file starts
    , hextoraw(
'0100' )          -- Internal file attributes
    , hextoraw(
'2000B681' )      -- External file attributes
    ,
little_endian( t_offs - 1 ) -- Relative offset of local file header
    ,
dbms_lob.substr( p_zipped_blob
    ,
utl_raw.cast_to_binary_integer( dbms_lob.substr( p_zipped_blob, 2, t_offs
       
     + 26 ), utl_raw.little_endian )
    , t_offs + 30
    ) -- File
name
    )
    );
    t_offs :=
dbms_lob.instr( p_zipped_blob, hextoraw( '504B0304' ), t_offs +
    32 );
  END loop;
  t_offs_end_header :=
dbms_lob.getlength( p_zipped_blob );
  dbms_lob.APPEND(
p_zipped_blob
  , utl_raw.concat(
hextoraw( '504B0506' ) -- End of central directory
  -- signature
  , hextoraw( '0000' )
       -- Number of this disk
  , hextoraw( '0000' )
       -- Disk where central directory starts
  , little_endian(
t_cnt, 2 ) -- Number of central directory records on this
  -- disk
  , little_endian(
t_cnt, 2 )                  
           -- Total number of central directory
records
  , little_endian( t_offs_end_header
- t_offs_dir_header ) -- Size of central
  -- directory
  , little_endian(
t_offs_dir_header )                
       -- Relative offset of local file header
  , little_endian(
nvl( utl_raw.LENGTH( t_comment ), 0 ), 2 ) -- ZIP file
  -- comment length
  , t_comment
  )
  );
END;
--
PROCEDURE finish_zip1(
    p_zipped_blob
IN out BLOB )
IS
  t_cnt pls_integer :=
0;
  t_offs    
       INTEGER;
  t_offs_dir_header
INTEGER;
  t_offs_end_header
INTEGER;
  t_comment RAW(32767)
:= utl_raw.cast_to_raw(
  'Implementation by
Anton Scheffer' );
  t_len pls_integer;
BEGIN
  t_offs_dir_header :=
dbms_lob.getlength( p_zipped_blob );
  t_offs    
       := 1;
  while
dbms_lob.substr( p_zipped_blob, utl_raw.LENGTH( c_local_file_header ),
  t_offs ) =
c_local_file_header
  loop
    t_cnt :=
t_cnt                    
         + 1;
    t_len :=
blob2num( p_zipped_blob, 2, t_offs + 28 );
   
dbms_lob.APPEND( p_zipped_blob
    ,
utl_raw.concat( hextoraw( '504B0102' ) -- Central directory file header
    -- signature
    , hextoraw(
'3F00' ) -- version 6.3
    ,
dbms_lob.substr( p_zipped_blob, 26, t_offs + 4 )
    , hextoraw(
'0000' ) -- File comment length
    , hextoraw(
'0000' ) -- Disk number where file starts
    , hextoraw(
'0000' ) -- Internal file attributes =>
    --  
  0000 binary file
    --  
  0100 (ascii)text file
    ,
    CASE
    WHEN
dbms_lob.substr( p_zipped_blob
      , 1
      ,
t_offs + 30 + blob2num( p_zipped_blob, 2, t_offs + 26 ) - 1
      )
      IN ( hextoraw( '2F' ) -- /
      ,
hextoraw( '5C' )            -- \
      )
      THEN
     
hextoraw( '10000000' ) -- a directory/folder
    ELSE
     
hextoraw( '2000B681' )      -- a file
    END  
                     
  -- External file attributes
    ,
little_endian( t_offs - 1 ) -- Relative offset of local file header
    ,
dbms_lob.substr( p_zipped_blob
    , blob2num(
p_zipped_blob, 2, t_offs + 26 ) + t_len
    , t_offs
                     
      + 30
    ) -- File
name + extra data field
    )
    );
    t_offs :=
t_offs + 30 + blob2num( p_zipped_blob, 4, t_offs + 18 ) --
    -- compressed
size
    + blob2num(
p_zipped_blob, 2, t_offs + 26 )  -- File name length
    + blob2num(
p_zipped_blob, 2, t_offs + 28 ); -- Extra field length
  END loop;
  t_offs_end_header :=
dbms_lob.getlength( p_zipped_blob );
  dbms_lob.APPEND(
p_zipped_blob
  , utl_raw.concat(
c_end_of_central_directory -- End of central directory
  -- signature
  , hextoraw( '0000' )
       -- Number of this disk
  , hextoraw( '0000' )
       -- Disk where central directory starts
  , little_endian(
t_cnt, 2 ) -- Number of central directory records on this
  -- disk
  , little_endian(
t_cnt, 2 )                  
           -- Total number of central directory
records
  , little_endian(
t_offs_end_header - t_offs_dir_header ) -- Size of central
  -- directory
  , little_endian(
t_offs_dir_header ) -- Offset of start of central directory,
  -- relative to start
of archive
  , little_endian(
nvl( utl_raw.LENGTH( t_comment ), 0 ), 2 ) -- ZIP file
  -- comment length
  , t_comment
  )
  );
END;
--
PROCEDURE save_zip(
    p_zipped_blob
IN BLOB
    ,
    p_dir IN
VARCHAR2
    ,
    p_filename IN
VARCHAR2
  )
IS
  t_fh
utl_file.file_type;
  t_len pls_integer :=
32767;
BEGIN
  t_fh :=
utl_file.fopen( p_dir, p_filename, 'wb' );
  FOR i IN 0 .. trunc(
( dbms_lob.getlength( p_zipped_blob ) - 1 ) / t_len )
  loop
   
utl_file.put_raw( t_fh, dbms_lob.substr( p_zipped_blob, t_len, i * t_len +
    1 ) );
  END loop;
  utl_file.fclose(
t_fh );
END;
END zip_blobs;
/
Conclusion: This Package will help us
to download blob files from Oracle.
Comments
Post a Comment