Skip to content

Commit 23a32ca

Browse files
committed
Add handling of Etag and If-Modified-Since headers to files served by mod_http_upload
1 parent b2e2bd7 commit 23a32ca

3 files changed

Lines changed: 117 additions & 20 deletions

File tree

src/ejabberd_http.erl

Lines changed: 103 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,9 @@ process_request(#state{request_method = Method,
517517
make_text_output(State, Status,
518518
apply_custom_headers(Headers, CustomHeaders), Output);
519519
{Status, Headers, {file, FileName}} ->
520-
make_file_output(State, Status, Headers, FileName);
520+
make_file_output(State, Status, Headers, FileName, []);
521+
{Status, Headers, {file, FileName, ReqHeaders}} ->
522+
make_file_output(State, Status, Headers, FileName, ReqHeaders);
521523
{Status, Reason, Headers, Output}
522524
when is_binary(Output) or is_list(Output) ->
523525
make_text_output(State, Status, Reason,
@@ -683,22 +685,107 @@ make_text_output(State, Status, Reason, Headers, Text) ->
683685
EncodedHdrs = make_headers(State, Status, Reason, Headers, Data2),
684686
[EncodedHdrs, Data2].
685687

686-
make_file_output(State, Status, Headers, FileName) ->
688+
parse_etags(Etags, WeakIgnore) ->
689+
lists:filtermap(
690+
fun(Value) ->
691+
case string:trim(Value) of
692+
<<"W/\"", _Rest/binary>> when WeakIgnore ->
693+
false;
694+
<<"W/\"", Rest/binary>> ->
695+
case string:split(Rest, <<"\"">>, trailing) of
696+
[Etag, _] -> {true, Etag};
697+
_ -> false
698+
end;
699+
<<"\"", Rest/binary>> ->
700+
case string:split(Rest, <<"\"">>, trailing) of
701+
[Etag, _] -> {true, Etag};
702+
_ -> false
703+
end;
704+
<<"*">> -> true;
705+
_ -> false
706+
end
707+
end,
708+
string:split(Etags, <<",">>, all)).
709+
710+
711+
process_etags(Etag, RequestHeaders) ->
712+
process_etags(Etag, RequestHeaders, if_match).
713+
714+
process_etags(Etag, RequestHeaders, if_match) ->
715+
case lists:keyfind('If-Match', 1, RequestHeaders) of
716+
{_, Header} ->
717+
Etags = parse_etags(Header, true),
718+
case lists:any(fun(V) -> V == <<"*">> orelse V == Etag end, Etags) of
719+
true -> process_etags(Etag, RequestHeaders, if_none_match);
720+
_ -> {true, 412}
721+
end;
722+
_ ->
723+
process_etags(Etag, RequestHeaders, if_none_match)
724+
end;
725+
process_etags(Etag, RequestHeaders, if_none_match) ->
726+
case lists:keyfind('If-None-Match', 1, RequestHeaders) of
727+
{_, Header} ->
728+
Etags = parse_etags(Header, false),
729+
case lists:any(fun(V) -> V == <<"*">> orelse V == Etag end, Etags) of
730+
true -> {true, 304};
731+
_ -> false
732+
end;
733+
_ ->
734+
false
735+
end.
736+
737+
process_if_modified_since(MTime, RequestHeaders) ->
738+
case lists:keyfind('If-Modified-Since', 1, RequestHeaders) of
739+
{_, Header} ->
740+
case httpd_util:convert_request_date(binary_to_list(Header)) of
741+
bad_date ->
742+
false;
743+
LM ->
744+
T1 = calendar:datetime_to_gregorian_seconds(
745+
calendar:universal_time_to_local_time(LM)),
746+
T2 = calendar:datetime_to_gregorian_seconds(MTime),
747+
case T1 >= T2 of
748+
true ->
749+
{true, 304};
750+
_-> false
751+
end
752+
end;
753+
_ ->
754+
false
755+
end.
756+
757+
make_file_output(State, Status, Headers, FileName, RequestHeaders) ->
687758
case file:read_file_info(FileName) of
688-
{ok, #file_info{size = Size}} when State#state.request_method == 'HEAD' ->
689-
make_headers(State, Status, <<"">>, Headers, Size);
690-
{ok, #file_info{size = Size}} ->
691-
case file:open(FileName, [raw, read]) of
692-
{ok, Fd} ->
693-
EncodedHdrs = make_headers(State, Status, <<"">>, Headers, Size),
694-
send_text(State, EncodedHdrs),
695-
send_file(State, Fd, Size, FileName),
696-
file:close(Fd),
697-
none;
698-
{error, Why} ->
699-
Reason = file_format_error(Why),
700-
?ERROR_MSG("Failed to open ~ts: ~ts", [FileName, Reason]),
701-
make_text_output(State, 404, Reason, [], <<>>)
759+
{ok, #file_info{size = Size, mtime = MTime} = FI} ->
760+
Etag = list_to_binary(httpd_util:create_etag(FI)),
761+
ExtraHeaders = [{<<"Last-Modified">>, httpd_util:rfc1123_date(MTime)},
762+
{<<"ETag">>, Etag}],
763+
case process_etags(Etag, RequestHeaders) of
764+
false ->
765+
case process_if_modified_since(MTime, RequestHeaders) of
766+
false ->
767+
if
768+
State#state.request_method == 'HEAD' ->
769+
make_headers(State, Status, <<"">>, ExtraHeaders ++ Headers, Size);
770+
true ->
771+
case file:open(FileName, [raw, read]) of
772+
{ok, Fd} ->
773+
EncodedHdrs = make_headers(State, Status, <<"">>, ExtraHeaders ++ Headers, Size),
774+
send_text(State, EncodedHdrs),
775+
send_file(State, Fd, Size, FileName),
776+
file:close(Fd),
777+
none;
778+
{error, Why} ->
779+
Reason = file_format_error(Why),
780+
?ERROR_MSG("Failed to open ~ts: ~ts", [FileName, Reason]),
781+
make_text_output(State, 404, Reason, [], <<>>)
782+
end
783+
end;
784+
{_, NewStatus} ->
785+
make_headers(State, NewStatus, <<"">>, ExtraHeaders ++ Headers, 0)
786+
end;
787+
{_, NewStatus} ->
788+
make_headers(State, NewStatus, <<"">>, ExtraHeaders ++ Headers, 0)
702789
end;
703790
{error, Why} ->
704791
Reason = file_format_error(Why),

src/mod_http_upload.erl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,7 @@ process(_LocalPath, #request{method = 'PUT', host = Host, ip = IP,
561561
[encode_addr(IP), Host, Error]),
562562
http_response(500)
563563
end;
564-
process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request0)
564+
process(_LocalPath, #request{method = Method, host = Host, ip = IP, headers = ReqHeaders} = Request0)
565565
when Method == 'GET';
566566
Method == 'HEAD' ->
567567
Request = Request0#request{host = redecode_url(Host)},
@@ -584,7 +584,7 @@ process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request0)
584584
end,
585585
Headers2 = [{<<"Content-Type">>, ContentType} | Headers1],
586586
Headers3 = ejabberd_http:apply_custom_headers(Headers2, CustomHeaders),
587-
http_response(200, Headers3, {file, Path});
587+
http_response(200, Headers3, {file, Path, ReqHeaders});
588588
{error, eacces} ->
589589
?WARNING_MSG("Cannot serve ~ts to ~ts: Permission denied",
590590
[Path, encode_addr(IP)]),

test/upload_tests.erl

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,20 @@ put_request(_Config, URL0, Data) ->
167167
get_request(_Config, URL0, Data) ->
168168
ct:comment("Getting ~B bytes from ~s", [size(Data), URL0]),
169169
URL = binary_to_list(URL0),
170-
{ok, {{"HTTP/1.1", 200, _}, _, Body}} =
170+
{ok, {{"HTTP/1.1", 200, _}, Headers, Body}} =
171171
httpc:request(get, {URL, []}, [], [{body_format, binary}]),
172172
ct:comment("Checking returned body"),
173-
Body = Data.
173+
Body = Data,
174+
ct:comment("Request had Etag"),
175+
Etag = ?match({_, Etag}, lists:keyfind("etag", 1, Headers), Etag),
176+
ct:comment("Request had Last-Modified"),
177+
LM = ?match({_, LM}, lists:keyfind("last-modified", 1, Headers), LM),
178+
ct:comment("Request with Etag are handled correctly"),
179+
{ok, {{"HTTP/1.1", 304, _}, _, _}} =
180+
httpc:request(get, {URL, [{"If-None-Match", ["\"",Etag,"\""]}]}, [], [{body_format, binary}]),
181+
ct:comment("Request with If-Modified-Since are handled correctly"),
182+
{ok, {{"HTTP/1.1", 304, _}, _, _}} =
183+
httpc:request(get, {URL, [{"If-Modified-Since", LM}]}, [], [{body_format, binary}]).
174184

175185
max_size_exceed(Config, NS) ->
176186
To = upload_jid(Config),

0 commit comments

Comments
 (0)