1
%% ``The contents of this file are subject to the Erlang Public License,
2
%% Version 1.1, (the "License"); you may not use this file except in
3
%% compliance with the License. You should have received a copy of the
4
%% Erlang Public License along with this software. If not, it can be
5
%% retrieved via the world wide web at http://www.erlang.org/.
7
%% Software distributed under the License is distributed on an "AS IS"
8
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
9
%% the License for the specific language governing rights and limitations
12
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
13
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
14
%% AB. All Rights Reserved.''
16
%% $Id: httpd_util.erl,v 1.1 2008/12/17 09:53:34 mikpe Exp $
19
-export([key1search/2, key1search/3, lookup/2, lookup/3, multi_lookup/2,
20
lookup_mime/2, lookup_mime/3, lookup_mime_default/2,
21
lookup_mime_default/3, reason_phrase/1, message/3, rfc1123_date/0,
22
rfc1123_date/1, day/1, month/1, decode_hex/1, decode_base64/1, encode_base64/1,
23
flatlength/1, split_path/1, split_script_path/1, suffix/1, to_upper/1,
24
to_lower/1, split/3, header/2, header/3, header/4, uniq/1,
25
make_name/2,make_name/3,make_name/4,strip/1,
26
hexlist_to_integer/1,integer_to_hexlist/1,
27
convert_request_date/1,create_etag/1,create_etag/2,getSize/1,
28
response_generated/1]).
30
%%Since hexlist_to_integer is a lousy name make a name convert
31
-export([encode_hex/1]).
32
-include("httpd.hrl").
36
key1search(TupleList,Key) ->
37
key1search(TupleList,Key,undefined).
39
key1search(TupleList,Key,Undefined) ->
40
case lists:keysearch(Key,1,TupleList) of
41
{value,{Key,Value}} ->
50
lookup(Table,Key,undefined).
52
lookup(Table,Key,Undefined) ->
53
case catch ets:lookup(Table,Key) of
62
multi_lookup(Table,Key) ->
63
remove_key(ets:lookup(Table,Key)).
67
remove_key([{_Key,Value}|Rest]) ->
68
[Value|remove_key(Rest)].
72
lookup_mime(ConfigDB,Suffix) ->
73
lookup_mime(ConfigDB,Suffix,undefined).
75
lookup_mime(ConfigDB,Suffix,Undefined) ->
76
[{mime_types,MimeTypesDB}|_]=ets:lookup(ConfigDB,mime_types),
77
case ets:lookup(MimeTypesDB,Suffix) of
80
[{Suffix,MimeType}|_] ->
84
%% lookup_mime_default
86
lookup_mime_default(ConfigDB,Suffix) ->
87
lookup_mime_default(ConfigDB,Suffix,undefined).
89
lookup_mime_default(ConfigDB,Suffix,Undefined) ->
90
[{mime_types,MimeTypesDB}|_]=ets:lookup(ConfigDB,mime_types),
91
case ets:lookup(MimeTypesDB,Suffix) of
93
case ets:lookup(ConfigDB,default_type) of
96
[{default_type,DefaultType}|_] ->
99
[{Suffix,MimeType}|_] ->
104
reason_phrase(100) -> "Continue";
105
reason_phrase(101) -> "Swithing protocol";
106
reason_phrase(200) -> "OK";
107
reason_phrase(201) -> "Created";
108
reason_phrase(202) -> "Accepted";
109
reason_phrase(204) -> "No Content";
110
reason_phrase(205) -> "Reset Content";
111
reason_phrase(206) -> "Partial Content";
112
reason_phrase(301) -> "Moved Permanently";
113
reason_phrase(302) -> "Moved Temporarily";
114
reason_phrase(304) -> "Not Modified";
115
reason_phrase(400) -> "Bad Request";
116
reason_phrase(401) -> "Unauthorized";
117
reason_phrase(402) -> "Payment Required";
118
reason_phrase(403) -> "Forbidden";
119
reason_phrase(404) -> "Not Found";
120
reason_phrase(405) -> "Method Not Allowed";
121
reason_phrase(408) -> "Request Timeout";
122
reason_phrase(411) -> "Length Required";
123
reason_phrase(414) -> "Request-URI Too Long";
124
reason_phrase(412) -> "Precondition Failed";
125
reason_phrase(416) -> "request Range Not Satisfiable";
126
reason_phrase(417) -> "Expectation failed";
127
reason_phrase(500) -> "Internal Server Error";
128
reason_phrase(501) -> "Not Implemented";
129
reason_phrase(502) -> "Bad Gateway";
130
reason_phrase(503) -> "Service Unavailable";
131
reason_phrase(_) -> "Internal Server Error".
135
message(301,URL,_) ->
136
"The document has moved <A HREF=\""++URL++"\">here</A>.";
137
message(304,_URL,_) ->
138
"The document has not been changed.";
139
message(400,none,_) ->
140
"Your browser sent a query that this server could not understand.";
141
message(401,none,_) ->
142
"This server could not verify that you
143
are authorized to access the document you
144
requested. Either you supplied the wrong
145
credentials (e.g., bad password), or your
146
browser does not understand how to supply
147
the credentials required.";
148
message(403,RequestURI,_) ->
149
"You do not have permission to access "++RequestURI++" on this server.";
150
message(404,RequestURI,_) ->
151
"The requested URL "++RequestURI++" was not found on this server.";
152
message(412,none,_) ->
153
"The requested preconditions where false";
154
message(414,ReasonPhrase,_) ->
155
"Message "++ReasonPhrase++".";
156
message(416,ReasonPhrase,_) ->
159
message(500,none,ConfigDB) ->
160
ServerAdmin=lookup(ConfigDB,server_admin,"unknown@unknown"),
161
"The server encountered an internal error or
162
misconfiguration and was unable to complete
164
<P>Please contact the server administrator "++ServerAdmin++",
165
and inform them of the time the error occurred
166
and anything you might have done that may have
168
message(501,{Method,RequestURI,HTTPVersion},_ConfigDB) ->
169
Method++" to "++RequestURI++" ("++HTTPVersion++") not supported.";
170
message(503,String,_ConfigDB) ->
171
"This service in unavailable due to: "++String.
173
%%convert_rfc_date(Date)->{{YYYY,MM,DD},{HH,MIN,SEC}}
175
convert_request_date([D,A,Y,DateType|Rest]) ->
176
Func=case DateType of
178
fun convert_rfc1123_date/1;
180
fun convert_ascii_date/1;
182
fun convert_rfc850_date/1
184
case catch Func([D,A,Y,DateType|Rest])of
191
convert_rfc850_date(DateStr) ->
192
case string:tokens(DateStr," ") of
193
[_WeekDay,Date,Time,_TimeZone|_Rest] ->
194
convert_rfc850_date(Date,Time);
199
convert_rfc850_date([D1,D2,_,M,O,N,_,Y1,Y2|_Rest],[H1,H2,_Col,M1,M2,_Col,S1,S2|_Rest2])->
200
Year=list_to_integer([50,48,Y1,Y2]),
201
Day=list_to_integer([D1,D2]),
202
Month=convert_month([M,O,N]),
203
Hour=list_to_integer([H1,H2]),
204
Min=list_to_integer([M1,M2]),
205
Sec=list_to_integer([S1,S2]),
206
{ok,{{Year,Month,Day},{Hour,Min,Sec}}};
207
convert_rfc850_date(_BadDate,_BadTime)->
210
convert_ascii_date([_D,_A,_Y,_SP,M,O,N,_SP,D1,D2,_SP,H1,H2,_Col,M1,M2,_Col,S1,S2,_SP,Y1,Y2,Y3,Y4|_Rest])->
211
Year=list_to_integer([Y1,Y2,Y3,Y4]),
214
list_to_integer([D2]);
216
list_to_integer([D1,D2])
218
Month=convert_month([M,O,N]),
219
Hour=list_to_integer([H1,H2]),
220
Min=list_to_integer([M1,M2]),
221
Sec=list_to_integer([S1,S2]),
222
{ok,{{Year,Month,Day},{Hour,Min,Sec}}};
223
convert_ascii_date(BadDate)->
225
convert_rfc1123_date([_D,_A,_Y,_C,_SP,D1,D2,_SP,M,O,N,_SP,Y1,Y2,Y3,Y4,_SP,H1,H2,_Col,M1,M2,_Col,S1,S2|Rest])->
226
Year=list_to_integer([Y1,Y2,Y3,Y4]),
227
Day=list_to_integer([D1,D2]),
228
Month=convert_month([M,O,N]),
229
Hour=list_to_integer([H1,H2]),
230
Min=list_to_integer([M1,M2]),
231
Sec=list_to_integer([S1,S2]),
232
{ok,{{Year,Month,Day},{Hour,Min,Sec}}};
233
convert_rfc1123_date(BadDate)->
236
convert_month("Jan")->1;
237
convert_month("Feb") ->2;
238
convert_month("Mar") ->3;
239
convert_month("Apr") ->4;
240
convert_month("May") ->5;
241
convert_month("Jun") ->6;
242
convert_month("Jul") ->7;
243
convert_month("Aug") ->8;
244
convert_month("Sep") ->9;
245
convert_month("Oct") ->10;
246
convert_month("Nov") ->11;
247
convert_month("Dec") ->12.
253
{{YYYY,MM,DD},{Hour,Min,Sec}}=calendar:universal_time(),
254
DayNumber=calendar:day_of_the_week({YYYY,MM,DD}),
255
lists:flatten(io_lib:format("~s, ~2.2.0w ~3.s ~4.4.0w ~2.2.0w:~2.2.0w:~2.2.0w GMT",
256
[day(DayNumber),DD,month(MM),YYYY,Hour,Min,Sec])).
258
rfc1123_date({{YYYY,MM,DD},{Hour,Min,Sec}}) ->
259
DayNumber=calendar:day_of_the_week({YYYY,MM,DD}),
260
lists:flatten(io_lib:format("~s, ~2.2.0w ~3.s ~4.4.0w ~2.2.0w:~2.2.0w:~2.2.0w GMT",
261
[day(DayNumber),DD,month(MM),YYYY,Hour,Min,Sec])).
267
uniq([First,First|Rest]) ->
269
uniq([First|Rest]) ->
300
decode_hex([$%,Hex1,Hex2|Rest]) ->
301
[hex2dec(Hex1)*16+hex2dec(Hex2)|decode_hex(Rest)];
302
decode_hex([First|Rest]) ->
303
[First|decode_hex(Rest)];
307
hex2dec(X) when X>=$0,X=<$9 -> X-$0;
308
hex2dec(X) when X>=$A,X=<$F -> X-$A+10;
309
hex2dec(X) when X>=$a,X=<$f -> X-$a+10.
311
%% decode_base64 (DEBUG STRING: QWxhZGRpbjpvcGVuIHNlc2FtZQ==)
315
decode_base64([Sextet1,Sextet2,$=,$=|Rest]) ->
317
(d(Sextet1) bsl 18) bor
319
Octet1=Bits2x6 bsr 16,
320
[Octet1|decode_base64(Rest)];
321
decode_base64([Sextet1,Sextet2,Sextet3,$=|Rest]) ->
323
(d(Sextet1) bsl 18) bor
324
(d(Sextet2) bsl 12) bor
326
Octet1=Bits3x6 bsr 16,
327
Octet2=(Bits3x6 bsr 8) band 16#ff,
328
[Octet1,Octet2|decode_base64(Rest)];
329
decode_base64([Sextet1,Sextet2,Sextet3,Sextet4|Rest]) ->
331
(d(Sextet1) bsl 18) bor
332
(d(Sextet2) bsl 12) bor
333
(d(Sextet3) bsl 6) bor
335
Octet1=Bits4x6 bsr 16,
336
Octet2=(Bits4x6 bsr 8) band 16#ff,
337
Octet3=Bits4x6 band 16#ff,
338
[Octet1,Octet2,Octet3|decode_base64(Rest)];
339
decode_base64(CatchAll) ->
342
d(X) when X >= $A, X =<$Z ->
344
d(X) when X >= $a, X =<$z ->
346
d(X) when X >= $0, X =<$9 ->
355
encode_base64([A]) ->
356
[e(A bsr 2), e((A band 3) bsl 4), $=, $=];
357
encode_base64([A,B]) ->
358
[e(A bsr 2), e(((A band 3) bsl 4) bor (B bsr 4)), e((B band 15) bsl 2), $=];
359
encode_base64([A,B,C|Ls]) ->
360
encode_base64_do(A,B,C, Ls).
361
encode_base64_do(A,B,C, Rest) ->
362
BB = (A bsl 16) bor (B bsl 8) bor C,
363
[e(BB bsr 18), e((BB bsr 12) band 63),
364
e((BB bsr 6) band 63), e(BB band 63)|encode_base64(Rest)].
366
e(X) when X >= 0, X < 26 -> X+65;
367
e(X) when X>25, X<52 -> X+71;
368
e(X) when X>51, X<62 -> X-4;
371
e(X) -> exit({bad_encode_base64_token, X}).
379
flatlength([H|T],L) when list(H) ->
380
flatlength(H,flatlength(T,L));
381
flatlength([H|T],L) when binary(H) ->
382
flatlength(T,L+size(H));
383
flatlength([H|T],L) ->
391
case regexp:match(Path,"[\?].*\$") of
392
%% A QUERY_STRING exists!
393
{match,Start,Length} ->
394
{httpd_util:decode_hex(string:substr(Path,1,Start-1)),
395
string:substr(Path,Start,Length)};
396
%% A possible PATH_INFO exists!
401
split_path([],SoFar) ->
402
{httpd_util:decode_hex(lists:reverse(SoFar)),[]};
403
split_path([$/|Rest],SoFar) ->
404
Path=httpd_util:decode_hex(lists:reverse(SoFar)),
405
case file:read_file_info(Path) of
406
{ok,FileInfo} when FileInfo#file_info.type == regular ->
409
split_path(Rest,[$/|SoFar]);
411
split_path(Rest,[$/|SoFar])
413
split_path([C|Rest],SoFar) ->
414
split_path(Rest,[C|SoFar]).
418
split_script_path(Path) ->
419
case split_script_path(Path, []) of
420
{Script, AfterPath} ->
421
{PathInfo, QueryString} = pathinfo_querystring(AfterPath),
422
{Script, {PathInfo, QueryString}};
427
pathinfo_querystring(Str) ->
428
pathinfo_querystring(Str, []).
429
pathinfo_querystring([], SoFar) ->
430
{lists:reverse(SoFar), []};
431
pathinfo_querystring([$?|Rest], SoFar) ->
432
{lists:reverse(SoFar), Rest};
433
pathinfo_querystring([C|Rest], SoFar) ->
434
pathinfo_querystring(Rest, [C|SoFar]).
436
split_script_path([$?|QueryString], SoFar) ->
437
Path = httpd_util:decode_hex(lists:reverse(SoFar)),
438
case file:read_file_info(Path) of
439
{ok,FileInfo} when FileInfo#file_info.type == regular ->
440
{Path, [$?|QueryString]};
446
split_script_path([], SoFar) ->
447
Path = httpd_util:decode_hex(lists:reverse(SoFar)),
448
case file:read_file_info(Path) of
449
{ok,FileInfo} when FileInfo#file_info.type == regular ->
456
split_script_path([$/|Rest], SoFar) ->
457
Path = httpd_util:decode_hex(lists:reverse(SoFar)),
458
case file:read_file_info(Path) of
459
{ok, FileInfo} when FileInfo#file_info.type == regular ->
462
split_script_path(Rest, [$/|SoFar]);
464
split_script_path(Rest, [$/|SoFar])
466
split_script_path([C|Rest], SoFar) ->
467
split_script_path(Rest,[C|SoFar]).
472
case filename:extension(Path) of
481
to_upper([C|Cs]) when C >= $a, C =< $z ->
482
[C-($a-$A)|to_upper(Cs)];
490
to_lower([C|Cs]) when C >= $A, C =< $Z ->
491
[C+($a-$A)|to_lower(Cs)];
500
lists:reverse(remove_ws(lists:reverse(remove_ws(Value)))).
502
remove_ws([$\s|Rest])->
504
remove_ws([$\t|Rest]) ->
511
split(String,RegExp,Limit) ->
512
case regexp:parse(RegExp) of
516
{ok,do_split(String,RegExp,Limit)}
519
do_split(String,RegExp,1) ->
522
do_split(String,RegExp,Limit) ->
523
case regexp:first_match(String,RegExp) of
524
{match,Start,Length} ->
525
[string:substr(String,1,Start-1)|
526
do_split(lists:nthtail(Start+Length-1,String),RegExp,Limit-1)];
532
header(StatusCode,Date)when list(Date)->
533
header(StatusCode,"text/plain",false);
535
header(StatusCode, PersistentConnection) when integer(StatusCode)->
536
Date = rfc1123_date(),
538
case PersistentConnection of
542
"Connection: close \r\n"
544
io_lib:format("HTTP/1.1 ~w ~s \r\nDate: ~s\r\nServer: ~s\r\n~s",
545
[StatusCode, httpd_util:reason_phrase(StatusCode),
546
Date, ?SERVER_SOFTWARE, Connection]).
548
%%----------------------------------------------------------------------
550
header(StatusCode, MimeType, Date) when list(Date) ->
551
header(StatusCode, MimeType, false,rfc1123_date());
554
header(StatusCode, MimeType, PersistentConnection) when integer(StatusCode) ->
555
header(StatusCode, MimeType, PersistentConnection,rfc1123_date()).
558
%%----------------------------------------------------------------------
560
header(416, MimeType,PersistentConnection,Date)->
562
case PersistentConnection of
566
"Connection: close \r\n"
568
io_lib:format("HTTP/1.1 ~w ~s \r\nDate: ~s\r\nServer: ~s\r\n"
569
"Content-Range:bytes *"
570
"Content-Type: ~s\r\n~s",
571
[416, httpd_util:reason_phrase(416),
572
Date, ?SERVER_SOFTWARE, MimeType, Connection]);
575
header(StatusCode, MimeType,PersistentConnection,Date) when integer(StatusCode)->
577
case PersistentConnection of
581
"Connection: close \r\n"
583
io_lib:format("HTTP/1.1 ~w ~s \r\nDate: ~s\r\nServer: ~s\r\n"
584
"Content-Type: ~s\r\n~s",
585
[StatusCode, httpd_util:reason_phrase(StatusCode),
586
Date, ?SERVER_SOFTWARE, MimeType, Connection]).
590
%% make_name/2, make_name/3
591
%% Prefix -> string()
592
%% First part of the name, e.g. "httpd"
593
%% Addr -> {A,B,C,D} | string() | undefined
594
%% The address part of the name.
595
%% e.g. "123.234.55.66" or {123,234,55,66} or "otp.ericsson.se"
596
%% for a host address or undefined if local host.
598
%% Last part of the name, such as the HTTPD server port
600
%% Postfix -> Any string that will be added last to the name
603
%% make_name("httpd","otp.ericsson.se",80) => httpd__otp_ericsson_se__80
604
%% make_name("httpd",undefined,8088) => httpd_8088
606
make_name(Prefix,Port) ->
607
make_name(Prefix,undefined,Port,"").
609
make_name(Prefix,Addr,Port) ->
610
make_name(Prefix,Addr,Port,"").
612
make_name(Prefix,"*",Port,Postfix) ->
613
make_name(Prefix,undefined,Port,Postfix);
615
make_name(Prefix,any,Port,Postfix) ->
616
make_name1(io_lib:format("~s_~w~s",[Prefix,Port,Postfix]));
618
make_name(Prefix,undefined,Port,Postfix) ->
619
make_name1(io_lib:format("~s_~w~s",[Prefix,Port,Postfix]));
621
make_name(Prefix,Addr,Port,Postfix) ->
623
Prefix ++ "__" ++ make_name2(Addr) ++ "__" ++
624
integer_to_list(Port) ++ Postfix,
625
make_name1(NameString).
627
make_name1(String) ->
628
list_to_atom(lists:flatten(String)).
630
make_name2({A,B,C,D}) ->
631
io_lib:format("~w_~w_~w_~w",[A,B,C,D]);
633
search_and_replace(Addr,$.,$_).
635
search_and_replace(S,A,B) ->
646
%%----------------------------------------------------------------------
647
%% Converts a string that constists of 0-9,A-F,a-f to a
649
%%----------------------------------------------------------------------
651
hexlist_to_integer([])->
655
%%When the string only contains one value its eaasy done.
657
hexlist_to_integer([Size]) when Size>=48 , Size=<57 ->
660
hexlist_to_integer([Size]) when Size>=65 , Size=<70 ->
663
hexlist_to_integer([Size]) when Size>=97 , Size=<102 ->
665
hexlist_to_integer([Size]) ->
668
hexlist_to_integer(Size) ->
669
Len=string:span(Size,"1234567890abcdefABCDEF"),
670
hexlist_to_integer2(Size,16 bsl (4 *(Len-2)),0).
672
hexlist_to_integer2([],_Pos,Sum)->
674
hexlist_to_integer2([HexVal|HexString],Pos,Sum)when HexVal>=48,HexVal=<57->
675
hexlist_to_integer2(HexString,Pos bsr 4,Sum+((HexVal-48)*Pos));
677
hexlist_to_integer2([HexVal|HexString],Pos,Sum)when HexVal>=65,HexVal=<70->
678
hexlist_to_integer2(HexString,Pos bsr 4,Sum+((HexVal-55)*Pos));
680
hexlist_to_integer2([HexVal|HexString],Pos,Sum)when HexVal>=97,HexVal=<102->
681
hexlist_to_integer2(HexString,Pos bsr 4,Sum+((HexVal-87)*Pos));
683
hexlist_to_integer2(_AfterHexString,_Pos,Sum)->
686
%%----------------------------------------------------------------------
687
%%Converts an integer to an hexlist
688
%%----------------------------------------------------------------------
690
integer_to_hexlist(Num).
693
integer_to_hexlist(Num)->
694
integer_to_hexlist(Num,getSize(Num),[]).
696
integer_to_hexlist(Num,Pot,Res) when Pot<0 ->
697
convert_to_ascii([Num|Res]);
699
integer_to_hexlist(Num,Pot,Res) ->
700
Position=(16 bsl (Pot*4)),
701
PosVal=Num div Position,
702
integer_to_hexlist(Num-(PosVal*Position),Pot-1,[PosVal|Res]).
703
convert_to_ascii(RevesedNum)->
704
convert_to_ascii(RevesedNum,[]).
706
convert_to_ascii([],Num)->
708
convert_to_ascii([Num|Reversed],Number)when Num>-1, Num<10 ->
709
convert_to_ascii(Reversed,[Num+48|Number]);
710
convert_to_ascii([Num|Reversed],Number)when Num>9, Num<16 ->
711
convert_to_ascii(Reversed,[Num+55|Number]);
712
convert_to_ascii(NumReversed,Number) ->
720
getSize(Num,Pot)when Num<(16 bsl(Pot *4)) ->
730
create_etag(FileInfo)->
731
create_etag(FileInfo#file_info.mtime,FileInfo#file_info.size).
733
create_etag({{Year,Month,Day},{Hour,Min,Sec}},Size)->
734
create_part([Year,Month,Day,Hour,Min,Sec])++io_lib:write(Size);
736
create_etag(FileInfo,Size)->
737
create_etag(FileInfo#file_info.mtime,Size).
739
create_part(Values)->
740
lists:map(fun(Val0)->
755
%%----------------------------------------------------------------------
756
%%Function that controls whether a response is generated or not
757
%%----------------------------------------------------------------------
758
response_generated(Info)->
759
case httpd_util:key1search(Info#mod.data,status) of
760
%% A status code has been generated!
761
{StatusCode,PhraseArgs,Reason}->
763
%%No status code control repsonsxe
765
case httpd_util:key1search(Info#mod.data, response) of
766
%% No response has been generated!
769
%% A response has been generated or sent!