Add handling of Etag and If-Modified-Since headers to files served by mod_http_upload

This commit is contained in:
Pawel Chmielowski
2026-03-17 12:21:26 +01:00
parent b2e2bd7ca8
commit 23a32cae1a
3 changed files with 117 additions and 20 deletions
+103 -16
View File
@@ -517,7 +517,9 @@ process_request(#state{request_method = Method,
make_text_output(State, Status,
apply_custom_headers(Headers, CustomHeaders), Output);
{Status, Headers, {file, FileName}} ->
make_file_output(State, Status, Headers, FileName);
make_file_output(State, Status, Headers, FileName, []);
{Status, Headers, {file, FileName, ReqHeaders}} ->
make_file_output(State, Status, Headers, FileName, ReqHeaders);
{Status, Reason, Headers, Output}
when is_binary(Output) or is_list(Output) ->
make_text_output(State, Status, Reason,
@@ -683,22 +685,107 @@ make_text_output(State, Status, Reason, Headers, Text) ->
EncodedHdrs = make_headers(State, Status, Reason, Headers, Data2),
[EncodedHdrs, Data2].
make_file_output(State, Status, Headers, FileName) ->
parse_etags(Etags, WeakIgnore) ->
lists:filtermap(
fun(Value) ->
case string:trim(Value) of
<<"W/\"", _Rest/binary>> when WeakIgnore ->
false;
<<"W/\"", Rest/binary>> ->
case string:split(Rest, <<"\"">>, trailing) of
[Etag, _] -> {true, Etag};
_ -> false
end;
<<"\"", Rest/binary>> ->
case string:split(Rest, <<"\"">>, trailing) of
[Etag, _] -> {true, Etag};
_ -> false
end;
<<"*">> -> true;
_ -> false
end
end,
string:split(Etags, <<",">>, all)).
process_etags(Etag, RequestHeaders) ->
process_etags(Etag, RequestHeaders, if_match).
process_etags(Etag, RequestHeaders, if_match) ->
case lists:keyfind('If-Match', 1, RequestHeaders) of
{_, Header} ->
Etags = parse_etags(Header, true),
case lists:any(fun(V) -> V == <<"*">> orelse V == Etag end, Etags) of
true -> process_etags(Etag, RequestHeaders, if_none_match);
_ -> {true, 412}
end;
_ ->
process_etags(Etag, RequestHeaders, if_none_match)
end;
process_etags(Etag, RequestHeaders, if_none_match) ->
case lists:keyfind('If-None-Match', 1, RequestHeaders) of
{_, Header} ->
Etags = parse_etags(Header, false),
case lists:any(fun(V) -> V == <<"*">> orelse V == Etag end, Etags) of
true -> {true, 304};
_ -> false
end;
_ ->
false
end.
process_if_modified_since(MTime, RequestHeaders) ->
case lists:keyfind('If-Modified-Since', 1, RequestHeaders) of
{_, Header} ->
case httpd_util:convert_request_date(binary_to_list(Header)) of
bad_date ->
false;
LM ->
T1 = calendar:datetime_to_gregorian_seconds(
calendar:universal_time_to_local_time(LM)),
T2 = calendar:datetime_to_gregorian_seconds(MTime),
case T1 >= T2 of
true ->
{true, 304};
_-> false
end
end;
_ ->
false
end.
make_file_output(State, Status, Headers, FileName, RequestHeaders) ->
case file:read_file_info(FileName) of
{ok, #file_info{size = Size}} when State#state.request_method == 'HEAD' ->
make_headers(State, Status, <<"">>, Headers, Size);
{ok, #file_info{size = Size}} ->
case file:open(FileName, [raw, read]) of
{ok, Fd} ->
EncodedHdrs = make_headers(State, Status, <<"">>, Headers, Size),
send_text(State, EncodedHdrs),
send_file(State, Fd, Size, FileName),
file:close(Fd),
none;
{error, Why} ->
Reason = file_format_error(Why),
?ERROR_MSG("Failed to open ~ts: ~ts", [FileName, Reason]),
make_text_output(State, 404, Reason, [], <<>>)
{ok, #file_info{size = Size, mtime = MTime} = FI} ->
Etag = list_to_binary(httpd_util:create_etag(FI)),
ExtraHeaders = [{<<"Last-Modified">>, httpd_util:rfc1123_date(MTime)},
{<<"ETag">>, Etag}],
case process_etags(Etag, RequestHeaders) of
false ->
case process_if_modified_since(MTime, RequestHeaders) of
false ->
if
State#state.request_method == 'HEAD' ->
make_headers(State, Status, <<"">>, ExtraHeaders ++ Headers, Size);
true ->
case file:open(FileName, [raw, read]) of
{ok, Fd} ->
EncodedHdrs = make_headers(State, Status, <<"">>, ExtraHeaders ++ Headers, Size),
send_text(State, EncodedHdrs),
send_file(State, Fd, Size, FileName),
file:close(Fd),
none;
{error, Why} ->
Reason = file_format_error(Why),
?ERROR_MSG("Failed to open ~ts: ~ts", [FileName, Reason]),
make_text_output(State, 404, Reason, [], <<>>)
end
end;
{_, NewStatus} ->
make_headers(State, NewStatus, <<"">>, ExtraHeaders ++ Headers, 0)
end;
{_, NewStatus} ->
make_headers(State, NewStatus, <<"">>, ExtraHeaders ++ Headers, 0)
end;
{error, Why} ->
Reason = file_format_error(Why),
+2 -2
View File
@@ -561,7 +561,7 @@ process(_LocalPath, #request{method = 'PUT', host = Host, ip = IP,
[encode_addr(IP), Host, Error]),
http_response(500)
end;
process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request0)
process(_LocalPath, #request{method = Method, host = Host, ip = IP, headers = ReqHeaders} = Request0)
when Method == 'GET';
Method == 'HEAD' ->
Request = Request0#request{host = redecode_url(Host)},
@@ -584,7 +584,7 @@ process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request0)
end,
Headers2 = [{<<"Content-Type">>, ContentType} | Headers1],
Headers3 = ejabberd_http:apply_custom_headers(Headers2, CustomHeaders),
http_response(200, Headers3, {file, Path});
http_response(200, Headers3, {file, Path, ReqHeaders});
{error, eacces} ->
?WARNING_MSG("Cannot serve ~ts to ~ts: Permission denied",
[Path, encode_addr(IP)]),
+12 -2
View File
@@ -167,10 +167,20 @@ put_request(_Config, URL0, Data) ->
get_request(_Config, URL0, Data) ->
ct:comment("Getting ~B bytes from ~s", [size(Data), URL0]),
URL = binary_to_list(URL0),
{ok, {{"HTTP/1.1", 200, _}, _, Body}} =
{ok, {{"HTTP/1.1", 200, _}, Headers, Body}} =
httpc:request(get, {URL, []}, [], [{body_format, binary}]),
ct:comment("Checking returned body"),
Body = Data.
Body = Data,
ct:comment("Request had Etag"),
Etag = ?match({_, Etag}, lists:keyfind("etag", 1, Headers), Etag),
ct:comment("Request had Last-Modified"),
LM = ?match({_, LM}, lists:keyfind("last-modified", 1, Headers), LM),
ct:comment("Request with Etag are handled correctly"),
{ok, {{"HTTP/1.1", 304, _}, _, _}} =
httpc:request(get, {URL, [{"If-None-Match", ["\"",Etag,"\""]}]}, [], [{body_format, binary}]),
ct:comment("Request with If-Modified-Since are handled correctly"),
{ok, {{"HTTP/1.1", 304, _}, _, _}} =
httpc:request(get, {URL, [{"If-Modified-Since", LM}]}, [], [{body_format, binary}]).
max_size_exceed(Config, NS) ->
To = upload_jid(Config),