/deps/webmachine/src/webmachine_util.erl
https://code.google.com/p/zotonic/ · Erlang · 303 lines · 242 code · 22 blank · 39 comment · 12 complexity · 89eb7016d5b0efae8fa4fb111fb77c26 MD5 · raw file
- %% @author Justin Sheehy <justin@basho.com>
- %% @author Andy Gross <andy@basho.com>
- %% @copyright 2007-2008 Basho Technologies
- %% (guess_mime/1 derived from code copyright 2007 Mochi Media, Inc.)
- %%
- %% Licensed under the Apache License, Version 2.0 (the "License");
- %% you may not use this file except in compliance with the License.
- %% You may obtain a copy of the License at
- %%
- %% http://www.apache.org/licenses/LICENSE-2.0
- %%
- %% Unless required by applicable law or agreed to in writing, software
- %% distributed under the License is distributed on an "AS IS" BASIS,
- %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- %% See the License for the specific language governing permissions and
- %% limitations under the License.
- %% @doc Utilities for parsing, quoting, and negotiation.
- -module(webmachine_util).
- -export([guess_mime/1]).
- -export([convert_request_date/1, compare_ims_dates/2]).
- -export([choose_media_type/2]).
- -export([choose_charset/2]).
- -export([choose_encoding/2]).
- -export([unquote_header/1]).
- -export([now_diff_milliseconds/2]).
- -export([media_type_to_detail/1]).
- -export([test/0]).
- convert_request_date(Date) ->
- try
- case httpd_util:convert_request_date(Date) of
- ReqDate -> ReqDate
- end
- catch
- error:_ -> bad_date
- end.
- %% returns true if D1 > D2
- compare_ims_dates(D1, D2) ->
- GD1 = calendar:datetime_to_gregorian_seconds(D1),
- GD2 = calendar:datetime_to_gregorian_seconds(D2),
- GD1 > GD2.
- %% @spec guess_mime(string()) -> string()
- %% @doc Guess the mime type of a file by the extension of its filename.
- guess_mime(File) ->
- case filename:extension(File) of
- ".html" ->
- "text/html";
- ".xhtml" ->
- "application/xhtml+xml";
- ".xml" ->
- "application/xml";
- ".css" ->
- "text/css";
- ".js" ->
- "application/x-javascript";
- ".jpg" ->
- "image/jpeg";
- ".jpeg" ->
- "image/jpeg";
- ".gif" ->
- "image/gif";
- ".png" ->
- "image/png";
- ".ico" ->
- "image/x-icon";
- ".swf" ->
- "application/x-shockwave-flash";
- ".zip" ->
- "application/zip";
- ".bz2" ->
- "application/x-bzip2";
- ".gz" ->
- "application/x-gzip";
- ".tar" ->
- "application/x-tar";
- ".tgz" ->
- "application/x-gzip";
- ".htc" ->
- "text/x-component";
- ".manifest" ->
- "text/cache-manifest";
- ".svg" ->
- "image/svg+xml";
- _ ->
- "text/plain"
- end.
- choose_media_type(Provided,AcceptHead) ->
- % Return the Content-Type we will serve for a request.
- % If there is no acceptable/available match, return the atom "none".
- % AcceptHead is the value of the request's Accept header
- % Provided is a list of media types the resource can provide.
- % each is either a string e.g. -- "text/html"
- % or a string and parameters e.g. -- {"text/html",[{level,1}]}
- % (the plain string case with no parameters is much more common)
- Requested = accept_header_to_media_types(AcceptHead),
- Prov1 = normalize_provided(Provided),
- choose_media_type1(Prov1,Requested).
- choose_media_type1(_Provided,[]) ->
- none;
- choose_media_type1(Provided,[H|T]) ->
- {_Pri,Type,Params} = H,
- case media_match({Type,Params}, Provided) of
- [] -> choose_media_type1(Provided,T);
- [{CT_T,CT_P}|_] -> format_content_type(CT_T,CT_P)
- end.
- media_match(_,[]) -> [];
- media_match({"*/*",[]},[H|_]) -> [H];
- media_match({Type,Params},Provided) ->
- [{T1,P1} || {T1,P1} <- Provided,
- media_type_match(Type,T1), media_params_match(Params,P1)].
- media_type_match(Req,Prov) ->
- case Req of
- "*" -> % might as well not break for lame (Gomez) clients
- true;
- "*/*" ->
- true;
- Prov ->
- true;
- _ ->
- [R1|R2] = string:tokens(Req,"/"),
- [P1,_P2] = string:tokens(Prov,"/"),
- case R2 of
- ["*"] ->
- case R1 of
- P1 -> true;
- _ -> false
- end;
- _ -> false
- end
- end.
- media_params_match(Req,Prov) ->
- lists:sort(Req) =:= lists:sort(Prov).
- prioritize_media(TyParam) ->
- {Type, Params} = TyParam,
- prioritize_media(Type,Params,[]).
- prioritize_media(Type,Params,Acc) ->
- case Params of
- [] ->
- {1, Type, Acc};
- _ ->
- [{Tok,Val}|Rest] = Params,
- case Tok of
- "q" ->
- QVal = case Val of
- "1" ->
- 1;
- [$.|_] -> list_to_float([$0|Val]);
- _ -> list_to_float(Val)
- end,
- {QVal, Type, Rest ++ Acc};
- _ ->
- prioritize_media(Type,Rest,[{Tok,Val}|Acc])
- end
- end.
- media_type_to_detail(MType) ->
- [CType|Params] = string:tokens(MType, ";"),
- MParams = [list_to_tuple([string:strip(KV) || KV <- string:tokens(X,"=")])
- || X <- Params],
- {CType, MParams}.
- accept_header_to_media_types(HeadVal) ->
- % given the value of an accept header, produce an ordered list
- % based on the q-values. Results are [{Type,Params}] with the
- % head of the list being the highest-priority requested type.
- try
- lists:reverse(lists:keysort(1,
- [prioritize_media(media_type_to_detail(MType)) ||
- MType <- [string:strip(X) || X <- string:tokens(HeadVal, ",")]]))
- catch _:_ -> []
- end.
- normalize_provided(Provided) ->
- [normalize_provided1(X) || X <- Provided].
- normalize_provided1(Type) when is_list(Type) -> {Type, []};
- normalize_provided1({Type,Params}) -> {Type, Params}.
- format_content_type(Type,[]) -> Type;
- format_content_type(Type,[H|T]) -> format_content_type(Type ++ "; " ++ H, T).
- choose_charset(CSets, AccCharHdr) -> do_choose(CSets, AccCharHdr, "ISO-8859-1").
- choose_encoding(Encs, AccEncHdr) -> do_choose(Encs, AccEncHdr, "identity").
- do_choose(Choices, Header, Default) ->
- Accepted = build_conneg_list(string:tokens(Header, ",")),
- DefaultPrio = [P || {P,C} <- Accepted, C =:= Default],
- StarPrio = [P || {P,C} <- Accepted, C =:= "*"],
- DefaultOkay = case DefaultPrio of
- [] ->
- case StarPrio of
- [0.0] -> no;
- _ -> yes
- end;
- [0.0] -> no;
- _ -> yes
- end,
- AnyOkay = case StarPrio of
- [] -> no;
- [0.0] -> no;
- _ -> yes
- end,
- do_choose(Default, DefaultOkay, AnyOkay, Choices, Accepted).
- do_choose(_Default, _DefaultOkay, _AnyOkay, [], []) ->
- none;
- do_choose(_Default, _DefaultOkay, _AnyOkay, [], _Accepted) ->
- none;
- do_choose(Default, DefaultOkay, AnyOkay, Choices, []) ->
- case AnyOkay of
- yes -> hd(Choices);
- no ->
- case DefaultOkay of
- yes ->
- case lists:member(Default, Choices) of
- true -> Default;
- _ -> none
- end;
- no -> none
- end
- end;
- do_choose(Default, DefaultOkay, AnyOkay, Choices, [AccPair|AccRest]) ->
- {Prio, Acc} = AccPair,
- case Prio of
- 0.0 ->
- do_choose(Default, DefaultOkay, AnyOkay,
- lists:delete(Acc, Choices), AccRest);
- _ ->
- LAcc = string:to_lower(Acc),
- LChoices = [string:to_lower(X) || X <- Choices],
- % doing this a little more work than needed in
- % order to be easily insensitive but preserving
- case lists:member(LAcc, LChoices) of
- true ->
- hd([X || X <- Choices,
- string:to_lower(X) =:= LAcc]);
- false -> do_choose(Default, DefaultOkay, AnyOkay,
- Choices, AccRest)
- end
- end.
- build_conneg_list(AccList) ->
- build_conneg_list(AccList, []).
- build_conneg_list([], Result) -> lists:reverse(lists:sort(Result));
- build_conneg_list([Acc|AccRest], Result) ->
- XPair = list_to_tuple([string:strip(X) || X <- string:tokens(Acc, ";")]),
- Pair = case XPair of
- {Choice, "q=" ++ PrioStr} ->
- case PrioStr of
- "0" -> {0.0, Choice};
- "1" -> {1.0, Choice};
- [$.|_] -> {list_to_float([$0|PrioStr]), Choice};
- _ -> {list_to_float(PrioStr), Choice}
- end;
- {Choice} ->
- {1.0, Choice}
- end,
- build_conneg_list(AccRest,[Pair|Result]).
- % (unquote_header copied from mochiweb_util since they don't export it)
- unquote_header("\"" ++ Rest) ->
- unquote_header(Rest, []);
- unquote_header(S) ->
- S.
- unquote_header("", Acc) ->
- lists:reverse(Acc);
- unquote_header("\"", Acc) ->
- lists:reverse(Acc);
- unquote_header([$\\, C | Rest], Acc) ->
- unquote_header(Rest, [C | Acc]);
- unquote_header([C | Rest], Acc) ->
- unquote_header(Rest, [C | Acc]).
- %% @type now() = {MegaSecs, Secs, MicroSecs}
- %% This is faster than timer:now_diff() because it does not use bignums.
- %% But it returns *milliseconds* (timer:now_diff returns microseconds.)
- %% From http://www.erlang.org/ml-archive/erlang-questions/200205/msg00027.html
- %% @doc Compute the difference between two now() tuples, in milliseconds.
- %% @spec now_diff_milliseconds(now(), now()) -> integer()
- now_diff_milliseconds({M,S,U}, {M,S1,U1}) ->
- ((S-S1) * 1000) + ((U-U1) div 1000);
- now_diff_milliseconds({M,S,U}, {M1,S1,U1}) ->
- ((M-M1)*1000000+(S-S1))*1000 + ((U-U1) div 1000).
- test() ->
- test_choose_media_type(),
- ok.
- test_choose_media_type() ->
- Provided = "text/html",
- ShouldMatch = ["*", "*/*", "text/*", "text/html"],
- WantNone = ["foo", "text/xml", "application/*", "foo/bar/baz"],
- [ Provided = choose_media_type([Provided], I) || I <- ShouldMatch ],
- [ none = choose_media_type([Provided], I) || I <- WantNone ],
- ok.