1 /* 2 * Copyright (c) 2001, 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 import java.io.*; 25 import java.net.*; 26 27 /* 28 * @test 29 * @bug 4255280 30 * @summary URL.getContent() loses first six bytes for ftp URLs 31 * @run main FtpGetContent 32 * @run main/othervm -Djava.net.preferIPv6Addresses=true FtpGetContent 33 */ 34 35 public class FtpGetContent { 36 static int filesize = 2048; 37 38 /** 39 * A class that simulates, on a separate, an FTP server. 40 */ 41 42 private class FtpServer extends Thread { 43 private final ServerSocket server; 44 private int port; 45 private boolean done = false; 46 private boolean portEnabled = true; 47 private boolean pasvEnabled = true; 48 private boolean extendedEnabled = true; 49 private String username; 50 private String password; 51 private String cwd; 52 private String filename; 53 private String type; 54 private boolean list = false; 55 56 /** 57 * This Inner class will handle ONE client at a time. 58 * That's where 99% of the protocol handling is done. 59 */ 60 61 private class FtpServerHandler extends Thread { 62 BufferedReader in; 63 PrintWriter out; 64 Socket client; 65 private final int ERROR = 0; 66 private final int USER = 1; 67 private final int PASS = 2; 68 private final int CWD = 3; 69 private final int CDUP = 4; 70 private final int PWD = 5; 71 private final int TYPE = 6; 72 private final int NOOP = 7; 73 private final int RETR = 8; 74 private final int PASV = 9; 75 private final int PORT = 10; 76 private final int LIST = 11; 77 private final int REIN = 12; 78 private final int QUIT = 13; 79 private final int STOR = 14; 80 private final int NLST = 15; 81 private final int RNFR = 16; 82 private final int RNTO = 17; 83 private final int EPSV = 18; 84 String[] cmds = { "USER", "PASS", "CWD", "CDUP", "PWD", "TYPE", 85 "NOOP", "RETR", "PASV", "PORT", "LIST", "REIN", 86 "QUIT", "STOR", "NLST", "RNFR", "RNTO", "EPSV"}; 87 private String arg = null; 88 private ServerSocket pasv = null; 89 private int data_port = 0; 90 private InetAddress data_addr = null; 91 92 /** 93 * Parses a line to match it with one of the supported FTP commands. 94 * Returns the command number. 95 */ 96 parseCmd(String cmd)97 private int parseCmd(String cmd) { 98 System.out.println("FTP server received command: " + 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 boolean 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(client.getInputStream())); 189 out = new PrintWriter(client.getOutputStream(), true); 190 out.println("220 tatooine FTP server (SunOS 5.8) ready."); 191 } catch (Exception ex) { 192 return; 193 } 194 while (!done) { 195 try { 196 str = in.readLine(); 197 res = parseCmd(str); 198 if ((res > PASS && res != QUIT) && !logged) { 199 out.println("530 Not logged in."); 200 continue; 201 } 202 switch (res) { 203 case ERROR: 204 out.println("500 '" + str + "': command not understood."); 205 break; 206 case USER: 207 if (!logged && !waitpass) { 208 username = str.substring(5); 209 password = null; 210 cwd = null; 211 if ("user2".equals(username)) { 212 out.println("230 Guest login ok, access restrictions apply."); 213 logged = true; 214 } else { 215 out.println("331 Password required for " + arg); 216 waitpass = true; 217 } 218 } else { 219 out.println("503 Bad sequence of commands."); 220 } 221 break; 222 case PASS: 223 if (!logged && waitpass) { 224 out.println("230 Guest login ok, access restrictions apply."); 225 password = str.substring(5); 226 logged = true; 227 waitpass = false; 228 } else 229 out.println("503 Bad sequence of commands."); 230 break; 231 case QUIT: 232 out.println("221 Goodbye."); 233 out.flush(); 234 out.close(); 235 if (pasv != null) 236 pasv.close(); 237 done = true; 238 break; 239 case TYPE: 240 out.println("200 Type set to " + arg + "."); 241 type = arg; 242 break; 243 case CWD: 244 out.println("250 CWD command successful."); 245 if (cwd == null) 246 cwd = str.substring(4); 247 else 248 cwd = cwd + "/" + str.substring(4); 249 break; 250 case CDUP: 251 out.println("250 CWD command successful."); 252 break; 253 case PWD: 254 out.println("257 \"" + cwd + "\" is current directory"); 255 break; 256 case EPSV: 257 if (!extendedEnabled || !pasvEnabled) { 258 out.println("500 EPSV is disabled, " + 259 "use PORT instead."); 260 continue; 261 } 262 if (!(server.getInetAddress() instanceof Inet6Address)) { 263 // pretend EPSV is not implemented 264 out.println("500 '" + str + "': command not understood."); 265 break; 266 } 267 if ("all".equalsIgnoreCase(arg)) { 268 out.println("200 EPSV ALL command successful."); 269 continue; 270 } 271 try { 272 if (pasv == null) 273 pasv = new ServerSocket(0, 0, server.getInetAddress()); 274 int port = pasv.getLocalPort(); 275 out.println("229 Entering Extended" + 276 " Passive Mode (|||" + port + "|)"); 277 } catch (IOException ssex) { 278 out.println("425 Can't build data connection:" + 279 " Connection refused."); 280 } 281 break; 282 case PASV: 283 if (!pasvEnabled) { 284 out.println("500 PASV is disabled, use PORT instead."); 285 continue; 286 } 287 try { 288 if (pasv == null) { 289 pasv = new ServerSocket(); 290 pasv.bind(new InetSocketAddress("127.0.0.1", 0)); 291 } 292 int port = pasv.getLocalPort(); 293 out.println("227 Entering Passive Mode (127,0,0,1," + 294 (port >> 8) + "," + (port & 0xff) +")"); 295 } catch (IOException ssex) { 296 out.println("425 Can't build data connection: Connection refused."); 297 } 298 break; 299 case PORT: 300 if (!portEnabled) { 301 out.println("500 PORT is disabled, use PASV instead"); 302 continue; 303 } 304 StringBuffer host; 305 int i=0, j=4; 306 while (j>0) { 307 i = arg.indexOf(',', i+1); 308 if (i < 0) 309 break; 310 j--; 311 } 312 if (j != 0) { 313 out.println("500 '" + arg + "': command not understood."); 314 continue; 315 } 316 try { 317 host = new StringBuffer(arg.substring(0,i)); 318 for (j=0; j < host.length(); j++) 319 if (host.charAt(j) == ',') 320 host.setCharAt(j, '.'); 321 String ports = arg.substring(i+1); 322 i = ports.indexOf(','); 323 data_port = Integer.parseInt(ports.substring(0,i)) << 8; 324 data_port += (Integer.parseInt(ports.substring(i+1))); 325 data_addr = InetAddress.getByName(host.toString()); 326 out.println("200 Command okay."); 327 } catch (Exception ex3) { 328 data_port = 0; 329 data_addr = null; 330 out.println("500 '" + arg + "': command not understood."); 331 } 332 break; 333 case RETR: 334 { 335 filename = str.substring(5); 336 OutputStream dout = getOutDataStream(); 337 if (dout != null) { 338 out.println("200 Command okay."); 339 BufferedOutputStream pout = new BufferedOutputStream(dout); 340 for (int x = 0; x < filesize ; x++) 341 pout.write(0); 342 pout.flush(); 343 pout.close(); 344 list = false; 345 } else 346 out.println("425 Can't build data connection: Connection refused."); 347 } 348 break; 349 case NLST: 350 filename = arg; 351 case LIST: 352 { 353 OutputStream dout = getOutDataStream(); 354 if (dout != null) { 355 out.println("200 Command okay."); 356 PrintWriter pout = new PrintWriter(new BufferedOutputStream(dout)); 357 pout.println("total 130"); 358 pout.println("drwxrwxrwt 7 sys sys 577 May 12 03:30 ."); 359 pout.println("drwxr-xr-x 39 root root 1024 Mar 27 12:55 .."); 360 pout.println("drwxrwxr-x 2 root root 176 Apr 10 12:02 .X11-pipe"); 361 pout.println("drwxrwxr-x 2 root root 176 Apr 10 12:02 .X11-unix"); 362 pout.println("drwxrwxrwx 2 root root 179 Mar 30 15:09 .pcmcia"); 363 pout.println("drwxrwxrwx 2 jladen staff 117 Mar 30 18:18 .removable"); 364 pout.println("drwxrwxrwt 2 root root 327 Mar 30 15:08 .rpc_door"); 365 pout.println("-rw-r--r-- 1 root other 21 May 5 16:59 hello2.txt"); 366 pout.println("-rw-rw-r-- 1 root sys 5968 Mar 30 15:08 ps_data"); 367 pout.flush(); 368 pout.close(); 369 list = true; 370 } else 371 out.println("425 Can't build data connection: Connection refused."); 372 } 373 break; 374 case STOR: 375 { 376 InputStream is = getInDataStream(); 377 if (is != null) { 378 out.println("200 Command okay."); 379 BufferedInputStream din = new BufferedInputStream(is); 380 int val; 381 do { 382 val = din.read(); 383 } while (val != -1); 384 din.close(); 385 } else 386 out.println("425 Can't build data connection: Connection refused."); 387 } 388 break; 389 } 390 } catch (IOException ioe) { 391 ioe.printStackTrace(); 392 try { 393 out.close(); 394 } catch (Exception ex2) { 395 } 396 done = true; 397 } 398 } 399 } 400 } 401 FtpServer(int port)402 public FtpServer(int port) { 403 this(null, 0); 404 } 405 FtpServer(InetAddress address, int port)406 public FtpServer(InetAddress address, int port) { 407 this.port = port; 408 try { 409 server = new ServerSocket(); 410 server.bind(new InetSocketAddress(address, port)); 411 } catch (IOException e) { 412 throw new RuntimeException(e); 413 } 414 } 415 FtpServer()416 public FtpServer() { 417 this(21); 418 } 419 getPort()420 public int getPort() { 421 if (server != null) 422 return server.getLocalPort(); 423 return 0; 424 } 425 getAuthority()426 public String getAuthority() { 427 InetAddress address = server.getInetAddress(); 428 String hostaddr = address.isAnyLocalAddress() 429 ? "localhost" : address.getHostAddress(); 430 if (hostaddr.indexOf(':') > -1) { 431 hostaddr = "[" + hostaddr +"]"; 432 } 433 return hostaddr + ":" + getPort(); 434 } 435 436 /** 437 * A way to tell the server that it can stop. 438 */ terminate()439 synchronized public void terminate() { 440 done = true; 441 } 442 done()443 synchronized boolean done() { 444 return done; 445 } 446 setPortEnabled(boolean ok)447 synchronized public void setPortEnabled(boolean ok) { 448 portEnabled = ok; 449 } 450 setPasvEnabled(boolean ok)451 synchronized public void setPasvEnabled(boolean ok) { 452 pasvEnabled = ok; 453 } 454 getUsername()455 String getUsername() { 456 return username; 457 } 458 getPassword()459 String getPassword() { 460 return password; 461 } 462 pwd()463 String pwd() { 464 return cwd; 465 } 466 getFilename()467 String getFilename() { 468 return filename; 469 } 470 getType()471 String getType() { 472 return type; 473 } 474 getList()475 boolean getList() { 476 return list; 477 } 478 479 /* 480 * All we got to do here is create a ServerSocket and wait for connections. 481 * When a connection happens, we just have to create a thread that will 482 * handle it. 483 */ run()484 public void run() { 485 try { 486 Socket client; 487 while (!done()) { 488 client = server.accept(); 489 (new FtpServerHandler(client)).start(); 490 } 491 } catch(Exception e) { 492 } finally { 493 try { server.close(); } catch (IOException unused) {} 494 } 495 } 496 } main(String[] args)497 public static void main(String[] args) throws Exception { 498 FtpGetContent test = new FtpGetContent(); 499 } 500 FtpGetContent()501 public FtpGetContent() throws Exception { 502 FtpServer server = null; 503 try { 504 InetAddress loopback = InetAddress.getLoopbackAddress(); 505 server = new FtpServer(loopback, 0); 506 server.start(); 507 String authority = server.getAuthority(); 508 509 // Now let's check the URL handler 510 511 URL url = new URL("ftp://" + authority + "/pub/BigFile"); 512 InputStream stream = (InputStream)url.openConnection(Proxy.NO_PROXY) 513 .getContent(); 514 byte[] buffer = new byte[1024]; 515 int totalBytes = 0; 516 int bytesRead = stream.read(buffer); 517 while (bytesRead != -1) { 518 totalBytes += bytesRead; 519 bytesRead = stream.read(buffer); 520 } 521 stream.close(); 522 if (totalBytes != filesize) 523 throw new RuntimeException("wrong file size!"); 524 } catch (IOException e) { 525 throw new RuntimeException(e.getMessage()); 526 } finally { 527 server.terminate(); 528 server.server.close(); 529 } 530 } 531 } 532