# frozen_string_literal: true describe FakeFtp::Server, 'commands', functional: true do let(:absolute?) { true } let(:data_port) { rand(16_000..19_000) } let(:data_addr_bits) { SpecHelper.local_addr_bits(data_port) } let(:client_port) { rand(19_000..22_000) } let(:client_addr_bits) { SpecHelper.local_addr_bits(client_port) } let(:data_server_port) { rand(22_000..24_000) } let(:client) do TCPSocket.open('127.0.0.1', client_port).tap { |s| s.sync = true } end let(:server) do FakeFtp::Server.new( client_port, data_port, debug: ENV['DEBUG'] == '1', absolute: absolute? ) end let(:data_server) do SpecHelper::FakeDataServer.new(data_server_port) end before { server.start } after do client.close server.stop end context 'general' do it 'should accept connections' do expect(SpecHelper.gets_with_timeout(client)) .to eql("220 Can has FTP?\r\n") end it 'should get unknown command response when nothing is sent' do SpecHelper.gets_with_timeout(client) client.write("\r\n") expect(SpecHelper.gets_with_timeout(client)) .to match(/^500 Unknown command "[^"]*"\r\n/) end it 'accepts QUIT' do SpecHelper.gets_with_timeout(client) client.write("QUIT\r\n") expect(SpecHelper.gets_with_timeout(client)).to eql("221 OMG bye!\r\n") end it 'should accept multiple commands in one session' do SpecHelper.gets_with_timeout(client) client.write("USER thing\r\n") SpecHelper.gets_with_timeout(client) client.write("PASS thing\r\n") SpecHelper.gets_with_timeout(client) client.write("ACCT thing\r\n") SpecHelper.gets_with_timeout(client) client.write("USER thing\r\n") end it 'should accept SITE command' do SpecHelper.gets_with_timeout(client) client.write("SITE umask\r\n") expect(SpecHelper.gets_with_timeout(client)).to eql "200 umask\r\n" end end context 'passive' do it 'accepts PASV' do expect(server.mode).to eql(:active) SpecHelper.gets_with_timeout(client) client.write("PASV\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("227 Entering Passive Mode (#{data_addr_bits})\r\n") expect(server.mode).to eql(:passive) end it 'responds with correct PASV port' do server.stop server.passive_port = 21_111 server.start SpecHelper.gets_with_timeout(client) client.write("PASV\r\n") addr_bits = SpecHelper.local_addr_bits(21_111) expect(SpecHelper.gets_with_timeout(client)) .to eql("227 Entering Passive Mode (#{addr_bits})\r\n") end it 'does not accept PASV if no port set' do server.stop server.passive_port = nil server.start SpecHelper.gets_with_timeout(client) client.write("PASV\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("502 Aww hell no, use Active\r\n") end end context 'active' do before :each do SpecHelper.gets_with_timeout(client) data_server.start end after :each do data_server.stop end it 'accepts PORT and connects to port' do client.write("PORT #{data_server.addr_bits}\r\n") expect(SpecHelper.gets_with_timeout(client)).to eql("200 Okay\r\n") end it 'should switch to :active on port command' do expect(server.mode).to eql(:active) client.write("PASV\r\n") SpecHelper.gets_with_timeout(client) expect(server.mode).to eql(:passive) client.write("PORT #{data_server.addr_bits}\r\n") expect(SpecHelper.gets_with_timeout(client)).to eql("200 Okay\r\n") expect(server.mode).to eql(:active) end end context 'authentication commands' do before :each do SpecHelper.gets_with_timeout(client) end it 'accepts USER' do client.write("USER some_dude\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("331 send your password\r\n") end it 'accepts anonymous USER' do client.write("USER anonymous\r\n") expect(SpecHelper.gets_with_timeout(client)).to eql("230 logged in\r\n") end it 'accepts PASS' do client.write("PASS password\r\n") expect(SpecHelper.gets_with_timeout(client)).to eql("230 logged in\r\n") end it 'accepts ACCT' do client.write("ACCT\r\n") expect(SpecHelper.gets_with_timeout(client)).to eql("230 WHATEVER!\r\n") end end [true, false].each do |absolute| let(:absolute?) { absolute } context "directory commands with absolute=#{absolute}" do before :each do SpecHelper.gets_with_timeout(client) end it 'returns directory on PWD' do client.write("PWD\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("257 \"/pub\" is current directory\r\n") end it 'says OK to any CWD, CDUP, without doing anything' do client.write("CWD somewhere/else\r\n") expect(SpecHelper.gets_with_timeout(client)).to eql("250 OK!\r\n") client.write("CDUP\r\n") expect(SpecHelper.gets_with_timeout(client)).to eql("250 OK!\r\n") end end end [true, false].each do |absolute| context "file commands with absolute=#{absolute}" do let(:absolute?) { absolute } let(:file_prefix) { absolute ? '/pub/' : '' } before :each do SpecHelper.gets_with_timeout(client) end it 'accepts TYPE ascii' do client.write("TYPE A\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("200 Type set to A.\r\n") end it 'accepts TYPE image' do client.write("TYPE I\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("200 Type set to I.\r\n") end it 'does not accept TYPEs other than ascii or image' do client.write("TYPE E\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("504 We don't allow those\r\n") client.write("TYPE N\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("504 We don't allow those\r\n") client.write("TYPE T\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("504 We don't allow those\r\n") client.write("TYPE C\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("504 We don't allow those\r\n") client.write("TYPE L\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("504 We don't allow those\r\n") end context 'passive' do let(:data_client) do TCPSocket.open('127.0.0.1', data_port).tap { |c| c.sync = true } end before :each do client.write("PASV\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("227 Entering Passive Mode (#{data_addr_bits})\r\n") end it 'accepts STOR with filename' do client.write("STOR some_file\r\n") expect(SpecHelper.gets_with_timeout(client)).to eql("125 Do it!\r\n") data_client.write('1234567890') data_client.close expect(SpecHelper.gets_with_timeout(client)).to eql("226 Did it!\r\n") expect(server.files).to include(file_prefix + 'some_file') expect(server.file(file_prefix + 'some_file').bytes).to eql(10) expect(server.file(file_prefix + 'some_file').data) .to eql('1234567890') end it 'accepts STOR with filename and trailing newline' do client.write("STOR some_file\r\n") SpecHelper.gets_with_timeout(client) data_client.write("1234567890\n") data_client.close expect(SpecHelper.gets_with_timeout(client)).to eql("226 Did it!\r\n") expect(server.files).to include(file_prefix + 'some_file') expect(server.file(file_prefix + 'some_file').bytes).to eql(11) expect(server.file(file_prefix + 'some_file').data) .to eql("1234567890\n") end it 'accepts STOR with filename and long file' do client.write("STOR some_file\r\n") expect(SpecHelper.gets_with_timeout(client)).to eql("125 Do it!\r\n") data_client.write('1234567890' * 10_000) data_client.close expect(SpecHelper.gets_with_timeout(client)).to eql("226 Did it!\r\n") expect(server.files).to include(file_prefix + 'some_file') end it 'accepts STOR with streams' do client.puts 'STOR some_file' expect(SpecHelper.gets_with_timeout(client)).to eql("125 Do it!\r\n") data_client.write '1234567890' data_client.flush data_client.write '1234567890' data_client.flush data_client.close expect(SpecHelper.gets_with_timeout(client)).to eql("226 Did it!\r\n") expect(server.file(file_prefix + 'some_file').data) .to eql('12345678901234567890') end it 'does not accept RETR without a filename' do client.write("RETR\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("501 No filename given\r\n") end it 'does not serve files that do not exist' do client.write("RETR some_file\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("550 File not found\r\n") end it 'accepts RETR with a filename' do server.add_file(file_prefix + 'some_file', '1234567890') client.write("RETR #{file_prefix}some_file\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("150 File status ok, about to open data connection\r\n") data = SpecHelper.gets_with_timeout(data_client, endwith: "\0") data_client.close expect(data).to eql('1234567890') expect(SpecHelper.gets_with_timeout(client)) .to eql("226 File transferred\r\n") end it 'accepts DELE with a filename' do server.add_file('some_file', '1234567890') client.write("DELE some_file\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("250 Delete operation successful.\r\n") expect(server.files).to_not include('some_file') end it 'gives error message when trying to delete a file ' \ 'that does not exist' do client.write("DELE non_existing_file\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("550 Delete operation failed.\r\n") end it 'accepts a LIST command' do server.add_file(file_prefix + 'some_file', '1234567890') server.add_file(file_prefix + 'another_file', '1234567890') client.puts("LIST\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("150 Listing status ok, about to open data connection\r\n") data = SpecHelper.gets_with_timeout(data_client, endwith: "\0") data_client.close expect(data).to eql([ SpecHelper.statline(server.file(file_prefix + 'some_file')), SpecHelper.statline(server.file(file_prefix + 'another_file')) ].join("\n")) expect(SpecHelper.gets_with_timeout(client)) .to eql("226 List information transferred\r\n") end it 'accepts a LIST command with a wildcard argument' do infiles = %w[test.jpg test-2.jpg test.txt].map do |f| "#{file_prefix}#{f}" end infiles.each do |f| server.add_file(f, '1234567890') end client.write("LIST *.jpg\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("150 Listing status ok, about to open data connection\r\n") data = SpecHelper.gets_with_timeout(data_client, endwith: "\0") data_client.close expect(data).to eql( infiles[0, 2].map do |f| SpecHelper.statline(server.file(f)) end.join("\n") ) expect(SpecHelper.gets_with_timeout(client)) .to eql("226 List information transferred\r\n") end it 'accepts a LIST command with multiple wildcard arguments' do infiles = %w[test.jpg test.gif test.txt].map do |f| "#{file_prefix}#{f}" end infiles.each do |file| server.add_file(file, '1234567890') end client.write("LIST *.jpg *.gif\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("150 Listing status ok, about to open data connection\r\n") data = SpecHelper.gets_with_timeout(data_client, endwith: "\0") data_client.close expect(data).to eql( infiles[0, 2].map do |f| SpecHelper.statline(server.file(f)) end.join("\n") ) expect(SpecHelper.gets_with_timeout(client)) .to eql("226 List information transferred\r\n") end it 'accepts an NLST command' do server.add_file('some_file', '1234567890') server.add_file('another_file', '1234567890') client.write("NLST\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("150 Listing status ok, about to open data connection\r\n") data = SpecHelper.gets_with_timeout(data_client, endwith: "\0") data_client.close expect(data).to eql("some_file\nanother_file\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("226 List information transferred\r\n") end it 'accepts an NLST command with wildcard arguments' do files = ['test.jpg', 'test.txt', 'test2.jpg'] files.each do |file| server.add_file(file, '1234567890') end client.write("NLST *.jpg\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("150 Listing status ok, about to open data connection\r\n") data = SpecHelper.gets_with_timeout(data_client, endwith: "\0") data_client.close expect(data).to eql("test.jpg\ntest2.jpg\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("226 List information transferred\r\n") end it 'should allow mdtm' do filename = file_prefix + 'file.txt' now = Time.now server.add_file(filename, 'some dummy content', now) client.write("MDTM #{filename}\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("213 #{now.strftime('%Y%m%d%H%M%S')}\r\n") end end context 'active' do before :each do data_server.start end after :each do data_server.stop end it 'creates a directory on MKD' do client.write("MKD some_dir\r\n") expect(SpecHelper.gets_with_timeout(client)).to eql("257 OK!\r\n") end it 'should save the directory after you CWD' do client.write("CWD /somewhere/else\r\n") expect(SpecHelper.gets_with_timeout(client)).to eql("250 OK!\r\n") client.write("PWD\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("257 \"/somewhere/else\" is current directory\r\n") end it 'CWD should add a / to the beginning of the directory' do client.write("CWD somewhere/else\r\n") expect(SpecHelper.gets_with_timeout(client)).to eql("250 OK!\r\n") client.write("PWD\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("257 \"/somewhere/else\" is current directory\r\n") end it 'should not change the directory on CDUP' do client.write("CDUP\r\n") expect(SpecHelper.gets_with_timeout(client)).to eql("250 OK!\r\n") client.write("PWD\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("257 \"/pub\" is current directory\r\n") end it 'sends error message if no PORT received' do client.write("STOR some_file\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("425 Ain't no data port!\r\n") end it 'accepts STOR with filename' do client.write("PORT #{data_server.addr_bits}\r\n") expect(SpecHelper.gets_with_timeout(client)).to eql("200 Okay\r\n") client.write("STOR some_other_file\r\n") expect(SpecHelper.gets_with_timeout(client)).to eql("125 Do it!\r\n") data_server.handler_sock.print('12345') data_server.handler_sock.close expect(SpecHelper.gets_with_timeout(client)).to eql("226 Did it!\r\n") expect(server.files).to include(file_prefix + 'some_other_file') expect(server.file(file_prefix + 'some_other_file').bytes).to eql(5) end it 'accepts RETR with a filename' do client.write("PORT #{data_server.addr_bits}\r\n") data_server.handler_sock expect(SpecHelper.gets_with_timeout(client)).to eql("200 Okay\r\n") server.add_file(file_prefix + 'some_file', '1234567890') client.write("RETR #{file_prefix}some_file\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("150 File status ok, about to open data connection\r\n") data = SpecHelper.gets_with_timeout( data_server.handler_sock, endwith: "\0" ) data_server.handler_sock.close expect(data).to eql('1234567890') expect(SpecHelper.gets_with_timeout(client)) .to eql("226 File transferred\r\n") end it 'accepts RNFR without filename' do client.write("RNFR\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("501 Send path name.\r\n") end it 'accepts RNTO without RNFR' do client.write("RNTO some_other_file\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("503 Send RNFR first.\r\n") end it 'accepts RNTO and RNFR without filename' do client.write("RNFR from_file\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("350 Send RNTO to complete rename.\r\n") client.write("RNTO\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("501 Send path name.\r\n") end it 'accepts RNTO and RNFR for not existing file' do client.write("RNFR from_file\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("350 Send RNTO to complete rename.\r\n") client.write("RNTO to_file\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("550 File not found.\r\n") end it 'accepts RNTO and RNFR' do server.add_file(file_prefix + 'from_file', '1234567890') client.write("RNFR from_file\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("350 Send RNTO to complete rename.\r\n") client.write("RNTO to_file\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("250 Path renamed.\r\n") expect(server.files).to include(file_prefix + 'to_file') expect(server.files).to_not include(file_prefix + 'from_file') end it 'accepts an NLST command' do client.write("PORT #{data_server.addr_bits}\r\n") data_server.handler_sock expect(SpecHelper.gets_with_timeout(client)).to eql("200 Okay\r\n") server.add_file('some_file', '1234567890') server.add_file('another_file', '1234567890') client.write("NLST\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("150 Listing status ok, about to open data connection\r\n") data = SpecHelper.gets_with_timeout( data_server.handler_sock, endwith: "\0" ) data_server.handler_sock.close expect(data).to eql("some_file\nanother_file\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("226 List information transferred\r\n") end it 'has a flavor' do client.write("PORT #{data_server.addr_bits}\r\n") data_server.handler_sock expect(SpecHelper.gets_with_timeout(client)).to eql("200 Okay\r\n") client.write("WAT\r\n") expect(SpecHelper.gets_with_timeout(client)) .to eql("418 Pizza Party\r\n") data = SpecHelper.gets_with_timeout( data_server.handler_sock, endwith: "\0" ) data_server.handler_sock.close expect(data).to_not be_nil expect(data).to_not be_empty end end end end end