CREATE OR REPLACE PACKAGE BODY sdsftp
AS
-- .///.
-- (0 o)
---------------0000--(_)--0000---------------
--
-- Sean D. Stuber
-- sean.stuber@gmail.com
--
-- oooO Oooo
--------------( )-----( )---------------
-- \ ( ) /
-- \_) (_/
-- 2010-09-16 Bug Fix: Buffer length in binary transfers set to null, meaning all.
-- Bug Fix: Files are closed in routines that generate their own file handle.
-- Routines that receive a file handle are left open.
-- Cosmetic: Minor formatting and variable name changes.
-- 2011-01-18 Enhancement: Added support for listing of remote directories
--
c_crlf CONSTANT CHAR(2) := CHR(13) || CHR(10);
-- Currently unused, would be used with the PORT command to do transfers
-- without passive mode.
c_default_ftp_data_port CONSTANT INTEGER := c_default_ftp_control_port - 1;
-- Per RFC 959, if account info (ACCT) is requested then a 332 code
-- should be returned from the PASS command instead of a Positive Completion
c_ftp_request_acct CONSTANT INTEGER := 332;
g_log_options INTEGER := c_log_dbms_output;
g_log_text VARCHAR2(32767) := NULL;
g_verbose BOOLEAN := c_default_verbose;
-- Parse an FTP reply string, extract the numeric code
FUNCTION reply_code(p_reply_text IN VARCHAR2)
RETURN INTEGER
IS
BEGIN
RETURN TO_NUMBER(SUBSTR(p_reply_text, 1, 3));
END reply_code;
-- When verbose mode is TRUE then additional information will be written to the logs.
-- If logging is turned off then verbose mode won't add anything.
PROCEDURE set_verbose(p_verbose IN BOOLEAN)
IS
BEGIN
g_verbose := NVL(p_verbose, c_default_verbose);
IF p_verbose
THEN
write_to_log('Switching logging to verbose mode', TRUE);
END IF;
END set_verbose;
-- Returns the current setting of verbose mode (true/false)
FUNCTION get_verbose
RETURN BOOLEAN
IS
BEGIN
RETURN g_verbose;
END get_verbose;
-- Turn off logging (options=0) or turn on different options (see c_log_xxxxx constants)
PROCEDURE set_log_options(p_log_options IN INTEGER)
IS
BEGIN
-- options big mask must be between 0 and sum of all possible log options.
IF p_log_options < 0
OR p_log_options > c_log_dbms_output + c_log_rolling_buffer + c_log_client_info
THEN
raise_application_error(
-20001,
'Invalid log options, must be between 0 and '
|| TO_CHAR(c_log_dbms_output + c_log_rolling_buffer + c_log_client_info),
TRUE);
END IF;
g_log_options := p_log_options;
END set_log_options;
-- Return current logging options
FUNCTION get_log_options
RETURN INTEGER
IS
BEGIN
RETURN g_log_options;
END get_log_options;
-- Clears (null) the logging buffers
PROCEDURE clear_log
IS
BEGIN
-- Clear the log buffer
g_log_text := NULL;
-- if logging to session client then clear that too
IF BITAND(g_log_options, c_log_client_info) > 0
THEN
DBMS_APPLICATION_INFO.set_client_info(NULL);
END IF;
END clear_log;
-- Return the current contents of the rolling log buffer
FUNCTION get_log_text
RETURN VARCHAR2
IS
BEGIN
RETURN g_log_text;
END get_log_text;
-- write the text to each logging option that is currently enabled
-- verbose comments will not be logged if verbose mode is off.
PROCEDURE write_to_log(v_text IN VARCHAR2, p_verbose IN BOOLEAN DEFAULT FALSE)
IS
BEGIN
-- If global verbose setting is ON (meaning log everything)
-- or if this text is not verbose then log it.
IF g_verbose OR NOT p_verbose
THEN
IF BITAND(g_log_options, c_log_dbms_output) > 0
THEN
DBMS_OUTPUT.put_line(v_text);
END IF;
IF BITAND(g_log_options, c_log_rolling_buffer) > 0
THEN
IF LENGTH(g_log_text) + LENGTH(c_crlf) + LENGTH(v_text) > 32767
THEN
g_log_text :=
SUBSTR(g_log_text, INSTR(g_log_text, c_crlf, LENGTH(v_text) + 2));
END IF;
g_log_text := g_log_text || v_text || c_crlf;
END IF;
IF BITAND(g_log_options, c_log_client_info) > 0
THEN
DBMS_APPLICATION_INFO.set_client_info(v_text);
END IF;
END IF;
END write_to_log;
-- Log in to the given server and return a connection object
PROCEDURE open(p_server IN VARCHAR2,
p_username IN VARCHAR2,
p_password IN VARCHAR2,
p_connection OUT connection,
p_local_directory IN VARCHAR2 DEFAULT NULL,
p_remote_directory IN VARCHAR2 DEFAULT NULL,
p_trans_method IN VARCHAR2 DEFAULT c_default_transfer_method,
p_timeout IN INTEGER DEFAULT NULL,
p_port IN INTEGER DEFAULT c_default_ftp_control_port,
p_account_info IN VARCHAR2 DEFAULT NULL
)
IS
v_connection connection;
BEGIN
write_to_log('opening connection to: ' || p_server || ':' || p_port, TRUE);
v_connection.tcp :=
UTL_TCP.open_connection(remote_host => p_server,
remote_port => p_port,
tx_timeout => p_timeout
);
read_reply(v_connection);
send_ftp_command(v_connection, 'USER', p_username, p_account_info);
send_ftp_command(v_connection, 'PASS', p_password, p_account_info);
-- If we haven't already been prompted for ACCT info
-- then send it now if we have it
IF p_account_info IS NOT NULL AND v_connection.account_info IS NULL
THEN
send_ftp_command(v_connection, 'ACCT', p_account_info);
v_connection.account_info := p_account_info;
END IF;
IF p_local_directory IS NOT NULL
THEN
local_cd(v_connection, p_local_directory);
END IF;
IF p_remote_directory IS NOT NULL
THEN
remote_cd(v_connection, p_remote_directory);
END IF;
set_transfer_method(v_connection, p_trans_method);
p_connection := v_connection;
END open;
-- Log in to the given server and return a connection object
FUNCTION open(p_server IN VARCHAR2,
p_username IN VARCHAR2,
p_password IN VARCHAR2,
p_local_directory IN VARCHAR2 DEFAULT NULL,
p_remote_directory IN VARCHAR2 DEFAULT NULL,
p_trans_method IN VARCHAR2 DEFAULT c_default_transfer_method,
p_timeout IN INTEGER DEFAULT NULL,
p_port IN INTEGER DEFAULT c_default_ftp_control_port,
p_account_info IN VARCHAR2 DEFAULT NULL
)
RETURN connection
IS
v_connection connection;
BEGIN
sdsftp.open(p_server => p_server,
p_username => p_username,
p_password => p_password,
p_connection => v_connection,
p_local_directory => p_local_directory,
p_remote_directory => p_remote_directory,
p_trans_method => p_trans_method,
p_timeout => p_timeout,
p_port => p_port,
p_account_info => p_account_info
);
RETURN v_connection;
END open;
-- Close the FTP, severs the TCP connection and clears the internal values
PROCEDURE close(p_connection IN OUT NOCOPY connection)
IS
BEGIN
write_to_log(
'closing connection: '
|| p_connection.tcp.remote_host
|| ':'
|| p_connection.tcp.remote_port,
TRUE);
send_ftp_command(p_connection, 'QUIT');
UTL_TCP.close_connection(p_connection.tcp);
p_connection.tcp := NULL;
p_connection.account_info := NULL;
p_connection.transfer_method := NULL;
p_connection.transfer_option := NULL;
p_connection.local_directory := NULL;
p_connection.last_reply := NULL;
write_to_log('connection closed', TRUE);
END close;
-- Request a separate connection on a different port for data transfer
-- PASV is the preferred transfer method opposed to PORT
-- per RFC 1123 (4.1.2.6) all servers must implement PASV.
FUNCTION get_passive_connection(p_connection IN OUT NOCOPY connection)
RETURN UTL_TCP.connection
IS
v_temp VARCHAR2(25);
v_host VARCHAR2(25);
v_port INTEGER;
v_pasv_connection UTL_TCP.connection;
BEGIN
write_to_log('requesting passive connection', TRUE);
send_ftp_command(p_connection, 'PASV');
-- PASV should respond with something of the form 227 zzzzzzzzzzzzzz (hhh,hhh,hhh,hhh,ppp,ppp)
-- where hhh,hhh,hhh,hhh is the host ip address, simply change ',' to '.' and it's ready to go
-- construct the port by taking the first part as the high byte of a 2-byte number (multiply by 256)
-- and the second part as the low byte of the 2-byte number (add it to the high byte)
-- per RFC1123 the host/port digits might not be enclosed in parentheses(),
-- therefore the parsing should be based on a scan of the digits themselves
v_temp := REGEXP_SUBSTR(p_connection.last_reply, '(\d{1,3},){5,5}\d{1,3}');
--
-- 9i and lower don't have regular expressions
-- so, we have to use other methods to strip out the host/port digits
-- v_temp :=
-- RTRIM(
-- LTRIM(
-- TRANSLATE(
-- SUBSTR(p_connection.last_reply, 5),
-- '0123456789,'
-- || TRANSLATE(SUBSTR(p_connection.last_reply, 5), CHR(0) || '0123456789,', CHR(0)),
-- '0123456789,'
-- ),
-- ','
-- ),
-- ','
-- );
-- v_temp should now look like this: 'hhh,hhh,hhh,hhh,ppp,ppp' (minus the quotes)
v_host := REPLACE(SUBSTR(v_temp, 1, INSTR(v_temp, ',', 1, 4) - 1), ',', '.');
v_temp := SUBSTR(v_temp, INSTR(v_temp, ',', 1, 4) + 1);
v_port :=
TO_NUMBER(SUBSTR(v_temp, 1, INSTR(v_temp, ',') - 1)) * 256
+ TO_NUMBER(SUBSTR(v_temp, INSTR(v_temp, ',') + 1));
write_to_log('opening passive connection', TRUE);
v_pasv_connection := UTL_TCP.open_connection(remote_host => v_host, remote_port => v_port);
RETURN v_pasv_connection;
EXCEPTION
WHEN OTHERS
THEN
write_to_log('ERROR in get_passive_connection');
write_to_log(' reply:' || p_connection.last_reply);
write_to_log(' host:' || v_host);
write_to_log(' port:' || v_port);
write_to_log(DBMS_UTILITY.format_error_stack || DBMS_UTILITY.format_error_backtrace);
RAISE;
END get_passive_connection;
-- Return the last reply from the server (multi-line replies will be returned as a single string)
FUNCTION get_last_reply(p_connection IN OUT NOCOPY connection)
RETURN VARCHAR2
IS
BEGIN
RETURN p_connection.last_reply;
END;
-- Read a reply from the server, including multi-line replies, concatenating them into a single reply string
PROCEDURE read_reply(p_connection IN OUT NOCOPY connection)
IS
-- FTP Replies (per rfc959)
-- replies begin with 3 digit codes xyz
-- the from can be either single line or multi-line
-- if single line, then xyz text
-- if multi-line, then xyz-text, followed by any number of lines, followed by xyz text
-- thus a reply will always end with the xyz code, a space and optionally some text.
--
-- xyz single line reply
--
-- xyz-start of multi-line reply
-- some text
-- some more text
-- xyz end of multi-line reply
--
-- 1yz Positive Preliminary reply
-- 2yz Positive Completion reply
-- 3yz Positive Intermediate reply
-- 4yz Transient Negative Completion reply
-- 5yz Permanent Negative Completion reply
--
-- x0z Syntax
-- x1z Information
-- x2z Connections
-- x3z Authentication and accounting
-- x4z Unspecified as yet.
-- x5z File system
--
-- The third digit gives a finer gradation of meaning in each
-- of the function categories, specified by the second digit.
v_reply VARCHAR2(32767) := NULL;
v_start_code VARCHAR2(3);
v_temp VARCHAR2(32767);
BEGIN
v_temp := UTL_TCP.get_line(p_connection.tcp, TRUE);
IF SUBSTR(v_temp, 4, 1) = ' '
THEN
-- 3 digits and a space is a normal, one line response
v_reply := v_temp;
ELSIF SUBSTR(v_temp, 4, 1) = '-'
THEN
v_start_code := SUBSTR(v_temp, 1, 3);
LOOP
v_temp := UTL_TCP.get_line(p_connection.tcp, TRUE);
IF v_reply IS NULL
THEN
v_reply := v_temp;
ELSE
v_reply := v_reply || UTL_TCP.crlf || v_temp;
END IF;
EXIT WHEN v_temp LIKE v_start_code || ' %';
END LOOP;
ELSE
raise_application_error(-20001, 'Invalid FTP Protocol reply: ' || v_temp, TRUE);
END IF;
p_connection.last_reply := v_reply;
IF g_log_options > 0
THEN
write_to_log(v_reply);
END IF;
IF SUBSTR(v_reply, 1, 1) = '4'
THEN
raise_application_error(-20001, 'Transient error from FTP server: ' || v_reply, TRUE);
ELSIF SUBSTR(v_reply, 1, 1) = '5'
THEN
raise_application_error(-20001, 'Permanent error from FTP server: ' || v_reply, TRUE);
END IF;
EXCEPTION
WHEN UTL_TCP.end_of_input
THEN
NULL;
END read_reply;
-- Send raw FTP protocol command (such as USER, PASS, STOR, RETR, etc)
PROCEDURE send_ftp_command(p_connection IN OUT NOCOPY connection,
p_command IN VARCHAR2,
p_arguments IN VARCHAR2 DEFAULT NULL,
p_account_info IN VARCHAR2 DEFAULT NULL
)
IS
v_code INTEGER;
BEGIN
IF p_arguments IS NULL
THEN
v_code := UTL_TCP.write_line(p_connection.tcp, p_command);
ELSE
v_code := UTL_TCP.write_line(p_connection.tcp, p_command || ' ' || p_arguments);
END IF;
read_reply(p_connection);
IF reply_code(p_connection.last_reply) = c_ftp_request_acct
THEN
send_ftp_command(p_connection, 'ACCT', NVL(p_connection.account_info, p_account_info));
p_connection.account_info := p_account_info;
END IF;
EXCEPTION
WHEN OTHERS
THEN
write_to_log('ERROR in send_ftp_command(' || p_command || ')');
write_to_log(DBMS_UTILITY.format_error_stack || DBMS_UTILITY.format_error_backtrace);
RAISE;
END send_ftp_command;
-- Rename a file or directory on the FTP server.
-- Note, this operation can be used to move files
PROCEDURE remote_rename(p_connection IN OUT NOCOPY connection,
p_old_name IN VARCHAR2,
p_new_name IN VARCHAR2
)
IS
BEGIN
write_to_log('renaming remote file: ' || p_old_name || ' to ' || p_new_name, TRUE);
send_ftp_command(p_connection, 'RNFR', p_old_name);
send_ftp_command(p_connection, 'RNTO', p_new_name);
EXCEPTION
WHEN OTHERS
THEN
write_to_log('ERROR in remote_rename(' || p_old_name || ',' || p_new_name || ')');
write_to_log(DBMS_UTILITY.format_error_stack || DBMS_UTILITY.format_error_backtrace);
RAISE;
END remote_rename;
-- Delete a file on the FTP server.
PROCEDURE remote_delete_file(p_connection IN OUT NOCOPY connection, p_file IN VARCHAR2)
IS
BEGIN
write_to_log('deleting remote file: ' || p_file, TRUE);
send_ftp_command(p_connection, 'DELE', p_file);
EXCEPTION
WHEN OTHERS
THEN
write_to_log('ERROR in remote_delete_file(' || p_file || ')');
write_to_log(DBMS_UTILITY.format_error_stack || DBMS_UTILITY.format_error_backtrace);
RAISE;
END remote_delete_file;
-- Create a directory on the FTP server.
PROCEDURE remote_create_directory(p_connection IN OUT NOCOPY connection,
p_directory IN VARCHAR2
)
IS
BEGIN
write_to_log('creating remote directory: ' || p_directory, TRUE);
send_ftp_command(p_connection, 'MKD', p_directory);
EXCEPTION
WHEN OTHERS
THEN
write_to_log('ERROR in remote_create_directory(' || p_directory || ')');
write_to_log(DBMS_UTILITY.format_error_stack || DBMS_UTILITY.format_error_backtrace);
RAISE;
END remote_create_directory;
-- Delete a directory on the FTP server.
PROCEDURE remote_delete_directory(p_connection IN OUT NOCOPY connection,
p_directory IN VARCHAR2
)
IS
BEGIN
write_to_log('deleting remote directory: ' || p_directory, TRUE);
send_ftp_command(p_connection, 'RMD', p_directory);
EXCEPTION
WHEN OTHERS
THEN
write_to_log('ERROR in remote_delete_directory(' || p_directory || ')');
write_to_log(DBMS_UTILITY.format_error_stack || DBMS_UTILITY.format_error_backtrace);
RAISE;
END remote_delete_directory;
-- Sets the transfer method, it will accept FTP protocol values of A, I, E
-- as well as the key words ASCII, IMAGE, EBCIDIC, BINARY, DEFAULT
-- The first three map to the protocol characters, BINARY is equivalent to I
-- DEFAULT will set the method equal to the default method defined in this package's
-- specification. If NULL, then no change will be made.
-- The A, E and I methods can also be parameterized if necessary (usually won't be)
-- The FTP protocol representation L is NOT supported at this time.
PROCEDURE set_transfer_method(p_connection IN OUT NOCOPY connection,
p_transfer_method IN VARCHAR2,
p_option IN VARCHAR2 DEFAULT NULL
)
IS
v_method VARCHAR2(10) := UPPER(SUBSTR(p_transfer_method, 1, 10));
v_option VARCHAR2(1) := UPPER(SUBSTR(p_option, 1, 1));
v_new_method VARCHAR2(3);
BEGIN
CASE
WHEN v_method IS NULL
THEN
-- Do nothing, if NULL is the new method, then just keep the current one
NULL;
WHEN v_method = 'DEFAULT'
THEN
v_new_method := SUBSTR(c_default_transfer_method, 1, 1);
WHEN v_method = 'BINARY'
THEN
v_new_method := 'I';
WHEN v_method IN ('A', 'ASCII', 'I', 'IMAGE', 'E', 'EBCDIC')
THEN
v_new_method := SUBSTR(v_method, 1, 1);
ELSE
raise_application_error(
-20001,
'SDSFTP.set_transfer_method INVALID method: ' || p_transfer_method,
TRUE);
END CASE;
IF v_new_method != NVL(p_connection.transfer_method, '---')
THEN
write_to_log('changing transfer method', TRUE);
IF v_option IN ('N', 'T', 'C')
THEN
send_ftp_command(p_connection, 'TYPE', v_new_method || ' ' || v_option);
ELSIF v_option IS NULL
THEN
send_ftp_command(p_connection, 'TYPE', v_new_method);
ELSE
raise_application_error(-20001,
'SDSFTP.set_transfer_method INVALID option: ' || p_option,
TRUE
);
END IF;
p_connection.transfer_method := v_new_method;
p_connection.transfer_option := v_option;
write_to_log('Transfer method changed', TRUE);
END IF;
END set_transfer_method;
-- Return the full name of the current transfer method for the given connection
-- Note, the FTP protocol representation L is NOT supported at this time.
FUNCTION get_transfer_method(p_connection IN OUT NOCOPY connection)
RETURN VARCHAR2
IS
BEGIN
RETURN CASE
WHEN p_connection.transfer_method = 'A' THEN 'ASCII'
WHEN p_connection.transfer_method = 'E' THEN 'EBCDIC'
WHEN p_connection.transfer_method = 'I' THEN 'IMAGE'
END;
END get_transfer_method;
-- Change the directory used locally for sending files from or retrieving files into
-- a directory can be an explicit path supported by utl_file_dir parameter
-- or it can be a directory object.
PROCEDURE local_cd(p_connection IN OUT NOCOPY connection, p_directory IN VARCHAR2)
IS
BEGIN
p_connection.local_directory := p_directory;
END local_cd;
-- Returns the current directory used locally
-- Unlike a server which will always have a current directory
-- the local client might be NULL if a local_cd has not occurred.
FUNCTION local_pwd(p_connection IN OUT NOCOPY connection)
RETURN VARCHAR2
IS
BEGIN
RETURN p_connection.local_directory;
END local_pwd;
-- Returns the current directory for the remote host
FUNCTION remote_pwd(p_connection IN OUT NOCOPY connection)
RETURN VARCHAR2
IS
v_temp VARCHAR2(32767);
BEGIN
write_to_log('Requesting remote directory', TRUE);
send_ftp_command(p_connection, 'PWD');
-- reply should be of the from 257 "directoryname"
-- strip the return code prefix and leading double quote
v_temp := SUBSTR(p_connection.last_reply, 6);
-- strip off trailing double-qoutes
v_temp := RTRIM(v_temp, '"');
-- double-qoutes within the name will be escaped with double-qoutes i.e. ""
-- un-escape any embedded double-quotes
v_temp := REPLACE(v_temp, '""', '"');
write_to_log('Remote directory is:' || v_temp, TRUE);
RETURN v_temp;
END remote_pwd;
-- Change to the given directory on the remote host
PROCEDURE remote_cd(p_connection IN OUT NOCOPY connection, p_directory IN VARCHAR2)
IS
BEGIN
write_to_log('Changing remote directory to: ' || p_directory, TRUE);
send_ftp_command(p_connection, 'CWD', p_directory);
write_to_log('Remote directory changed', TRUE);
END remote_cd;
-- Move up to the parent directory on the remote host
PROCEDURE remote_cdup(p_connection IN OUT NOCOPY connection)
IS
BEGIN
write_to_log('Changing to remote parent directory', TRUE);
send_ftp_command(p_connection, 'CDUP');
write_to_log('Changed to remote parent directory', TRUE);
END;
-- Given a local clob, send it to the remote host to be saved as a file
-- by default the current transfer method can be used or it can be
-- changed as part of this procedure
PROCEDURE put_clob(p_connection IN OUT NOCOPY connection,
p_local_clob IN OUT NOCOPY CLOB,
p_remote_file IN VARCHAR2,
p_trans_method IN VARCHAR2 DEFAULT NULL
)
IS
v_read_cnt INTEGER;
v_write_cnt INTEGER;
v_lob_length INTEGER;
v_index INTEGER;
v_chunk VARCHAR2(32767);
v_data UTL_TCP.connection;
BEGIN
set_transfer_method(p_connection, p_trans_method);
v_data := get_passive_connection(p_connection);
send_ftp_command(p_connection, 'STOR', p_remote_file);
write_to_log('storing file on: ' || v_data.remote_host || ':' || v_data.remote_port, TRUE);
v_lob_length := DBMS_LOB.getlength(p_local_clob);
v_index := 1;
WHILE v_index <= v_lob_length
LOOP
v_read_cnt := 32767;
DBMS_LOB.read(p_local_clob, v_read_cnt, v_index, v_chunk);
v_write_cnt := UTL_TCP.write_text(v_data, v_chunk, NULL);
UTL_TCP.flush(v_data);
v_index := v_index + v_write_cnt;
END LOOP;
write_to_log(
'closing passive connection: ' || v_data.remote_host || ':' || v_data.remote_port,
TRUE);
UTL_TCP.close_connection(v_data);
read_reply(p_connection);
EXCEPTION
WHEN OTHERS
THEN
write_to_log('ERROR in put_clob(' || p_remote_file || ')');
write_to_log(DBMS_UTILITY.format_error_stack || DBMS_UTILITY.format_error_backtrace);
write_to_log(
'closing passive connection: ' || v_data.remote_host || ':' || v_data.remote_port,
TRUE);
UTL_TCP.close_connection(v_data);
RAISE;
END put_clob;
-- Given a local clob, send it to the remote host to be saved as a file
-- by default the current transfer method will be ignored
-- and the procedure will temporarily switch to Image/Binary transfer
-- if force_binary is FALSE then the current method will be used.
PROCEDURE put_blob(p_connection IN OUT NOCOPY connection,
p_local_blob IN OUT NOCOPY BLOB,
p_remote_file IN VARCHAR2,
p_force_binary IN BOOLEAN DEFAULT TRUE
)
IS
v_save_method VARCHAR2(1);
v_read_cnt INTEGER;
v_write_cnt INTEGER;
v_lob_length INTEGER;
v_index INTEGER;
v_chunk RAW(32767);
v_data UTL_TCP.connection;
BEGIN
IF p_force_binary AND p_connection.transfer_method != 'I'
THEN
v_save_method := p_connection.transfer_method;
write_to_log('Saving current transfer method: ' || v_save_method, TRUE);
write_to_log('Setting transfer method for BLOB binary transfer', TRUE);
set_transfer_method(p_connection, 'IMAGE');
END IF;
v_data := get_passive_connection(p_connection);
send_ftp_command(p_connection, 'STOR', p_remote_file);
write_to_log('storing file on: ' || v_data.remote_host || ':' || v_data.remote_port, TRUE);
v_lob_length := DBMS_LOB.getlength(p_local_blob);
v_index := 1;
WHILE v_index <= v_lob_length
LOOP
v_read_cnt := 32767;
DBMS_LOB.read(p_local_blob, v_read_cnt, v_index, v_chunk);
v_write_cnt := UTL_TCP.write_raw(v_data, v_chunk, NULL);
UTL_TCP.flush(v_data);
v_index := v_index + v_write_cnt;
END LOOP;
write_to_log(
'closing passive connection: ' || v_data.remote_host || ':' || v_data.remote_port,
TRUE);
UTL_TCP.close_connection(v_data);
read_reply(p_connection);
IF p_force_binary AND v_save_method != 'I'
THEN
write_to_log('Restoring previous transfer method:' || v_save_method, TRUE);
set_transfer_method(p_connection, v_save_method, SUBSTR(v_save_method, 3, 1));
END IF;
EXCEPTION
WHEN OTHERS
THEN
write_to_log('ERROR in put_blob(' || p_remote_file || ')');
write_to_log(DBMS_UTILITY.format_error_stack || DBMS_UTILITY.format_error_backtrace);
write_to_log(
'closing passive connection: ' || v_data.remote_host || ':' || v_data.remote_port,
TRUE);
UTL_TCP.close_connection(v_data);
RAISE;
END put_blob;
-- Read a remote file directly into a local clob
-- there will be no OS file created locally by this
-- by default the current transfer method will be used
-- but it can be changed as part of this procedure call
PROCEDURE get_clob(p_connection IN OUT NOCOPY connection,
p_remote_file IN VARCHAR2,
p_local_clob IN OUT NOCOPY CLOB,
p_trans_method IN VARCHAR2 DEFAULT NULL
)
IS
v_char_cnt INTEGER;
v_chunk VARCHAR2(32767);
v_error VARCHAR(32767);
v_data UTL_TCP.connection;
BEGIN
v_data := get_passive_connection(p_connection);
set_transfer_method(p_connection, p_trans_method);
send_ftp_command(p_connection, 'RETR', p_remote_file);
write_to_log('retrieving file from: ' || v_data.remote_host || ':' || v_data.remote_port,
TRUE
);
LOOP
BEGIN
v_char_cnt := UTL_TCP.read_text(v_data, v_chunk, 32767);
DBMS_LOB.writeappend(p_local_clob, v_char_cnt, v_chunk);
EXCEPTION
WHEN UTL_TCP.end_of_input
THEN
EXIT;
WHEN OTHERS
THEN
RAISE;
END;
END LOOP;
write_to_log(
'closing passive connection: ' || v_data.remote_host || ':' || v_data.remote_port,
TRUE);
UTL_TCP.close_connection(v_data);
read_reply(p_connection);
EXCEPTION
WHEN OTHERS
THEN
write_to_log('ERROR in get_clob(' || p_remote_file || ')');
write_to_log(DBMS_UTILITY.format_error_stack || DBMS_UTILITY.format_error_backtrace);
write_to_log(
'closing passive connection: ' || v_data.remote_host || ':' || v_data.remote_port,
TRUE);
UTL_TCP.close_connection(v_data);
RAISE;
END get_clob;
-- Read a remote file directly into a local clob
-- there will be no OS file created locally by this
-- by default the current transfer method will be used
-- but it can be changed as part of this procedure call
FUNCTION get_clob(p_connection IN OUT NOCOPY connection,
p_remote_file IN VARCHAR2,
p_trans_method IN VARCHAR2 DEFAULT NULL
)
RETURN CLOB
IS
v_clob CLOB;
BEGIN
DBMS_LOB.createtemporary(v_clob, TRUE);
get_clob(p_connection, p_remote_file, v_clob, p_trans_method);
RETURN v_clob;
EXCEPTION
WHEN OTHERS
THEN
IF DBMS_LOB.istemporary(v_clob) = 1
THEN
IF DBMS_LOB.ISOPEN(v_clob) = 1
THEN
DBMS_LOB.close(v_clob);
END IF;
DBMS_LOB.freetemporary(v_clob);
END IF;
RAISE;
END get_clob;
-- Read a remote file directly into a local blob
-- there will be no OS file created locally by this
-- by default the current transfer method will be ignored
-- and the procedure will temporarily switch to Image/Binary transfer
-- if force_binary is FALSE then the current method will be used.
PROCEDURE get_blob(p_connection IN OUT NOCOPY connection,
p_remote_file IN VARCHAR2,
p_local_blob IN OUT NOCOPY BLOB,
p_force_binary IN BOOLEAN DEFAULT TRUE
)
IS
v_save_method VARCHAR2(1);
v_byte_cnt INTEGER;
v_chunk RAW(32767);
v_error VARCHAR(32767);
v_data UTL_TCP.connection;
BEGIN
IF p_force_binary AND p_connection.transfer_method != 'I'
THEN
v_save_method := p_connection.transfer_method;
write_to_log('Saving current transfer method: ' || v_save_method, TRUE);
write_to_log('Setting transfer method for BLOB binary transfer', TRUE);
set_transfer_method(p_connection, 'IMAGE');
END IF;
v_data := get_passive_connection(p_connection);
send_ftp_command(p_connection, 'RETR', p_remote_file);
write_to_log('retrieving file from: ' || v_data.remote_host || ':' || v_data.remote_port,
TRUE
);
LOOP
BEGIN
v_byte_cnt := UTL_TCP.read_raw(v_data, v_chunk, 32767);
DBMS_LOB.writeappend(p_local_blob, v_byte_cnt, v_chunk);
EXCEPTION
WHEN UTL_TCP.end_of_input
THEN
EXIT;
WHEN OTHERS
THEN
RAISE;
END;
END LOOP;
write_to_log(
'closing passive connection: ' || v_data.remote_host || ':' || v_data.remote_port,
TRUE);
UTL_TCP.close_connection(v_data);
read_reply(p_connection);
IF p_force_binary AND v_save_method != 'I'
THEN
write_to_log('Restoring previous transfer method:' || v_save_method, TRUE);
set_transfer_method(p_connection, v_save_method, SUBSTR(v_save_method, 3, 1));
END IF;
EXCEPTION
WHEN OTHERS
THEN
write_to_log('ERROR in get_blob(' || p_remote_file || ')');
write_to_log(DBMS_UTILITY.format_error_stack || DBMS_UTILITY.format_error_backtrace);
write_to_log(
'closing passive connection: ' || v_data.remote_host || ':' || v_data.remote_port,
TRUE);
UTL_TCP.close_connection(v_data);
RAISE;
END get_blob;
-- Read a remote file directly into a local blob
-- there will be no OS file created locally by this
-- by default the current transfer method will be ignored
-- and the procedure will temporarily switch to Image/Binary transfer
-- if force_binary is FALSE then the current method will be used.
FUNCTION get_blob(p_connection IN OUT NOCOPY connection,
p_remote_file IN VARCHAR2,
p_force_binary IN BOOLEAN DEFAULT TRUE
)
RETURN BLOB
IS
v_blob BLOB;
BEGIN
DBMS_LOB.createtemporary(v_blob, TRUE);
sdsftp.get_blob(p_connection, p_remote_file, v_blob, p_force_binary);
RETURN v_blob;
EXCEPTION
WHEN OTHERS
THEN
IF DBMS_LOB.istemporary(v_blob) = 1
THEN
IF DBMS_LOB.ISOPEN(v_blob) = 1
THEN
DBMS_LOB.close(v_blob);
END IF;
DBMS_LOB.freetemporary(v_blob);
END IF;
RAISE;
END get_blob;
-- Given a local file, send it to the remote host to be saved as a file
-- by default the current transfer method will be used but it can be
-- overridden. The procedure is overloaded 3 ways based on available values.
-- This version reads from a local utl_file pointer and sends that file
-- to the remote server.
-- with directory and local file, with utl_file pointer,
-- or just file names with current directory
PROCEDURE put_file(p_connection IN OUT NOCOPY connection,
p_local_file IN UTL_FILE.file_type,
p_remote_file IN VARCHAR2,
p_trans_method IN VARCHAR2 DEFAULT NULL
)
IS
v_byte_cnt INTEGER;
v_textchunk VARCHAR2(32767);
v_binchunk RAW(32767);
v_data UTL_TCP.connection;
BEGIN
set_transfer_method(p_connection, p_trans_method);
v_data := get_passive_connection(p_connection);
send_ftp_command(p_connection, 'STOR', p_remote_file);
write_to_log('writing file to: ' || v_data.remote_host || ':' || v_data.remote_port, TRUE);
LOOP
BEGIN
IF p_connection.transfer_method = 'I'
THEN
UTL_FILE.get_raw(file => p_local_file, buffer => v_binchunk, len => 32767);
v_byte_cnt := UTL_TCP.write_raw(v_data, v_binchunk, NULL);
ELSE
UTL_FILE.get_line(file => p_local_file, buffer => v_textchunk, len => 32767);
v_byte_cnt := UTL_TCP.write_text(v_data, v_textchunk, NULL);
END IF;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
EXIT;
WHEN OTHERS
THEN
RAISE;
END;
END LOOP;
write_to_log(
'closing passive connection: ' || v_data.remote_host || ':' || v_data.remote_port,
TRUE);
UTL_TCP.close_connection(v_data);
read_reply(p_connection);
EXCEPTION
WHEN OTHERS
THEN
write_to_log('ERROR in put_file(' || p_remote_file || ')');
write_to_log(DBMS_UTILITY.format_error_stack || DBMS_UTILITY.format_error_backtrace);
write_to_log(
'closing passive connection: ' || v_data.remote_host || ':' || v_data.remote_port,
TRUE);
UTL_TCP.close_connection(v_data);
RAISE;
END put_file;
-- Given a local file, send it to the remote host to be saved as a file
-- by default the current transfer method will be used but it can be
-- overridden. The procedure is overloaded 3 ways based on available values.
-- This version reads from a file from a local directory and sends that file
-- to the remote server.
PROCEDURE put_file(p_connection IN OUT NOCOPY connection,
p_local_directory IN VARCHAR2,
p_local_file IN VARCHAR2,
p_remote_file IN VARCHAR2,
p_trans_method IN VARCHAR2 DEFAULT NULL
)
IS
v_file UTL_FILE.file_type;
BEGIN
IF p_local_directory IS NULL
THEN
write_to_log(
'Local directory not set prior to get_file('
|| p_local_directory
|| ','
|| p_local_file
|| ')');
raise_application_error(
-20001,
'SDSFTP.PUT_FILE - Local directory must be specified to GET files',
TRUE);
END IF;
IF p_remote_file IS NULL
THEN
write_to_log(
'NULL file name used for put_file('
|| p_local_directory
|| ','
|| p_local_file
|| ')');
raise_application_error(-20001,
'SDSFTP.GET_FILE - Can not PUT a file into a NULL file name',
TRUE
);
END IF;
IF NVL(UPPER(p_trans_method), p_connection.transfer_method) IN ('BINARY', 'IMAGE', 'I')
THEN
v_file := UTL_FILE.fopen(p_local_directory, p_local_file, 'rb', 32767);
ELSE
v_file := UTL_FILE.fopen(p_local_directory, p_local_file, 'r', 32767);
END IF;
sdsftp.put_file(p_connection => p_connection,
p_local_file => v_file,
p_remote_file => p_remote_file,
p_trans_method => p_trans_method
);
UTL_FILE.fclose(v_file);
EXCEPTION
WHEN OTHERS
THEN
write_to_log('ERROR in put_file(' || p_remote_file || ')');
write_to_log(DBMS_UTILITY.format_error_stack || DBMS_UTILITY.format_error_backtrace);
IF UTL_FILE.is_open(v_file)
THEN
UTL_FILE.fclose(v_file);
END IF;
RAISE;
END put_file;
-- Given a local file, send it to the remote host to be saved as a file
-- by default the current transfer method will be used but it can be
-- overridden. The procedure is overloaded 3 ways based on available values.
-- This version reads from a file from the current local directory.
PROCEDURE put_file(p_connection IN OUT NOCOPY connection,
p_local_file IN VARCHAR2,
p_trans_method IN VARCHAR2 DEFAULT NULL
)
IS
BEGIN
IF p_connection.local_directory IS NULL
THEN
write_to_log('Local directory not set prior to put_file(' || p_local_file || ')');
raise_application_error(
-20001,
'SDSFTP.PUT_FILE - Local directory must be specified to PUT files',
TRUE);
END IF;
sdsftp.put_file(p_connection => p_connection,
p_local_directory => p_connection.local_directory,
p_local_file => p_local_file,
p_remote_file => p_local_file,
p_trans_method => p_trans_method
);
END put_file;
-- Read remote file to local UTL_FILE file handle
-- by default the current transfer mode will be used but it can be changed
-- This is the driver function that all other "get_file" procedures invoke
PROCEDURE get_file(p_connection IN OUT NOCOPY connection,
p_remote_file IN VARCHAR2,
p_local_file IN OUT NOCOPY UTL_FILE.file_type,
p_trans_method IN VARCHAR2 DEFAULT NULL
)
IS
v_transfer_cnt INTEGER; -- bytes for raw, characters for Text
v_textchunk VARCHAR2(32767);
v_binchunk RAW(32767);
v_data UTL_TCP.connection;
BEGIN
set_transfer_method(p_connection, p_trans_method);
v_data := get_passive_connection(p_connection);
send_ftp_command(p_connection, 'RETR', p_remote_file);
write_to_log('retrieving file from: ' || v_data.remote_host || ':' || v_data.remote_port,
TRUE
);
LOOP
BEGIN
IF p_connection.transfer_method = 'I'
THEN
v_transfer_cnt := UTL_TCP.read_raw(v_data, v_binchunk, 32767);
UTL_FILE.put_raw(file => p_local_file, buffer => v_binchunk, autoflush => TRUE);
ELSE
v_transfer_cnt := UTL_TCP.read_text(v_data, v_textchunk, 32767);
UTL_FILE.put(file => p_local_file, buffer => v_textchunk);
END IF;
UTL_FILE.fflush(p_local_file);
EXCEPTION
WHEN UTL_TCP.end_of_input
THEN
EXIT;
WHEN OTHERS
THEN
RAISE;
END;
END LOOP;
write_to_log(
'closing passive connection: ' || v_data.remote_host || ':' || v_data.remote_port,
TRUE);
UTL_TCP.close_connection(v_data);
read_reply(p_connection);
EXCEPTION
WHEN OTHERS
THEN
write_to_log('ERROR in get_file(' || p_remote_file || ')');
write_to_log(DBMS_UTILITY.format_error_stack || DBMS_UTILITY.format_error_backtrace);
write_to_log(
'closing passive connection: ' || v_data.remote_host || ':' || v_data.remote_port,
TRUE);
UTL_TCP.close_connection(v_data);
RAISE;
END get_file;
-- Read remote file to selected local directory and new local name
-- by default the current transfer mode will be used but it can be changed
PROCEDURE get_file(p_connection IN OUT NOCOPY connection,
p_remote_file IN VARCHAR2,
p_local_directory IN VARCHAR2,
p_local_file IN VARCHAR2,
p_trans_method IN VARCHAR2 DEFAULT NULL
)
IS
v_file UTL_FILE.file_type;
BEGIN
IF p_local_directory IS NULL
THEN
write_to_log('Local directory not set prior to get_file(' || p_remote_file || ')');
raise_application_error(
-20001,
'SDSFTP.GET_FILE - Local directory must be specified to GET files',
TRUE);
END IF;
IF p_local_file IS NULL
THEN
write_to_log('NULL file name used for get_file(' || p_remote_file || ')');
raise_application_error(-20001,
'SDSFTP.GET_FILE - Can not GET a file into a NULL file name',
TRUE
);
END IF;
IF NVL(UPPER(p_trans_method), p_connection.transfer_method) IN ('BINARY', 'IMAGE', 'I')
THEN
v_file := UTL_FILE.fopen(p_local_directory, p_local_file, 'wb', 32767);
ELSE
v_file := UTL_FILE.fopen(p_local_directory, p_local_file, 'w', 32767);
END IF;
sdsftp.get_file(p_connection => p_connection,
p_remote_file => p_remote_file,
p_local_file => v_file,
p_trans_method => p_trans_method
);
UTL_FILE.fclose(v_file);
EXCEPTION
WHEN OTHERS
THEN
write_to_log('ERROR in get_file(' || p_remote_file || ')');
write_to_log(DBMS_UTILITY.format_error_stack || DBMS_UTILITY.format_error_backtrace);
IF UTL_FILE.is_open(v_file)
THEN
UTL_FILE.fclose(v_file);
END IF;
RAISE;
END get_file;
-- Read remote file to current local directory with the same name
-- by default the current transfer mode will be used but it can be changed
PROCEDURE get_file(p_connection IN OUT NOCOPY connection,
p_remote_file IN VARCHAR2,
p_trans_method IN VARCHAR2 DEFAULT NULL
)
IS
BEGIN
IF p_connection.local_directory IS NULL
THEN
write_to_log('Local directory not set prior to get_file(' || p_remote_file || ')');
raise_application_error(
-20001,
'SDSFTP.GET_FILE - Local directory must be specified to GET files',
TRUE);
END IF;
sdsftp.get_file(p_connection => p_connection,
p_remote_file => p_remote_file,
p_local_directory => p_connection.local_directory,
p_local_file => p_remote_file,
p_trans_method => p_trans_method
);
END get_file;
-- Given a path, return the list of files, but default it return the full
-- file information but that may be changed to just the file names themselves.
-- Supported transfer methods are ASCII and EBCDIC only.
-- The current transfer method will be restored after listing if it was
-- changed by this routine.
-- Note the FTP RFC does not specify what the format will be so all information
-- is returned simply as a collection of strings.
PROCEDURE get_file_list(p_connection IN OUT NOCOPY connection,
p_remote_path IN VARCHAR2,
p_list OUT file_list,
p_names_only IN BOOLEAN DEFAULT FALSE,
p_trans_method IN VARCHAR2 DEFAULT 'ASCII'
)
IS
v_transfer_cnt INTEGER; -- bytes for raw, characters for Text
v_textchunk VARCHAR2(32767);
v_binchunk RAW(32767);
v_data UTL_TCP.connection;
v_save_method VARCHAR2(1) := NULL;
v_index INTEGER := 1;
BEGIN
IF p_trans_method NOT IN ('A', 'ASCII', 'E', 'EBCDIC')
THEN
raise_application_error(-20001,
'SDSFTP.get_file_list INVALID method: ' || p_trans_method,
TRUE
);
END IF;
IF p_trans_method != get_transfer_method(p_connection)
THEN
v_save_method := p_connection.transfer_method;
write_to_log('Saving current transfer method: ' || v_save_method, TRUE);
write_to_log('Setting ' || p_trans_method || ' method for remote file listing', TRUE);
set_transfer_method(p_connection, p_trans_method);
END IF;
v_data := get_passive_connection(p_connection);
IF p_names_only
THEN
send_ftp_command(p_connection, 'NLST', p_remote_path);
ELSE
send_ftp_command(p_connection, 'LIST', p_remote_path);
END IF;
write_to_log(
'retrieving file list from: ' || v_data.remote_host || ':' || v_data.remote_port,
TRUE);
p_list.delete;
LOOP
BEGIN
v_transfer_cnt := UTL_TCP.read_line(v_data, v_textchunk, TRUE, FALSE);
p_list(v_index) := v_textchunk;
v_index := v_index + 1;
EXCEPTION
WHEN UTL_TCP.end_of_input
THEN
EXIT;
WHEN OTHERS
THEN
RAISE;
END;
END LOOP;
write_to_log(
'closing passive connection: ' || v_data.remote_host || ':' || v_data.remote_port,
TRUE);
UTL_TCP.close_connection(v_data);
read_reply(p_connection);
IF v_save_method IS NOT NULL
THEN
set_transfer_method(p_connection, v_save_method);
END IF;
EXCEPTION
WHEN OTHERS
THEN
write_to_log('ERROR in get_file_list(' || p_remote_path || ')');
write_to_log(DBMS_UTILITY.format_error_stack || DBMS_UTILITY.format_error_backtrace);
write_to_log(
'closing passive connection: ' || v_data.remote_host || ':' || v_data.remote_port,
TRUE);
UTL_TCP.close_connection(v_data);
IF v_save_method IS NOT NULL
THEN
set_transfer_method(p_connection, v_save_method);
END IF;
RAISE;
END get_file_list;
-- Given a path, return the list of files, but default it return the full
-- file information but that may be changed to just the file names themselves.
-- Supported transfer methods are ASCII and EBCDIC only.
-- The current transfer method will be restored after listing if it was
-- changed by this routine.
-- Note the FTP RFC does not specify what the format will be so all information
-- is returned simply as a collection of strings.
FUNCTION get_file_list(p_connection IN OUT NOCOPY connection,
p_remote_path IN VARCHAR2,
p_names_only IN BOOLEAN DEFAULT FALSE,
p_trans_method IN VARCHAR2 DEFAULT 'ASCII'
)
RETURN file_list
IS
p_list file_list;
BEGIN
get_file_list(p_connection, p_remote_path, p_list, p_names_only, p_trans_method);
RETURN p_list;
END;
END sdsftp;
/
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
221:
222:
223:
224:
225:
226:
227:
228:
229:
230:
231:
232:
233:
234:
235:
236:
237:
238:
239:
240:
241:
242:
243:
244:
245:
246:
247:
248:
249:
250:
251:
252:
253:
254:
255:
256:
257:
258:
259:
260:
261:
262:
263:
264:
265:
266:
267:
268:
269:
270:
271:
272:
273:
274:
275:
276:
277:
278:
279:
280:
281:
282:
283:
284:
285:
286:
287:
288:
289:
290:
291:
292:
293:
294:
295:
296:
297:
298:
299:
300:
301:
302:
303:
304:
305:
306:
307:
308:
309:
310:
311:
312:
313:
314:
315:
316:
317:
318:
319:
320:
321:
322:
323:
324:
325:
326:
327:
328:
329:
330:
331:
332:
333:
334:
335:
336:
337:
338:
339:
340:
341:
342:
343:
344:
345:
346:
347:
348:
349:
350:
351:
352:
353:
354:
355:
356:
357:
358:
359:
360:
361:
362:
363:
364:
365:
366:
367:
368:
369:
370:
371:
372:
373:
374:
375:
376:
377:
378:
379:
380:
381:
382:
383:
384:
385:
386:
387:
388:
389:
390:
391:
392:
393:
394:
395:
396:
397:
398:
399:
400:
401:
402:
403:
404:
405:
406:
407:
408:
409:
410:
411:
412:
413:
414:
415:
416:
417:
418:
419:
420:
421:
422:
423:
424:
425:
426:
427:
428:
429:
430:
431:
432:
433:
434:
435:
436:
437:
438:
439:
440:
441:
442:
443:
444:
445:
446:
447:
448:
449:
450:
451:
452:
453:
454:
455:
456:
457:
458:
459:
460:
461:
462:
463:
464:
465:
466:
467:
468:
469:
470:
471:
472:
473:
474:
475:
476:
477:
478:
479:
480:
481:
482:
483:
484:
485:
486:
487:
488:
489:
490:
491:
492:
493:
494:
495:
496:
497:
498:
499:
500:
501:
502:
503:
504:
505:
506:
507:
508:
509:
510:
511:
512:
513:
514:
515:
516:
517:
518:
519:
520:
521:
522:
523:
524:
525:
526:
527:
528:
529:
530:
531:
532:
533:
534:
535:
536:
537:
538:
539:
540:
541:
542:
543:
544:
545:
546:
547:
548:
549:
550:
551:
552:
553:
554:
555:
556:
557:
558:
559:
560:
561:
562:
563:
564:
565:
566:
567:
568:
569:
570:
571:
572:
573:
574:
575:
576:
577:
578:
579:
580:
581:
582:
583:
584:
585:
586:
587:
588:
589:
590:
591:
592:
593:
594:
595:
596:
597:
598:
599:
600:
601:
602:
603:
604:
605:
606:
607:
608:
609:
610:
611:
612:
613:
614:
615:
616:
617:
618:
619:
620:
621:
622:
623:
624:
625:
626:
627:
628:
629:
630:
631:
632:
633:
634:
635:
636:
637:
638:
639:
640:
641:
642:
643:
644:
645:
646:
647:
648:
649:
650:
651:
652:
653:
654:
655:
656:
657:
658:
659:
660:
661:
662:
663:
664:
665:
666:
667:
668:
669:
670:
671:
672:
673:
674:
675:
676:
677:
678:
679:
680:
681:
682:
683:
684:
685:
686:
687:
688:
689:
690:
691:
692:
693:
694:
695:
696:
697:
698:
699:
700:
701:
702:
703:
704:
705:
706:
707:
708:
709:
710:
711:
712:
713:
714:
715:
716:
717:
718:
719:
720:
721:
722:
723:
724:
725:
726:
727:
728:
729:
730:
731:
732:
733:
734:
735:
736:
737:
738:
739:
740:
741:
742:
743:
744:
745:
746:
747:
748:
749:
750:
751:
752:
753:
754:
755:
756:
757:
758:
759:
760:
761:
762:
763:
764:
765:
766:
767:
768:
769:
770:
771:
772:
773:
774:
775:
776:
777:
778:
779:
780:
781:
782:
783:
784:
785:
786:
787:
788:
789:
790:
791:
792:
793:
794:
795:
796:
797:
798:
799:
800:
801:
802:
803:
804:
805:
806:
807:
808:
809:
810:
811:
812:
813:
814:
815:
816:
817:
818:
819:
820:
821:
822:
823:
824:
825:
826:
827:
828:
829:
830:
831:
832:
833:
834:
835:
836:
837:
838:
839:
840:
841:
842:
843:
844:
845:
846:
847:
848:
849:
850:
851:
852:
853:
854:
855:
856:
857:
858:
859:
860:
861:
862:
863:
864:
865:
866:
867:
868:
869:
870:
871:
872:
873:
874:
875:
876:
877:
878:
879:
880:
881:
882:
883:
884:
885:
886:
887:
888:
889:
890:
891:
892:
893:
894:
895:
896:
897:
898:
899:
900:
901:
902:
903:
904:
905:
906:
907:
908:
909:
910:
911:
912:
913:
914:
915:
916:
917:
918:
919:
920:
921:
922:
923:
924:
925:
926:
927:
928:
929:
930:
931:
932:
933:
934:
935:
936:
937:
938:
939:
940:
941:
942:
943:
944:
945:
946:
947:
948:
949:
950:
951:
952:
953:
954:
955:
956:
957:
958:
959:
960:
961:
962:
963:
964:
965:
966:
967:
968:
969:
970:
971:
972:
973:
974:
975:
976:
977:
978:
979:
980:
981:
982:
983:
984:
985:
986:
987:
988:
989:
990:
991:
992:
993:
994:
995:
996:
997:
998:
999:
1000:
1001:
1002:
1003:
1004:
1005:
1006:
1007:
1008:
1009:
1010:
1011:
1012:
1013:
1014:
1015:
1016:
1017:
1018:
1019:
1020:
1021:
1022:
1023:
1024:
1025:
1026:
1027:
1028:
1029:
1030:
1031:
1032:
1033:
1034:
1035:
1036:
1037:
1038:
1039:
1040:
1041:
1042:
1043:
1044:
1045:
1046:
1047:
1048:
1049:
1050:
1051:
1052:
1053:
1054:
1055:
1056:
1057:
1058:
1059:
1060:
1061:
1062:
1063:
1064:
1065:
1066:
1067:
1068:
1069:
1070:
1071:
1072:
1073:
1074:
1075:
1076:
1077:
1078:
1079:
1080:
1081:
1082:
1083:
1084:
1085:
1086:
1087:
1088:
1089:
1090:
1091:
1092:
1093:
1094:
1095:
1096:
1097:
1098:
1099:
1100:
1101:
1102:
1103:
1104:
1105:
1106:
1107:
1108:
1109:
1110:
1111:
1112:
1113:
1114:
1115:
1116:
1117:
1118:
1119:
1120:
1121:
1122:
1123:
1124:
1125:
1126:
1127:
1128:
1129:
1130:
1131:
1132:
1133:
1134:
1135:
1136:
1137:
1138:
1139:
1140:
1141:
1142:
1143:
1144:
1145:
1146:
1147:
1148:
1149:
1150:
1151:
1152:
1153:
1154:
1155:
1156:
1157:
1158:
1159:
1160:
1161:
1162:
1163:
1164:
1165:
1166:
1167:
1168:
1169:
1170:
1171:
1172:
1173:
1174:
1175:
1176:
1177:
1178:
1179:
1180:
1181:
1182:
1183:
1184:
1185:
1186:
1187:
1188:
1189:
1190:
1191:
1192:
1193:
1194:
1195:
1196:
1197:
1198:
1199:
1200:
1201:
1202:
1203:
1204:
1205:
1206:
1207:
1208:
1209:
1210:
1211:
1212:
1213:
1214:
1215:
1216:
1217:
1218:
1219:
1220:
1221:
1222:
1223:
1224:
1225:
1226:
1227:
1228:
1229:
1230:
1231:
1232:
1233:
1234:
1235:
1236:
1237:
1238:
1239:
1240:
1241:
1242:
1243:
1244:
1245:
1246:
1247:
1248:
1249:
1250:
1251:
1252:
1253:
1254:
1255:
1256:
1257:
1258:
1259:
1260:
1261:
1262:
1263:
1264:
1265:
1266:
1267:
1268:
1269:
1270:
1271:
1272:
1273:
1274:
1275:
1276:
1277:
1278:
1279:
1280:
1281:
1282:
1283:
1284:
1285:
1286:
1287:
1288:
1289:
1290:
1291:
1292:
1293:
1294:
1295:
1296:
1297:
1298:
1299:
1300:
1301:
1302:
1303:
1304:
1305:
1306:
1307:
1308:
1309:
1310:
1311:
1312:
1313:
1314:
1315:
1316:
1317:
1318:
1319:
1320:
1321:
1322:
1323:
1324:
1325:
1326:
1327:
1328:
1329:
1330:
1331:
1332:
1333:
1334:
1335:
1336:
1337:
1338:
1339:
1340:
1341:
1342:
1343:
1344:
1345:
1346:
1347:
1348:
1349:
1350:
1351:
1352:
1353:
1354:
1355:
1356:
1357:
1358:
1359:
1360:
1361:
1362:
1363:
1364:
1365:
1366:
1367:
1368:
1369:
1370:
1371:
1372:
1373:
1374:
1375:
1376:
1377:
1378:
1379:
1380:
1381:
1382:
1383:
1384:
1385:
by: perptechdba on 2010-06-02 at 11:49:20ID: 15262