1/**
2 * $RCSfile$
3 * $Revision$
4 * $Date$
5 *
6 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18package org.jivesoftware.smack.proxy;
19
20import java.io.BufferedReader;
21import java.io.IOException;
22import java.io.InputStream;
23import java.io.StringReader;
24import java.net.HttpURLConnection;
25import java.net.InetAddress;
26import java.net.Socket;
27import java.net.UnknownHostException;
28import javax.net.SocketFactory;
29import org.jivesoftware.smack.util.StringUtils;
30
31import java.util.regex.Matcher;
32import java.util.regex.Pattern;
33
34/**
35 * Http Proxy Socket Factory which returns socket connected to Http Proxy
36 *
37 * @author Atul Aggarwal
38 */
39class HTTPProxySocketFactory
40    extends SocketFactory
41{
42
43    private ProxyInfo proxy;
44
45    public HTTPProxySocketFactory(ProxyInfo proxy)
46    {
47        this.proxy = proxy;
48    }
49
50    public Socket createSocket(String host, int port)
51        throws IOException, UnknownHostException
52    {
53        return httpProxifiedSocket(host, port);
54    }
55
56    public Socket createSocket(String host ,int port, InetAddress localHost,
57                                int localPort)
58        throws IOException, UnknownHostException
59    {
60        return httpProxifiedSocket(host, port);
61    }
62
63    public Socket createSocket(InetAddress host, int port)
64        throws IOException
65    {
66        return httpProxifiedSocket(host.getHostAddress(), port);
67
68    }
69
70    public Socket createSocket( InetAddress address, int port,
71                                InetAddress localAddress, int localPort)
72        throws IOException
73    {
74        return httpProxifiedSocket(address.getHostAddress(), port);
75    }
76
77    private Socket httpProxifiedSocket(String host, int port)
78        throws IOException
79    {
80        String proxyhost = proxy.getProxyAddress();
81        int proxyPort = proxy.getProxyPort();
82        Socket socket = new Socket(proxyhost,proxyPort);
83        String hostport = "CONNECT " + host + ":" + port;
84        String proxyLine;
85        String username = proxy.getProxyUsername();
86        if (username == null)
87        {
88            proxyLine = "";
89        }
90        else
91        {
92            String password = proxy.getProxyPassword();
93            proxyLine = "\r\nProxy-Authorization: Basic " + StringUtils.encodeBase64(username + ":" + password);
94        }
95        socket.getOutputStream().write((hostport + " HTTP/1.1\r\nHost: "
96            + hostport + proxyLine + "\r\n\r\n").getBytes("UTF-8"));
97
98        InputStream in = socket.getInputStream();
99        StringBuilder got = new StringBuilder(100);
100        int nlchars = 0;
101
102        while (true)
103        {
104            char c = (char) in.read();
105            got.append(c);
106            if (got.length() > 1024)
107            {
108                throw new ProxyException(ProxyInfo.ProxyType.HTTP, "Recieved " +
109                    "header of >1024 characters from "
110                    + proxyhost + ", cancelling connection");
111            }
112            if (c == -1)
113            {
114                throw new ProxyException(ProxyInfo.ProxyType.HTTP);
115            }
116            if ((nlchars == 0 || nlchars == 2) && c == '\r')
117            {
118                nlchars++;
119            }
120            else if ((nlchars == 1 || nlchars == 3) && c == '\n')
121            {
122                nlchars++;
123            }
124            else
125            {
126                nlchars = 0;
127            }
128            if (nlchars == 4)
129            {
130                break;
131            }
132        }
133
134        if (nlchars != 4)
135        {
136            throw new ProxyException(ProxyInfo.ProxyType.HTTP, "Never " +
137                "received blank line from "
138                + proxyhost + ", cancelling connection");
139        }
140
141        String gotstr = got.toString();
142
143        BufferedReader br = new BufferedReader(new StringReader(gotstr));
144        String response = br.readLine();
145
146        if (response == null)
147        {
148            throw new ProxyException(ProxyInfo.ProxyType.HTTP, "Empty proxy " +
149                "response from " + proxyhost + ", cancelling");
150        }
151
152        Matcher m = RESPONSE_PATTERN.matcher(response);
153        if (!m.matches())
154        {
155            throw new ProxyException(ProxyInfo.ProxyType.HTTP , "Unexpected " +
156                "proxy response from " + proxyhost + ": " + response);
157        }
158
159        int code = Integer.parseInt(m.group(1));
160
161        if (code != HttpURLConnection.HTTP_OK)
162        {
163            throw new ProxyException(ProxyInfo.ProxyType.HTTP);
164        }
165
166        return socket;
167    }
168
169    private static final Pattern RESPONSE_PATTERN
170        = Pattern.compile("HTTP/\\S+\\s(\\d+)\\s(.*)\\s*");
171
172}
173