001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.net.ftp;
019    
020    import java.io.BufferedReader;
021    import java.io.IOException;
022    import java.io.InputStream;
023    import java.io.InputStreamReader;
024    import java.io.OutputStream;
025    import java.io.UnsupportedEncodingException;
026    import java.net.Socket;
027    import java.net.SocketException;
028    import java.util.ArrayList;
029    import java.util.List;
030    
031    import org.apache.commons.net.util.Base64;
032    
033    /**
034     * Experimental attempt at FTP client that tunnels over an HTTP proxy connection.
035     *
036     * @author rory
037     * @since 2.2
038     */
039    public class FTPHTTPClient extends FTPClient {
040        private final String proxyHost;
041        private final int proxyPort;
042        private final String proxyUsername;
043        private final String proxyPassword;
044        private String host;
045        private int port;
046    
047        private final byte[] CRLF;
048        private final Base64 base64 = new Base64();
049    
050        public FTPHTTPClient(String proxyHost, int proxyPort, String proxyUser, String proxyPass) {
051            this.proxyHost = proxyHost;
052            this.proxyPort = proxyPort;
053            this.proxyUsername = proxyUser;
054            this.proxyPassword = proxyPass;
055    
056            try {
057                CRLF = "\r\n".getBytes(getControlEncoding());
058            } catch (UnsupportedEncodingException e) {
059                throw new RuntimeException(e);
060            }
061        }
062    
063        public FTPHTTPClient(String proxyHost, int proxyPort) {
064            this(proxyHost, proxyPort, null, null);
065        }
066    
067    
068        @Override
069        protected Socket _openDataConnection_(int command, String arg)
070        throws IOException {
071            Socket socket = new Socket(host, port);
072            InputStream is = socket.getInputStream();
073            OutputStream os = socket.getOutputStream();
074    
075            tunnelHandshake(host, port, is, os);
076    
077            return socket;
078        }
079    
080        @Override
081        public void connect(String host, int port) throws SocketException,
082        IOException {
083            this.host = host;
084            this.port = port;
085    
086            _socket_ = new Socket(proxyHost, proxyPort);
087            _input_ = _socket_.getInputStream();
088            _output_ = _socket_.getOutputStream();
089            try {
090                tunnelHandshake(host, port, _input_, _output_);
091            }
092            catch (Exception e) {
093                IOException ioe = new IOException("Could not connect to " + host);
094                ioe.initCause(e);
095                throw ioe;
096            }
097        }
098    
099        private void tunnelHandshake(String host, int port, InputStream input, OutputStream output) throws IOException,
100        UnsupportedEncodingException {
101            final String connectString = "CONNECT "  + host + ":" + port  + " HTTP/1.1";
102    
103            _output_.write(connectString.getBytes(getControlEncoding()));
104            _output_.write(CRLF);
105    
106            if (proxyUsername != null && proxyPassword != null) {
107                final String header = "Proxy-Authorization: Basic "
108                    + base64.encode(proxyUsername + ":" + proxyPassword) + "\r\n";
109                _output_.write(header.getBytes("UTF-8"));
110                _output_.write(CRLF);
111    
112                List<String> response = new ArrayList<String>();
113                BufferedReader reader = new BufferedReader(
114                        new InputStreamReader(_input_));
115    
116                for (String line = reader.readLine(); line != null
117                && line.length() > 0; line = reader.readLine()) {
118                    response.add(line);
119                }
120    
121                int size = response.size();
122                if (size == 0) {
123                    throw new IOException("No response from proxy");
124                }
125    
126                String code = null;
127                String resp = response.get(0);
128                if (resp.startsWith("HTTP/") && resp.length() >= 12) {
129                    code = resp.substring(9, 12);
130                } else {
131                    throw new IOException("Invalid response from proxy: " + resp);
132                }
133    
134                if (!"200".equals(code)) {
135                    StringBuilder msg = new StringBuilder();
136                    msg.append("HTTPTunnelConnector: connection failed\r\n");
137                    msg.append("Response received from the proxy:\r\n");
138                    for (String line : response) {
139                        msg.append(line);
140                        msg.append("\r\n");
141                    }
142                    throw new IOException(msg.toString());
143                }
144            }
145        }
146    }
147    
148