1 /* 2 * ==================================================================== 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * ==================================================================== 20 * 21 * This software consists of voluntary contributions made by many 22 * individuals on behalf of the Apache Software Foundation. For more 23 * information on the Apache Software Foundation, please see 24 * <http://www.apache.org/>. 25 * 26 */ 27 28 29 package org.apache.hc.client5.http.protocol; 30 31 32 import java.io.IOException; 33 import java.time.Instant; 34 35 import org.apache.hc.client5.http.utils.DateUtils; 36 import org.apache.hc.client5.http.validator.ETag; 37 import org.apache.hc.client5.http.validator.ValidatorType; 38 import org.apache.hc.core5.annotation.Contract; 39 import org.apache.hc.core5.annotation.ThreadingBehavior; 40 import org.apache.hc.core5.http.EntityDetails; 41 import org.apache.hc.core5.http.Header; 42 import org.apache.hc.core5.http.HttpException; 43 import org.apache.hc.core5.http.HttpHeaders; 44 import org.apache.hc.core5.http.HttpRequest; 45 import org.apache.hc.core5.http.HttpRequestInterceptor; 46 import org.apache.hc.core5.http.ProtocolException; 47 import org.apache.hc.core5.http.protocol.HttpContext; 48 import org.apache.hc.core5.util.Args; 49 50 /** 51 * <p> 52 * The {@code RequestIfRange} interceptor ensures that the request adheres to the RFC guidelines for the 'If-Range' header. 53 * The "If-Range" header field is used in conjunction with the Range header to conditionally request parts of a representation. 54 * If the validator given in the "If-Range" header matches the current validator for the representation, then the server should respond with the specified range of the document. 55 * If they do not match, the server should return the entire representation. 56 * </p> 57 * 58 * <p> 59 * Key points: 60 * </p> 61 * <ul> 62 * <li>A client MUST NOT generate an If-Range header field in a request that does not contain a Range header field.</li> 63 * <li>An origin server MUST ignore an If-Range header field received in a request for a target resource that does not support Range requests.</li> 64 * <li>A client MUST NOT generate an If-Range header field containing an entity tag that is marked as weak.</li> 65 * <li>A client MUST NOT generate an If-Range header field containing an HTTP-date unless the client has no entity tag for the corresponding representation and the date is a strong validator.</li> 66 * <li>A server that receives an If-Range header field on a Range request MUST evaluate the condition before performing the method.</li> 67 * </ul> 68 * 69 * @since 5.4 70 */ 71 @Contract(threading = ThreadingBehavior.IMMUTABLE) 72 public class RequestIfRange implements HttpRequestInterceptor { 73 74 /** 75 * Default instance of {@link RequestIfRange}. 76 * 77 * @since 5.4 78 */ 79 public static final RequestIfRange INSTANCE = new RequestIfRange(); 80 81 public RequestIfRange() { 82 super(); 83 } 84 85 /** 86 * Processes the given request to ensure it adheres to the RFC guidelines for the 'If-Range' header. 87 * 88 * @param request The HTTP request to be processed. 89 * @param entity The entity details of the request. 90 * @param context The HTTP context. 91 * @throws HttpException If the request does not adhere to the RFC guidelines. 92 * @throws IOException If an I/O error occurs. 93 */ 94 @Override 95 public void process(final HttpRequest request, final EntityDetails entity, final HttpContext context) 96 throws HttpException, IOException { 97 Args.notNull(request, "HTTP request"); 98 99 final Header ifRangeHeader = request.getFirstHeader(HttpHeaders.IF_RANGE); 100 101 // If there's no If-Range header, just return 102 if (ifRangeHeader == null) { 103 return; 104 } 105 106 // If there's an If-Range header but no Range header, throw an exception 107 if (!request.containsHeader(HttpHeaders.RANGE)) { 108 throw new ProtocolException("Request with 'If-Range' header must also contain a 'Range' header."); 109 } 110 111 final ETag eTag = ETag.get(request); 112 113 // If there's a weak ETag in the If-Range header, throw an exception 114 if (eTag != null && eTag.getType() == ValidatorType.WEAK) { 115 throw new ProtocolException("'If-Range' header must not contain a weak entity tag."); 116 } 117 118 final Instant dateInstant = DateUtils.parseStandardDate(request, HttpHeaders.DATE); 119 120 if (dateInstant == null) { 121 return; 122 } 123 124 final Instant lastModifiedInstant = DateUtils.parseStandardDate(request, HttpHeaders.LAST_MODIFIED); 125 126 if (lastModifiedInstant == null) { 127 // If there's no Last-Modified header, we exit early because we can't deduce that it is strong. 128 return; 129 } 130 131 // If the difference between the Last-Modified and Date headers is less than 1 second, throw an exception 132 if (lastModifiedInstant.plusSeconds(1).isAfter(dateInstant) && eTag != null) { 133 throw new ProtocolException("'If-Range' header with a Date must be a strong validator."); 134 } 135 } 136 137 }