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