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