CREATE OR REPLACE PACKAGE mip_virus_check IS

  -- Author  : HARDYA
  -- Created : 23/01/2008 09:55:52
  -- Purpose : Virus checking package
  -- Updates : MULLENMD - added functionality to run the virus checker for a file
  --                      added load file (to replace BLOB)
  PROCEDURE write_file(p_name     IN wwv_flow_files.NAME%TYPE,
                       p_location IN VARCHAR2 DEFAULT 'WEBMIP_VIRUS',
                       p_fs_name  IN VARCHAR2);

  /**
    check_file - allows MIP to run an anti-virus scan on a given file

    %param p_name - the APEX name of the file
    %param p_location - the directory on the server where the file will be tested
   */
  PROCEDURE check_file(p_name     IN wwv_flow_files.NAME%TYPE,
                       p_location IN VARCHAR2 DEFAULT 'WEBMIP_VIRUS');
  --

  /**
    get_fs_name - returns the file name from teh APEX doc's URI

    %param p_name - the APEX doc URI (in format unique ID/filename)
    %return VARCHAR2 - the actual name of the file
  */
  FUNCTION get_fs_name(p_name IN documents.uri%TYPE) RETURN VARCHAR2;
END mip_virus_check;
/
CREATE OR REPLACE PACKAGE BODY mip_virus_check IS
  /**
   Updates:
     MM 25-Feb-2008 added the load file
  */
  PROCEDURE pl(p_in   VARCHAR2
              ,p_line IN NUMBER DEFAULT NULL) IS
  BEGIN
    $IF mip_debug_constants.debugging OR mip_debug_constants.friendly_messages 
    $THEN
    mip_debug.pl(p_unit => $$PLSQL_UNIT
                ,p_line => p_line
                ,p_in   => p_in);
    $END
    NULL;
    END pl;

  PROCEDURE write_file(p_name     IN wwv_flow_files.NAME%TYPE,
                       p_location IN VARCHAR2 DEFAULT 'WEBMIP_VIRUS',
                       p_fs_name  IN VARCHAR2) IS
    l_lob_loc     BLOB;
    l_buffer      RAW(32767);
    l_buffer_size BINARY_INTEGER;
    l_amount      BINARY_INTEGER;
    l_offset      NUMBER(38) := 1;
    l_chunksize   INTEGER;
    l_out_file    utl_file.file_type;

  BEGIN
    pl('Writing file ' || p_name || ', ' || p_fs_name);
    SELECT blob_content
      INTO l_lob_loc
      FROM wwv_flow_files
     WHERE upper(NAME) = upper(p_name);
    pl('Got file from APEX.  Downloading');
    l_chunksize := dbms_lob.getchunksize(l_lob_loc);

    IF (l_chunksize < 32767) THEN
      l_buffer_size := l_chunksize;
    ELSE
      l_buffer_size := 32767;
    END IF;

    l_amount := l_buffer_size;

    dbms_lob.OPEN(l_lob_loc, dbms_lob.lob_readonly);

    l_out_file := utl_file.fopen(location     => p_location,
                                 filename     => p_fs_name,
                                 open_mode    => 'wb',
                                 max_linesize => 32767);

    WHILE l_amount >= l_buffer_size LOOP

      dbms_lob.READ(lob_loc => l_lob_loc,
                    amount  => l_amount,
                    offset  => l_offset,
                    buffer  => l_buffer);

      l_offset := l_offset + l_amount;

      utl_file.put_raw(file      => l_out_file,
                       buffer    => l_buffer,
                       autoflush => TRUE);

    --utl_file.fflush(file => l_out_file);

    END LOOP;

    utl_file.fflush(file => l_out_file);

    utl_file.fclose(l_out_file);

    dbms_lob.CLOSE(l_lob_loc);
  END write_file;
  --

  /**
   load_file - procedure to reload the resultant file after virus checking is complete.

   %param p_uri - the file's unique URI as stored in the Apex view
  */
  PROCEDURE load_file(p_uri wwv_flow_files.NAME%TYPE) IS
    l_directory_path     all_directories.directory_path%TYPE;
    l_source_file        BFILE;
    l_source_file_length BINARY_INTEGER;
    l_blob               BLOB;
    l_filename           VARCHAR2(250);
  BEGIN

    pl('reload_after_av');
    dbms_lob.createtemporary(lob_loc => l_blob, cache => TRUE);
    -- lock the table by selecting for update
    SELECT f.blob_content
      INTO l_blob
      FROM wwv_flow_files f

     WHERE f.NAME = p_uri
       FOR UPDATE;

    SELECT directory_path
      INTO l_directory_path
      FROM all_directories
     WHERE directory_name = 'WEBMIP_VIRUS';

    l_filename := REPLACE(p_uri, '/', '!');

    l_source_file        := bfilename('WEBMIP_VIRUS', l_filename);
    l_source_file_length := dbms_lob.getlength(l_source_file);

    dbms_lob.OPEN(file_loc  => l_source_file,
                  open_mode => dbms_lob.lob_readonly);

    dbms_lob.loadfromfile(dest_lob => l_blob,
                          src_lob  => l_source_file,
                          amount   => l_source_file_length);

    -- update the BLOB
    UPDATE wwv_flow_files f
       SET f.blob_content = l_blob
     WHERE f.NAME = p_uri;

    --close the file
    dbms_lob.fileclose(file_loc => l_source_file);

    pl('Loaded:' || l_filename);
  END load_file;
  --

  /*\**
      trigger_av_scan - triggers the anti-virus scan on the file

      %param p_location - the location of the file on the file server
      %param p_fs_name - the name of the file to check
    *\
  FUNCTION trigger_av_scan(p_fs_name  IN VARCHAR2) RETURN BOOLEAN IS
    av_cmd VARCHAR2(250);
    av_checker VARCHAR2(250);
    l_location VARCHAR2(250);
  BEGIN
    dbms_output.put_line('Checking file.');
    av_checker := cout_system_configuration.get_configuration_item('AV_LOCATION');
    l_location := cout_system_configuration.get_configuration_item('AV_DIRECTORY');

    av_cmd := '"' || av_checker ||'" /automation '||l_location||'\'||p_fs_name;

    dbms_output.put_line('about to check');
    dbms_output.put_line(av_cmd);

    mip_systemcall.syscall(p_cmd_str => av_cmd);

    dbms_output.put_line('Finished checking');

    RETURN TRUE;
  EXCEPTION
    WHEN OTHERS THEN
      dbms_output.put_line(SQLERRM);
      RETURN NULL;
  END trigger_av_scan;
  --*/

  /**
    check_file_exists - tests if the file is still on the server
                        if the file is no longer there then it has
                        failed the virus scan and been quarantined

    %param p_location - the location of the file on the file server
    %param p_fs_name - the name of the file to check
  */
  FUNCTION check_file_exists(p_location IN VARCHAR2 DEFAULT 'WEBMIP_VIRUS',
                             p_fs_name  IN VARCHAR2) RETURN BOOLEAN IS
    l_file_exists BOOLEAN;
    l_file_length NUMBER;
    l_block_size  NUMBER;
  BEGIN
    utl_file.fgetattr(location    => p_location,
                      filename    => p_fs_name,
                      fexists     => l_file_exists,
                      file_length => l_file_length,
                      block_size  => l_block_size);
    pl('File exists:' || sys.diutil.bool_to_int(l_file_exists));
    RETURN l_file_exists;
  EXCEPTION
    WHEN OTHERS THEN
      RETURN FALSE;
  END check_file_exists;
  --

  /**
    delete_file_from_server - removes the files from the server once AV
                              scan is complete.  Housekeeping.

    %param p_location - the location of the file on the file server
    %param p_fs_name - the name of the file to check
  */
  PROCEDURE delete_file_from_server(p_location IN VARCHAR2 DEFAULT 'WEBMIP_VIRUS',
                                    p_fs_name  IN VARCHAR2) IS
  BEGIN
    utl_file.fremove(location => p_location, filename => p_fs_name);
  END delete_file_from_server;
  --

  /**
    delete_file_from_MIP - removes an infected file from the MIP system

    %param p_location - the location of the file on the file server
    %param p_fs_name - the name of the file to check
  */
  PROCEDURE delete_file_from_mip(p_doc_id documents.id%TYPE) IS
    l_result BOOLEAN;
  BEGIN
    l_result := mip_files.delete_file(p_id         => p_doc_id,
                                      p_doc_status => 'REMOVED MALICIOUS',
                                      p_reason     => 'Malicious content found - deleted by Anti-virus.');

  END delete_file_from_mip;
  --

  /**
    check_file - allows MIP to run an anti-virus scan on a given file

    %param p_name - the APEX name of the file
    %param p_location - the directory on the server where the file will be tested
   */
  PROCEDURE check_file(p_name     IN wwv_flow_files.NAME%TYPE,
                       p_location IN VARCHAR2 DEFAULT 'WEBMIP_VIRUS') IS
    av_scan_result BOOLEAN;
    file_result    BOOLEAN;
    l_fs_name      VARCHAR2(250);
    l_doc_id       documents.id%TYPE;
  BEGIN
    -- need to remove the slash from the URI so we can store multiple files
    -- change it to a "!" for now.
    l_fs_name := REPLACE(p_name, '/', '!');

    SELECT doc.id INTO l_doc_id FROM documents doc WHERE doc.uri = p_name;

    -- write the file to the file system, trigger a scan, check if the file is still there
    -- and then act accordingly if it isn't.
    write_file(p_name     => p_name,
               p_location => p_location,
               p_fs_name  => l_fs_name);
 
    --dbms_lock.sleep(5);

    pl('Checking file exists:' || l_fs_name);
    file_result := check_file_exists(p_fs_name => l_fs_name);

    IF NOT file_result THEN
      delete_file_from_mip(p_doc_id => l_doc_id);
      pl('File removed from MIP');
    ELSE
      -- reload the leftovers into MIP
      load_file(p_uri => p_name);

      pl('Delete file from server');
      delete_file_from_server(p_location => p_location,
                              p_fs_name  => l_fs_name);

      mip_documents.set_doc_available(p_docu_id     => l_doc_id,
                                      p_description => 'Virus checked by MIP');

    END IF;
  EXCEPTION
    WHEN no_data_found THEN
      delete_file_from_mip(p_doc_id => l_doc_id);
      pl('File uploaded was empty and has been removed from MIP');
      
  END check_file;
  --

  /**
    get_fs_name - returns the file name from the APEX doc's URI

    %param p_name - the APEX doc URI (in format unique ID/filename)
    %return VARCHAR2 - the actual name of the file
  */
  FUNCTION get_fs_name(p_name IN documents.uri%TYPE) RETURN VARCHAR2 IS
    ret_fs_name   VARCHAR2(250);
    seperator_pos NUMBER;
  BEGIN
    seperator_pos := instr(p_name, '/', -1);

    IF NOT seperator_pos = 0 THEN
      ret_fs_name := substr(p_name, seperator_pos + 1);

      RETURN ret_fs_name;
    ELSE
      RETURN NULL;
    END IF;
  END get_fs_name;
  --
END mip_virus_check;
/
