1use strict;
2use Test::More tests => 18;
3use POSIX qw(dup2);
4use IO::Handle;
5use FileHandle;
6
7use Net::FTPServer::InMem::Server;
8
9pipe INFD0, OUTFD0 or die "pipe: $!";
10pipe INFD1, OUTFD1 or die "pipe: $!";
11my $pid = fork ();
12die unless defined $pid;
13unless ($pid) {			# Child process (the server).
14  POSIX::dup2 (fileno INFD0, 0);
15  POSIX::dup2 (fileno OUTFD1, 1);
16  close INFD0;
17  close OUTFD0;
18  close INFD1;
19  close OUTFD1;
20  my $ftps = Net::FTPServer::InMem::Server->run
21    (['--test', '-d', '-C', '/dev/null']);
22  exit;
23}
24
25# Parent process (the test script).
26close INFD0;
27close OUTFD1;
28OUTFD0->autoflush (1);
29
30$_ = <INFD1>;
31print OUTFD0 "USER rich\r\n";
32$_ = <INFD1>;
33ok (/^331/);
34
35print OUTFD0 "PASS 123456\r\n";
36$_ = <INFD1>;
37ok (/^230 Welcome rich\./);
38
39# Use binary mode.
40print OUTFD0 "TYPE I\r\n";
41$_ = <INFD1>;
42ok (/^200/);
43
44# Enter passive mode and get a port number.
45print OUTFD0 "PASV\r\n";
46$_ = <INFD1>;
47ok (/^227 Entering Passive Mode \(127,0,0,1,(.*),(.*)\)/);
48
49my $port = $1 * 256 + $2;
50
51# Upload a large binary file.
52print OUTFD0 "STOR test\r\n";
53$_ = <INFD1>;
54ok (/^150/);
55
56# Connect to the passive mode port.
57my $sock = new IO::Socket::INET
58  (PeerAddr => "127.0.0.1:$port",
59   Proto => "tcp")
60  or die "socket: $!";
61
62my $buffer = "";
63
64for (my $i = 0; $i < 10000; ++$i)
65  {
66    my $c = chr (int (rand 256));
67    $buffer .= $c;
68  }
69$sock->print ($buffer);
70$sock->close;
71
72# Check the return code.
73$_ = <INFD1>;
74ok (/^226/);
75
76# Grab random parts of the file and check.
77ok (download_and_check ("test", 5000,  substr ($buffer, 5000)));
78ok (download_and_check ("test", 1000,  substr ($buffer, 1000)));
79ok (download_and_check ("test", 500,   substr ($buffer, 500)));
80ok (download_and_check ("test", 10000, substr ($buffer, 10000)));
81ok (download_and_check ("test", 0,     substr ($buffer, 0)));
82
83# Upload a smallish ascii file.
84print OUTFD0 "TYPE A\r\n";
85$_ = <INFD1>;
86ok (/^200/);
87
88print OUTFD0 "STOR asctest\r\n";
89$_ = <INFD1>;
90ok (/^150/);
91
92$sock = new IO::Socket::INET
93  (PeerAddr => "127.0.0.1:$port",
94   Proto => "tcp")
95  or die "socket: $!";
96
97$sock->print ("0123456789\r\n",
98	      "abcdefghij\r\n",
99	      "klmnopqrst\r\n",
100	      "uvwxyzABCD\r\n");
101$sock->close;
102
103$_ = <INFD1>;
104ok (/^226/);
105
106# Try a restartable download, in ASCII mode. Note how we are counting
107# those end of line characters.
108print OUTFD0 "REST 33\r\n";
109$_ = <INFD1>;
110ok (/^350/);
111
112print OUTFD0 "RETR asctest\r\n";
113$_ = <INFD1>;
114ok (/^150/);
115
116$sock = new IO::Socket::INET
117  (PeerAddr => "127.0.0.1:$port",
118   Proto => "tcp")
119  or die "socket: $!";
120
121$_ = $sock->getline;
122ok ($_ && $_ eq "uvwxyzABCD\r\n");
123
124$sock->close;
125
126$_ = <INFD1>;
127ok (/^226/);
128
129print OUTFD0 "QUIT\r\n";
130$_ = <INFD1>;
131
132exit;
133
134sub download_and_check
135  {
136    my $filename = shift;
137    my $restart = shift;
138    my $expected_data = shift;
139
140    # Perform restartable download.
141    print OUTFD0 "REST $restart\r\n";
142    $_ = <INFD1>;
143    return 0 unless /^350/;
144
145    # Download.
146    print OUTFD0 "RETR $filename\r\n";
147    $_ = <INFD1>;
148    return 0 unless /^150/;
149
150    # Connect to the passive mode port.
151    my $sock = new IO::Socket::INET
152      (PeerAddr => "127.0.0.1:$port",
153       Proto => "tcp")
154	or die "socket: $!";
155
156    # Read the data.
157    my $actual_data;
158    my $r = $sock->read ($actual_data, 30000);
159
160    $sock->close;
161
162    # Check the return code.
163    $_ = <INFD1>;
164    return 0 unless /^226/;
165
166    # Check the data.
167    return 0 unless $r == length ($expected_data);
168
169    return $expected_data eq $actual_data;
170  }
171
172__END__
173