1 /*
2  * Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 import java.io.*;
25 import java.net.*;
26 
27 /*
28  * @test
29  * @bug 4255280
30  * @summary URL.getContent() loses first six bytes for ftp URLs
31  * @run main FtpGetContent
32  * @run main/othervm -Djava.net.preferIPv6Addresses=true FtpGetContent
33  */
34 
35 public class FtpGetContent {
36     static int filesize = 2048;
37 
38     /**
39      * A class that simulates, on a separate, an FTP server.
40      */
41 
42     private class FtpServer extends Thread {
43         private final ServerSocket    server;
44         private int port;
45         private boolean done = false;
46         private boolean portEnabled = true;
47         private boolean pasvEnabled = true;
48         private boolean extendedEnabled = true;
49         private String username;
50         private String password;
51         private String cwd;
52         private String filename;
53         private String type;
54         private boolean list = false;
55 
56         /**
57          * This Inner class will handle ONE client at a time.
58          * That's where 99% of the protocol handling is done.
59          */
60 
61         private class FtpServerHandler extends Thread {
62             BufferedReader in;
63             PrintWriter out;
64             Socket client;
65             private final int ERROR = 0;
66             private final int USER = 1;
67             private final int PASS = 2;
68             private final int CWD = 3;
69             private final int CDUP = 4;
70             private final int PWD = 5;
71             private final int TYPE = 6;
72             private final int NOOP = 7;
73             private final int RETR = 8;
74             private final int PASV = 9;
75             private final int PORT = 10;
76             private final int LIST = 11;
77             private final int REIN = 12;
78             private final int QUIT = 13;
79             private final int STOR = 14;
80             private final int NLST = 15;
81             private final int RNFR = 16;
82             private final int RNTO = 17;
83             private final int EPSV = 18;
84             String[] cmds = { "USER", "PASS", "CWD", "CDUP", "PWD", "TYPE",
85                               "NOOP", "RETR", "PASV", "PORT", "LIST", "REIN",
86                               "QUIT", "STOR", "NLST", "RNFR", "RNTO", "EPSV"};
87             private String arg = null;
88             private ServerSocket pasv = null;
89             private int data_port = 0;
90             private InetAddress data_addr = null;
91 
92             /**
93              * Parses a line to match it with one of the supported FTP commands.
94              * Returns the command number.
95              */
96 
parseCmd(String cmd)97             private int parseCmd(String cmd) {
98                 System.out.println("FTP server received command: " + cmd);
99                 if (cmd == null || cmd.length() < 3)
100                     return ERROR;
101                 int blank = cmd.indexOf(' ');
102                 if (blank < 0)
103                     blank = cmd.length();
104                 if (blank < 3)
105                     return ERROR;
106                 String s = cmd.substring(0, blank);
107                 if (cmd.length() > blank+1)
108                     arg = cmd.substring(blank+1, cmd.length());
109                 else
110                     arg = null;
111                 for (int i = 0; i < cmds.length; i++) {
112                     if (s.equalsIgnoreCase(cmds[i]))
113                         return i+1;
114                 }
115                 return ERROR;
116             }
117 
FtpServerHandler(Socket cl)118             public FtpServerHandler(Socket cl) {
119                 client = cl;
120             }
121 
isPasvSet()122             protected boolean isPasvSet() {
123                 if (pasv != null && !pasvEnabled) {
124                     try {
125                         pasv.close();
126                     } catch (IOException ex) {
127                     }
128                     pasv = null;
129                 }
130                 if (pasvEnabled && pasv != null)
131                     return true;
132                 return false;
133             }
134 
135             /**
136              * Open the data socket with the client. This can be the
137              * result of a "PASV" or "PORT" command.
138              */
139 
getOutDataStream()140             protected OutputStream getOutDataStream() {
141                 try {
142                     if (isPasvSet()) {
143                         Socket s = pasv.accept();
144                         return s.getOutputStream();
145                     }
146                     if (data_addr != null) {
147                         Socket s = new Socket(data_addr, data_port);
148                         data_addr = null;
149                         data_port = 0;
150                         return s.getOutputStream();
151                     }
152                 } catch (Exception e) {
153                     e.printStackTrace();
154                 }
155                 return null;
156             }
157 
getInDataStream()158             protected InputStream getInDataStream() {
159                 try {
160                     if (isPasvSet()) {
161                         Socket s = pasv.accept();
162                         return s.getInputStream();
163                     }
164                     if (data_addr != null) {
165                         Socket s = new Socket(data_addr, data_port);
166                         data_addr = null;
167                         data_port = 0;
168                         return s.getInputStream();
169                     }
170                 } catch (Exception e) {
171                     e.printStackTrace();
172                 }
173                 return null;
174             }
175 
176             /**
177              * Handles the protocol exchange with the client.
178              */
179 
run()180             public void run() {
181                 boolean done = false;
182                 String str;
183                 int res;
184                 boolean logged = false;
185                 boolean waitpass = false;
186 
187                 try {
188                     in = new BufferedReader(new InputStreamReader(client.getInputStream()));
189                     out = new PrintWriter(client.getOutputStream(), true);
190                     out.println("220 tatooine FTP server (SunOS 5.8) ready.");
191                 } catch (Exception ex) {
192                     return;
193                 }
194                 while (!done) {
195                     try {
196                         str = in.readLine();
197                         res = parseCmd(str);
198                         if ((res > PASS && res != QUIT) && !logged) {
199                             out.println("530 Not logged in.");
200                             continue;
201                         }
202                         switch (res) {
203                         case ERROR:
204                             out.println("500 '" + str + "': command not understood.");
205                             break;
206                         case USER:
207                             if (!logged && !waitpass) {
208                                 username = str.substring(5);
209                                 password = null;
210                                 cwd = null;
211                                 if ("user2".equals(username)) {
212                                     out.println("230 Guest login ok, access restrictions apply.");
213                                     logged = true;
214                                 } else {
215                                     out.println("331 Password required for " + arg);
216                                     waitpass = true;
217                                 }
218                             } else {
219                                 out.println("503 Bad sequence of commands.");
220                             }
221                             break;
222                         case PASS:
223                             if (!logged && waitpass) {
224                                 out.println("230 Guest login ok, access restrictions apply.");
225                                 password = str.substring(5);
226                                 logged = true;
227                                 waitpass = false;
228                             } else
229                                 out.println("503 Bad sequence of commands.");
230                             break;
231                         case QUIT:
232                             out.println("221 Goodbye.");
233                             out.flush();
234                             out.close();
235                             if (pasv != null)
236                                 pasv.close();
237                             done = true;
238                             break;
239                         case TYPE:
240                             out.println("200 Type set to " + arg + ".");
241                             type = arg;
242                             break;
243                         case CWD:
244                             out.println("250 CWD command successful.");
245                             if (cwd == null)
246                                 cwd = str.substring(4);
247                             else
248                                 cwd = cwd + "/" + str.substring(4);
249                             break;
250                         case CDUP:
251                             out.println("250 CWD command successful.");
252                             break;
253                         case PWD:
254                             out.println("257 \"" + cwd + "\" is current directory");
255                             break;
256                         case EPSV:
257                             if (!extendedEnabled || !pasvEnabled) {
258                                 out.println("500 EPSV is disabled, " +
259                                                 "use PORT instead.");
260                                 continue;
261                             }
262                             if (!(server.getInetAddress() instanceof Inet6Address)) {
263                                 // pretend EPSV is not implemented
264                                 out.println("500 '" + str + "': command not understood.");
265                                 break;
266                             }
267                             if ("all".equalsIgnoreCase(arg)) {
268                                 out.println("200 EPSV ALL command successful.");
269                                 continue;
270                             }
271                             try {
272                                 if (pasv == null)
273                                     pasv = new ServerSocket(0, 0, server.getInetAddress());
274                                 int port = pasv.getLocalPort();
275                                 out.println("229 Entering Extended" +
276                                         " Passive Mode (|||" + port + "|)");
277                             } catch (IOException ssex) {
278                                 out.println("425 Can't build data connection:" +
279                                                 " Connection refused.");
280                             }
281                             break;
282                         case PASV:
283                             if (!pasvEnabled) {
284                                 out.println("500 PASV is disabled, use PORT instead.");
285                                 continue;
286                             }
287                             try {
288                                 if (pasv == null) {
289                                     pasv = new ServerSocket();
290                                     pasv.bind(new InetSocketAddress("127.0.0.1", 0));
291                                 }
292                                 int port = pasv.getLocalPort();
293                                 out.println("227 Entering Passive Mode (127,0,0,1," +
294                                             (port >> 8) + "," + (port & 0xff) +")");
295                             } catch (IOException ssex) {
296                                 out.println("425 Can't build data connection: Connection refused.");
297                             }
298                             break;
299                         case PORT:
300                             if (!portEnabled) {
301                                 out.println("500 PORT is disabled, use PASV instead");
302                                 continue;
303                             }
304                             StringBuffer host;
305                             int i=0, j=4;
306                             while (j>0) {
307                                 i = arg.indexOf(',', i+1);
308                                 if (i < 0)
309                                     break;
310                                 j--;
311                             }
312                             if (j != 0) {
313                                 out.println("500 '" + arg + "': command not understood.");
314                                 continue;
315                             }
316                             try {
317                                 host = new StringBuffer(arg.substring(0,i));
318                                 for (j=0; j < host.length(); j++)
319                                     if (host.charAt(j) == ',')
320                                         host.setCharAt(j, '.');
321                                 String ports = arg.substring(i+1);
322                                 i = ports.indexOf(',');
323                                 data_port = Integer.parseInt(ports.substring(0,i)) << 8;
324                                 data_port += (Integer.parseInt(ports.substring(i+1)));
325                                 data_addr = InetAddress.getByName(host.toString());
326                                 out.println("200 Command okay.");
327                             } catch (Exception ex3) {
328                                 data_port = 0;
329                                 data_addr = null;
330                                 out.println("500 '" + arg + "': command not understood.");
331                             }
332                             break;
333                         case RETR:
334                             {
335                                 filename = str.substring(5);
336                                 OutputStream dout = getOutDataStream();
337                                 if (dout != null) {
338                                     out.println("200 Command okay.");
339                                     BufferedOutputStream pout = new BufferedOutputStream(dout);
340                                     for (int x = 0; x < filesize ; x++)
341                                         pout.write(0);
342                                     pout.flush();
343                                     pout.close();
344                                     list = false;
345                                 } else
346                                     out.println("425 Can't build data connection: Connection refused.");
347                             }
348                             break;
349                         case NLST:
350                             filename = arg;
351                         case LIST:
352                             {
353                                 OutputStream dout = getOutDataStream();
354                                 if (dout != null) {
355                                     out.println("200 Command okay.");
356                                     PrintWriter pout = new PrintWriter(new BufferedOutputStream(dout));
357                                     pout.println("total 130");
358                                     pout.println("drwxrwxrwt   7 sys      sys          577 May 12 03:30 .");
359                                     pout.println("drwxr-xr-x  39 root     root        1024 Mar 27 12:55 ..");
360                                     pout.println("drwxrwxr-x   2 root     root         176 Apr 10 12:02 .X11-pipe");
361                                     pout.println("drwxrwxr-x   2 root     root         176 Apr 10 12:02 .X11-unix");
362                                     pout.println("drwxrwxrwx   2 root     root         179 Mar 30 15:09 .pcmcia");
363                                     pout.println("drwxrwxrwx   2 jladen   staff        117 Mar 30 18:18 .removable");
364                                     pout.println("drwxrwxrwt   2 root     root         327 Mar 30 15:08 .rpc_door");
365                                     pout.println("-rw-r--r--   1 root     other         21 May  5 16:59 hello2.txt");
366                                     pout.println("-rw-rw-r--   1 root     sys         5968 Mar 30 15:08 ps_data");
367                                     pout.flush();
368                                     pout.close();
369                                     list = true;
370                                 } else
371                                     out.println("425 Can't build data connection: Connection refused.");
372                             }
373                             break;
374                         case STOR:
375                             {
376                                 InputStream is = getInDataStream();
377                                 if (is != null) {
378                                     out.println("200 Command okay.");
379                                     BufferedInputStream din = new BufferedInputStream(is);
380                                     int val;
381                                     do {
382                                         val = din.read();
383                                     } while (val != -1);
384                                     din.close();
385                                 } else
386                                     out.println("425 Can't build data connection: Connection refused.");
387                             }
388                             break;
389                         }
390                     } catch (IOException ioe) {
391                         ioe.printStackTrace();
392                         try {
393                             out.close();
394                         } catch (Exception ex2) {
395                         }
396                         done = true;
397                     }
398                 }
399             }
400         }
401 
FtpServer(int port)402         public FtpServer(int port) {
403             this(null, 0);
404         }
405 
FtpServer(InetAddress address, int port)406         public FtpServer(InetAddress address, int port) {
407             this.port = port;
408             try {
409                 server = new ServerSocket();
410                 server.bind(new InetSocketAddress(address, port));
411             } catch (IOException e) {
412                 throw new RuntimeException(e);
413             }
414         }
415 
FtpServer()416         public FtpServer() {
417             this(21);
418         }
419 
getPort()420         public int getPort() {
421             if (server != null)
422                 return server.getLocalPort();
423             return 0;
424         }
425 
getAuthority()426         public String getAuthority() {
427             InetAddress address = server.getInetAddress();
428             String hostaddr = address.isAnyLocalAddress()
429                 ? "localhost" : address.getHostAddress();
430             if (hostaddr.indexOf(':') > -1) {
431                 hostaddr = "[" + hostaddr +"]";
432             }
433             return hostaddr + ":" + getPort();
434         }
435 
436         /**
437          * A way to tell the server that it can stop.
438          */
terminate()439         synchronized public void terminate() {
440             done = true;
441         }
442 
done()443         synchronized boolean done() {
444             return done;
445         }
446 
setPortEnabled(boolean ok)447         synchronized public void setPortEnabled(boolean ok) {
448             portEnabled = ok;
449         }
450 
setPasvEnabled(boolean ok)451         synchronized public void setPasvEnabled(boolean ok) {
452             pasvEnabled = ok;
453         }
454 
getUsername()455         String getUsername() {
456             return username;
457         }
458 
getPassword()459         String getPassword() {
460             return password;
461         }
462 
pwd()463         String pwd() {
464             return cwd;
465         }
466 
getFilename()467         String getFilename() {
468             return filename;
469         }
470 
getType()471         String getType() {
472             return type;
473         }
474 
getList()475         boolean getList() {
476             return list;
477         }
478 
479         /*
480          * All we got to do here is create a ServerSocket and wait for connections.
481          * When a connection happens, we just have to create a thread that will
482          * handle it.
483          */
run()484         public void run() {
485             try {
486                 Socket client;
487                 while (!done()) {
488                     client = server.accept();
489                     (new FtpServerHandler(client)).start();
490                 }
491             } catch(Exception e) {
492             } finally {
493                 try { server.close(); } catch (IOException unused) {}
494             }
495         }
496     }
main(String[] args)497     public static void main(String[] args) throws Exception {
498         FtpGetContent test = new FtpGetContent();
499     }
500 
FtpGetContent()501     public FtpGetContent() throws Exception {
502         FtpServer server = null;
503         try {
504             InetAddress loopback = InetAddress.getLoopbackAddress();
505             server = new FtpServer(loopback, 0);
506             server.start();
507             String authority = server.getAuthority();
508 
509             // Now let's check the URL handler
510 
511             URL url = new URL("ftp://" + authority + "/pub/BigFile");
512             InputStream stream = (InputStream)url.openConnection(Proxy.NO_PROXY)
513                                  .getContent();
514             byte[] buffer = new byte[1024];
515             int totalBytes = 0;
516             int bytesRead = stream.read(buffer);
517             while (bytesRead != -1) {
518                 totalBytes += bytesRead;
519                 bytesRead = stream.read(buffer);
520             }
521             stream.close();
522             if (totalBytes != filesize)
523                 throw new RuntimeException("wrong file size!");
524         } catch (IOException e) {
525             throw new RuntimeException(e.getMessage());
526         } finally {
527             server.terminate();
528             server.server.close();
529         }
530     }
531 }
532