class EL_FTP_STREAM_SOCKET
NETWORK_STREAM_SOCKET for file transfer protocol (FTP)
note
description: "${NETWORK_STREAM_SOCKET} for file transfer protocol (FTP)"
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-05-17 8:42:21 GMT (Friday 17th May 2024)"
revision: "4"
class
EL_FTP_STREAM_SOCKET
inherit
NETWORK_STREAM_SOCKET
export
{NONE} all
{ANY} accept, accepted, close, connect, is_closed, is_open_write, is_open_read,
last_string, listen, port, put_string, read_stream, set_timeout, write
end
TRANSFER_COMMAND_CONSTANTS
rename
Http_end_of_header_line as Carriage_return_new_line
end
create
make_control, make_data, make_server
feature {NONE} -- Initialization
make_control (a_resource: EL_FTP_NETWORK_RESOURCE)
do
resource := a_resource
make_client_by_port (a_resource.address.port, a_resource.address.host)
end
make_data (a_resource: EL_FTP_NETWORK_RESOURCE; port_specification: STRING)
-- parse specification from ftp reply like: "227 entering passive mode (213,171,193,5,210,246)."
require
valid_port_specification: port_specification.has ('(') and then port_specification.occurrences (',') = 5
local
s: EL_STRING_8_ROUTINES; number_list, ip_address: STRING; index, i: INTEGER
port_number, byte: INTEGER
do
resource := a_resource
number_list := s.substring_to_reversed (port_specification, '(')
index := number_list.last_index_of (')', number_list.count)
if index > 0 then
number_list.keep_head (index - 1)
index := number_list.count
from i := 0 until i > 8 loop
byte := s.substring_to_reversed_from (number_list, ',', $index).to_integer
port_number := port_number | (byte |<< i)
i := i + 8
end
ip_address := number_list.substring (1, index)
s.replace_character (ip_address, ',', '.')
else
create ip_address.make_empty
end
make_client_by_port (port_number, ip_address)
end
make_server (a_resource: EL_FTP_NETWORK_RESOURCE)
do
resource := a_resource
make_server_by_port (0)
end
feature -- Status query
has_error: BOOLEAN
do
Result := resource.has_error
end
feature -- Basic operations
do_command (parts: ARRAY [STRING]; reply_out: STRING)
do
if attached Packet_buffer.empty as buffer and then attached Packet_data as data then
across parts as list loop
if buffer.count > 0 then
buffer.append_character (' ')
end
buffer.append (list.item)
end
buffer.append (Carriage_return_new_line)
data.set_from_pointer (buffer.area.base_address, buffer.count)
put_managed_pointer (data, 0, buffer.count)
data.set_from_pointer (default_pointer, 0)
end
get_reply (reply_out)
reply_out.adjust; reply_out.to_lower
end
get_reply (reply_out: STRING)
require
socket_readable: is_open_read
local
go_on: BOOLEAN; received: BOOLEAN
do
reply_out.wipe_out
from until has_error or else (reply_out.count > 0 and not go_on) loop
if ready_for_reading then
read_line
reply_out.append (last_string)
reply_out.append (Carriage_return_new_line)
go_on := is_multi_line (last_string)
received := True
else
resource.set_connection_timeout_error
end
end
if not received then
resource.set_transmission_error
end
end
feature {NONE} -- Implementation
is_multi_line (str: STRING): BOOLEAN
-- check for dash character in 4th position after leading whitespace
-- indicating a multi-line message such as:
-- 220-FTP Server Ready
-- 220-Please note the following:
-- 220-Welcome to the server.
-- 220 Some final note.
do
if has_response_code (str) then
Result := str.valid_index (4) and then str [4] = '-'
end
end
has_response_code (str: STRING): BOOLEAN
-- `True' if `str' starts with 3 digits
local
i, digit_count: INTEGER; break: BOOLEAN
do
Result := True
from i := 1 until i > str.count or break loop
if str [i].is_digit then
digit_count := digit_count + 1
else
break := True
end
i := i + 1
end
Result := digit_count = 3
end
feature {NONE} -- Internal attributes
resource: EL_FTP_NETWORK_RESOURCE
feature {NONE} -- Buffers
Packet_buffer: EL_STRING_8_BUFFER
once
create Result
end
Packet_data: MANAGED_POINTER
once
create Result.share_from_pointer (default_pointer, 0)
end
end