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