Skip to content

Commit 47f2db1

Browse files
authored
fix: properly process If-Range headers in client requests (#8741)
* fix: properly process If-Range headers in client requests * chore: add autest for range requests * refactor: only check the presence of Range headers once
1 parent c6ec02f commit 47f2db1

File tree

8 files changed

+331
-46
lines changed

8 files changed

+331
-46
lines changed

proxy/http/HttpTransact.cc

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3025,7 +3025,6 @@ HttpTransact::build_response_from_cache(State *s, HTTPWarningCode warning_code)
30253025
s->next_action = SM_ACTION_INTERNAL_CACHE_NOOP;
30263026
break;
30273027

3028-
case HTTP_STATUS_RANGE_NOT_SATISFIABLE:
30293028
// Check if cached response supports Range. If it does, append
30303029
// Range transformation plugin
30313030
// A little misnomer. HTTP_STATUS_RANGE_NOT_SATISFIABLE
@@ -3042,7 +3041,8 @@ HttpTransact::build_response_from_cache(State *s, HTTPWarningCode warning_code)
30423041
// Check if cached response supports Range. If it does, append
30433042
// Range transformation plugin
30443043
// only if the cached response is a 200 OK
3045-
if (client_response_code == HTTP_STATUS_OK && client_request->presence(MIME_PRESENCE_RANGE)) {
3044+
if (client_response_code == HTTP_STATUS_OK && client_request->presence(MIME_PRESENCE_RANGE) &&
3045+
HttpTransactCache::validate_ifrange_header_if_any(client_request, cached_response)) {
30463046
s->state_machine->do_range_setup_if_necessary();
30473047
if (s->range_setup == RANGE_NOT_SATISFIABLE) {
30483048
build_error_response(s, HTTP_STATUS_RANGE_NOT_SATISFIABLE, "Requested Range Not Satisfiable", "default");
@@ -4210,6 +4210,7 @@ HttpTransact::handle_cache_operation_on_forward_server_response(State *s)
42104210
s->cache_info.action = CACHE_DO_DELETE;
42114211
s->next_action = SM_ACTION_SERVER_READ;
42124212
} else {
4213+
// No need to worry about If-Range headers because the request isn't conditional
42134214
if (s->hdr_info.client_request.presence(MIME_PRESENCE_RANGE)) {
42144215
s->state_machine->do_range_setup_if_necessary();
42154216
// Check client request range header if we cached a stealed content with cacheable=false
@@ -4250,7 +4251,10 @@ HttpTransact::handle_cache_operation_on_forward_server_response(State *s)
42504251
s->cache_info.action = CACHE_DO_UPDATE;
42514252
s->next_action = SM_ACTION_SERVER_READ;
42524253
} else {
4253-
if (s->hdr_info.client_request.presence(MIME_PRESENCE_RANGE)) {
4254+
auto *client_request = &s->hdr_info.client_request;
4255+
auto *cached_response = s->cache_info.object_read->response_get();
4256+
if (client_request->presence(MIME_PRESENCE_RANGE) &&
4257+
HttpTransactCache::validate_ifrange_header_if_any(client_request, cached_response)) {
42544258
s->state_machine->do_range_setup_if_necessary();
42554259
// Note that even if the Range request is not satisfiable, we
42564260
// update and serve this cache. This will give a 200 response to
@@ -4438,7 +4442,9 @@ HttpTransact::handle_cache_operation_on_forward_server_response(State *s)
44384442
ink_assert(s->cache_info.object_read != nullptr);
44394443
s->cache_info.action = CACHE_DO_REPLACE;
44404444

4441-
if (s->hdr_info.client_request.presence(MIME_PRESENCE_RANGE)) {
4445+
auto *client_request = &s->hdr_info.client_request;
4446+
if (client_request->presence(MIME_PRESENCE_RANGE) &&
4447+
HttpTransactCache::validate_ifrange_header_if_any(client_request, base_response)) {
44424448
s->state_machine->do_range_setup_if_necessary();
44434449
}
44444450
}
@@ -4450,7 +4456,9 @@ HttpTransact::handle_cache_operation_on_forward_server_response(State *s)
44504456
s->cache_info.action = CACHE_DO_NO_ACTION;
44514457
} else {
44524458
s->cache_info.action = CACHE_DO_WRITE;
4453-
if (s->hdr_info.client_request.presence(MIME_PRESENCE_RANGE)) {
4459+
auto *client_request = &s->hdr_info.client_request;
4460+
if (client_request->presence(MIME_PRESENCE_RANGE) &&
4461+
HttpTransactCache::validate_ifrange_header_if_any(client_request, base_response)) {
44544462
s->state_machine->do_range_setup_if_necessary();
44554463
}
44564464
}

proxy/http/HttpTransactCache.cc

Lines changed: 46 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,14 +1224,16 @@ HttpTransactCache::CalcVariability(const OverridableHttpConfigParams *http_confi
12241224
HTTP_STATUS_PRECONDITION_FAILED is returned if one fails; otherwise,
12251225
the response's status code is returned.
12261226
1227-
If the request is a RANGE request with If-range,
1228-
HTTP_STATUS_RANGE_NOT_SATISFIABLE is returned if the If-range condition
1229-
is not satisfied (or fails); that means the document is changed and
1230-
the whole document should be returned with 200 status code. Otherwise,
1231-
the response's status code is returned.
1227+
If the request is a RANGE request with If-range, the response's
1228+
status code is returned. This is because in the case of If-range
1229+
headers, it's not so useful to perform checks on it at the same time
1230+
as other conditional headers. Upon encountering an invalid If-range
1231+
header, the server must ignore the RANGE and If-range headers
1232+
entirely. As such, it's more appropriate to validate If-range
1233+
headers when processing RANGE headers. On other occasions, it's
1234+
easier to treat If-range requests as plain non-conditional ones.
12321235
1233-
@return status code: HTTP_STATUS_NOT_MODIFIED,
1234-
HTTP_STATUS_PRECONDITION_FAILED, or HTTP_STATUS_RANGE_NOT_SATISFIABLE.
1236+
@return status code: HTTP_STATUS_NOT_MODIFIED or HTTP_STATUS_PRECONDITION_FAILED
12351237
12361238
*/
12371239
HTTPStatus
@@ -1244,7 +1246,7 @@ HttpTransactCache::match_response_to_request_conditionals(HTTPHdr *request, HTTP
12441246
ink_assert(response->status_get() != HTTP_STATUS_RANGE_NOT_SATISFIABLE);
12451247

12461248
if (!(request->presence(MIME_PRESENCE_IF_MODIFIED_SINCE | MIME_PRESENCE_IF_NONE_MATCH | MIME_PRESENCE_IF_UNMODIFIED_SINCE |
1247-
MIME_PRESENCE_IF_MATCH | MIME_PRESENCE_RANGE))) {
1249+
MIME_PRESENCE_IF_MATCH))) {
12481250
return response->status_get();
12491251
}
12501252

@@ -1357,47 +1359,50 @@ HttpTransactCache::match_response_to_request_conditionals(HTTPHdr *request, HTTP
13571359
return response_code;
13581360
}
13591361

1360-
// Handling If-Range header:
1361-
// As Range && If-Range don't occur often, we want to put the
1362-
// If-Range code in the end
1363-
if (request->presence(MIME_PRESENCE_RANGE) && request->presence(MIME_PRESENCE_IF_RANGE)) {
1364-
int raw_len, comma_sep_list_len;
1362+
return response->status_get();
1363+
}
13651364

1366-
const char *if_value = request->value_get(MIME_FIELD_IF_RANGE, MIME_LEN_IF_RANGE, &comma_sep_list_len);
1365+
/**
1366+
Validates the contents of If-range headers in client requests. The presence of Range
1367+
headers needs to be checked before calling this method.
13671368
1368-
// this is an ETag, similar to If-Match
1369-
if (!if_value || if_value[0] == '"' || (comma_sep_list_len > 1 && if_value[1] == '/')) {
1370-
if (!if_value) {
1371-
if_value = "";
1372-
comma_sep_list_len = 0;
1373-
}
1369+
@return Whether the condition specified by If-range is met, if there is any.
1370+
If there's no If-range header, then true.
13741371
1375-
const char *raw_etags = response->value_get(MIME_FIELD_ETAG, MIME_LEN_ETAG, &raw_len);
1372+
*/
1373+
bool
1374+
HttpTransactCache::validate_ifrange_header_if_any(HTTPHdr *request, HTTPHdr *response)
1375+
{
1376+
if (!request->presence(MIME_PRESENCE_IF_RANGE)) {
1377+
return true;
1378+
}
13761379

1377-
if (!raw_etags) {
1378-
raw_etags = "";
1379-
raw_len = 0;
1380-
}
1380+
int raw_len, comma_sep_list_len;
13811381

1382-
if (do_strings_match_strongly(raw_etags, raw_len, if_value, comma_sep_list_len)) {
1383-
return response->status_get();
1384-
} else {
1385-
return HTTP_STATUS_RANGE_NOT_SATISFIABLE;
1386-
}
1382+
const char *if_value = request->value_get(MIME_FIELD_IF_RANGE, MIME_LEN_IF_RANGE, &comma_sep_list_len);
1383+
1384+
// this is an ETag, similar to If-Match
1385+
if (!if_value || if_value[0] == '"' || (comma_sep_list_len > 1 && if_value[1] == '/')) {
1386+
if (!if_value) {
1387+
if_value = "";
1388+
comma_sep_list_len = 0;
13871389
}
1388-
// this a Date, similar to If-Unmodified-Since but must be an exact match
1389-
else {
1390-
// lm_value is zero if Last-modified not exists
1391-
ink_time_t lm_value = response->get_last_modified();
13921390

1393-
// condition fails if Last-modified not exists
1394-
if ((request->get_if_range_date() != lm_value) || (lm_value == 0)) {
1395-
return HTTP_STATUS_RANGE_NOT_SATISFIABLE;
1396-
} else {
1397-
return response->status_get();
1398-
}
1391+
const char *raw_etags = response->value_get(MIME_FIELD_ETAG, MIME_LEN_ETAG, &raw_len);
1392+
1393+
if (!raw_etags) {
1394+
raw_etags = "";
1395+
raw_len = 0;
13991396
}
1397+
1398+
return do_strings_match_strongly(raw_etags, raw_len, if_value, comma_sep_list_len);
14001399
}
14011400

1402-
return response->status_get();
1401+
// this a Date, similar to If-Unmodified-Since but must be an exact match
1402+
1403+
// lm_value is zero if Last-modified not exists
1404+
ink_time_t lm_value = response->get_last_modified();
1405+
1406+
// condition fails if Last-modified not exists
1407+
return (request->get_if_range_date() == lm_value) && (lm_value != 0);
14031408
}

proxy/http/HttpTransactCache.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,6 @@ class HttpTransactCache
8080

8181
static HTTPStatus match_response_to_request_conditionals(HTTPHdr *ua_request, HTTPHdr *c_response,
8282
ink_time_t response_received_time);
83+
84+
static bool validate_ifrange_header_if_any(HTTPHdr *ua_request, HTTPHdr *c_response);
8385
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
HTTP/1.1 200 OK
2+
Server: ATS/``
3+
Cache-Control: max-age=``
4+
Last-Modified: Thu, 10 Feb 2022 00:00:00 GMT
5+
ETag: range
6+
Content-Length: 11
7+
Date: ``
8+
Age: ``
9+
Connection: keep-alive
10+
11+
0123456789
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
HTTP/1.1 206 Partial Content
2+
Server: ATS/``
3+
Cache-Control: max-age=1
4+
Last-Modified: Thu, 10 Feb 2022 00:00:00 GMT
5+
Content-Length: 5
6+
Date: ``
7+
Etag: range
8+
Age: ``
9+
Content-Range: bytes 1-5/11
10+
Connection: keep-alive
11+
12+
12345``
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
HTTP/1.1 206 Partial Content
2+
Server: ATS/``
3+
Cache-Control: max-age=300
4+
Last-Modified: Thu, 10 Feb 2022 00:00:00 GMT
5+
ETag: range
6+
Content-Length: 5
7+
Date: ``
8+
Age: ``
9+
Content-Range: bytes 1-5/11
10+
Connection: keep-alive
11+
12+
12345``
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
HTTP/1.1 416 Requested Range Not Satisfiable
2+
Date: ``
3+
Connection: keep-alive
4+
Server: ATS/``
5+
Cache-Control: no-store
6+
``

0 commit comments

Comments
 (0)