001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.request; 018 019import java.sql.Time; 020import java.time.Instant; 021import java.util.ArrayList; 022import java.util.Collections; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.Iterator; 026import java.util.List; 027import java.util.Locale; 028import java.util.Map; 029import java.util.Set; 030import org.apache.wicket.util.lang.Args; 031import org.apache.wicket.util.time.Instants; 032 033/** 034 * A multivalue map of headers names and header values suitable for processing http request and 035 * response headers. 036 * 037 * @author Peter Ertl 038 * 039 * @since 1.5 040 */ 041public class HttpHeaderCollection 042{ 043 private final Map<HeaderKey, List<Object>> headers; 044 045 /** returned in case no header values were found */ 046 private static final String[] NO_VALUES = new String[0]; 047 048 /** 049 * Constructor. 050 */ 051 public HttpHeaderCollection() 052 { 053 headers = new HashMap<>(); 054 } 055 056 /** 057 * internally add new object to header values 058 * 059 * @param name 060 * header name 061 * @param object 062 * header value (can be a string or a {@link Time} object 063 */ 064 private void internalAdd(String name, Object object) 065 { 066 final HeaderKey key = new HeaderKey(name); 067 068 List<Object> values = headers.get(key); 069 070 if (values == null) 071 { 072 values = new ArrayList<>(); 073 headers.put(key, values); 074 } 075 values.add(object); 076 } 077 078 /** 079 * set header value (and remove previous values) 080 * 081 * @param name 082 * header name 083 * @param value 084 * header value 085 */ 086 public void setHeader(String name, String value) 087 { 088 // remove previous values 089 removeHeader(name); 090 091 // add new values 092 addHeader(name, value); 093 } 094 095 /** 096 * add header value 097 * 098 * @param name 099 * header name 100 * @param value 101 * header value 102 */ 103 public void addHeader(String name, String value) 104 { 105 // be lenient and strip leading / trailing blanks 106 value = Args.notNull(value, "value").trim(); 107 108 internalAdd(name, value); 109 } 110 111 /** 112 * add date header value 113 * 114 * @param name 115 * header name 116 * @param time 117 * timestamp 118 */ 119 public void addDateHeader(String name, Instant time) 120 { 121 internalAdd(name, time); 122 } 123 124 /** 125 * add date header value 126 * 127 * @param name 128 * header name 129 * @param time 130 * timestamp 131 */ 132 public void setDateHeader(String name, Instant time) 133 { 134 // remove previous values 135 removeHeader(name); 136 137 // add time object to values 138 addDateHeader(name, time); 139 } 140 141 /** 142 * remove header values for header name 143 * 144 * @param name 145 * header name 146 */ 147 public void removeHeader(String name) 148 { 149 final HeaderKey key = new HeaderKey(name); 150 final Iterator<Map.Entry<HeaderKey, List<Object>>> it = headers.entrySet().iterator(); 151 152 while (it.hasNext()) 153 { 154 final Map.Entry<HeaderKey, List<Object>> header = it.next(); 155 156 if (header.getKey().equals(key)) 157 { 158 it.remove(); 159 } 160 } 161 } 162 163 private String valueToString(Object value) 164 { 165 if (value instanceof Instant) 166 { 167 return Instants.toRFC7231Format((Instant)value); 168 } 169 else 170 { 171 return value.toString(); 172 } 173 } 174 175 /** 176 * check if header is defined 177 * 178 * @param name 179 * header name 180 * @return <code>true</code> if header has one or more values 181 */ 182 public boolean containsHeader(String name) 183 { 184 final HeaderKey searchKey = new HeaderKey(name); 185 186 // get the header value (case might differ) 187 for (HeaderKey key : headers.keySet()) 188 { 189 if (key.equals(searchKey)) 190 { 191 return true; 192 } 193 } 194 return false; 195 } 196 197 /** 198 * returns names of headers 199 * 200 * @return set of header names 201 */ 202 public Set<String> getHeaderNames() 203 { 204 if (headers.isEmpty()) 205 { 206 return Collections.emptySet(); 207 } 208 209 final Set<String> names = new HashSet<>(headers.size()); 210 211 for (HeaderKey key : headers.keySet()) 212 { 213 names.add(key.getName()); 214 } 215 return names; 216 } 217 218 /** 219 * get header values (dates will be converted into strings) 220 * 221 * @param name 222 * header name 223 * 224 * @return array of header values or empty array if not found 225 */ 226 public String[] getHeaderValues(String name) 227 { 228 final List<Object> objects = headers.get(new HeaderKey(name)); 229 230 if (objects == null) 231 { 232 return NO_VALUES; 233 } 234 235 final String[] values = new String[objects.size()]; 236 237 for (int i = 0; i < values.length; i++) 238 { 239 values[i] = valueToString(objects.get(i)); 240 } 241 return values; 242 } 243 244 /** 245 * Gets the header identified with the name as a String. 246 * @param name 247 * @return {@code null} when the header was not found 248 */ 249 public String getHeader(String name) 250 { 251 final List<Object> objects = headers.get(new HeaderKey(name)); 252 253 if (objects == null || objects.isEmpty()) 254 { 255 return null; 256 } 257 return valueToString(objects.get(0)); 258 } 259 260 /** 261 * Gets the header identified with the name as a Time 262 * @param name 263 * @return {@code null} when the header was not found 264 */ 265 public Instant getDateHeader(String name) 266 { 267 final List<Object> objects = headers.get(new HeaderKey(name)); 268 269 if (objects.isEmpty()) 270 { 271 return null; 272 } 273 Object object = objects.get(0); 274 275 if ((object instanceof Instant) == false) 276 { 277 throw new IllegalStateException("header value is not of type date"); 278 } 279 return (Instant)object; 280 } 281 282 /** 283 * Check if collection is empty 284 * 285 * @return <code>true</code> if collection is empty, <code>false</code> otherwise 286 */ 287 public boolean isEmpty() 288 { 289 return headers.isEmpty(); 290 } 291 292 /** 293 * get number of headers 294 * 295 * @return count 296 */ 297 public int getCount() 298 { 299 return headers.size(); 300 } 301 302 /** 303 * clear all headers 304 */ 305 public void clear() 306 { 307 headers.clear(); 308 } 309 310 /** 311 * key for header collection 312 */ 313 private static class HeaderKey 314 { 315 private final String key; 316 private final String name; 317 318 private HeaderKey(String name) 319 { 320 this.name = Args.notEmpty(name, "name").trim(); 321 key = this.name.toLowerCase(Locale.US); 322 } 323 324 public String getName() 325 { 326 return name; 327 } 328 329 @Override 330 public boolean equals(Object o) 331 { 332 if (this == o) 333 return true; 334 335 if (!(o instanceof HeaderKey)) 336 return false; 337 338 HeaderKey that = (HeaderKey)o; 339 340 if (!key.equals(that.key)) 341 return false; 342 343 return true; 344 } 345 346 @Override 347 public int hashCode() 348 { 349 return key.hashCode(); 350 } 351 } 352}