1 /*
2  * Copyright (c) 2002, 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 /*
25  * @test
26  * @bug 4673103
27  * @library /test/lib
28  * @run main/othervm/timeout=140 MarkResetTest
29  * @summary URLConnection.getContent() hangs over FTP for DOC, PPT, XLS files
30  */
31 
32 import java.io.BufferedReader;
33 import java.io.File;
34 import java.io.FileInputStream;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.InputStreamReader;
38 import java.io.OutputStream;
39 import java.io.PrintWriter;
40 import java.io.UncheckedIOException;
41 import java.net.InetAddress;
42 import java.net.InetSocketAddress;
43 import java.net.Proxy;
44 import java.net.ServerSocket;
45 import java.net.Socket;
46 import java.net.URL;
47 import java.net.URLConnection;
48 import java.nio.file.Files;
49 import java.nio.file.Paths;
50 
51 import jdk.test.lib.net.URIBuilder;
52 
53 public class MarkResetTest {
54     private static final String FILE_NAME = "EncDec.doc";
55 
56     /**
57      * A class that simulates, on a separate, an FTP server.
58      */
59     private class FtpServer extends Thread {
60         private final ServerSocket server;
61         private volatile boolean done = false;
62         private boolean pasvEnabled = true;
63         private boolean portEnabled = true;
64         private boolean extendedEnabled = true;
65 
66         /**
67          * This Inner class will handle ONE client at a time.
68          * That's where 99% of the protocol handling is done.
69          */
70 
71         private class FtpServerHandler extends Thread {
72             BufferedReader in;
73             PrintWriter out;
74             Socket client;
75             private final int ERROR = 0;
76             private final int USER = 1;
77             private final int PASS = 2;
78             private final int CWD =  3;
79             private final int TYPE = 4;
80             private final int RETR = 5;
81             private final int PASV = 6;
82             private final int PORT = 7;
83             private final int QUIT = 8;
84             private final int EPSV = 9;
85             String[] cmds = { "USER", "PASS", "CWD",
86                                 "TYPE", "RETR", "PASV",
87                                 "PORT", "QUIT", "EPSV"};
88             private String arg = null;
89             private ServerSocket pasv = null;
90             private int data_port = 0;
91             private InetAddress data_addr = null;
92 
93             /**
94              * Parses a line to match it with one of the supported FTP commands.
95              * Returns the command number.
96              */
97 
parseCmd(String cmd)98             private int parseCmd(String 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                 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(
189                                                 client.getInputStream()));
190                     out = new PrintWriter(client.getOutputStream(), true);
191                     out.println("220 tatooine FTP server (SunOS 5.8) ready.");
192                 } catch (Exception ex) {
193                     return;
194                 }
195                 while (!done) {
196                     try {
197                         str = in.readLine();
198                         res = parseCmd(str);
199                         if ((res > PASS && res != QUIT) && !logged) {
200                             out.println("530 Not logged in.");
201                             continue;
202                         }
203                         switch (res) {
204                         case ERROR:
205                             out.println("500 '" + str +
206                                         "': command not understood.");
207                             break;
208                         case USER:
209                             if (!logged && !waitpass) {
210                                 out.println("331 Password required for " + arg);
211                                 waitpass = true;
212                             } else {
213                                 out.println("503 Bad sequence of commands.");
214                             }
215                             break;
216                         case PASS:
217                             if (!logged && waitpass) {
218                                 out.println("230-Welcome to the FTP server!");
219                                 out.println("ab");
220                                 out.println("230 Guest login ok, " +
221                                             "access restrictions apply.");
222                                 logged = true;
223                                 waitpass = false;
224                             } else
225                                 out.println("503 Bad sequence of commands.");
226                             break;
227                         case QUIT:
228                             out.println("221 Goodbye.");
229                             out.flush();
230                             out.close();
231                             if (pasv != null)
232                                 pasv.close();
233                             done = true;
234                             break;
235                         case TYPE:
236                             out.println("200 Type set to " + arg + ".");
237                             break;
238                         case CWD:
239                             out.println("250 CWD command successful.");
240                             break;
241                         case EPSV:
242                             if (!extendedEnabled || !pasvEnabled) {
243                                 out.println("500 EPSV is disabled, " +
244                                                 "use PORT instead.");
245                                 continue;
246                             }
247                             if ("all".equalsIgnoreCase(arg)) {
248                                 out.println("200 EPSV ALL command successful.");
249                                 continue;
250                             }
251                             try {
252                                 if (pasv == null) {
253                                     pasv = new ServerSocket();
254                                     pasv.bind(new InetSocketAddress(server.getInetAddress(), 0));
255                                 }
256                                 int port = pasv.getLocalPort();
257                                 out.println("229 Entering Extended" +
258                                         " Passive Mode (|||" + port + "|)");
259                             } catch (IOException ssex) {
260                                 out.println("425 Can't build data connection:" +
261                                                 " Connection refused.");
262                             }
263                             break;
264 
265                         case PASV:
266                             if (!pasvEnabled) {
267                                 out.println("500 PASV is disabled, " +
268                                                 "use PORT instead.");
269                                 continue;
270                             }
271                             try {
272                                 if (pasv == null) {
273                                     pasv = new ServerSocket();
274                                     pasv.bind(new InetSocketAddress("127.0.0.1", 0));
275                                 }
276                                 int port = pasv.getLocalPort();
277 
278                                 // Parenthesis are optional, so let's be
279                                 // nasty and don't put them
280                                 out.println("227 Entering Passive Mode" +
281                                                 " 127,0,0,1," +
282                                             (port >> 8) + "," + (port & 0xff));
283                             } catch (IOException ssex) {
284                                 out.println("425 Can't build data connection:" +
285                                                  "Connection refused.");
286                             }
287                             break;
288                         case PORT:
289                             if (!portEnabled) {
290                                 out.println("500 PORT is disabled, " +
291                                                 "use PASV instead");
292                                 continue;
293                             }
294                             StringBuffer host;
295                             int i = 0, j = 4;
296                             while (j > 0) {
297                                 i = arg.indexOf(',', i + 1);
298                                 if (i < 0)
299                                     break;
300                                 j--;
301                             }
302                             if (j != 0) {
303                                 out.println("500 '" + arg + "':" +
304                                             " command not understood.");
305                                 continue;
306                             }
307                             try {
308                                 host = new StringBuffer(arg.substring(0, i));
309                                 for (j = 0; j < host.length(); j++)
310                                     if (host.charAt(j) == ',')
311                                         host.setCharAt(j, '.');
312                                 String ports = arg.substring(i+1);
313                                 i = ports.indexOf(',');
314                                 data_port = Integer.parseInt(
315                                                 ports.substring(0, i)) << 8;
316                                 data_port += (Integer.parseInt(
317                                                 ports.substring(i+1)));
318                                 data_addr = InetAddress.getByName(
319                                                         host.toString());
320                                 out.println("200 Command okay.");
321                             } catch (Exception ex3) {
322                                 data_port = 0;
323                                 data_addr = null;
324                                 out.println("500 '" + arg + "':" +
325                                              " command not understood.");
326                             }
327                             break;
328                         case RETR:
329                             {
330                                 File file = new File(arg);
331                                 if (!file.exists()) {
332                                    System.out.println("File not found");
333                                    out.println("200 Command okay.");
334                                    out.println("550 '" + arg +
335                                             "' No such file or directory.");
336                                    break;
337                                 }
338                                 FileInputStream fin = new FileInputStream(file);
339                                 OutputStream dout = getOutDataStream();
340                                 if (dout != null) {
341                                    out.println("150 Binary data connection" +
342                                                 " for " + arg +
343                                                 " (" + client.getInetAddress().
344                                                 getHostAddress() + ") (" +
345                                                 file.length() + " bytes).");
346                                     int c;
347                                     int len = 0;
348                                     while ((c = fin.read()) != -1) {
349                                         dout.write(c);
350                                         len++;
351                                     }
352                                     dout.flush();
353                                     dout.close();
354                                     fin.close();
355                                    out.println("226 Binary Transfer complete.");
356                                 } else {
357                                     out.println("425 Can't build data" +
358                                         " connection: Connection refused.");
359                                 }
360                             }
361                             break;
362                         }
363                     } catch (IOException ioe) {
364                         ioe.printStackTrace();
365                         try {
366                             out.close();
367                         } catch (Exception ex2) {
368                         }
369                         done = true;
370                     }
371                 }
372             }
373         }
374 
FtpServer(int port)375         public FtpServer(int port) {
376             this(InetAddress.getLoopbackAddress(), port);
377         }
378 
FtpServer(InetAddress address, int port)379         public FtpServer(InetAddress address, int port) {
380             try {
381                 if (address == null) {
382                     server = new ServerSocket(port);
383                 } else {
384                     server = new ServerSocket();
385                     server.bind(new InetSocketAddress(address, port));
386                 }
387             } catch (IOException e) {
388                 throw new UncheckedIOException(e);
389             }
390         }
391 
FtpServer()392         public FtpServer() {
393             this(null, 21);
394         }
395 
getPort()396         public int getPort() {
397             return server.getLocalPort();
398         }
399 
400         /**
401          * A way to tell the server that it can stop.
402          */
terminate()403         synchronized public void terminate() {
404             done = true;
405         }
406 
407 
408         /*
409          * All we got to do here is create a ServerSocket and wait for a
410          * connection. When a connection happens, we just have to create
411          * a thread that will handle it.
412          */
run()413         public void run() {
414             try {
415                 System.out.println("FTP server waiting for connections at: "
416                         + server.getLocalSocketAddress());
417                 Socket client;
418                 client = server.accept();
419                 (new FtpServerHandler(client)).start();
420                 server.close();
421             } catch (Exception e) {
422             }
423         }
424     }
425 
main(String[] args)426     public static void main(String[] args) throws Exception {
427         Files.copy(Paths.get(System.getProperty("test.src"), FILE_NAME),
428                 Paths.get(".", FILE_NAME));
429         new MarkResetTest();
430     }
431 
MarkResetTest()432     public MarkResetTest() {
433         FtpServer server = null;
434         try {
435             server = new FtpServer(0);
436             server.start();
437             int port = 0;
438             while (port == 0) {
439                 Thread.sleep(500);
440                 port = server.getPort();
441             }
442 
443             URL url = URIBuilder.newBuilder()
444                     .scheme("ftp")
445                     .loopback()
446                     .port(port)
447                     .path("/" + FILE_NAME)
448                     .toURL();
449 
450             URLConnection con = url.openConnection(Proxy.NO_PROXY);
451             System.out.println("getContent: " + con.getContent());
452             System.out.println("getContent-length: " + con.getContentLength());
453 
454             InputStream is = con.getInputStream();
455 
456             /**
457              * guessContentTypeFromStream method calls mark and reset methods
458              * on the given stream. Make sure that calling
459              * guessContentTypeFromStream repeatedly does not affect
460              * reading from the stream afterwards
461              */
462             System.out.println("Call GuessContentTypeFromStream()" +
463                                 " several times..");
464             for (int i = 0; i < 5; i++) {
465                 System.out.println((i + 1) + " mime-type: " +
466                         con.guessContentTypeFromStream(is));
467             }
468 
469             int len = 0;
470             int c;
471             while ((c = is.read()) != -1) {
472                 len++;
473             }
474             is.close();
475             System.out.println("read: " + len + " bytes of the file");
476 
477             // We're done!
478             server.terminate();
479             server.interrupt();
480 
481             // Did we pass ?
482             if (len != (new File(FILE_NAME)).length()) {
483                 throw new Exception("Failed to read the file correctly");
484             }
485             System.out.println("PASSED: File read correctly");
486         } catch (Exception e) {
487             e.printStackTrace();
488             try {
489                 server.terminate();
490                 server.interrupt();
491             } catch (Exception ex) {
492             }
493             throw new RuntimeException("FTP support error: " + e.getMessage());
494         }
495     }
496 }
497