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
31import java.io.BufferedReader;
32import java.io.File;
33import java.io.FileInputStream;
34import java.io.IOException;
35import java.io.InputStream;
36import java.io.InputStreamReader;
37import java.io.OutputStream;
38import java.io.PrintWriter;
39import java.net.InetAddress;
40import java.net.ServerSocket;
41import java.net.Socket;
42import java.net.URL;
43import java.net.URLConnection;
44import java.nio.file.Files;
45import java.nio.file.Paths;
46
47public 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
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
113            public FtpServerHandler(Socket cl) {
114                client = cl;
115            }
116
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
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
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
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
366        public FtpServer(int port) {
367            this.port = port;
368        }
369
370        public FtpServer() {
371            this(21);
372        }
373
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         */
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         */
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
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
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