class EL_FTP_IMPLEMENTATION
Constants base on list of raw ftp commands
note
description: "[
Constants base on [http://www.nsftools.com/tips/RawFTP.htm list of raw ftp commands]
]"
author: "Finnian Reilly"
copyright: "Copyright (c) 2001-2022 Finnian Reilly"
contact: "finnian at eiffel hyphen loop dot com"
license: "MIT license (See: en.wikipedia.org/wiki/MIT_License)"
date: "2024-11-05 14:26:21 GMT (Tuesday 5th November 2024)"
revision: "25"
deferred class
EL_FTP_IMPLEMENTATION
inherit
EL_FTP_NETWORK_RESOURCE
rename
make as make_ftp,
last_reply as last_reply_utf_8
end
EL_ITERATION_ROUTINES
EL_FILE_OPEN_ROUTINES
rename
Open as File_open,
Read as Read_from
end
EL_SHARED_STRING_8_BUFFER_POOL
feature -- Access
last_reply: ZSTRING
do
create Result.make_from_utf_8 (last_reply_utf_8)
Result.right_adjust
end
feature -- Measurement
file_size (file_path: FILE_PATH): INTEGER
do
send_path (Command.size, file_path, << Reply.file_status >>)
if last_succeeded then
Result := String_8.substring_to_reversed (last_reply_utf_8, ' ').to_integer
end
end
last_entry_count: INTEGER
-- directory entry count set by `read_entry_count' or
feature -- Status query
last_succeeded: BOOLEAN
do
Result := error_code = 0
end
feature {NONE} -- Sending commands
send (cmd: IMMUTABLE_STRING_8; utf_8_path: detachable STRING; codes: ARRAY [NATURAL_16])
require
valid_path: cmd [cmd.count] = '%S' implies attached utf_8_path
local
utf_8_cmd: STRING; substitute_index: INTEGER
do
substitute_index := cmd.index_of ('%S', 1)
if attached String_8_pool.borrowed_item as borrowed then
if substitute_index > 0 then
if attached utf_8_path as path then
utf_8_cmd := borrowed.empty
utf_8_cmd.append_substring (cmd, 1, substitute_index - 1)
utf_8_cmd.append (path)
else
utf_8_cmd := Empty_string_8
error_code := Wrong_command
end
else
utf_8_cmd := cmd
end
if error_code = Wrong_command then
display_command_error (cmd, Error.missing_argument)
else
attempt (agent try_send (utf_8_cmd, codes, ?), 3)
end
borrowed.return
end
end
send_command (parts: ARRAY [STRING]; valid_replies: ARRAY [NATURAL_16] error_type_code: INTEGER): BOOLEAN
do
if attached main_socket as socket then
socket.do_command (parts, last_reply_utf_8)
if not has_error then
Result := valid_replies.has (last_reply_code)
if not Result then
error_code := error_type_code
end
end
else
error_code := no_socket_to_connect
end
end
send_passive_mode_command: BOOLEAN
-- Send passive mode command. Did it work?
do
Result := send_command (
<< Ftp_passive_mode_command >>, Reply.valid_enter_passive_mode, Wrong_command
)
end
send_password: BOOLEAN
-- Send password. Did it work?
do
Result := send_command (
<< Ftp_password_command, address.password >>, Reply.valid_password, Access_denied
)
end
send_path (cmd: IMMUTABLE_STRING_8; a_path: EL_PATH; codes: ARRAY [NATURAL_16])
-- send command `cmd' with `path' argument and possible success `codes'
do
send (cmd, a_path.to_unix.to_utf_8, codes)
end
send_port_command: BOOLEAN
-- Send PORT command. Did it work?
require
data_socket_exists: attached data_socket
do
if attached data_socket as socket then
Result := send_command (
<< Ftp_port_command, new_localhost_port_string (socket.port) >>, Reply.valid_response, Wrong_command
)
end
end
send_transfer_command: BOOLEAN
local
cmd: STRING
do
if initiating_listing and data_socket = Void then
if passive_mode then
Result := enter_passive_mode_for_data
else
Result := send_port_command
end
if Result then
send (Command.name_list, address.path, << Reply.about_to_open_data_connection >>)
if has_error then
error_code := Permission_denied
end
end
else
if attached main_socket as socket then
if passive_mode then
Result := enter_passive_mode_for_data
else
Result := send_port_command
end
if Result then
if Read_mode then
cmd := Ftp_retrieve_command
else
check write_mode: write_mode end
cmd := Ftp_store_command
end
Result := send_command (
<< cmd, address.path >>, << Reply.about_to_open_data_connection >>, Permission_denied
)
if Result and then Read_mode then
set_resource_size (last_reply_utf_8)
end
end
else
error_code := no_socket_to_connect
end
end
end
send_transfer_mode_command: BOOLEAN
-- Send transfer mode command. Did it work?
do
Result := send_command (
<< Is_binary_mode_command [is_binary_mode] >>, Reply.valid_response, Wrong_command
)
end
send_username: BOOLEAN
-- Send username. Did it work?
do
Result := send_command (
<< Ftp_user_command, address.username >>, Reply.valid_username, No_such_user
)
end
try_send (utf_8_command: STRING; valid_replies: ARRAY [NATURAL_16]; done: BOOLEAN_REF)
do
reset_error
if send_command (<< utf_8_command >>, valid_replies, Wrong_command) then
done.set_item (True)
end
if has_error then
display_command_error (utf_8_command, error_text (error_code))
end
rescue
close_sockets
login
retry
end
feature {NONE} -- Implementation
display_command_error (cmd: STRING; message: READABLE_STRING_GENERAL)
local
upper_command: STRING
do
if is_lio_enabled then
upper_command := String_8.substring_to (cmd, ' ')
upper_command.to_upper
lio.put_labeled_string (upper_command + " error", message)
lio.put_new_line
end
end
display_error (message: READABLE_STRING_GENERAL)
do
if is_lio_enabled then
lio.put_labeled_string (Error.label, message)
lio.put_new_line
end
end
display_reply_error
do
if is_lio_enabled then
lio.put_labeled_string (Error.label + " server reply", last_reply)
lio.put_new_line
end
end
enter_passive_mode_for_data: BOOLEAN
do
Result := send_passive_mode_command
if Result and then attached new_data_socket as socket then
socket.connect
data_socket := socket
else
Result := False
end
end
initiate_file_listing (dir_path: DIR_PATH)
do
push_address_path (dir_path.to_unix.to_utf_8)
set_passive_mode
initiating_listing := True
last_entry_count := 0
initiate_transfer
pop_address_path
end
initiate_transfer
local
socket: like accepted_socket
do
if is_proxy_used then
check attached proxy_connection as l_proxy then
l_proxy.initiate_transfer
end
else
if not passive_mode then
create socket.make_server (Current)
data_socket := socket
socket.set_timeout (timeout)
socket.listen (1)
end
if send_transfer_command then
debug Io.error.put_string ("Accepting socket...%N") end
if passive_mode then
accepted_socket := data_socket
elseif attached socket then
socket.accept
socket := socket.accepted
check l_socket_attached: attached socket end
accepted_socket := socket
end
if attached accepted_socket then
debug Io.error.put_string ("Socket accepted%N") end
transfer_initiated := True
is_packet_pending := True
else
error_code := Connection_refused
end
end
end
ensure then
connection_established: attached data_socket as l_data_socket and then
(l_data_socket.is_open_read or l_data_socket.is_open_write)
rescue
error_code := Connection_refused
end
pop_address_path
do
address.path.share (Stored_path)
end
push_address_path (new_path: STRING)
do
Stored_path.share (address.path)
address.path.share (new_path)
end
reply_contains (str: READABLE_STRING_8): BOOLEAN
do
Result := last_reply_utf_8.has_substring (str)
end
reset_file_listing
do
transfer_initiated := False; is_packet_pending := False
initiating_listing := False
data_socket := Void
end
transfer_file (source_path, destination_path: FILE_PATH)
do
attempt (agent try_transfer_file (source_path, destination_path, ?), 3)
end
transfer_file_data (a_file_path: FILE_PATH)
--
local
packet: PACKET; bytes_read: INTEGER
do
create packet.make (Default_packet_size)
if attached main_socket as socket
and then attached open_raw (a_file_path, Read_from) as file_in
then
from until file_in.after loop
file_in.read_to_managed_pointer (packet.data, 0, packet.count)
bytes_read := file_in.bytes_read
if bytes_read > 0 then
if bytes_read /= packet.count then
packet.data.resize (bytes_read)
end
data_socket.write (packet)
end
end
data_socket.close
data_socket := Void
is_packet_pending := false
file_in.close
socket.get_reply (last_reply_utf_8)
end
end
try_transfer_file (source_path, destination_path: FILE_PATH; done: BOOLEAN_REF)
local
l_error_code: INTEGER; l_reply: STRING
do
push_address_path (destination_path.to_unix.to_utf_8)
set_passive_mode
initiate_transfer
pop_address_path
if transfer_initiated then
transfer_file_data (source_path)
transfer_initiated := False
end
if has_error then
l_error_code := error_code; l_reply := last_reply_utf_8 -- save
if file_size (destination_path) = File.byte_count (source_path) then
-- was false alarm
reset_error; done.set_item (True)
else
error_code := l_error_code; last_reply_utf_8 := l_reply -- restore
end
else
done.set_item (True)
end
ensure
address_path_unchanged: old address.path ~ address.path
unattached_data_socket: not attached data_socket
end
feature {NONE} -- Deferred
login
deferred
end
feature {NONE} -- Internal attributes
created_directory_set: EL_HASH_SET [DIR_PATH]
reply_parser: EL_FTP_REPLY_PARSER
feature {NONE} -- Buffers
Stored_path: STRING
once
create Result.make_empty
end
end