001package com.nimbusds.oauth2.sdk.http;
002
003
004import java.io.BufferedReader;
005import java.io.IOException;
006import java.io.PrintWriter;
007import java.net.MalformedURLException;
008import java.net.URL;
009import java.net.URLEncoder;
010import java.util.Enumeration;
011import java.util.Map;
012
013import javax.servlet.http.HttpServletRequest;
014import javax.servlet.http.HttpServletResponse;
015
016import org.apache.commons.lang3.StringUtils;
017
018import net.jcip.annotations.ThreadSafe;
019
020import com.nimbusds.oauth2.sdk.ParseException;
021
022
023/**
024 * HTTP servlet utilities.
025 */
026@ThreadSafe
027public class ServletUtils {
028
029
030        /**
031         * Reconstructs the request URL string for the specified servlet
032         * request. The host part is always the local IP address. The query
033         * string and fragment is always omitted.
034         *
035         * @param request The servlet request. Must not be {@code null}.
036         *
037         * @return The reconstructed request URL string.
038         */
039        private static String reconstructRequestURLString(final HttpServletRequest request) {
040
041                StringBuilder sb = new StringBuilder("http");
042
043                if (request.isSecure())
044                        sb.append('s');
045
046                sb.append("://");
047
048                String localAddress = request.getLocalAddr();
049
050                if (localAddress.contains(".")) {
051                        // IPv3 address
052                        sb.append(localAddress);
053                } else if (localAddress.contains(":")) {
054                        // IPv6 address, see RFC 2732
055                        sb.append('[');
056                        sb.append(localAddress);
057                        sb.append(']');
058                } else {
059                        // Don't know what to do
060                }
061
062                if (! request.isSecure() && request.getLocalPort() != 80) {
063                        // HTTP plain at port other than 80
064                        sb.append(':');
065                        sb.append(request.getLocalPort());
066                }
067
068                if (request.isSecure() && request.getLocalPort() != 443) {
069                        // HTTPS at port other than 443 (default TLS)
070                        sb.append(':');
071                        sb.append(request.getLocalPort());
072                }
073
074                String path = request.getRequestURI();
075
076                if (path != null)
077                        sb.append(path);
078
079                return sb.toString();
080        }
081
082
083        /**
084         * Creates a new HTTP request from the specified HTTP servlet request.
085         *
086         * @param sr The servlet request. Must not be {@code null}.
087         *
088         * @return The HTTP request.
089         *
090         * @throws IllegalArgumentException The the servlet request method is
091         *                                  not GET, POST, PUT or DELETE or the
092         *                                  content type header value couldn't
093         *                                  be parsed.
094         * @throws IOException              For a POST or PUT body that
095         *                                  couldn't be read due to an I/O
096         *                                  exception.
097         */
098        public static HTTPRequest createHTTPRequest(final HttpServletRequest sr)
099                throws IOException {
100
101                return createHTTPRequest(sr, -1);
102        }
103
104
105        /**
106         * Creates a new HTTP request from the specified HTTP servlet request.
107         *
108         * @param sr              The servlet request. Must not be
109         *                        {@code null}.
110         * @param maxEntityLength The maximum entity length to accept, -1 for
111         *                        no limit.
112         *
113         * @return The HTTP request.
114         *
115         * @throws IllegalArgumentException The the servlet request method is
116         *                                  not GET, POST, PUT or DELETE or the
117         *                                  content type header value couldn't
118         *                                  be parsed.
119         * @throws IOException              For a POST or PUT body that
120         *                                  couldn't be read due to an I/O
121         *                                  exception.
122         */
123        public static HTTPRequest createHTTPRequest(final HttpServletRequest sr, final long maxEntityLength)
124                throws IOException {
125
126                HTTPRequest.Method method = HTTPRequest.Method.valueOf(sr.getMethod().toUpperCase());
127
128                String urlString = reconstructRequestURLString(sr);
129
130                URL url;
131
132                try {
133                        url = new URL(urlString);
134
135                } catch (MalformedURLException e) {
136
137                        throw new IllegalArgumentException("Invalid request URL: " + e.getMessage() + ": " + urlString, e);
138                }
139
140                HTTPRequest request = new HTTPRequest(method, url);
141
142                try {
143                        request.setContentType(sr.getContentType());
144
145                } catch (ParseException e) {
146
147                        throw new IllegalArgumentException("Invalid Content-Type header value: " + e.getMessage(), e);
148                }
149
150                Enumeration<String> headerNames = sr.getHeaderNames();
151
152                while (headerNames.hasMoreElements()) {
153                        final String headerName = headerNames.nextElement();
154                        request.setHeader(headerName, sr.getHeader(headerName));
155                }
156
157                if (method.equals(HTTPRequest.Method.GET) || method.equals(HTTPRequest.Method.DELETE)) {
158
159                        request.setQuery(sr.getQueryString());
160
161                } else if (method.equals(HTTPRequest.Method.POST) || method.equals(HTTPRequest.Method.PUT)) {
162
163                        // read body
164                        StringBuilder body = new StringBuilder(256);
165
166                        BufferedReader reader = sr.getReader();
167
168                        char[] cbuf = new char[256];
169
170                        int readChars;
171
172                        while ((readChars = reader.read(cbuf)) != -1) {
173
174                                body.append(cbuf, 0, readChars);
175
176                                if (maxEntityLength > 0 && body.length() > maxEntityLength) {
177                                        throw new IOException("Request entity body is too large, limit is " + maxEntityLength + " chars");
178                                }
179                        }
180
181                        reader.close();
182
183                        request.setQuery(body.toString());
184
185                        // Some application servers are emptying the content in case of application/x-www-form-urlencoded
186                        if (StringUtils.isEmpty(request.getQuery()) && request.getContentType() != null && request.getContentType()
187                                .getBaseType().equals(CommonContentTypes.APPLICATION_URLENCODED.getBaseType())) {
188
189                                StringBuilder recreatedContent = new StringBuilder();
190
191                                for (Map.Entry<String, String[]> entry : sr.getParameterMap().entrySet()) {
192                                        if (recreatedContent.length() > 0) {
193                                                recreatedContent.append('&');
194                                        }
195                                        recreatedContent.append(URLEncoder.encode(entry.getKey(), "UTF8"));
196                                        recreatedContent.append('=');
197                                        recreatedContent.append(URLEncoder.encode(entry.getValue()[0], "UTF8"));
198                                }
199                                request.setQuery(recreatedContent.toString());
200                        }
201                }
202
203                return request;
204        }
205
206
207        /**
208         * Applies the status code, headers and content of the specified HTTP
209         * response to a HTTP servlet response.
210         *
211         * @param httpResponse    The HTTP response. Must not be {@code null}.
212         * @param servletResponse The HTTP servlet response. Must not be
213         *                        {@code null}.
214         *
215         * @throws IOException If the response content couldn't be written.
216         */
217        public static void applyHTTPResponse(final HTTPResponse httpResponse,
218                                             final HttpServletResponse servletResponse)
219                throws IOException {
220
221                // Set the status code
222                servletResponse.setStatus(httpResponse.getStatusCode());
223
224
225                // Set the headers, but only if explicitly specified
226                for (Map.Entry<String,String> header : httpResponse.getHeaders().entrySet()) {
227                        servletResponse.setHeader(header.getKey(), header.getValue());
228                }
229
230                if (httpResponse.getContentType() != null)
231                        servletResponse.setContentType(httpResponse.getContentType().toString());
232
233
234                // Write out the content
235
236                if (httpResponse.getContent() != null) {
237
238                        PrintWriter writer = servletResponse.getWriter();
239                        writer.print(httpResponse.getContent());
240                        writer.close();
241                }
242        }
243
244
245        /**
246         * Prevents public instantiation.
247         */
248        private ServletUtils() {
249
250        }
251}