/* * Copyright (c) 1994, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /** * FTP stream opener. */ package sun.net.www.protocol.ftp; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.BufferedInputStream; import java.io.FilterInputStream; import java.io.FilterOutputStream; import java.io.FileNotFoundException; import java.net.URL; import java.net.SocketPermission; import java.net.UnknownHostException; import java.net.InetSocketAddress; import java.net.URI; import java.net.Proxy; import java.net.ProxySelector; import java.util.StringTokenizer; import java.util.Iterator; import java.security.Permission; import sun.net.NetworkClient; import sun.net.www.MessageHeader; import sun.net.www.MeteredStream; import sun.net.www.URLConnection; import sun.net.www.protocol.http.HttpURLConnection; import sun.net.ftp.FtpClient; import sun.net.ftp.FtpProtocolException; import sun.net.ProgressSource; import sun.net.ProgressMonitor; import sun.net.www.ParseUtil; import sun.security.action.GetPropertyAction; /** * This class Opens an FTP input (or output) stream given a URL. * It works as a one shot FTP transfer : *
*
* UrlConnection con = url.openConnection();
* InputStream is = con.getInputStream();
* ...
* is.close();URL url = new URL("ftp://ftp.sun.com/pub/test.txt");
*
* @see sun.net.ftp.FtpClient
*/
public class FtpURLConnection extends URLConnection {
// In case we have to use proxies, we use HttpURLConnection
HttpURLConnection http = null;
private Proxy instProxy;
InputStream is = null;
OutputStream os = null;
FtpClient ftp = null;
Permission permission;
String password;
String user;
String host;
String pathname;
String filename;
String fullpath;
int port;
static final int NONE = 0;
static final int ASCII = 1;
static final int BIN = 2;
static final int DIR = 3;
int type = NONE;
/* Redefine timeouts from java.net.URLConnection as we need -1 to mean
* not set. This is to ensure backward compatibility.
*/
private int connectTimeout = NetworkClient.DEFAULT_CONNECT_TIMEOUT;;
private int readTimeout = NetworkClient.DEFAULT_READ_TIMEOUT;;
/**
* For FTP URLs we need to have a special InputStream because we
* need to close 2 sockets after we're done with it :
* - The Data socket (for the file).
* - The command socket (FtpClient).
* Since that's the only class that needs to see that, it is an inner class.
*/
protected class FtpInputStream extends FilterInputStream {
FtpClient ftp;
FtpInputStream(FtpClient cl, InputStream fd) {
super(new BufferedInputStream(fd));
ftp = cl;
}
@Override
public void close() throws IOException {
super.close();
if (ftp != null) {
ftp.close();
}
}
}
/**
* For FTP URLs we need to have a special OutputStream because we
* need to close 2 sockets after we're done with it :
* - The Data socket (for the file).
* - The command socket (FtpClient).
* Since that's the only class that needs to see that, it is an inner class.
*/
protected class FtpOutputStream extends FilterOutputStream {
FtpClient ftp;
FtpOutputStream(FtpClient cl, OutputStream fd) {
super(fd);
ftp = cl;
}
@Override
public void close() throws IOException {
super.close();
if (ftp != null) {
ftp.close();
}
}
}
/**
* Creates an FtpURLConnection from a URL.
*
* @param url The URL
to retrieve or store.
*/
public FtpURLConnection(URL url) {
this(url, null);
}
/**
* Same as FtpURLconnection(URL) with a per connection proxy specified
*/
FtpURLConnection(URL url, Proxy p) {
super(url);
instProxy = p;
host = url.getHost();
port = url.getPort();
String userInfo = url.getUserInfo();
if (userInfo != null) { // get the user and password
int delimiter = userInfo.indexOf(':');
if (delimiter == -1) {
user = ParseUtil.decode(userInfo);
password = null;
} else {
user = ParseUtil.decode(userInfo.substring(0, delimiter++));
password = ParseUtil.decode(userInfo.substring(delimiter));
}
}
}
private void setTimeouts() {
if (ftp != null) {
if (connectTimeout >= 0) {
ftp.setConnectTimeout(connectTimeout);
}
if (readTimeout >= 0) {
ftp.setReadTimeout(readTimeout);
}
}
}
/**
* Connects to the FTP server and logs in.
*
* @throws FtpLoginException if the login is unsuccessful
* @throws FtpProtocolException if an error occurs
* @throws UnknownHostException if trying to connect to an unknown host
*/
public synchronized void connect() throws IOException {
if (connected) {
return;
}
Proxy p = null;
if (instProxy == null) { // no per connection proxy specified
/**
* Do we have to use a proxy?
*/
ProxySelector sel = java.security.AccessController.doPrivileged(
new java.security.PrivilegedActionInputStream
to the connection.
*
* @throws IOException if already opened for output
* @throws FtpProtocolException if errors occur during the transfert.
*/
@Override
public InputStream getInputStream() throws IOException {
if (!connected) {
connect();
}
if (http != null) {
return http.getInputStream();
}
if (os != null) {
throw new IOException("Already opened for output");
}
if (is != null) {
return is;
}
MessageHeader msgh = new MessageHeader();
boolean isAdir = false;
try {
decodePath(url.getPath());
if (filename == null || type == DIR) {
ftp.setAsciiType();
cd(pathname);
if (filename == null) {
is = new FtpInputStream(ftp, ftp.list(null));
} else {
is = new FtpInputStream(ftp, ftp.nameList(filename));
}
} else {
if (type == ASCII) {
ftp.setAsciiType();
} else {
ftp.setBinaryType();
}
cd(pathname);
is = new FtpInputStream(ftp, ftp.getFileStream(filename));
}
/* Try to get the size of the file in bytes. If that is
successful, then create a MeteredStream. */
try {
long l = ftp.getLastTransferSize();
msgh.add("content-length", Long.toString(l));
if (l > 0) {
// Wrap input stream with MeteredStream to ensure read() will always return -1
// at expected length.
// Check if URL should be metered
boolean meteredInput = ProgressMonitor.getDefault().shouldMeterInput(url, "GET");
ProgressSource pi = null;
if (meteredInput) {
pi = new ProgressSource(url, "GET", l);
pi.beginTracking();
}
is = new MeteredStream(is, pi, l);
}
} catch (Exception e) {
e.printStackTrace();
/* do nothing, since all we were doing was trying to
get the size in bytes of the file */
}
if (isAdir) {
msgh.add("content-type", "text/plain");
msgh.add("access-type", "directory");
} else {
msgh.add("access-type", "file");
String ftype = guessContentTypeFromName(fullpath);
if (ftype == null && is.markSupported()) {
ftype = guessContentTypeFromStream(is);
}
if (ftype != null) {
msgh.add("content-type", ftype);
}
}
} catch (FileNotFoundException e) {
try {
cd(fullpath);
/* if that worked, then make a directory listing
and build an html stream with all the files in
the directory */
ftp.setAsciiType();
is = new FtpInputStream(ftp, ftp.list(null));
msgh.add("content-type", "text/plain");
msgh.add("access-type", "directory");
} catch (IOException ex) {
throw new FileNotFoundException(fullpath);
} catch (FtpProtocolException ex2) {
throw new FileNotFoundException(fullpath);
}
} catch (FtpProtocolException ftpe) {
throw new IOException(ftpe);
}
setProperties(msgh);
return is;
}
/**
* Get the OutputStream to store the remote file. It will issue the
* "put" command to the ftp server.
*
* @return the OutputStream
to the connection.
*
* @throws IOException if already opened for input or the URL
* points to a directory
* @throws FtpProtocolException if errors occur during the transfert.
*/
@Override
public OutputStream getOutputStream() throws IOException {
if (!connected) {
connect();
}
if (http != null) {
OutputStream out = http.getOutputStream();
// getInputStream() is neccessary to force a writeRequests()
// on the http client.
http.getInputStream();
return out;
}
if (is != null) {
throw new IOException("Already opened for input");
}
if (os != null) {
return os;
}
decodePath(url.getPath());
if (filename == null || filename.length() == 0) {
throw new IOException("illegal filename for a PUT");
}
try {
if (pathname != null) {
cd(pathname);
}
if (type == ASCII) {
ftp.setAsciiType();
} else {
ftp.setBinaryType();
}
os = new FtpOutputStream(ftp, ftp.putFileStream(filename, false));
} catch (FtpProtocolException e) {
throw new IOException(e);
}
return os;
}
String guessContentTypeFromFilename(String fname) {
return guessContentTypeFromName(fname);
}
/**
* Gets the Permission
associated with the host & port.
*
* @return The Permission
object.
*/
@Override
public Permission getPermission() {
if (permission == null) {
int urlport = url.getPort();
urlport = urlport < 0 ? FtpClient.defaultPort() : urlport;
String urlhost = this.host + ":" + urlport;
permission = new SocketPermission(urlhost, "connect");
}
return permission;
}
/**
* Sets the general request property. If a property with the key already
* exists, overwrite its value with the new value.
*
* @param key the keyword by which the request is known
* (e.g., "accept
").
* @param value the value associated with it.
* @throws IllegalStateException if already connected
* @see #getRequestProperty(java.lang.String)
*/
@Override
public void setRequestProperty(String key, String value) {
super.setRequestProperty(key, value);
if ("type".equals(key)) {
if ("i".equalsIgnoreCase(value)) {
type = BIN;
} else if ("a".equalsIgnoreCase(value)) {
type = ASCII;
} else if ("d".equalsIgnoreCase(value)) {
type = DIR;
} else {
throw new IllegalArgumentException(
"Value of '" + key +
"' request property was '" + value +
"' when it must be either 'i', 'a' or 'd'");
}
}
}
/**
* Returns the value of the named general request property for this
* connection.
*
* @param key the keyword by which the request is known (e.g., "accept").
* @return the value of the named general request property for this
* connection.
* @throws IllegalStateException if already connected
* @see #setRequestProperty(java.lang.String, java.lang.String)
*/
@Override
public String getRequestProperty(String key) {
String value = super.getRequestProperty(key);
if (value == null) {
if ("type".equals(key)) {
value = (type == ASCII ? "a" : type == DIR ? "d" : "i");
}
}
return value;
}
@Override
public void setConnectTimeout(int timeout) {
if (timeout < 0) {
throw new IllegalArgumentException("timeouts can't be negative");
}
connectTimeout = timeout;
}
@Override
public int getConnectTimeout() {
return (connectTimeout < 0 ? 0 : connectTimeout);
}
@Override
public void setReadTimeout(int timeout) {
if (timeout < 0) {
throw new IllegalArgumentException("timeouts can't be negative");
}
readTimeout = timeout;
}
@Override
public int getReadTimeout() {
return readTimeout < 0 ? 0 : readTimeout;
}
}