1 /*
2 * The contents of this file are subject to the terms of the Common Development and
3 * Distribution License (the License). You may not use this file except in compliance with the
4 * License.
5 *
6 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7 * specific language governing permission and limitations under the License.
8 *
9 * When distributing Covered Software, include this CDDL Header Notice in each file and include
10 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11 * Header, with the fields enclosed by brackets [] replaced by your own identifying
12 * information: "Portions copyright [year] [name of copyright owner]".
13 *
14 * Copyright 2012-2015 ForgeRock AS.
15 */
16
17 package org.forgerock.json.resource.http;
18
19 import java.util.Collections;
20 import java.util.LinkedHashMap;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Set;
24 import java.util.TreeMap;
25
26 import org.forgerock.services.context.AbstractContext;
27 import org.forgerock.services.context.Context;
28 import org.forgerock.http.protocol.Header;
29 import org.forgerock.json.JsonValue;
30 import org.forgerock.util.Factory;
31 import org.forgerock.util.LazyMap;
32
33 /** A {@link Context} containing information relating to the originating HTTP request. */
34 public final class HttpContext extends AbstractContext {
35
36 // TODO: security parameters such as user name, etc?
37 /**
38 * Attribute in the serialized JSON form that holds the request headers.
39 * @see #HttpContext(JsonValue, ClassLoader)
40 */
41 public static final String ATTR_HEADERS = "headers";
42
43 /**
44 * Attribute in the serialized JSON form that holds the query and/or form parameters.
45 * @see #HttpContext(JsonValue, ClassLoader)
46 */
47 public static final String ATTR_PARAMETERS = "parameters";
48
49 /**
50 * Attribute in the serialised JSON form that holds the HTTP method of the request.
51 * @see #HttpContext(JsonValue, ClassLoader)
52 */
53 public static final String ATTR_METHOD = "method";
54
55 /**
56 * Attribute in the serialised JSON form that holds the full URI of the request, excluding anything beyond the
57 * path component (i.e., no query parameters).
58 * @see #HttpContext(JsonValue, ClassLoader)
59 */
60 public static final String ATTR_PATH = "path";
61
62 private final Map<String, List<String>> headers;
63 private final Map<String, List<String>> parameters;
64
65 HttpContext(Context parent, final org.forgerock.http.protocol.Request req) {
66 super(parent, "http");
67 data.put(ATTR_METHOD, HttpUtils.getMethod(req));
68 data.put(ATTR_PATH, getRequestPath(req));
69 this.headers = Collections.unmodifiableMap(new LazyMap<>(
70 new Factory<Map<String, List<String>>>() {
71 @Override
72 public Map<String, List<String>> newInstance() {
73 Map<String, List<String>> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
74 for (Map.Entry<String, Header> header : req.getHeaders().asMapOfHeaders().entrySet()) {
75 String name = header.getKey();
76 List<String> values = header.getValue().getValues();
77 result.put(name, values);
78 }
79 return result;
80 }
81 }));
82 data.put(ATTR_HEADERS, headers);
83 this.parameters = Collections.unmodifiableMap(new LazyMap<>(
84 new Factory<Map<String, List<String>>>() {
85 @Override
86 public Map<String, List<String>> newInstance() {
87 Map<String, List<String>> result = new LinkedHashMap<>();
88 Set<Map.Entry<String, List<String>>> parameters = req.getForm().entrySet();
89 for (Map.Entry<String, List<String>> parameter : parameters) {
90 String name = parameter.getKey();
91 List<String> values = parameter.getValue();
92 result.put(name, values);
93 }
94 return result;
95 }
96 }));
97 data.put(ATTR_PARAMETERS, parameters);
98 }
99
100 /**
101 * Restore from JSON representation.
102 *
103 * @param savedContext
104 * The JSON representation from which this context's attributes
105 * should be parsed. Must be a JSON Object that contains {@link #ATTR_HEADERS} and
106 * {@link #ATTR_PARAMETERS} attributes.
107 * @param classLoader
108 * The ClassLoader which can properly resolve the persisted class-name.
109 */
110 public HttpContext(final JsonValue savedContext, final ClassLoader classLoader) {
111 super(savedContext, classLoader);
112 this.headers = data.get(ATTR_HEADERS).required().asMapOfList(String.class);
113 this.parameters = data.get(ATTR_PARAMETERS).required().asMapOfList(String.class);
114 }
115
116 private String getRequestPath(org.forgerock.http.protocol.Request req) {
117 return new StringBuilder()
118 .append(req.getUri().getScheme())
119 .append("://")
120 .append(req.getUri().getRawAuthority())
121 .append(req.getUri().getRawPath()).toString();
122 }
123
124 /**
125 * Returns an unmodifiable list containing the values of the named HTTP
126 * request header.
127 *
128 * @param name
129 * The name of the HTTP request header.
130 * @return An unmodifiable list containing the values of the named HTTP
131 * request header, which may be empty if the header is not present
132 * in the request.
133 */
134 public List<String> getHeader(String name) {
135 List<String> header = headers.get(name);
136 return Collections.unmodifiableList(header != null ? header : Collections.<String> emptyList());
137 }
138
139 /**
140 * Returns the first value of the named HTTP request header.
141 *
142 * @param name
143 * The name of the HTTP request header.
144 * @return The first value of the named HTTP request header, or {@code null}
145 * if the header is not present in the request.
146 */
147 public String getHeaderAsString(String name) {
148 List<String> header = getHeader(name);
149 return header.isEmpty() ? null : header.get(0);
150 }
151
152 /**
153 * Returns an unmodifiable map of the HTTP request headers.
154 *
155 * @return An unmodifiable map of the HTTP request headers.
156 */
157 public Map<String, List<String>> getHeaders() {
158 return headers;
159 }
160
161 /**
162 * Returns the effective HTTP method, taking into account presence of the
163 * {@code X-HTTP-Method-Override} header.
164 *
165 * @return The effective HTTP method, taking into account presence of the
166 * {@code X-HTTP-Method-Override} header.
167 */
168 public String getMethod() {
169 return data.get(ATTR_METHOD).asString();
170 }
171
172 /**
173 * Returns an unmodifiable list containing the values of the named HTTP
174 * request parameter.
175 *
176 * @param name
177 * The name of the HTTP request parameter.
178 * @return An unmodifiable list containing the values of the named HTTP
179 * request parameter, which may be empty if the parameter is not
180 * present in the request.
181 */
182 public List<String> getParameter(String name) {
183 final List<String> parameter = parameters.get(name);
184 return Collections.unmodifiableList(parameter != null ? parameter : Collections.<String> emptyList());
185 }
186
187 /**
188 * Returns the first value of the named HTTP request parameter.
189 *
190 * @param name
191 * The name of the HTTP request parameter.
192 * @return The first value of the named HTTP request parameter, or
193 * {@code null} if the parameter is not present in the request.
194 */
195 public String getParameterAsString(String name) {
196 final List<String> parameter = getParameter(name);
197 return parameter.isEmpty() ? null : parameter.get(0);
198 }
199
200 /**
201 * Returns an unmodifiable map of the HTTP request parameters.
202 *
203 * @return An unmodifiable map of the HTTP request parameters.
204 */
205 public Map<String, List<String>> getParameters() {
206 return parameters;
207 }
208
209 /**
210 * Returns the HTTP request path.
211 *
212 * @return The HTTP request path.
213 */
214 public String getPath() {
215 return data.get(ATTR_PATH).asString();
216 }
217 }