PageRenderTime 39ms CodeModel.GetById 14ms app.highlight 21ms RepoModel.GetById 1ms app.codeStats 0ms

/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
  1%% @author Justin Sheehy <justin@basho.com>
  2%% @author Andy Gross <andy@basho.com>
  3%% @copyright 2007-2008 Basho Technologies
  4%% (guess_mime/1 derived from code copyright 2007 Mochi Media, Inc.)
  5%%
  6%%    Licensed under the Apache License, Version 2.0 (the "License");
  7%%    you may not use this file except in compliance with the License.
  8%%    You may obtain a copy of the License at
  9%%
 10%%        http://www.apache.org/licenses/LICENSE-2.0
 11%%
 12%%    Unless required by applicable law or agreed to in writing, software
 13%%    distributed under the License is distributed on an "AS IS" BASIS,
 14%%    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15%%    See the License for the specific language governing permissions and
 16%%    limitations under the License.
 17
 18%% @doc Utilities for parsing, quoting, and negotiation.
 19
 20-module(webmachine_util).
 21-export([guess_mime/1]).
 22-export([convert_request_date/1, compare_ims_dates/2]).
 23-export([choose_media_type/2]).
 24-export([choose_charset/2]).
 25-export([choose_encoding/2]).
 26-export([unquote_header/1]).
 27-export([now_diff_milliseconds/2]).
 28-export([media_type_to_detail/1]).
 29-export([test/0]).
 30
 31convert_request_date(Date) ->
 32    try 
 33	case httpd_util:convert_request_date(Date) of
 34	    ReqDate -> ReqDate
 35	end
 36    catch
 37	error:_ -> bad_date
 38    end.
 39
 40%% returns true if D1 > D2
 41compare_ims_dates(D1, D2) ->
 42    GD1 = calendar:datetime_to_gregorian_seconds(D1),
 43    GD2 = calendar:datetime_to_gregorian_seconds(D2),
 44    GD1 > GD2.
 45
 46%% @spec guess_mime(string()) -> string()
 47%% @doc  Guess the mime type of a file by the extension of its filename.
 48guess_mime(File) ->
 49    case filename:extension(File) of
 50	".html" ->
 51	    "text/html";
 52	".xhtml" ->
 53	    "application/xhtml+xml";
 54	".xml" ->
 55	    "application/xml";
 56	".css" ->
 57	    "text/css";
 58	".js" ->
 59	    "application/x-javascript";
 60	".jpg" ->
 61	    "image/jpeg";
 62	".jpeg" ->
 63	    "image/jpeg";
 64	".gif" ->
 65	    "image/gif";
 66	".png" ->
 67	    "image/png";
 68	".ico" ->
 69	    "image/x-icon";
 70	".swf" ->
 71	    "application/x-shockwave-flash";
 72	".zip" ->
 73	    "application/zip";
 74	".bz2" ->
 75	    "application/x-bzip2";
 76	".gz" ->
 77	    "application/x-gzip";
 78	".tar" ->
 79	    "application/x-tar";
 80	".tgz" ->
 81	    "application/x-gzip";
 82        ".htc" ->
 83            "text/x-component";
 84	".manifest" ->
 85	    "text/cache-manifest";
 86        ".svg" ->
 87            "image/svg+xml";
 88	_ ->
 89	    "text/plain"
 90    end.
 91
 92choose_media_type(Provided,AcceptHead) ->
 93    % Return the Content-Type we will serve for a request.
 94    % If there is no acceptable/available match, return the atom "none".
 95    % AcceptHead is the value of the request's Accept header
 96    % Provided is a list of media types the resource can provide.
 97    %  each is either a string e.g. -- "text/html"
 98    %   or a string and parameters e.g. -- {"text/html",[{level,1}]}
 99    % (the plain string case with no parameters is much more common)
