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