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