| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.harmony.luni.internal.net.www.protocol.ftp; |
| |
| import java.io.BufferedInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InterruptedIOException; |
| import java.io.OutputStream; |
| import java.net.InetSocketAddress; |
| import java.net.Proxy; |
| import java.net.ProxySelector; |
| import java.net.ServerSocket; |
| import java.net.Socket; |
| import java.net.SocketPermission; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.net.URLStreamHandler; |
| import java.security.Permission; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.apache.harmony.luni.internal.net.www.MimeTable; |
| import org.apache.harmony.luni.net.NetUtil; |
| import org.apache.harmony.luni.util.Msg; |
| |
| public class FtpURLConnection extends URLConnection { |
| |
| private static final int FTP_PORT = 21; |
| |
| // FTP Reply Constants |
| private static final int FTP_DATAOPEN = 125; |
| |
| private static final int FTP_OPENDATA = 150; |
| |
| private static final int FTP_OK = 200; |
| |
| private static final int FTP_USERREADY = 220; |
| |
| private static final int FTP_TRANSFEROK = 226; |
| |
| // private static final int FTP_PASV = 227; |
| |
| private static final int FTP_LOGGEDIN = 230; |
| |
| private static final int FTP_FILEOK = 250; |
| |
| private static final int FTP_PASWD = 331; |
| |
| // private static final int FTP_DATAERROR = 451; |
| |
| // private static final int FTP_ERROR = 500; |
| |
| private static final int FTP_NOTFOUND = 550; |
| |
| private Socket controlSocket; |
| |
| private Socket dataSocket; |
| |
| private ServerSocket acceptSocket; |
| |
| private InputStream ctrlInput; |
| |
| private InputStream inputStream; |
| |
| private OutputStream ctrlOutput; |
| |
| private int dataPort; |
| |
| private String username = "anonymous"; //$NON-NLS-1$ |
| |
| private String password = ""; //$NON-NLS-1$ |
| |
| private String replyCode; |
| |
| private String hostName; |
| |
| private Proxy proxy; |
| |
| private Proxy currentProxy; |
| |
| private URI uri; |
| |
| /** |
| * FtpURLConnection constructor comment. |
| * |
| * @param url |
| */ |
| protected FtpURLConnection(URL url) { |
| super(url); |
| hostName = url.getHost(); |
| String parse = url.getUserInfo(); |
| if (parse != null) { |
| int split = parse.indexOf(':'); |
| if (split >= 0) { |
| username = parse.substring(0, split); |
| password = parse.substring(split + 1); |
| } else { |
| username = parse; |
| } |
| } |
| uri = null; |
| try { |
| uri = url.toURI(); |
| } catch (URISyntaxException e) { |
| // do nothing. |
| } |
| } |
| |
| /** |
| * FtpURLConnection constructor. |
| * |
| * @param url |
| * @param proxy |
| */ |
| protected FtpURLConnection(URL url, Proxy proxy) { |
| this(url); |
| this.proxy = proxy; |
| } |
| |
| /** |
| * Change the server directory to that specified in the URL |
| */ |
| private void cd() throws IOException { |
| int idx = url.getFile().lastIndexOf('/'); |
| |
| if (idx > 0) { |
| String dir = url.getFile().substring(0, idx); |
| write("CWD " + dir + "\r\n"); //$NON-NLS-1$ //$NON-NLS-2$ |
| int reply = getReply(); |
| if (reply != FTP_FILEOK && dir.length() > 0 && dir.charAt(0) == '/') { |
| write("CWD " + dir.substring(1) + "\r\n"); //$NON-NLS-1$ //$NON-NLS-2$ |
| reply = getReply(); |
| } |
| if (reply != FTP_FILEOK) { |
| throw new IOException(Msg.getString("K0094")); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| /** |
| * Establishes the connection to the resource specified by this |
| * <code>URL</code> |
| * |
| * @see #connected |
| * @see java.io.IOException |
| * @see URLStreamHandler |
| */ |
| @Override |
| public void connect() throws IOException { |
| // Use system-wide ProxySelect to select proxy list, |
| // then try to connect via elements in the proxy list. |
| List<Proxy> proxyList = null; |
| if (null != proxy) { |
| proxyList = new ArrayList<Proxy>(1); |
| proxyList.add(proxy); |
| } else { |
| proxyList = NetUtil.getProxyList(uri); |
| } |
| if (null == proxyList) { |
| currentProxy = null; |
| connectInternal(); |
| } else { |
| ProxySelector selector = ProxySelector.getDefault(); |
| Iterator<Proxy> iter = proxyList.iterator(); |
| boolean connectOK = false; |
| String failureReason = ""; //$NON-NLS-1$ |
| while (iter.hasNext() && !connectOK) { |
| currentProxy = iter.next(); |
| try { |
| connectInternal(); |
| connectOK = true; |
| } catch (IOException ioe) { |
| failureReason = ioe.getLocalizedMessage(); |
| // If connect failed, callback "connectFailed" |
| // should be invoked. |
| if (null != selector && Proxy.NO_PROXY != currentProxy) { |
| selector.connectFailed(uri, currentProxy.address(), ioe); |
| } |
| } |
| } |
| if (!connectOK) { |
| // K0097=Unable to connect to server\: {0} |
| throw new IOException(Msg.getString("K0097", failureReason)); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| private void connectInternal() throws IOException { |
| int port = url.getPort(); |
| int connectTimeout = getConnectTimeout(); |
| if (port <= 0) { |
| port = FTP_PORT; |
| } |
| if (null == currentProxy || Proxy.Type.HTTP == currentProxy.type()) { |
| controlSocket = new Socket(); |
| } else { |
| controlSocket = new Socket(currentProxy); |
| } |
| InetSocketAddress addr = new InetSocketAddress(hostName, port); |
| controlSocket.connect(addr, connectTimeout); |
| connected = true; |
| ctrlOutput = controlSocket.getOutputStream(); |
| ctrlInput = controlSocket.getInputStream(); |
| login(); |
| setType(); |
| if (!getDoInput()) { |
| cd(); |
| } |
| |
| try { |
| acceptSocket = new ServerSocket(0); |
| dataPort = acceptSocket.getLocalPort(); |
| /* Cannot set REUSEADDR so we need to send a PORT command */ |
| port(); |
| if (connectTimeout == 0) { |
| // set timeout rather than zero as before |
| connectTimeout = 3000; |
| } |
| acceptSocket.setSoTimeout(getConnectTimeout()); |
| if (getDoInput()) { |
| getFile(); |
| } else { |
| sendFile(); |
| } |
| dataSocket = acceptSocket.accept(); |
| dataSocket.setSoTimeout(getReadTimeout()); |
| acceptSocket.close(); |
| } catch (InterruptedIOException e) { |
| throw new IOException(Msg.getString("K0095")); //$NON-NLS-1$ |
| } |
| if (getDoInput()) { |
| // BEGIN android-modified |
| inputStream = new FtpURLInputStream( |
| new BufferedInputStream(dataSocket.getInputStream(), 8192), |
| controlSocket); |
| // END android-modified |
| } |
| |
| } |
| |
| /** |
| * Returns the content type of the resource. Just takes a guess based on the |
| * name. |
| */ |
| @Override |
| public String getContentType() { |
| String result = guessContentTypeFromName(url.getFile()); |
| if (result == null) { |
| return MimeTable.UNKNOWN; |
| } |
| return result; |
| } |
| |
| private void getFile() throws IOException { |
| int reply; |
| String file = url.getFile(); |
| write("RETR " + file + "\r\n"); //$NON-NLS-1$ //$NON-NLS-2$ |
| reply = getReply(); |
| if (reply == FTP_NOTFOUND && file.length() > 0 && file.charAt(0) == '/') { |
| write("RETR " + file.substring(1) + "\r\n"); //$NON-NLS-1$ //$NON-NLS-2$ |
| reply = getReply(); |
| } |
| if (!(reply == FTP_OPENDATA || reply == FTP_TRANSFEROK)) { |
| throw new FileNotFoundException(Msg.getString("K0096", reply)); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Creates a input stream for writing to this URL Connection. |
| * |
| * @return The input stream to write to |
| * @throws IOException |
| * Cannot read from URL or error creating InputStream |
| * |
| * @see #getContent() |
| * @see #getOutputStream() |
| * @see java.io.InputStream |
| * @see java.io.IOException |
| * |
| */ |
| @Override |
| public InputStream getInputStream() throws IOException { |
| if (!connected) { |
| connect(); |
| } |
| return inputStream; |
| } |
| |
| /** |
| * Returns the permission object (in this case, SocketPermission) with the |
| * host and the port number as the target name and "resolve, connect" as the |
| * action list. |
| * |
| * @return the permission object required for this connection |
| * @throws IOException |
| * thrown when an IO exception occurs during the creation of the |
| * permission object. |
| */ |
| @Override |
| public Permission getPermission() throws IOException { |
| int port = url.getPort(); |
| if (port <= 0) { |
| port = FTP_PORT; |
| } |
| return new SocketPermission(hostName + ":" + port, "connect, resolve"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| /** |
| * Creates a output stream for writing to this URL Connection. |
| * |
| * @return The output stream to write to |
| * @throws IOException |
| * when the OutputStream could not be created |
| * |
| * @see #getContent() |
| * @see #getInputStream() |
| * @see java.io.IOException |
| * |
| */ |
| |
| @Override |
| public OutputStream getOutputStream() throws IOException { |
| if (!connected) { |
| connect(); |
| } |
| return dataSocket.getOutputStream(); |
| } |
| |
| private int getReply() throws IOException { |
| byte[] code = new byte[3]; |
| ctrlInput.read(code, 0, code.length); |
| replyCode = new String(code, "ISO8859_1"); //$NON-NLS-1$ |
| boolean multiline = false; |
| if (ctrlInput.read() == '-') { |
| multiline = true; |
| } |
| readLine(); /* Skip the rest of the first line */ |
| if (multiline) { |
| while (readMultiLine()) {/* Read all of a multiline reply */ |
| } |
| } |
| return Integer.parseInt(new String(code, "ISO8859_1")); //$NON-NLS-1$ |
| } |
| |
| private void login() throws IOException { |
| int reply; |
| reply = getReply(); |
| if (reply == FTP_USERREADY) { |
| } else { |
| throw new IOException(Msg.getString("K0097", url.getHost())); //$NON-NLS-1$ |
| } |
| write("USER " + username + "\r\n"); //$NON-NLS-1$ //$NON-NLS-2$ |
| reply = getReply(); |
| if (reply == FTP_PASWD || reply == FTP_LOGGEDIN) { |
| } else { |
| throw new IOException(Msg.getString("K0098", url.getHost())); //$NON-NLS-1$ |
| } |
| if (reply == FTP_PASWD) { |
| write("PASS " + password + "\r\n"); //$NON-NLS-1$ //$NON-NLS-2$ |
| reply = getReply(); |
| if (!(reply == FTP_OK || reply == FTP_USERREADY || reply == FTP_LOGGEDIN)) { |
| throw new IOException(Msg.getString("K0098", url.getHost())); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| private void port() throws IOException { |
| write("PORT " //$NON-NLS-1$ |
| + controlSocket.getLocalAddress().getHostAddress().replace('.', |
| ',') + ',' + (dataPort >> 8) + ',' |
| + (dataPort & 255) |
| + "\r\n"); //$NON-NLS-1$ |
| if (getReply() != FTP_OK) { |
| throw new IOException(Msg.getString("K0099")); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Read a line of text and return it for possible parsing |
| */ |
| private String readLine() throws IOException { |
| StringBuffer sb = new StringBuffer(); |
| int c; |
| while ((c = ctrlInput.read()) != '\n') { |
| sb.append((char) c); |
| } |
| return sb.toString(); |
| } |
| |
| private boolean readMultiLine() throws IOException { |
| String line = readLine(); |
| if (line.length() < 4) { |
| return true; |
| } |
| if (line.substring(0, 3).equals(replyCode) |
| && (line.charAt(3) == (char) 32)) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Issue the STOR command to the server with the file as the parameter |
| */ |
| private void sendFile() throws IOException { |
| int reply; |
| write("STOR " //$NON-NLS-1$ |
| + url.getFile().substring(url.getFile().lastIndexOf('/') + 1, |
| url.getFile().length()) + "\r\n"); //$NON-NLS-1$ |
| reply = getReply(); |
| if (!(reply == FTP_OPENDATA || reply == FTP_OK || reply == FTP_DATAOPEN)) { |
| throw new IOException(Msg.getString("K009a")); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Set the flag if this <code>URLConnection</code> supports input (read). |
| * It cannot be set after the connection is made. FtpURLConnections cannot |
| * support both input and output |
| * |
| * @param newValue * |
| * @throws IllegalAccessError |
| * when this method attempts to change the flag after connected |
| * |
| * @see #doInput |
| * @see #getDoInput() |
| * @see java.lang.IllegalAccessError |
| * @see #setDoInput(boolean) |
| */ |
| @Override |
| public void setDoInput(boolean newValue) { |
| if (connected) { |
| throw new IllegalAccessError(); |
| } |
| this.doInput = newValue; |
| this.doOutput = !newValue; |
| } |
| |
| /** |
| * Set the flag if this <code>URLConnection</code> supports output(read). |
| * It cannot be set after the connection is made.\ FtpURLConnections cannot |
| * support both input and output. |
| * |
| * @param newValue |
| * |
| * @throws IllegalAccessError |
| * when this method attempts to change the flag after connected |
| * |
| * @see #doOutput |
| * @see java.lang.IllegalAccessError |
| * @see #setDoOutput(boolean) |
| */ |
| @Override |
| public void setDoOutput(boolean newValue) { |
| if (connected) { |
| throw new IllegalAccessError(); |
| } |
| this.doOutput = newValue; |
| this.doInput = !newValue; |
| } |
| |
| /** |
| * Set the type of the file transfer. Only Image is supported |
| */ |
| private void setType() throws IOException { |
| write("TYPE I\r\n"); //$NON-NLS-1$ |
| if (getReply() != FTP_OK) { |
| throw new IOException(Msg.getString("K009b")); //$NON-NLS-1$ |
| } |
| } |
| |
| private void write(String command) throws IOException { |
| ctrlOutput.write(command.getBytes("ISO8859_1")); //$NON-NLS-1$ |
| } |
| } |