100    Requested = accept_header_to_media_types(AcceptHead),
101    Prov1 = normalize_provided(Provided),
102    choose_media_type1(Prov1,Requested).
103choose_media_type1(_Provided,[]) ->
104    none;
105choose_media_type1(Provided,[H|T]) ->
106    {_Pri,Type,Params} = H,
107    case media_match({Type,Params}, Provided) of
108	[] -> choose_media_type1(Provided,T);
109	[{CT_T,CT_P}|_] -> format_content_type(CT_T,CT_P)
110    end.
111
112media_match(_,[]) -> [];
113media_match({"*/*",[]},[H|_]) -> [H];
114media_match({Type,Params},Provided) ->
115    [{T1,P1} || {T1,P1} <- Provided,
116		media_type_match(Type,T1), media_params_match(Params,P1)].
117media_type_match(Req,Prov) ->
118    case Req of
119	"*" -> % might as well not break for lame (Gomez) clients
120	    true;
121	"*/*" ->
122	    true;
123	Prov ->
124	    true;
125	_ ->
126	    [R1|R2] = string:tokens(Req,"/"),
127	    [P1,_P2] = string:tokens(Prov,"/"),
128	    case R2 of
129		["*"] ->
130		    case R1 of
131			P1 -> true;
132			_ -> false
133		    end;
134		_ -> false
135	    end
136    end.
137media_params_match(Req,Prov) ->
138    lists:sort(Req) =:= lists:sort(Prov).	    
139
140prioritize_media(TyParam) ->
141    {Type, Params} = TyParam,
142    prioritize_media(Type,Params,[]).    
143prioritize_media(Type,Params,Acc) ->
144    case Params of
145	[] ->
146	    {1, Type, Acc};
147	_ ->
148	    [{Tok,Val}|Rest] = Params,
149	    case Tok of
150		"q" ->
151		    QVal = case Val of
152			"1" ->
153			    1;
154			[$.|_] -> list_to_float([$0|Val]);
155			_ -> list_to_float(Val)
156		    end,
157		    {QVal, Type, Rest ++ Acc};
158		_ ->
159		    prioritize_media(Type,Rest,[{Tok,Val}|Acc])
160	    end
161    end.
162
163media_type_to_detail(MType) ->
164    [CType|Params] = string:tokens(MType, ";"),
165    MParams = [list_to_tuple([string:strip(KV) || KV <- string:tokens(X,"=")])
166                || X <- Params],
167    {CType, MParams}.                       
168
169accept_header_to_media_types(HeadVal) ->
170    % given the value of an accept header, produce an ordered list
171    % based on the q-values.  Results are [{Type,Params}] with the
172    % head of the list being the highest-priority requested type.
173    try
174        lists:reverse(lists:keysort(1,
175         [prioritize_media(media_type_to_detail(MType)) ||
176             MType <- [string:strip(X) || X <- string:tokens(HeadVal, ",")]]))
177    catch _:_ -> []
178    end.
179
180normalize_provided(Provided) ->
181    [normalize_provided1(X) || X <- Provided].
182normalize_provided1(Type) when is_list(Type) -> {Type, []};
183normalize_provided1({Type,Params}) -> {Type, Params}.
184
185format_content_type(Type,[]) -> Type;
186format_content_type(Type,[H|T]) -> format_content_type(Type ++ "; " ++ H, T).
187
188choose_charset(CSets, AccCharHdr) -> do_choose(CSets, AccCharHdr, "ISO-8859-1").
189
190choose_encoding(Encs, AccEncHdr) -> do_choose(Encs, AccEncHdr, "identity").
191
192do_choose(Choices, Header, Default) ->
193    Accepted = build_conneg_list(string:tokens(Header, ",")),
194    DefaultPrio = [P || {P,C} <- Accepted, C =:= Default],
195    StarPrio = [P || {P,C} <- Accepted, C =:= "*"],
196    DefaultOkay = case DefaultPrio of
197        [] ->
198            case StarPrio of
199                [0.0] -> no;
200                _ -> yes
201            end;
202        [0.0] -> no;
203        _ -> yes
204    end,
205    AnyOkay = case StarPrio of
206        [] -> no;
207        [0.0] -> no;
208        _ -> yes
209    end,
210    do_choose(Default, DefaultOkay, AnyOkay, Choices, Accepted).
211do_choose(_Default, _DefaultOkay, _AnyOkay, [], []) ->
212    none;
213do_choose(_Default, _DefaultOkay, _AnyOkay, [], _Accepted) ->
214    none;
215do_choose(Default, DefaultOkay, AnyOkay, Choices, []) ->
216    case AnyOkay of
217        yes -> hd(Choices);
218        no ->
219            case DefaultOkay of
220                yes ->
221                    case lists:member(Default, Choices) of
222                        true -> Default;
223                        _ -> none
224                    end;
225                no -> none
226            end
227    end;
228do_choose(Default, DefaultOkay, AnyOkay, Choices, [AccPair|AccRest]) ->
229    {Prio, Acc} = AccPair,
230    case Prio of
231        0.0 ->
232            do_choose(Default, DefaultOkay, AnyOkay,
233                            lists:delete(Acc, Choices), AccRest);
234        _ ->
235            LAcc = string:to_lower(Acc),
236            LChoices = [string:to_lower(X) || X <- Choices],
237            % doing this a little more work than needed in
238            % order to be easily insensitive but preserving
239            case lists:member(LAcc, LChoices) of
240                true -> 
241                    hd([X || X <- Choices,
242                             string:to_lower(X) =:= LAcc]);
243                false -> do_choose(Default, DefaultOkay, AnyOkay,
244                                         Choices, AccRest)
245            end
246    end.
247
248build_conneg_list(AccList) ->
249    build_conneg_list(AccList, []).
250build_conneg_list([], Result) -> lists:reverse(lists:sort(Result));
251build_conneg_list([Acc|AccRest], Result) ->
252    XPair = list_to_tuple([string:strip(X) || X <- string:tokens(Acc, ";")]),
253    Pair = case XPair of
254        {Choice, "q=" ++ PrioStr} ->
255            case PrioStr of
256                "0" -> {0.0, Choice};
257                "1" -> {1.0, Choice};
258                [$.|_] -> {list_to_float([$0|PrioStr]), Choice};
259                _ -> {list_to_float(PrioStr), Choice}
260            end;
261        {Choice} ->
262            {1.0, Choice}
263    end,
264    build_conneg_list(AccRest,[Pair|Result]).
265
266% (unquote_header copied from mochiweb_util since they don't export it)
267unquote_header("\"" ++ Rest) ->
268    unquote_header(Rest, []);
269unquote_header(S) ->
270    S.
271unquote_header("", Acc) ->
272    lists:reverse(Acc);
273unquote_header("\"", Acc) ->
274    lists:reverse(Acc);
275unquote_header([$\\, C | Rest], Acc) ->
276    unquote_header(Rest, [C | Acc]);
277unquote_header([C | Rest], Acc) ->
278    unquote_header(Rest, [C | Acc]).
279
280%% @type now() = {MegaSecs, Secs, MicroSecs}
281
282%% This is faster than timer:now_diff() because it does not use bignums.
283%% But it returns *milliseconds*  (timer:now_diff returns microseconds.)
284%% From http://www.erlang.org/ml-archive/erlang-questions/200205/msg00027.html
285
286%% @doc  Compute the difference between two now() tuples, in milliseconds.
287%% @spec now_diff_milliseconds(now(), now()) -> integer()
288now_diff_milliseconds({M,S,U}, {M,S1,U1}) ->
289    ((S-S1) * 1000) + ((U-U1) div 1000);
290now_diff_milliseconds({M,S,U}, {M1,S1,U1}) ->
291    ((M-M1)*1000000+(S-S1))*1000 + ((U-U1) div 1000).
292
293test() ->
294    test_choose_media_type(),
295    ok.
296
297test_choose_media_type() ->
298    Provided = "text/html",
299    ShouldMatch = ["*", "*/*", "text/*", "text/html"],
300    WantNone = ["foo", "text/xml", "application/*", "foo/bar/baz"],
301    [ Provided = choose_media_type([Provided], I) || I <- ShouldMatch ],
302    [ none = choose_media_type([Provided], I) || I <- WantNone ],
303    ok.