couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From cml...@apache.org
Subject svn commit: r642432 [11/16] - in /incubator/couchdb/trunk: ./ bin/ build-contrib/ etc/ etc/conf/ etc/default/ etc/init/ etc/launchd/ etc/logrotate.d/ share/ share/server/ share/www/ share/www/browse/ share/www/image/ share/www/script/ share/www/style/ ...
Date Fri, 28 Mar 2008 23:32:30 GMT
Added: incubator/couchdb/trunk/src/couch_inets/mod_htaccess.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couch_inets/mod_htaccess.erl?rev=642432&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couch_inets/mod_htaccess.erl (added)
+++ incubator/couchdb/trunk/src/couch_inets/mod_htaccess.erl Fri Mar 28 16:32:19 2008
@@ -0,0 +1,1075 @@
+%% ``The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved via the world wide web at http://www.erlang.org/.
+%%%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%% 
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%% 
+%%     $Id$
+%%
+
+-module(mod_htaccess).
+
+-export([do/1, load/2]).
+
+-include("httpd.hrl").
+
+%% We will not make the change to use base64 in stdlib in inets just yet.
+%% it will be included in the next major release of inets. 
+-compile({nowarn_deprecated_function, {http_base_64, encode, 1}}).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Public methods that interface the eswapi                         %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%  
+
+%----------------------------------------------------------------------
+% Public method called by the webbserver to insert the data about
+% Names on accessfiles
+%----------------------------------------------------------------------
+load("AccessFileName" ++ FileNames, _Context)->
+    CleanFileNames=httpd_conf:clean(FileNames),
+    {ok,[],{access_files,string:tokens(CleanFileNames," ")}}.
+
+
+%----------------------------------------------------------------------
+% Public method that the webbserver calls to control the page 
+%----------------------------------------------------------------------
+do(Info)->
+    case httpd_util:key1search(Info#mod.data,status) of
+	{_Status_code, _PhraseArgs, _Reason}->
+	    {proceed,Info#mod.data};
+	undefined ->
+	    control_path(Info)
+    end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%                                                                  %%
+%% The functions that start the control if there is a accessfile    %%
+%% and if so controls if the dir is allowed or not                  %%
+%%                                                                  %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%----------------------------------------------------------------------
+%Info = record mod as specified in httpd.hrl           
+%returns either {proceed,Info#mod.data}
+%{proceed,[{status,403....}|Info#mod.data]}
+%{proceed,[{status,401....}|Info#mod.data]}
+%{proceed,[{status,500....}|Info#mod.data]}
+%----------------------------------------------------------------------
+control_path(Info) ->
+    Path = mod_alias:path(Info#mod.data,
+			  Info#mod.config_db,
+			  Info#mod.request_uri),
+    case isErlScriptOrNotAccessibleFile(Path,Info) of
+	true->
+	    {proceed,Info#mod.data};
+	false->
+	    case getHtAccessData(Path,Info)of
+		{ok,public}->
+		    %%There was no restrictions on the page continue
+		    {proceed,Info#mod.data};
+		{error, _Reason} ->
+		    %%Something got wrong continue or quit??????????????????/
+                   {proceed,Info#mod.data};
+		{accessData,AccessData}->
+		    controlAllowedMethod(Info,AccessData)
+	    end
+    end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%                                                                  %%
+%% These methods controls that the method the client used in the    %%
+%% request is one of the limited                                    %%
+%%                                                                  %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%----------------------------------------------------------------------
+%Control that if the accessmethod used is in the list of modes to challenge
+%
+%Info is the mod record as specified in httpd.hrl
+%AccessData is an ets table whit the data in the .htaccessfiles
+%----------------------------------------------------------------------
+controlAllowedMethod(Info,AccessData)->
+    case allowedRequestMethod(Info,AccessData) of
+	allow->
+	    %%The request didnt use one of the limited methods
+	    ets:delete(AccessData),
+	    {proceed,Info#mod.data};
+	challenge->
+	    authenticateUser(Info,AccessData)
+    end.
+
+%----------------------------------------------------------------------
+%Check the specified access method in the .htaccessfile
+%----------------------------------------------------------------------
+allowedRequestMethod(Info,AccessData)->
+    case ets:lookup(AccessData,limit) of
+	[{limit,all}]->
+	    challenge;
+	[{limit,Methods}]->
+	    isLimitedRequestMethod(Info,Methods)
+    end.
+
+
+%----------------------------------------------------------------------
+%Check the specified accessmethods in the .htaccesfile against the users 
+%accessmethod
+%
+%Info is the record from the do call
+%Methods is a list of the methods specified in the .htaccessfile
+%----------------------------------------------------------------------
+isLimitedRequestMethod(Info,Methods)->
+    case lists:member(Info#mod.method,Methods) of
+	true->
+	    challenge;
+	false ->
+	    allow
+    end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%                                                                  %%
+%% These methods controls that the user comes from an allowwed net  %%
+%% and if so wheather its a valid user or a challenge shall be      %%
+%% generated                                                        %%
+%%                                                                  %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%----------------------------------------------------------------------
+%The first thing to control is that the user is from a network
+%that has access to the page
+%---------------------------------------------------------------------- 
+authenticateUser(Info,AccessData)->
+    case controlNet(Info,AccessData) of
+	allow->
+	    %the network is ok control that it is an allowed user
+	    authenticateUser2(Info,AccessData);
+	deny->
+	    %The user isnt allowed to access the pages from that network
+	    ets:delete(AccessData),
+	    {proceed,[{status,{403,Info#mod.request_uri,
+	    "Restricted area not allowed from your network"}}|Info#mod.data]}
+    end.
+
+
+%----------------------------------------------------------------------
+%The network the user comes from is allowed to view the resources 
+%control whether the user needsto supply a password or not 
+%----------------------------------------------------------------------
+authenticateUser2(Info,AccessData)->
+    case ets:lookup(AccessData,require) of
+	[{require,AllowedUsers}]->
+	    case ets:lookup(AccessData,auth_name) of
+		[{auth_name,Realm}]->
+		    authenticateUser2(Info,AccessData,Realm,AllowedUsers);
+		_NoAuthName->
+		    ets:delete(AccessData),
+		    {break,[{status,{500,none,
+				     ?NICE("mod_htaccess:AuthName directive " 
+					   "not specified")}}]}
+	    end;
+	[] ->
+	    %%No special user is required the network is ok so let
+	    %%the user in
+	    ets:delete(AccessData),
+	    {proceed,Info#mod.data}
+    end.
+
+
+%----------------------------------------------------------------------
+%The user must send a userId and a password to get the resource
+%Control if its already in the http-request
+%if the file with users is bad send an 500 response
+%----------------------------------------------------------------------
+authenticateUser2(Info,AccessData,Realm,AllowedUsers)->
+    case authenticateUser(Info,AccessData,AllowedUsers) of
+	allow ->
+	    ets:delete(AccessData),
+	    {user,Name, _Pwd} = getAuthenticatingDataFromHeader(Info),
+	    {proceed, [{remote_user_name,Name}|Info#mod.data]};
+	challenge->  
+	    ets:delete(AccessData),
+	    ReasonPhrase = httpd_util:reason_phrase(401),
+	    Message = httpd_util:message(401,none,Info#mod.config_db),
+	    {proceed,
+	     [{response,
+	       {401,
+		["WWW-Authenticate: Basic realm=\"",Realm,
+		 "\"\r\n\r\n","<HTML>\n<HEAD>\n<TITLE>",
+		 ReasonPhrase,"</TITLE>\n",
+		 "</HEAD>\n<BODY>\n<H1>",ReasonPhrase,
+		 "</H1>\n",Message,"\n</BODY>\n</HTML>\n"]}}|
+	      Info#mod.data]};
+	deny->
+	    ets:delete(AccessData),
+	    {break,[{status,{500,none,
+			     ?NICE("mod_htaccess:Bad path to user " 
+				   "or group file")}}]}
+    end.
+
+                                                                      
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%                                                                  %%
+%% Methods that validate the netwqork the user comes from           %%
+%% according to the allowed networks                                %%
+%%                                                                  %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%--------------------------------------------------------------------- 
+%Controls the users networkaddress agains the specifed networks to 
+%allow or deny
+%
+%returns either allow or deny
+%----------------------------------------------------------------------
+controlNet(Info,AccessData)->
+    UserNetwork=getUserNetworkAddress(Info),
+    case getAllowDenyOrder(AccessData) of
+	{_deny,[],_allow,[]}->
+	    allow;
+	{deny,[],allow,AllowedNetworks}->
+	    controlIfAllowed(AllowedNetworks,UserNetwork,allow,deny);
+	{allow,AllowedNetworks,deny,[]}->
+	    controlIfAllowed(AllowedNetworks,UserNetwork,allow,deny);
+	
+	{deny,DeniedNetworks,allow,[]}->
+	    controlIfAllowed(DeniedNetworks,UserNetwork,allow,deny);
+	{allow,[],deny,DeniedNetworks}->
+	    controlIfAllowed(DeniedNetworks,UserNetwork,allow,deny);
+        
+	{deny,DeniedNetworks,allow,AllowedNetworks}->		    
+	    controlDenyAllow(DeniedNetworks,AllowedNetworks,UserNetwork); 
+	{allow,AllowedNetworks,deny,DeniedNetworks}->		 
+	    controlAllowDeny(AllowedNetworks,DeniedNetworks,UserNetwork)
+    end.
+
+
+%----------------------------------------------------------------------
+%Returns the users IP-Number
+%----------------------------------------------------------------------
+getUserNetworkAddress(Info)->
+    {_Socket,Address}=(Info#mod.init_data)#init_data.peername,
+    Address.
+
+
+%----------------------------------------------------------------------
+%Control the users Ip-number against the ip-numbers in the .htaccessfile
+%----------------------------------------------------------------------
+controlIfAllowed(AllowedNetworks,UserNetwork,IfAllowed,IfDenied)->
+    case AllowedNetworks of
+	[{allow,all}]->
+	   IfAllowed;
+	[{deny,all}]->
+	    IfDenied;
+        [{deny,Networks}]->
+	    memberNetwork(Networks,UserNetwork,IfDenied,IfAllowed);
+	[{allow,Networks}]->
+	    memberNetwork(Networks,UserNetwork,IfAllowed,IfDenied);
+	_Error->
+	    IfDenied
+    end.
+
+
+%---------------------------------------------------------------------%
+%The Denycontrol isn't neccessary to preform since the allow control  %
+%override the deny control                                            %
+%---------------------------------------------------------------------% 
+controlDenyAllow(_DeniedNetworks, AllowedNetworks, UserNetwork)->
+    case AllowedNetworks of
+	[{allow, all}]->
+	    allow;
+	[{allow, Networks}]->
+	  case memberNetwork(Networks, UserNetwork) of
+	      true->
+		  allow;
+	      false->
+		  deny
+	  end
+    end.
+
+
+%----------------------------------------------------------------------%
+%Control that the user is in the allowed list if so control that the   %
+%network is in the denied list                             
+%----------------------------------------------------------------------%
+controlAllowDeny(AllowedNetworks,DeniedNetworks,UserNetwork)->
+    case controlIfAllowed(AllowedNetworks,UserNetwork,allow,deny) of
+	allow->
+	    controlIfAllowed(DeniedNetworks,UserNetwork,deny,allow);
+	deny ->
+	    deny
+    end.
+	    
+%----------------------------------------------------------------------
+%Controls if the users Ipnumber is in the list of either denied or
+%allowed networks
+%----------------------------------------------------------------------	
+memberNetwork(Networks,UserNetwork,IfTrue,IfFalse)->
+    case memberNetwork(Networks,UserNetwork) of
+	true->
+	    IfTrue;
+	false->
+	    IfFalse
+    end.
+
+
+%----------------------------------------------------------------------
+%regexp match the users ip-address against the networks in the list of 
+%ipadresses or subnet addresses.
+memberNetwork(Networks,UserNetwork)->
+    case lists:filter(fun(Net)->
+			      case regexp:match(UserNetwork,
+						formatRegexp(Net)) of
+				  {match,1,_}->
+				      true;
+				  _NotSubNet ->
+				      false
+			      end
+		      end,Networks) of
+	[]->
+	    false;
+	_MemberNetWork ->
+	    true
+    end.
+
+
+%----------------------------------------------------------------------
+%Creates a regexp from an ip-number i.e "127.0.0-> "^127[.]0[.]0.*"
+%"127.0.0.-> "^127[.]0[.]0[.].*"
+%----------------------------------------------------------------------
+formatRegexp(Net)->	    
+    [SubNet1|SubNets]=string:tokens(Net,"."),
+    NetRegexp=lists:foldl(fun(SubNet,Newnet)->
+				  Newnet ++ "[.]" ++SubNet
+			  end,"^"++SubNet1,SubNets),
+    case string:len(Net)-string:rchr(Net,$.) of
+	0->
+	    NetRegexp++"[.].*";
+	_->
+	    NetRegexp++".*"
+    end.
+
+%----------------------------------------------------------------------
+%If the user has specified if the allow or deny check shall be preformed
+%first get that order if no order is specified take 
+%allow - deny since its harder that deny - allow
+%----------------------------------------------------------------------
+getAllowDenyOrder(AccessData)->
+    case ets:lookup(AccessData,order) of
+	[{order,{deny,allow}}]->
+	    {deny,ets:lookup(AccessData,deny),
+	     allow,ets:lookup(AccessData,allow)};
+	_DefaultOrder->
+	    {allow,ets:lookup(AccessData,allow),
+	     deny,ets:lookup(AccessData,deny)}
+    end.
+                                                                      
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%                                                                  %%
+%% The methods that validates the user                              %%
+%%                                                                  %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%----------------------------------------------------------------------
+%Control if there is anyu autheticating data in threquest header
+%if so it controls it against the users in the list Allowed Users
+%----------------------------------------------------------------------
+authenticateUser(Info,AccessData,AllowedUsers)->
+    case getAuthenticatingDataFromHeader(Info) of
+	{user,User,PassWord}->
+	    authenticateUser(Info,AccessData,AllowedUsers,
+			     {user,User,PassWord});
+	{error,nouser}->
+	    challenge; 
+	{error, _BadData}->
+	    challenge 
+    end.
+
+
+%----------------------------------------------------------------------
+%Returns the Autheticating data in the http-request
+%----------------------------------------------------------------------
+getAuthenticatingDataFromHeader(Info)->              
+    PrsedHeader=Info#mod.parsed_header,
+    case httpd_util:key1search(PrsedHeader,"authorization" ) of
+	undefined->
+	    {error,nouser};
+	[$B,$a,$s,$i,$c,$\ |EncodedString] = Credentials ->
+	    case (catch http_base_64:decode(EncodedString)) of
+		{'EXIT',{function_clause, _}} ->
+		    {error, Credentials};
+		UnCodedString ->
+		    case httpd_util:split(UnCodedString,":",2) of
+			{ok,[User,PassWord]}->
+			    {user,User,PassWord};
+			{error,Error}->
+			    {error,Error}
+		    end
+	    end;
+	BadCredentials ->
+	    {error,BadCredentials}
+    end.
+
+%----------------------------------------------------------------------
+%Returns a list of all members of the allowed groups
+%----------------------------------------------------------------------
+getGroupMembers(Groups,AllowedGroups)->
+    Allowed=lists:foldl(fun({group,Name,Members},AllowedMembers)->
+				case lists:member(Name,AllowedGroups) of
+				    true->
+					AllowedMembers++Members;
+				    false ->
+					AllowedMembers
+				end
+	       end,[],Groups),
+    {ok,Allowed}.
+    
+authenticateUser(Info,AccessData,{{users,[]},{groups,Groups}},User)->
+    authenticateUser(Info,AccessData,{groups,Groups},User);
+authenticateUser(Info,AccessData,{{users,Users},{groups,[]}},User)->
+    authenticateUser(Info,AccessData,{users,Users},User);
+
+authenticateUser(Info,AccessData,{{users,Users},{groups,Groups}},User)->
+    AllowUser=authenticateUser(Info,AccessData,{users,Users},User),
+    AllowGroup=authenticateUser(Info,AccessData,{groups,Groups},User),
+    case {AllowGroup,AllowUser} of
+	{_,allow}->
+	    allow;
+	{allow,_}->
+	    allow;
+	{challenge,_}->
+	    challenge;
+	{_,challenge}->
+	    challenge;
+	{_deny,_deny}->
+	    deny
+    end;
+    
+
+%----------------------------------------------------------------------
+%Controls that the user is a member in one of the allowed group
+%----------------------------------------------------------------------
+authenticateUser(Info,AccessData,{groups,AllowedGroups},{user,User,PassWord})->
+    case getUsers(AccessData,group_file) of
+	{group_data,Groups}->
+	    {ok, Members } = getGroupMembers(Groups,AllowedGroups),
+	    authenticateUser(Info,AccessData,{users,Members},
+			     {user,User,PassWord});
+	{error, _BadData}->
+	    deny
+    end;
+
+
+%----------------------------------------------------------------------
+%Control that the user is one of the allowed users and that the passwd is ok
+%----------------------------------------------------------------------
+authenticateUser(_Info,AccessData,{users,AllowedUsers},{user,User,PassWord})->
+    case lists:member(User,AllowedUsers) of
+       true->
+	    %Get the usernames and passwords from the file
+	    case getUsers(AccessData,user_file) of
+		{error, _BadData}->
+		    deny;
+		{user_data,Users}-> 
+		    %Users is a list of the users in
+		    %the userfile [{user,User,Passwd}]
+		    checkPassWord(Users,{user,User,PassWord})
+	    end;
+	false ->
+	    challenge
+    end.
+
+
+%----------------------------------------------------------------------
+%Control that the user User={user,"UserName","PassWd"} is
+%member of the list of Users
+%----------------------------------------------------------------------
+checkPassWord(Users,User)->
+    case lists:member(User,Users) of
+	true->
+	    allow;
+	false->
+	    challenge
+    end.
+
+
+%----------------------------------------------------------------------
+%Get the users in the specified file
+%UserOrGroup is an atom that specify if its a group file or a user file
+%i.e. group_file or user_file
+%----------------------------------------------------------------------
+getUsers({file,FileName},UserOrGroup)->
+    case file:open(FileName,[read]) of
+        {ok,AccessFileHandle} ->
+	    getUsers({stream,AccessFileHandle},[],UserOrGroup);
+        {error,Reason} ->
+	    {error,{Reason,FileName}}
+    end;
+
+
+%----------------------------------------------------------------------
+%The method that starts the lokkong for user files
+%----------------------------------------------------------------------
+
+getUsers(AccessData,UserOrGroup)->
+    case ets:lookup(AccessData,UserOrGroup) of
+	[{UserOrGroup,File}]->
+	    getUsers({file,File},UserOrGroup);
+	_ ->
+	    {error,noUsers}
+    end.
+    
+
+%----------------------------------------------------------------------
+%Reads data from the filehandle File to the list FileData and when its
+%reach the end it returns the list in a tuple {user_file|group_file,FileData}
+%----------------------------------------------------------------------
+getUsers({stream,File},FileData,UserOrGroup)->
+    case io:get_line(File,[]) of
+        eof when UserOrGroup==user_file->
+	    {user_data,FileData};
+	eof when UserOrGroup ==group_file->
+	   {group_data,FileData};
+        Line ->
+	    getUsers({stream,File},
+		     formatUser(Line,FileData,UserOrGroup),UserOrGroup)
+    end.
+
+                                                                      
+%----------------------------------------------------------------------
+%If the line is a comment remove it
+%----------------------------------------------------------------------
+formatUser([$#|_UserDataComment],FileData,_UserOrgroup)->
+    FileData;
+
+
+%----------------------------------------------------------------------
+%The user name in the file is Username:Passwd\n 
+%Remove the newline sign and split the user name in  
+%UserName and Password
+%----------------------------------------------------------------------
+formatUser(UserData,FileData,UserOrGroup)->
+    case string:tokens(UserData," \r\n")of
+	[User| _Whitespace] when UserOrGroup==user_file->
+	    case string:tokens(User,":") of
+		[Name,PassWord]->
+		    [{user,Name,PassWord}|FileData];
+		_Error->
+		    FileData
+	    end;
+	GroupData when UserOrGroup==group_file ->
+	    parseGroupData(GroupData,FileData);
+	_Error ->
+	    FileData
+    end.
+
+
+%----------------------------------------------------------------------
+%if everything is right GroupData is on the form
+% ["groupName:", "Member1", "Member2", "Member2"
+%----------------------------------------------------------------------
+parseGroupData([GroupName|GroupData],FileData)->
+    [{group,formatGroupName(GroupName),GroupData}|FileData].
+
+
+%----------------------------------------------------------------------
+%the line in the file is GroupName: Member1 Member2 .....MemberN
+%Remove the : from the group name
+%---------------------------------------------------------------------- 
+formatGroupName(GroupName)->
+    string:strip(GroupName,right,$:).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%                                                                  %%
+%%  Functions that parses the accessfiles                           %%
+%%                                                                  %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%----------------------------------------------------------------------
+%Control that the asset is a real file and not a request for an virtual
+%asset
+%----------------------------------------------------------------------
+isErlScriptOrNotAccessibleFile(Path, _Info)->
+    case file:read_file_info(Path) of
+	{ok,_fileInfo}->
+	    false;
+	{error,_Reason} ->
+	    true
+    end.
+
+
+%----------------------------------------------------------------------
+%Path=PathToTheRequestedFile=String
+%Innfo=record#mod
+%----------------------------------------------------------------------
+getHtAccessData(Path,Info)->
+    HtAccessFileNames=getHtAccessFileNames(Info),
+    case getData(Path,Info,HtAccessFileNames) of
+	{ok,public}->
+	    {ok,public};	
+	{accessData,AccessData}->
+	    {accessData,AccessData};
+	{error,Reason} ->
+	    {error,Reason}
+    end.
+
+
+%----------------------------------------------------------------------
+%returns the names of the accessfiles
+%----------------------------------------------------------------------
+getHtAccessFileNames(Info)->
+    case httpd_util:lookup(Info#mod.config_db,access_files) of
+	undefined->
+	    [".htaccess"];
+	Files->
+	    Files
+    end.
+%----------------------------------------------------------------------
+%HtAccessFileNames=["accessfileName1",..."AccessFileName2"]
+%----------------------------------------------------------------------
+getData(Path,Info,HtAccessFileNames)->	    
+    case regexp:split(Path,"/") of
+	{error,Error}->
+	    {error,Error};
+	{ok,SplittedPath}->
+	    getData2(HtAccessFileNames,SplittedPath,Info)
+	end.
+
+
+%----------------------------------------------------------------------
+%Add to together the data in the Splittedpath up to the path 
+%that is the alias or the document root
+%Since we do not need to control after any accessfiles before here
+%----------------------------------------------------------------------
+getData2(HtAccessFileNames,SplittedPath,Info)->	
+    case getRootPath(SplittedPath,Info) of
+	{error,Path}->
+	    {error,Path};
+	{ok,StartPath,RestOfSplittedPath} ->
+	    getData2(HtAccessFileNames,StartPath,RestOfSplittedPath,Info)
+    end.
+
+
+%----------------------------------------------------------------------
+%HtAccessFilenames is a list the names the accesssfiles can have
+%Path is the shortest match agains all alias and documentroot
+%rest of splitted path is a list of the parts of the path
+%Info is the mod recod from the server  
+%----------------------------------------------------------------------
+getData2(HtAccessFileNames, StartPath, RestOfSplittedPath, _Info)->
+    case getHtAccessFiles(HtAccessFileNames,StartPath,RestOfSplittedPath) of
+	[]->
+	    %No accessfile qiut its a public directory
+	    {ok,public};
+	Files ->
+	    loadAccessFilesData(Files)
+    end.
+
+
+%----------------------------------------------------------------------
+%Loads the data in the accessFiles specifiied by 
+% AccessFiles=["/hoem/public/html/accefile",
+%               "/home/public/html/priv/accessfile"]
+%----------------------------------------------------------------------
+loadAccessFilesData(AccessFiles)->
+    loadAccessFilesData(AccessFiles,ets:new(accessData,[])).
+
+
+%----------------------------------------------------------------------
+%Returns the found data
+%----------------------------------------------------------------------
+contextToValues(AccessData)->
+    case ets:lookup(AccessData,context) of
+	[{context,Values}]->
+	    ets:delete(AccessData,context),
+	    insertContext(AccessData,Values),
+	    {accessData,AccessData};
+	_Error->
+	    {error,errorInAccessFile}
+    end.
+
+
+insertContext(_AccessData, [])->
+    ok;
+
+insertContext(AccessData,[{allow,From}|Values])->
+    insertDenyAllowContext(AccessData,{allow,From}),
+    insertContext(AccessData,Values);
+   
+insertContext(AccessData,[{deny,From}|Values])->
+    insertDenyAllowContext(AccessData,{deny,From}),
+    insertContext(AccessData,Values);
+
+insertContext(AccessData,[{require,{GrpOrUsr,Members}}|Values])->    
+    case ets:lookup(AccessData,require) of
+	[]when GrpOrUsr==users->
+	    ets:insert(AccessData,{require,{{users,Members},{groups,[]}}});
+
+	[{require,{{users,Users},{groups,Groups}}}]when GrpOrUsr==users ->
+	    ets:insert(AccessData,{require,{{users,Users++Members},
+					   {groups,Groups}}});
+	[]when GrpOrUsr==groups->
+	    ets:insert(AccessData,{require,{{users,[]},{groups,Members}}});
+
+	[{require,{{users,Users},{groups,Groups}}}]when GrpOrUsr==groups ->
+	    ets:insert(AccessData,{require,{{users,Users},
+					   {groups,Groups++Members}}})    
+    end,
+    insertContext(AccessData,Values);
+
+	
+
+%%limit and order directive need no transforming they areis just to insert
+insertContext(AccessData,[Elem|Values])->   
+    ets:insert(AccessData,Elem),
+    insertContext(AccessData,Values).
+    
+
+insertDenyAllowContext(AccessData,{AllowDeny,From})->
+    case From of
+	all ->
+	    ets:insert(AccessData,{AllowDeny,all});
+	_AllowedSubnets ->
+	    case ets:lookup(AccessData,AllowDeny) of
+		[]->
+		    ets:insert(AccessData,{AllowDeny,From});
+		[{AllowDeny,all}]->
+		    ok;
+		[{AllowDeny,Networks}]->
+		    ets:insert(AccessData,{allow,Networks++From})
+	    end
+    end.
+
+loadAccessFilesData([],AccessData)->
+    %preform context to limits
+    contextToValues(AccessData),
+    {accessData,AccessData};
+
+%----------------------------------------------------------------------
+%Takes each file in the list and load the data to the ets table 
+%AccessData
+%----------------------------------------------------------------------
+loadAccessFilesData([FileName|FileNames],AccessData)->
+    case loadAccessFileData({file,FileName},AccessData) of
+	overRide->
+	    loadAccessFilesData(FileNames,AccessData);
+	noOverRide ->
+	    {accessData,AccessData};
+	error->
+	    ets:delete(AccessData),
+	    {error,errorInAccessFile}
+    end.
+
+%----------------------------------------------------------------------
+%opens the filehandle to the specified file
+%----------------------------------------------------------------------
+loadAccessFileData({file,FileName},AccessData)->
+    case file:open(FileName,[read]) of
+        {ok,AccessFileHandle}->
+	    loadAccessFileData({stream,AccessFileHandle},AccessData,[]);
+        {error, _Reason} ->
+	    overRide
+    end.
+
+%----------------------------------------------------------------------
+%%look att each line in the file and add them to the database
+%%When end of file is reached control i overrride is allowed
+%% if so return 
+%----------------------------------------------------------------------
+loadAccessFileData({stream,File},AccessData,FileData)->
+    case io:get_line(File,[]) of
+        eof->
+	    insertData(AccessData,FileData),
+	    case ets:match_object(AccessData,{'_',error}) of
+		[]->
+		    %Case we got no error control that we can override a
+		    %at least some of the values
+		    case ets:match_object(AccessData,
+					  {allow_over_ride,none}) of
+			[]->
+			    overRide;
+			_NoOverride->
+			    noOverRide
+		    end;
+		_ ->
+		    error
+	    end;
+	Line ->
+	    loadAccessFileData({stream,File},AccessData,
+			       insertLine(string:strip(Line,left),FileData))
+    end.
+
+%----------------------------------------------------------------------
+%AccessData is a ets table where the previous found data is inserted
+%FileData is a list of the directives in the last parsed file
+%before insertion a control is done that the directive is allowed to
+%override
+%----------------------------------------------------------------------
+insertData(AccessData,{{context,Values},FileData})->
+    insertData(AccessData,[{context,Values}|FileData]);
+
+insertData(AccessData,FileData)->
+    case ets:lookup(AccessData,allow_over_ride) of
+	[{allow_over_ride,all}]->
+	    lists:foreach(fun(Elem)->
+				  ets:insert(AccessData,Elem)
+			  end,FileData);
+	[]->
+	    lists:foreach(fun(Elem)->
+				  ets:insert(AccessData,Elem)
+			  end,FileData);
+	[{allow_over_ride,Directives}]when list(Directives)->
+	    lists:foreach(fun({Key,Value})->
+				  case lists:member(Key,Directives) of
+				      true->
+					  ok;
+				      false ->
+					  ets:insert(AccessData,{Key,Value})
+				  end
+			  end,FileData);
+	[{allow_over_ride,_}]->
+	    %Will never appear if the user 
+	    %aint doing very strang econfig files
+	    ok
+    end.
+%----------------------------------------------------------------------
+%Take a line in the accessfile and transform it into a tuple that 
+%later can be inserted in to the ets:table				
+%----------------------------------------------------------------------      
+%%%Here is the alternatives that resides inside the limit context
+
+insertLine("order"++ Order, {{context, Values}, FileData})->
+    {{context,[{order,getOrder(Order)}|Values]},FileData};
+%%Let the user place a tab in the beginning
+insertLine([$\t,$o,$r,$d,$e,$r|Order],{{context,Values},FileData})->
+     {{context,[{order,getOrder(Order)}|Values]},FileData};
+
+insertLine("allow" ++ Allow, {{context, Values}, FileData})->
+    {{context,[{allow,getAllowDenyData(Allow)}|Values]},FileData};
+insertLine([$\t,$a,$l,$l,$o,$w|Allow],{{context,Values},FileData})->
+    {{context,[{allow,getAllowDenyData(Allow)}|Values]},FileData};
+
+insertLine("deny" ++ Deny, {{context,Values}, FileData})->
+    {{context,[{deny,getAllowDenyData(Deny)}|Values]},FileData};
+insertLine([$\t, $d,$e,$n,$y|Deny],{{context,Values},FileData})->
+    {{context,[{deny,getAllowDenyData(Deny)}|Values]},FileData};
+
+insertLine("require" ++ Require, {{context, Values}, FileData})->
+    {{context,[{require,getRequireData(Require)}|Values]},FileData};
+insertLine([$\t,$r,$e,$q,$u,$i,$r,$e|Require],{{context,Values},FileData})->
+    {{context,[{require,getRequireData(Require)}|Values]},FileData};
+
+insertLine("</Limit" ++ _EndLimit, {Context,FileData})->
+    [Context | FileData];
+insertLine("<Limit" ++ Limit, FileData)->
+    {{context,[{limit,getLimits(Limit)}]}, FileData};
+
+insertLine([$A,$u,$t,$h,$U,$s,$e,$r,$F,$i,$l,$e,$\ |AuthUserFile],FileData)->
+    [{user_file,string:strip(AuthUserFile,right,$\n)}|FileData];
+
+insertLine([$A,$u,$t,$h,$G,$r,$o,$u,$p,$F,$i,$l,$e,$\ |AuthGroupFile],
+           FileData)->
+    [{group_file,string:strip(AuthGroupFile,right,$\n)}|FileData];
+
+insertLine("AllowOverRide" ++ AllowOverRide, FileData)->
+    [{allow_over_ride,getAllowOverRideData(AllowOverRide)}
+     | FileData];
+
+insertLine([$A,$u,$t,$h,$N,$a,$m,$e,$\ |AuthName],FileData)->
+    [{auth_name,string:strip(AuthName,right,$\n)}|FileData];
+
+insertLine("AuthType" ++ AuthType,FileData)->
+    [{auth_type,getAuthorizationType(AuthType)}|FileData];
+
+insertLine(_BadDirectiveOrComment,FileData)->
+    FileData.
+
+%----------------------------------------------------------------------
+%transform the Data specified about override to a form that is ieasier 
+%handled later
+%Override data="all"|"md5"|"Directive1 .... DirectioveN"
+%----------------------------------------------------------------------
+
+getAllowOverRideData(OverRideData)->
+   case string:tokens(OverRideData," \r\n") of
+       ["all" ++ _] ->
+	   all;
+       ["none" ++ _]->
+	   none;
+       Directives ->
+	   getOverRideDirectives(Directives)
+   end.
+
+getOverRideDirectives(Directives)->
+    lists:map(fun(Directive)->
+		      transformDirective(Directive)
+	      end,Directives).
+transformDirective("AuthUserFile" ++  _)->
+    user_file;
+transformDirective("AuthGroupFile" ++ _) ->
+    group_file;
+transformDirective("AuthName" ++ _)->
+    auth_name;
+transformDirective("AuthType" ++ _)-> 
+    auth_type;
+transformDirective(_UnAllowedOverRideDirective) ->
+    unallowed.
+%----------------------------------------------------------------------
+%Replace the string that specify which method to use for authentication
+%and replace it with the atom for easier mathing
+%----------------------------------------------------------------------   
+getAuthorizationType(AuthType)->
+    [Arg | _Crap] = string:tokens(AuthType,"\n\r\ "),
+    case Arg of
+	"Basic"->
+	    basic;
+	"MD5" ->
+	    md5;
+	_What ->
+	    error
+    end.
+%----------------------------------------------------------------------
+%Returns a list of the specified methods to limit or the atom all
+%----------------------------------------------------------------------
+getLimits(Limits)->
+    case regexp:split(Limits,">")of
+	{ok,[_NoEndOnLimit]}->
+	    error;
+	{ok, [Methods | _Crap]}->
+	    case regexp:split(Methods," ")of
+		{ok,[]}->
+		    all;
+		{ok,SplittedMethods}->
+		    SplittedMethods;
+		{error, _Error}->
+		    error
+	    end;
+	{error,_Error}->
+	    error
+    end.
+
+
+%----------------------------------------------------------------------
+% Transform the order to prefrom deny allow control to a tuple of atoms
+%----------------------------------------------------------------------
+getOrder(Order)->
+    [First | _Rest]=lists:map(fun(Part)->
+		      list_to_atom(Part)
+	      end,string:tokens(Order," \n\r")),
+    case First of
+	deny->
+	    {deny,allow};
+	allow->
+	    {allow,deny};
+	_Error->
+	    error
+    end.
+
+%----------------------------------------------------------------------
+% The string AllowDeny is "from all" or "from Subnet1 Subnet2...SubnetN"
+%----------------------------------------------------------------------
+getAllowDenyData(AllowDeny)->
+    case string:tokens(AllowDeny," \n\r") of
+	[_From|AllowDenyData] when length(AllowDenyData)>=1->
+	    case lists:nth(1,AllowDenyData) of
+		"all" ->
+		    all;
+		_Hosts->
+		    AllowDenyData
+	    end;
+	_ ->
+	    error
+    end.
+%----------------------------------------------------------------------
+% Fix the string that describes who is allowed to se the page
+%----------------------------------------------------------------------
+getRequireData(Require)->
+    [UserOrGroup|UserData]=string:tokens(Require," \n\r"),
+    case UserOrGroup of
+	"user"->
+	    {users,UserData};
+	"group" ->
+	    {groups,UserData};
+	_Whatever ->
+	    error
+    end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%                                                                  %%
+%% Methods that collects the searchways to the accessfiles          %%
+%%                                                                  %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%----------------------------------------------------------------------
+% Get the whole path to the different accessfiles
+%----------------------------------------------------------------------	
+getHtAccessFiles(HtAccessFileNames,Path,RestOfSplittedPath)->
+    getHtAccessFiles(HtAccessFileNames,Path,RestOfSplittedPath,[]).
+
+getHtAccessFiles(HtAccessFileNames,Path,[[]],HtAccessFiles)->
+    HtAccessFiles ++ accessFilesOfPath(HtAccessFileNames,Path++"/");   
+    
+getHtAccessFiles(_HtAccessFileNames, _Path, [], HtAccessFiles)->
+    HtAccessFiles;
+getHtAccessFiles(HtAccessFileNames,Path,[NextDir|RestOfSplittedPath],
+		 AccessFiles)->   
+    getHtAccessFiles(HtAccessFileNames,Path++"/"++NextDir,RestOfSplittedPath,
+		     AccessFiles ++ 
+		     accessFilesOfPath(HtAccessFileNames,Path++"/")).   
+    
+
+%----------------------------------------------------------------------
+%Control if therer are any accessfies in the path
+%----------------------------------------------------------------------
+accessFilesOfPath(HtAccessFileNames,Path)->
+    lists:foldl(fun(HtAccessFileName,Files)->
+			case file:read_file_info(Path++HtAccessFileName) of
+			    {ok, _}->
+				[Path++HtAccessFileName|Files];
+			    {error,_Error} ->
+				Files
+			end
+		end,[],HtAccessFileNames).
+
+
+%----------------------------------------------------------------------
+%Sake the splitted path and joins it up to the documentroot or the alias
+%that match first
+%----------------------------------------------------------------------
+
+getRootPath(SplittedPath, Info)->
+    DocRoot=httpd_util:lookup(Info#mod.config_db,document_root,"/"),
+    PresumtiveRootPath=
+	[DocRoot|lists:map(fun({_Alias,RealPath})->
+				   RealPath
+			   end,
+		 httpd_util:multi_lookup(Info#mod.config_db,alias))],
+    getRootPath(PresumtiveRootPath,SplittedPath,Info).
+
+
+getRootPath(PresumtiveRootPath,[[],Splittedpath],Info)->
+    getRootPath(PresumtiveRootPath,["/",Splittedpath],Info);
+
+ 
+getRootPath(PresumtiveRootPath,[Part,NextPart|SplittedPath],Info)->
+    case lists:member(Part,PresumtiveRootPath)of
+	true->
+	    {ok,Part,[NextPart|SplittedPath]};
+	false ->
+	    getRootPath(PresumtiveRootPath,
+			[Part++"/"++NextPart|SplittedPath],Info)
+    end;
+
+getRootPath(PresumtiveRootPath, [Part], _Info)->
+    case lists:member(Part,PresumtiveRootPath)of
+	true->
+	    {ok,Part,[]};
+	false ->
+	    {error,Part}
+    end.

Added: incubator/couchdb/trunk/src/couch_inets/mod_include.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couch_inets/mod_include.erl?rev=642432&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couch_inets/mod_include.erl (added)
+++ incubator/couchdb/trunk/src/couch_inets/mod_include.erl Fri Mar 28 16:32:19 2008
@@ -0,0 +1,594 @@
+%% ``The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved via the world wide web at http://www.erlang.org/.
+%% 
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%% 
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%% 
+%%     $Id$
+%%
+-module(mod_include).
+-export([do/1,parse/2,config/6,include/6,echo/6,fsize/6,flastmod/6,exec/6]).
+
+-include("httpd.hrl").
+
+-define(VMODULE,"INCLUDE").
+
+%% do
+
+do(Info) ->
+    case Info#mod.method of
+	"GET" ->
+	    case httpd_util:key1search(Info#mod.data,status) of
+		%% A status code has been generated!
+		{_StatusCode, _PhraseArgs, _Reason} ->
+		    {proceed,Info#mod.data};
+		%% No status code has been generated!
+		undefined ->
+		    case httpd_util:key1search(Info#mod.data, response) of
+			%% No response has been generated!
+			undefined ->
+			    do_include(Info);
+			%% A response has been generated or sent!
+			_Response ->
+			    {proceed,Info#mod.data}
+		    end
+	    end;
+	%% Not a GET method!
+	_ ->
+	    {proceed,Info#mod.data}
+    end.
+
+do_include(Info) ->
+    Path = mod_alias:path(Info#mod.data,Info#mod.config_db,
+			  Info#mod.request_uri),
+    Suffix = httpd_util:suffix(Path),
+    case httpd_util:lookup_mime_default(Info#mod.config_db,Suffix) of
+	"text/x-server-parsed-html" ->
+	    HeaderStart = [{content_type, "text/html"}], 
+	    case send_in(Info, Path, HeaderStart, file:read_file_info(Path)) of
+		{ok, ErrorLog, Size} ->
+		    {proceed, [{response, {already_sent, 200, Size}},
+			       {mime_type, "text/html"} |
+			       lists:append(ErrorLog, Info#mod.data)]};
+		{error, Reason} ->
+		    {proceed,
+		     [{status,send_error(Reason,Info,Path)}|Info#mod.data]}
+	    end;
+	_ -> %% Unknown mime type, ignore
+	    {proceed,Info#mod.data}
+    end.
+
+
+%%
+%% config directive
+%%
+
+config(_Info, Context, ErrorLog, TagList, ValueList, R) ->
+    case verify_tags("config",[errmsg,timefmt,sizefmt],
+		     TagList,ValueList) of
+	ok ->
+	    {ok,update_context(TagList,ValueList,Context),ErrorLog,"",R};
+	{error,Reason} ->
+	    {ok,Context,[{internal_info,Reason}|ErrorLog],
+	     httpd_util:key1search(Context,errmsg,""),R}
+    end.
+
+update_context([],[],Context) ->
+    Context;
+update_context([Tag|R1],[Value|R2],Context) ->
+    update_context(R1,R2,[{Tag,Value}|Context]).
+
+verify_tags(Command,ValidTags,TagList,ValueList) when length(TagList)==length(ValueList) ->
+    verify_tags(Command, ValidTags, TagList);
+verify_tags(Command, _ValidTags, _TagList, _ValueList) ->
+    {error, ?NICE(Command ++ " directive has spurious tags")}.
+
+verify_tags(_Command, _ValidTags, []) ->
+    ok;
+verify_tags(Command, ValidTags, [Tag|Rest]) ->
+    case lists:member(Tag, ValidTags) of
+	true ->
+	    verify_tags(Command, ValidTags, Rest);
+	false ->
+	    {error, ?NICE(Command++" directive has a spurious tag ("++
+			 atom_to_list(Tag)++")")}
+    end.
+
+%%
+%% include directive
+%%
+
+include(Info,Context,ErrorLog,[virtual],[VirtualPath],R) ->
+    Aliases = httpd_util:multi_lookup(Info#mod.config_db,alias),
+    {_, Path, _AfterPath} =
+	mod_alias:real_name(Info#mod.config_db, VirtualPath, Aliases),
+    include(Info,Context,ErrorLog,R,Path);
+include(Info, Context, ErrorLog, [file], [FileName], R) ->
+    Path = file(Info#mod.config_db, Info#mod.request_uri, FileName),
+    include(Info, Context, ErrorLog, R, Path);
+include(_Info, Context, ErrorLog, _TagList, _ValueList, R) ->
+    {ok, Context,
+     [{internal_info,?NICE("include directive has a spurious tag")}|
+      ErrorLog], httpd_util:key1search(Context, errmsg, ""), R}.
+
+include(Info, Context, ErrorLog, R, Path) ->
+    case file:read_file(Path) of
+	{ok, Body} ->
+	    {ok, NewContext, NewErrorLog, Result} =
+		parse(Info, binary_to_list(Body), Context, ErrorLog, []),
+	    {ok, NewContext, NewErrorLog, Result, R};
+	{error, _Reason} ->
+	    {ok, Context, 
+	     [{internal_info, ?NICE("Can't open "++Path)}|ErrorLog],
+	     httpd_util:key1search(Context, errmsg, ""), R}
+    end.
+
+file(ConfigDB, RequestURI, FileName) ->
+    Aliases = httpd_util:multi_lookup(ConfigDB, alias),
+    {_, Path, _AfterPath}
+	= mod_alias:real_name(ConfigDB, RequestURI, Aliases),
+    Pwd = filename:dirname(Path),
+    filename:join(Pwd, FileName).
+
+%%
+%% echo directive
+%%
+
+echo(Info,Context,ErrorLog,[var],["DOCUMENT_NAME"],R) ->
+    {ok,Context,ErrorLog,document_name(Info#mod.data,Info#mod.config_db,
+				       Info#mod.request_uri),R};
+echo(Info,Context,ErrorLog,[var],["DOCUMENT_URI"],R) ->
+    {ok,Context,ErrorLog,document_uri(Info#mod.config_db,
+				      Info#mod.request_uri),R};
+echo(Info,Context,ErrorLog,[var],["QUERY_STRING_UNESCAPED"],R) ->
+    {ok,Context,ErrorLog,query_string_unescaped(Info#mod.request_uri),R};
+echo(_Info,Context,ErrorLog,[var],["DATE_LOCAL"],R) ->
+    {ok,Context,ErrorLog,date_local(),R};
+echo(_Info,Context,ErrorLog,[var],["DATE_GMT"],R) ->
+    {ok,Context,ErrorLog,date_gmt(),R};
+echo(Info,Context,ErrorLog,[var],["LAST_MODIFIED"],R) ->
+    {ok,Context,ErrorLog,last_modified(Info#mod.data,Info#mod.config_db,
+				       Info#mod.request_uri),R};
+echo(_Info, Context, ErrorLog, _TagList, _ValueList, R) ->
+    {ok,Context,
+     [{internal_info,?NICE("echo directive has a spurious tag")}|
+      ErrorLog],"(none)",R}.
+
+document_name(Data,ConfigDB,RequestURI) ->
+    Path = mod_alias:path(Data,ConfigDB,RequestURI),
+    case regexp:match(Path,"[^/]*\$") of
+	{match,Start,Length} ->
+	    string:substr(Path,Start,Length);
+	nomatch ->
+	    "(none)"
+    end.
+
+document_uri(ConfigDB, RequestURI) ->
+    Aliases = httpd_util:multi_lookup(ConfigDB, alias),
+    
+    {_, Path, AfterPath}  = mod_alias:real_name(ConfigDB, RequestURI, Aliases),
+    
+    VirtualPath = string:substr(RequestURI, 1, 
+				length(RequestURI)-length(AfterPath)),
+    {match, Start, Length} = regexp:match(Path,"[^/]*\$"),
+    FileName = string:substr(Path,Start,Length),
+    case regexp:match(VirtualPath, FileName++"\$") of
+	{match, _, _} ->
+	    httpd_util:decode_hex(VirtualPath)++AfterPath;
+	nomatch ->
+	    string:strip(httpd_util:decode_hex(VirtualPath),right,$/)++
+		"/"++FileName++AfterPath
+    end.
+
+query_string_unescaped(RequestURI) ->
+  case regexp:match(RequestURI,"[\?].*\$") of
+    {match,Start,Length} ->
+      %% Escape all shell-special variables with \
+      escape(string:substr(RequestURI,Start+1,Length-1));      
+    nomatch ->
+      "(none)"
+  end.
+
+escape([]) -> [];
+escape([$;|R]) -> [$\\,$;|escape(R)];
+escape([$&|R]) -> [$\\,$&|escape(R)];
+escape([$(|R]) -> [$\\,$(|escape(R)];
+escape([$)|R]) -> [$\\,$)|escape(R)];
+escape([$||R]) -> [$\\,$||escape(R)];
+escape([$^|R]) -> [$\\,$^|escape(R)];
+escape([$<|R]) -> [$\\,$<|escape(R)];
+escape([$>|R]) -> [$\\,$>|escape(R)];
+escape([$\n|R]) -> [$\\,$\n|escape(R)];
+escape([$ |R]) -> [$\\,$ |escape(R)];
+escape([$\t|R]) -> [$\\,$\t|escape(R)];
+escape([C|R]) -> [C|escape(R)].
+
+date_local() ->
+  {{Year,Month,Day},{Hour,Minute,Second}}=calendar:local_time(),
+  %% Time format hard-wired to: "%a %b %e %T %Y" according to strftime(3)
+  io_lib:format("~s ~s ~2w ~2.2.0w:~2.2.0w:~2.2.0w ~w",
+		[httpd_util:day(calendar:day_of_the_week(Year,Month,Day)),
+		 httpd_util:month(Month),Day,Hour,Minute,Second,Year]).
+
+date_gmt() ->
+  {{Year,Month,Day},{Hour,Minute,Second}}=calendar:universal_time(),
+  %% Time format hard-wired to: "%a %b %e %T %Z %Y" according to strftime(3)
+  io_lib:format("~s ~s ~2w ~2.2.0w:~2.2.0w:~2.2.0w GMT ~w",
+		[httpd_util:day(calendar:day_of_the_week(Year,Month,Day)),
+		 httpd_util:month(Month),Day,Hour,Minute,Second,Year]).
+
+last_modified(Data,ConfigDB,RequestURI) ->
+  {ok,FileInfo}=file:read_file_info(mod_alias:path(Data,ConfigDB,RequestURI)),
+  {{Year,Month,Day},{Hour,Minute,Second}}=FileInfo#file_info.mtime,
+  io_lib:format("~s ~s ~2w ~2.2.0w:~2.2.0w:~2.2.0w ~w",
+		[httpd_util:day(calendar:day_of_the_week(Year,Month,Day)),
+		 httpd_util:month(Month),Day,Hour,Minute,Second,Year]).
+
+%%
+%% fsize directive
+%%
+
+fsize(Info,Context,ErrorLog,[virtual],[VirtualPath],R) ->
+  Aliases = httpd_util:multi_lookup(Info#mod.config_db,alias),
+  {_,Path, _AfterPath}=
+    mod_alias:real_name(Info#mod.config_db,VirtualPath,Aliases),
+  fsize(Info, Context, ErrorLog, R, Path);
+fsize(Info,Context,ErrorLog,[file],[FileName],R) ->
+  Path = file(Info#mod.config_db,Info#mod.request_uri,FileName),
+  fsize(Info,Context,ErrorLog,R,Path);
+fsize(_Info, Context, ErrorLog, _TagList, _ValueList, R) ->
+  {ok,Context,[{internal_info,?NICE("fsize directive has a spurious tag")}|
+	       ErrorLog],httpd_util:key1search(Context,errmsg,""),R}.
+
+fsize(_Info, Context, ErrorLog, R, Path) ->
+    case file:read_file_info(Path) of
+	{ok,FileInfo} ->
+	    case httpd_util:key1search(Context,sizefmt) of
+		"bytes" ->
+		    {ok,Context,ErrorLog,
+		     integer_to_list(FileInfo#file_info.size),R};
+		"abbrev" ->
+		    Size = integer_to_list(trunc(FileInfo#file_info.size/1024+1))++"k",
+		    {ok,Context,ErrorLog,Size,R};
+		Value->
+		    {ok,Context,
+		     [{internal_info,
+		       ?NICE("fsize directive has a spurious tag value ("++
+			     Value++")")}|
+		      ErrorLog],
+		     httpd_util:key1search(Context, errmsg, ""), R}
+	    end;
+	{error, _Reason} ->
+	    {ok,Context,[{internal_info,?NICE("Can't open "++Path)}|ErrorLog],
+	     httpd_util:key1search(Context,errmsg,""),R}
+    end.
+
+%%
+%% flastmod directive
+%%
+
+flastmod(#mod{config_db = Db} = Info, 
+	 Context, ErrorLog, [virtual], [VirtualPath],R) ->
+    Aliases = httpd_util:multi_lookup(Db,alias),
+    {_,Path, _AfterPath} = mod_alias:real_name(Db, VirtualPath, Aliases),
+    flastmod(Info,Context,ErrorLog,R,Path);
+flastmod(#mod{config_db = Db, request_uri = RequestUri} = Info, 
+	 Context, ErrorLog, [file], [FileName], R) ->
+    Path = file(Db, RequestUri, FileName),
+    flastmod(Info, Context, ErrorLog, R, Path);
+flastmod(_Info, Context, ErrorLog, _TagList, _ValueList, R) ->
+    {ok,Context,
+     [{internal_info,?NICE("flastmod directive has a spurious tag")}|
+      ErrorLog],httpd_util:key1search(Context,errmsg,""),R}.
+
+flastmod(_Info, Context, ErrorLog, R, File) ->
+    case file:read_file_info(File) of
+	{ok, FileInfo} ->
+	    {{Yr,Mon,Day},{Hour,Minute,Second}}=FileInfo#file_info.mtime,
+	    Result =
+		io_lib:format("~s ~s ~2w ~w:~w:~w ~w",
+			      [httpd_util:day(
+				 calendar:day_of_the_week(Yr,Mon, Day)),
+			       httpd_util:month(Mon),Day,Hour,Minute,Second, Yr]),
+	    {ok, Context, ErrorLog, Result, R};
+	{error, _Reason} ->
+	    {ok,Context,[{internal_info,?NICE("Can't open "++File)}|ErrorLog],
+	     httpd_util:key1search(Context,errmsg,""),R}
+    end.
+
+%%
+%% exec directive
+%%
+
+exec(Info,Context,ErrorLog,[cmd],[Command],R) ->
+    cmd(Info,Context,ErrorLog,R,Command);
+exec(Info,Context,ErrorLog,[cgi],[RequestURI],R) ->
+    cgi(Info,Context,ErrorLog,R,RequestURI);
+exec(_Info, Context, ErrorLog, _TagList, _ValueList, R) ->
+    {ok, Context,
+     [{internal_info,?NICE("exec directive has a spurious tag")}|
+      ErrorLog], httpd_util:key1search(Context,errmsg,""),R}.
+
+%% cmd
+
+cmd(Info, Context, ErrorLog, R, Command) ->
+    process_flag(trap_exit,true),    
+    Env  = env(Info),
+    Dir  = filename:dirname(Command),
+    Port = (catch open_port({spawn,Command},[stream,{cd,Dir},{env,Env}])),
+    case Port of
+	P when port(P) ->
+	    {NewErrorLog, Result} = proxy(Port, ErrorLog),
+	    {ok, Context, NewErrorLog, Result, R};
+	{'EXIT', Reason} ->
+	    exit({open_port_failed,Reason,
+		  [{uri,Info#mod.request_uri},{script,Command},
+		   {env,Env},{dir,Dir}]});
+	O ->
+	    exit({open_port_failed,O,
+		  [{uri,Info#mod.request_uri},{script,Command},
+		   {env,Env},{dir,Dir}]})
+    end.
+
+env(Info) ->
+    [{"DOCUMENT_NAME",document_name(Info#mod.data,Info#mod.config_db,
+				    Info#mod.request_uri)},
+     {"DOCUMENT_URI", document_uri(Info#mod.config_db, Info#mod.request_uri)},
+     {"QUERY_STRING_UNESCAPED", query_string_unescaped(Info#mod.request_uri)},
+     {"DATE_LOCAL", date_local()},
+     {"DATE_GMT", date_gmt()},
+     {"LAST_MODIFIED", last_modified(Info#mod.data, Info#mod.config_db,
+				     Info#mod.request_uri)}
+    ].
+
+%% cgi
+
+cgi(Info, Context, ErrorLog, R, RequestURI) ->
+    ScriptAliases = httpd_util:multi_lookup(Info#mod.config_db, script_alias),
+    case mod_alias:real_script_name(Info#mod.config_db, RequestURI,
+				    ScriptAliases) of
+	{Script, AfterScript} ->
+	    exec_script(Info,Script,AfterScript,ErrorLog,Context,R);
+	not_a_script ->
+	    {ok, Context,
+	     [{internal_info, ?NICE(RequestURI++" is not a script")}|
+	      ErrorLog], httpd_util:key1search(Context, errmsg, ""),R}
+    end.
+
+remove_header([]) ->
+    [];
+remove_header([$\n,$\n|Rest]) ->
+    Rest;
+remove_header([_C|Rest]) ->
+    remove_header(Rest).
+
+
+exec_script(#mod{config_db = Db, request_uri = RequestUri} = Info, 
+	    Script, _AfterScript, ErrorLog, Context, R) ->
+    process_flag(trap_exit,true),    
+    Aliases = httpd_util:multi_lookup(Db, alias),
+    {_, Path, AfterPath} = mod_alias:real_name(Db, RequestUri, Aliases),
+    Env  = env(Info) ++ mod_cgi:env(Info, Path, AfterPath),
+    Dir  = filename:dirname(Path),
+    Port = (catch open_port({spawn,Script},[stream,{env, Env},{cd, Dir}])),
+    case Port of
+	P when port(P) ->
+	    %% Send entity body to port.
+	    Res = case Info#mod.entity_body of
+		      [] ->
+			  true;
+		      EntityBody ->
+			  (catch port_command(Port, EntityBody))
+		  end,
+	    case Res of
+		{'EXIT', Reason} ->
+		    exit({open_cmd_failed,Reason,
+			  [{mod,?MODULE},{port,Port},
+			   {uri,RequestUri},
+			   {script,Script},{env,Env},{dir,Dir},
+			   {ebody_size,sz(Info#mod.entity_body)}]});
+		true ->
+		    {NewErrorLog, Result} = proxy(Port, ErrorLog),
+		    {ok, Context, NewErrorLog, remove_header(Result), R}
+	    end;
+	{'EXIT', Reason} ->
+	    exit({open_port_failed,Reason,
+		  [{mod,?MODULE},{uri,RequestUri},{script,Script},
+		   {env,Env},{dir,Dir}]});
+	O ->
+	    exit({open_port_failed,O,
+		  [{mod,?MODULE},{uri,RequestUri},{script,Script},
+		   {env,Env},{dir,Dir}]})
+    end.
+    
+
+%%
+%% Port communication
+%%
+
+proxy(Port, ErrorLog) ->
+    process_flag(trap_exit, true),
+    proxy(Port, ErrorLog, []).
+
+proxy(Port, ErrorLog, Result) ->
+    receive
+	{Port, {data, Response}} ->
+	    proxy(Port, ErrorLog, lists:append(Result,Response));
+	{'EXIT', Port, normal} when port(Port) ->
+	    process_flag(trap_exit, false),
+	    {ErrorLog, Result};
+	{'EXIT', Port, _Reason} when port(Port) ->
+	    process_flag(trap_exit, false),
+	    {[{internal_info,
+	       ?NICE("Scrambled output from CGI-script")}|ErrorLog],
+	     Result};
+	{'EXIT', Pid, Reason} when pid(Pid) ->
+	    process_flag(trap_exit, false),
+	    {'EXIT', Pid, Reason};
+	%% This should not happen!
+	_WhatEver ->
+	    process_flag(trap_exit, false),
+	    {ErrorLog, Result}
+    end.
+
+
+%% ------
+%% Temporary until I figure out a way to fix send_in_chunks
+%% (comments and directives that start in one chunk but end
+%% in another is not handled).
+%%
+
+send_in(Info, Path, Head, {ok,FileInfo}) ->
+    case file:read_file(Path) of
+	{ok, Bin} ->
+	    send_in1(Info, binary_to_list(Bin), Head, FileInfo);
+	{error, Reason} ->
+	    {error, {read,Reason}}
+    end;
+send_in(_Info , _Path, _Head,{error,Reason}) ->
+    {error, {open,Reason}}.
+
+send_in1(Info, Data, Head, FileInfo) ->
+    {ok, _Context, Err, ParsedBody} = parse(Info,Data,?DEFAULT_CONTEXT,[],[]),
+    Size = length(ParsedBody),
+    LastModified = case catch httpd_util:rfc1123_date(FileInfo#file_info.mtime) of
+		       Date when list(Date) -> [{last_modified,Date}];
+		       _ -> []
+		   end,
+    Head1 = case Info#mod.http_version of 
+		"HTTP/1.1"->
+		    Head ++ [{content_length, integer_to_list(Size)},  
+			     {etag, httpd_util:create_etag(FileInfo,Size)}|
+			     LastModified];
+		_->
+		    %% i.e http/1.0 and http/0.9
+		    Head ++  [{content_length, integer_to_list(Size)}|  
+			      LastModified]
+	    end,
+    httpd_response:send_header(Info, 200, Head1),
+    httpd_socket:deliver(Info#mod.socket_type,Info#mod.socket, ParsedBody),
+    {ok, Err, Size}.
+
+
+parse(Info,Body) ->
+  parse(Info, Body, ?DEFAULT_CONTEXT, [], []).
+
+parse(_Info, [], Context, ErrorLog, Result) ->
+    {ok, Context, lists:reverse(ErrorLog), lists:reverse(Result)};
+parse(Info,[$<,$!,$-,$-,$#|R1],Context,ErrorLog,Result) ->
+  case catch parse0(R1,Context) of
+    {parse_error,Reason} ->
+      parse(Info,R1,Context,[{internal_info,?NICE(Reason)}|ErrorLog],
+	    [$#,$-,$-,$!,$<|Result]);
+    {ok,Context,Command,TagList,ValueList,R2} ->
+	  {ok,NewContext,NewErrorLog,MoreResult,R3}=
+	      handle(Info,Context,ErrorLog,Command,TagList,ValueList,R2),
+	  parse(Info,R3,NewContext,NewErrorLog,
+		lists:reverse(MoreResult)++Result)
+  end;
+parse(Info,[$<,$!,$-,$-|R1],Context,ErrorLog,Result) ->
+  case catch parse5(R1,[],0) of
+    {parse_error,Reason} ->
+	  parse(Info,R1,Context,
+		[{internal_info,?NICE(Reason)}|ErrorLog],Result);
+      {Comment,R2} ->
+      parse(Info,R2,Context,ErrorLog,Comment++Result)
+  end;
+parse(Info,[C|R],Context,ErrorLog,Result) ->
+  parse(Info,R,Context,ErrorLog,[C|Result]).
+
+handle(Info,Context,ErrorLog,Command,TagList,ValueList,R) ->
+  case catch apply(?MODULE,Command,[Info,Context,ErrorLog,TagList,ValueList,
+				    R]) of
+    {'EXIT',{undef,_}} ->
+      throw({parse_error,"Unknown command "++atom_to_list(Command)++
+	     " in parsed doc"});
+    Result ->
+      Result
+  end.
+
+parse0([], _Context) ->
+  throw({parse_error,"Premature EOF in parsed file"});
+parse0([$-,$-,$>|_R], _Context) ->
+  throw({parse_error,"Premature EOF in parsed file"});
+parse0([$ |R], Context) ->
+  parse0(R,Context);
+parse0(String, Context) ->
+  parse1(String, Context,"").
+
+parse1([], _Context, _Command) ->
+  throw({parse_error,"Premature EOF in parsed file"});
+parse1([$-,$-,$>|_R], _Context, _Command) ->
+  throw({parse_error,"Premature EOF in parsed file"});
+parse1([$ |R], Context, Command) ->
+  parse2(R,Context,list_to_atom(lists:reverse(Command)),[],[],"");
+parse1([C|R], Context, Command) ->
+  parse1(R,Context,[C|Command]).
+
+parse2([], _Context, _Command, _TagList, _ValueList, _Tag) ->
+  throw({parse_error,"Premature EOF in parsed file"});
+parse2([$-,$-,$>|R], Context, Command, TagList, ValueList, _Tag) ->
+  {ok,Context,Command,TagList,ValueList,R};
+parse2([$ |R],Context,Command,TagList,ValueList,Tag) ->
+  parse2(R,Context,Command,TagList,ValueList,Tag);
+parse2([$=|R],Context,Command,TagList,ValueList,Tag) ->
+  parse3(R,Context,Command,[list_to_atom(lists:reverse(Tag))|TagList],
+	 ValueList);
+parse2([C|R],Context,Command,TagList,ValueList,Tag) ->
+  parse2(R,Context,Command,TagList,ValueList,[C|Tag]).
+
+parse3([], _Context, _Command, _TagList, _ValueList) ->
+  throw({parse_error,"Premature EOF in parsed file"});
+parse3([$-,$-,$>|_R], _Context, _Command, _TagList, _ValueList) ->
+  throw({parse_error,"Premature EOF in parsed file"});
+parse3([$ |R], Context, Command, TagList, ValueList) ->
+  parse3(R, Context, Command, TagList, ValueList);
+parse3([$"|R], Context, Command, TagList, ValueList) ->
+  parse4(R,Context,Command,TagList,ValueList,"");
+parse3(_String, _Context, _Command, _TagList, _ValueList) ->
+  throw({parse_error,"Premature EOF in parsed file"}).
+
+parse4([], _Context, _Command, _TagList, _ValueList, _Value) ->
+  throw({parse_error,"Premature EOF in parsed file"});
+parse4([$-,$-,$>|_R], _Context, _Command, _TagList, _ValueList, _Value) ->
+  throw({parse_error,"Premature EOF in parsed file"});
+parse4([$"|R],Context,Command,TagList,ValueList,Value) ->
+  parse2(R,Context,Command,TagList,[lists:reverse(Value)|ValueList],"");
+parse4([C|R],Context,Command,TagList,ValueList,Value) ->
+  parse4(R,Context,Command,TagList,ValueList,[C|Value]).
+
+parse5([], _Comment, _Depth) ->
+  throw({parse_error,"Premature EOF in parsed file"});
+parse5([$<,$!,$-,$-|R],Comment,Depth) ->
+  parse5(R,[$-,$-,$!,$<|Comment],Depth+1);
+parse5([$-,$-,$>|R],Comment,0) ->
+  {">--"++Comment++"--!<",R};
+parse5([$-,$-,$>|R],Comment,Depth) ->
+  parse5(R,[$>,$-,$-|Comment],Depth-1);
+parse5([C|R],Comment,Depth) ->
+  parse5(R,[C|Comment],Depth).
+
+
+sz(B) when binary(B) -> {binary,size(B)};
+sz(L) when list(L)   -> {list,length(L)};
+sz(_)                -> undefined.
+
+%% send_error - Handle failure to send the file
+%%
+send_error({open,Reason},Info,Path) -> 
+    httpd_file:handle_error(Reason, "open", Info, Path);
+send_error({read,Reason},Info,Path) -> 
+    httpd_file:handle_error(Reason, "read", Info, Path).
+
+
+
+

Added: incubator/couchdb/trunk/src/couch_inets/mod_log.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couch_inets/mod_log.erl?rev=642432&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couch_inets/mod_log.erl (added)
+++ incubator/couchdb/trunk/src/couch_inets/mod_log.erl Fri Mar 28 16:32:19 2008
@@ -0,0 +1,253 @@
+%% ``The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved via the world wide web at http://www.erlang.org/.
+%% 
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%% 
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%% 
+%%     $Id$
+%%
+-module(mod_log).
+-export([do/1,error_log/5,security_log/2,load/2,store/2,remove/1]).
+
+-export([report_error/2]).
+
+-include("httpd.hrl").
+-define(VMODULE,"LOG").
+
+%% do
+
+do(Info) ->
+    AuthUser = auth_user(Info#mod.data),
+    Date     = custom_date(),
+    log_internal_info(Info,Date,Info#mod.data),
+    case httpd_util:key1search(Info#mod.data,status) of
+	%% A status code has been generated!
+	{StatusCode, _PhraseArgs, Reason} ->
+	    transfer_log(Info,"-",AuthUser,Date,StatusCode,0),
+	    if
+		StatusCode >= 400 ->
+		    error_log(Info,Date,Reason);
+		true ->
+		    not_an_error
+	    end,
+	    {proceed,Info#mod.data};
+	%% No status code has been generated!
+	undefined ->
+	    case httpd_util:key1search(Info#mod.data,response) of
+		{already_sent,StatusCode,Size} ->
+		    transfer_log(Info,"-",AuthUser,Date,StatusCode,Size),
+		    {proceed,Info#mod.data};
+		{response, Head, _Body} ->
+		    Size = httpd_util:key1search(Head,content_length,unknown),
+		    Code = httpd_util:key1search(Head,code,unknown),
+		    transfer_log(Info, "-", AuthUser, Date, Code, Size),
+		    {proceed, Info#mod.data};
+		{_StatusCode, Response} ->
+		    transfer_log(Info,"-",AuthUser,Date,200,
+				 httpd_util:flatlength(Response)),
+		    {proceed,Info#mod.data};
+		undefined ->
+		    transfer_log(Info,"-",AuthUser,Date,200,0),
+		    {proceed,Info#mod.data}
+	    end
+    end.
+
+custom_date() ->
+    LocalTime     = calendar:local_time(),
+    UniversalTime = calendar:universal_time(),
+    Minutes       = round(diff_in_minutes(LocalTime, UniversalTime)),
+    {{YYYY,MM,DD},{Hour,Min,Sec}} = LocalTime,
+    Date = 
+	io_lib:format("~.2.0w/~.3s/~.4w:~.2.0w:~.2.0w:~.2.0w ~c~.2.0w~.2.0w",
+		      [DD, httpd_util:month(MM), YYYY, Hour, Min, Sec, 
+		       sign(Minutes),
+		       abs(Minutes) div 60, abs(Minutes) rem 60]),  
+    lists:flatten(Date).
+
+diff_in_minutes(L,U) ->
+    (calendar:datetime_to_gregorian_seconds(L) -
+     calendar:datetime_to_gregorian_seconds(U))/60.
+
+sign(Minutes) when Minutes > 0 ->
+    $+;
+sign(_Minutes) ->
+    $-.
+
+auth_user(Data) ->
+    case httpd_util:key1search(Data,remote_user) of
+	undefined ->
+	    "-";
+	RemoteUser ->
+	    RemoteUser
+    end.
+
+%% log_internal_info
+
+log_internal_info(_Info, _Date, []) ->
+    ok;
+log_internal_info(Info,Date,[{internal_info,Reason}|Rest]) ->
+    error_log(Info,Date,Reason),
+    log_internal_info(Info,Date,Rest);
+log_internal_info(Info,Date,[_|Rest]) ->
+    log_internal_info(Info,Date,Rest).
+
+%% transfer_log
+
+transfer_log(Info,RFC931,AuthUser,Date,StatusCode,Bytes) ->
+    case httpd_util:lookup(Info#mod.config_db,transfer_log) of
+	undefined ->
+	    no_transfer_log;
+	TransferLog ->
+	    {_PortNumber, RemoteHost} = 
+		(Info#mod.init_data)#init_data.peername,
+	    case (catch io:format(TransferLog, 
+				  "~s ~s ~s [~s] \"~s\" ~w ~w~n",
+				  [RemoteHost, RFC931, AuthUser, 
+				   Date, Info#mod.request_line,
+				   StatusCode, Bytes])) of
+		ok ->
+		    ok;
+		Error ->
+		    error_logger:error_report(Error)
+	    end
+    end.
+
+%% security log
+
+security_log(Info, Reason) ->
+    case httpd_util:lookup(Info#mod.config_db, security_log) of
+	undefined ->
+	    no_security_log;
+	SecurityLog ->
+	    io:format(SecurityLog,"[~s] ~s~n", [custom_date(), Reason])
+    end.
+
+%% error_log
+
+error_log(Info, Date, Reason) ->
+    case httpd_util:lookup(Info#mod.config_db, error_log) of
+	undefined ->
+	    no_error_log;
+	ErrorLog ->
+	    {_PortNumber, RemoteHost} = 
+		(Info#mod.init_data)#init_data.peername,
+	    io:format(ErrorLog,
+		      "[~s] access to ~s failed for ~s, reason: ~p~n",
+		      [Date,Info#mod.request_uri,RemoteHost,Reason])
+    end.
+
+error_log(_SocketType, _Socket, ConfigDB, {_PortNumber, RemoteHost}, Reason) ->
+    case httpd_util:lookup(ConfigDB, error_log) of
+	undefined ->
+	    no_error_log;
+	ErrorLog ->
+	    Date = custom_date(),
+	    io:format(ErrorLog,"[~s] server crash for ~s, reason: ~p~n",
+		      [Date,RemoteHost,Reason]),
+	    ok
+    end.
+
+
+report_error(ConfigDB, Error) ->
+    case httpd_util:lookup(ConfigDB, error_log) of
+	undefined ->
+	    no_error_log;
+	ErrorLog ->
+	    Date = custom_date(),
+	    io:format(ErrorLog,"[~s] reporting error: ~s~n",[Date,Error]),
+	    ok
+    end.
+
+%%
+%% Configuration
+%%
+
+%% load
+
+load([$T,$r,$a,$n,$s,$f,$e,$r,$L,$o,$g,$ |TransferLog],[]) ->
+    {ok,[],{transfer_log,httpd_conf:clean(TransferLog)}};
+load([$E,$r,$r,$o,$r,$L,$o,$g,$ |ErrorLog],[]) ->
+    {ok,[],{error_log,httpd_conf:clean(ErrorLog)}};
+load([$S,$e,$c,$u,$r,$i,$t,$y,$L,$o,$g,$ |SecurityLog], []) ->
+    {ok, [], {security_log, httpd_conf:clean(SecurityLog)}}.
+
+%% store
+
+store({transfer_log,TransferLog},ConfigList) ->
+    case create_log(TransferLog,ConfigList) of
+	{ok,TransferLogStream} ->
+	    {ok,{transfer_log,TransferLogStream}};
+	{error,Reason} ->
+	    {error,Reason}
+    end;
+store({error_log,ErrorLog},ConfigList) ->
+    case create_log(ErrorLog,ConfigList) of
+	{ok,ErrorLogStream} ->
+	    {ok,{error_log,ErrorLogStream}};
+	{error,Reason} ->
+	    {error,Reason}
+    end;
+store({security_log, SecurityLog},ConfigList) ->
+    case create_log(SecurityLog, ConfigList) of
+	{ok, SecurityLogStream} ->
+	    {ok, {security_log, SecurityLogStream}};
+	{error, Reason} ->
+	    {error, Reason}
+    end.
+
+create_log(LogFile,ConfigList) ->
+    Filename = httpd_conf:clean(LogFile),
+    case filename:pathtype(Filename) of
+	absolute ->
+	    case file:open(Filename,read_write) of
+		{ok,LogStream} ->
+		    file:position(LogStream,{eof,0}),
+		    {ok,LogStream};
+		{error,_} ->
+		    {error,?NICE("Can't create "++Filename)}
+	    end;
+	volumerelative ->
+	    case file:open(Filename,read_write) of
+		{ok,LogStream} ->
+		    file:position(LogStream,{eof,0}),
+		    {ok,LogStream};
+		{error,_} ->
+		    {error,?NICE("Can't create "++Filename)}
+	    end;
+	relative ->
+	    case httpd_util:key1search(ConfigList,server_root) of
+		undefined ->
+		    {error,
+		     ?NICE(Filename++
+			   " is an invalid logfile name beacuse ServerRoot is not defined")};
+		ServerRoot ->
+		    AbsoluteFilename=filename:join(ServerRoot,Filename),
+		    case file:open(AbsoluteFilename,read_write) of
+			{ok,LogStream} ->
+			    file:position(LogStream,{eof,0}),
+			    {ok,LogStream};
+			{error, _Reason} ->
+			    {error,?NICE("Can't create "++AbsoluteFilename)}
+		    end
+	    end
+    end.
+
+%% remove
+
+remove(ConfigDB) ->
+    lists:foreach(fun([Stream]) -> file:close(Stream) end,
+		  ets:match(ConfigDB,{transfer_log,'$1'})),
+    lists:foreach(fun([Stream]) -> file:close(Stream) end,
+		  ets:match(ConfigDB,{error_log,'$1'})),
+    lists:foreach(fun([Stream]) -> file:close(Stream) end,
+		  ets:match(ConfigDB,{security_log,'$1'})),
+    ok.

Added: incubator/couchdb/trunk/src/couch_inets/mod_range.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couch_inets/mod_range.erl?rev=642432&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couch_inets/mod_range.erl (added)
+++ incubator/couchdb/trunk/src/couch_inets/mod_range.erl Fri Mar 28 16:32:19 2008
@@ -0,0 +1,416 @@
+%% ``The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved via the world wide web at http://www.erlang.org/.
+%% 
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%% 
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%% 
+%%     $Id$
+%%
+-module(mod_range).
+-export([do/1]).
+-include("httpd.hrl").
+
+%% do
+
+do(Info) ->
+    ?DEBUG("do -> entry",[]),
+    case Info#mod.method of
+	"GET" ->
+	    case httpd_util:key1search(Info#mod.data,status) of
+		%% A status code has been generated!
+		{_StatusCode,  _PhraseArgs, _Reason} ->
+		    {proceed,Info#mod.data};
+		%% No status code has been generated!
+		undefined ->
+		    case httpd_util:key1search(Info#mod.data,response) of
+			%% No response has been generated!
+			undefined ->
+			    case httpd_util:key1search(Info#mod.parsed_header,
+						       "range") of
+				undefined ->
+				    %Not a range response
+				    {proceed,Info#mod.data};
+				Range ->
+				    %%Control that there weren't a
+				    %%if-range field that stopped The
+				    %%range request in favor for the
+				    %%whole file
+				    case httpd_util:key1search(Info#mod.data,
+							       if_range) of
+					send_file ->
+					    {proceed,Info#mod.data};
+					_undefined ->
+					    do_get_range(Info,Range)
+				    end
+			    end; 			
+			%% A response has been generated or sent!
+			_Response ->
+			    {proceed, Info#mod.data}
+		    end
+	    end;
+	%% Not a GET method!
+	_ ->
+	    {proceed,Info#mod.data}
+    end.
+
+do_get_range(Info,Ranges) ->
+    ?DEBUG("do_get_range -> Request URI: ~p",[Info#mod.request_uri]), 
+     Path = mod_alias:path(Info#mod.data, Info#mod.config_db, 
+			  Info#mod.request_uri),
+    {FileInfo, LastModified} = get_modification_date(Path),
+    send_range_response(Path, Info, Ranges, FileInfo, LastModified).
+
+
+send_range_response(Path, Info, Ranges, FileInfo, LastModified)->
+    case parse_ranges(Ranges) of
+	error->
+	    ?ERROR("send_range_response-> Unparsable range request",[]),
+	    {proceed,Info#mod.data};
+	{multipart,RangeList}->
+	    send_multi_range_response(Path, Info, RangeList);
+	{Start,Stop}->
+	    send_range_response(Path, Info, Start, Stop, FileInfo, 
+				LastModified)
+    end.
+%%More than one range specified
+%%Send a multipart reponse to the user
+%
+%%An example of an multipart range response
+
+% HTTP/1.1 206 Partial Content
+% Date:Wed 15 Nov 1995 04:08:23 GMT
+% Last-modified:Wed 14 Nov 1995 04:08:23 GMT 
+% Content-type: multipart/byteranges; boundary="SeparatorString" 
+%
+% --"SeparatorString"
+% Content-Type: application/pdf
+% Content-Range: bytes 500-600/1010
+% .... The data..... 101 bytes
+%
+% --"SeparatorString"
+% Content-Type: application/pdf
+% Content-Range: bytes 700-1009/1010
+% .... The data.....
+
+
+
+send_multi_range_response(Path,Info,RangeList)->
+    case file:open(Path, [raw,binary]) of
+	{ok, FileDescriptor} ->
+	    file:close(FileDescriptor),
+	    ?DEBUG("send_multi_range_response -> FileDescriptor: ~p",
+		   [FileDescriptor]),
+	    Suffix = httpd_util:suffix(Path),
+	    PartMimeType = httpd_util:lookup_mime_default(Info#mod.config_db,
+							  Suffix,"text/plain"),
+	    {FileInfo,  LastModified} = get_modification_date(Path),
+	    case valid_ranges(RangeList,Path,FileInfo) of
+		{ValidRanges,true}->
+		    ?DEBUG("send_multi_range_response ->Ranges are valid:",[]),
+		    %Apache breaks the standard by sending the size
+		    %field in the Header.
+		    Header = 
+			[{code,206},
+			 {content_type, "multipart/byteranges;boundary" 
+			  "=RangeBoundarySeparator"}, 
+			 {etag, httpd_util:create_etag(FileInfo)} | 
+			 LastModified],
+		    ?DEBUG("send_multi_range_response -> Valid Ranges: ~p",
+			   [RagneList]),
+		    Body = {fun send_multiranges/4,
+			    [ValidRanges, Info, PartMimeType, Path]},
+		    {proceed,[{response,
+			       {response, Header, Body}} | Info#mod.data]};
+		_ ->
+		    {proceed, [{status, {416, "Range not valid",
+					 bad_range_boundaries }}]}
+	    end;
+	{error, _Reason} ->
+	    ?ERROR("do_get -> failed open file: ~p",[_Reason]),
+	    {proceed,Info#mod.data}
+    end.
+
+send_multiranges(ValidRanges,Info,PartMimeType,Path)->    
+    ?DEBUG("send_multiranges -> Start sending the ranges",[]),
+    case file:open(Path, [raw,binary]) of
+	{ok,FileDescriptor} ->
+	    lists:foreach(fun(Range)->
+				  send_multipart_start(Range,
+						       Info,
+						       PartMimeType,
+						       FileDescriptor)
+			  end,ValidRanges),
+	    file:close(FileDescriptor),
+	    %%Sends an end of the multipart
+	    httpd_socket:deliver(Info#mod.socket_type,Info#mod.socket,
+				 "\r\n--RangeBoundarySeparator--"),
+	    sent;
+	_ ->
+	    close
+    end.
+   
+send_multipart_start({{Start,End},{StartByte,EndByte,Size}},Info,
+		     PartMimeType,FileDescriptor)when StartByte<Size->
+    PartHeader=["\r\n--RangeBoundarySeparator\r\n","Content-type: ",
+		PartMimeType,"\r\n",
+                "Content-Range:bytes=",integer_to_list(StartByte),"-",
+		integer_to_list(EndByte),"/",
+		integer_to_list(Size),"\r\n\r\n"],
+    send_part_start(Info#mod.socket_type, Info#mod.socket, PartHeader,
+		    FileDescriptor, Start, End);
+
+
+send_multipart_start({{Start,End},{StartByte,EndByte,Size}}, Info,
+		     PartMimeType, FileDescriptor)->
+    PartHeader=["\r\n--RangeBoundarySeparator\r\n","Content-type: ",
+		PartMimeType,"\r\n",
+                "Content-Range:bytes=",integer_to_list(Size-(StartByte-Size)),
+		"-",integer_to_list(EndByte),"/",
+		integer_to_list(Size),"\r\n\r\n"],
+    send_part_start(Info#mod.socket_type, Info#mod.socket, PartHeader,
+		    FileDescriptor, Start, End).
+
+send_part_start(SocketType, Socket, PartHeader, FileDescriptor, Start, End)->
+    case httpd_socket:deliver(SocketType, Socket, PartHeader) of
+	ok ->
+	    send_part_start(SocketType,Socket,FileDescriptor,Start,End);
+	_ ->
+	    close
+    end.    
+
+send_range_response(Path, Info, Start, Stop, FileInfo, LastModified)->
+    case file:open(Path, [raw,binary]) of
+	{ok, FileDescriptor} ->
+	    file:close(FileDescriptor),
+	    ?DEBUG("send_range_response -> FileDescriptor: ~p",
+		   [FileDescriptor]),
+	    Suffix = httpd_util:suffix(Path),
+	    MimeType = httpd_util:lookup_mime_default(Info#mod.config_db,
+						      Suffix,"text/plain"),
+	    Size = get_range_size(Start,Stop,FileInfo),
+	    case valid_range(Start,Stop,FileInfo) of
+		{true,StartByte,EndByte,TotByte}->
+		    Head =[{code,206},{content_type, MimeType}, 
+			   {etag, httpd_util:create_etag(FileInfo)},
+			   {content_range,["bytes=",
+					   integer_to_list(StartByte),"-",
+					   integer_to_list(EndByte),"/",
+					   integer_to_list(TotByte)]},
+			   {content_length, Size} | LastModified],
+		    BodyFunc = fun send_range_body/5,
+		    Arg = [Info#mod.socket_type, 
+			  Info#mod.socket, Path, Start, Stop], 
+		    {proceed,[{response,{response ,Head,  {BodyFunc,Arg}}}|
+			      Info#mod.data]};
+		{false,Reason} ->
+		    {proceed, [{status, {416, Reason, bad_range_boundaries }}]}
+	    end;
+	{error, _Reason} ->
+	    ?ERROR("send_range_response -> failed open file: ~p",[_Reason]),
+	    {proceed,Info#mod.data}
+    end.
+
+
+send_range_body(SocketType,Socket,Path,Start,End) ->
+    ?DEBUG("mod_range -> send_range_body",[]),
+    case file:open(Path, [raw,binary]) of
+	{ok,FileDescriptor} ->
+	    send_part_start(SocketType,Socket,FileDescriptor,Start,End),
+	    file:close(FileDescriptor);
+	_ ->
+	    close
+    end.
+
+send_part_start(SocketType,Socket,FileDescriptor,Start,End) ->
+    case Start of
+	from_end ->
+	    file:position(FileDescriptor,{eof,End}),
+	    send_body(SocketType,Socket,FileDescriptor);
+	from_start ->
+	    file:position(FileDescriptor,{bof,End}),
+	    send_body(SocketType,Socket,FileDescriptor);
+	Byte when integer(Byte) ->
+	    file:position(FileDescriptor,{bof,Start}),
+	    send_part(SocketType,Socket,FileDescriptor,End)
+    end,
+    sent.
+
+
+%%This function could replace send_body by calling it with Start=0 end
+%%=FileSize But i gues it would be stupid when we look at performance
+send_part(SocketType,Socket,FileDescriptor,End)->
+    case file:position(FileDescriptor,{cur,0}) of
+	{ok,NewPos} ->
+	   if 
+	       NewPos > End ->
+		   ok;
+	       true ->
+		   Size = get_file_chunk_size(NewPos,End,?FILE_CHUNK_SIZE),
+		   case file:read(FileDescriptor,Size) of
+		       eof ->
+			   ok;
+		       {error, _Reason} ->
+			   ok;
+		       {ok,Binary} ->
+			   case httpd_socket:deliver(SocketType,Socket,
+						     Binary) of
+			       socket_closed ->
+				   ?LOG("send_range of body -> socket "   
+					"closed while sending",[]),
+				   socket_close;
+			       _ ->
+				   send_part(SocketType,Socket,
+					     FileDescriptor,End)
+			   end
+		   end
+	   end;
+	_->
+	    ok
+    end.
+
+%% validate that the range is in the limits of the file
+valid_ranges(RangeList, _Path, FileInfo)->
+    lists:mapfoldl(fun({Start,End},Acc)->
+			case Acc of 
+			    true ->
+				case valid_range(Start,End,FileInfo) of
+				    {true,StartB,EndB,Size}->
+					{{{Start,End},
+					  {StartB,EndB,Size}},true};
+				    _ ->
+					false
+				end;
+			    _ ->
+				{false,false}
+			end
+		   end,true,RangeList).
+				 
+			      
+
+valid_range(from_end,End,FileInfo)->
+    Size=FileInfo#file_info.size,
+    if
+	End < Size ->
+	    {true,(Size+End),Size-1,Size};
+	true ->
+	    false
+    end;
+valid_range(from_start,End,FileInfo)->
+  Size=FileInfo#file_info.size,
+    if
+	End < Size ->
+	    {true,End,Size-1,Size};
+	true ->
+	    false
+    end;
+
+valid_range(Start,End,FileInfo)when Start=<End->
+    case FileInfo#file_info.size of
+	FileSize when Start< FileSize ->
+	    case FileInfo#file_info.size of
+		Size when End<Size ->
+		    {true,Start,End,FileInfo#file_info.size};
+		Size ->
+		    {true,Start,Size-1,Size}
+	    end;
+	_->
+	    {false,"The size of the range is negative"}
+    end;
+		      
+valid_range(_Start,_End,_FileInfo)->
+    {false,"Range starts out of file boundaries"}.
+%% Find the modification date of the file
+get_modification_date(Path)->
+    case file:read_file_info(Path) of
+	{ok, FileInfo0} ->
+	    case (catch httpd_util:rfc1123_date(FileInfo0#file_info.mtime)) of
+		Date when is_list(Date) ->
+		    {FileInfo0, [{last_modified, Date}]};
+		_ ->
+		    {FileInfo0, []}
+	    end;
+	_ ->
+	    {#file_info{}, []}
+    end.
+
+%Calculate the size of the chunk to read
+	
+get_file_chunk_size(Position, End, DefaultChunkSize) 
+  when (Position+DefaultChunkSize) =< End->
+    DefaultChunkSize;
+get_file_chunk_size(Position, End, _DefaultChunkSize)->
+    (End-Position) +1.
+
+
+
+%Get the size of the range to send. Remember that
+%A range is from startbyte up to endbyte which means that
+%the nuber of byte in a range is (StartByte-EndByte)+1
+
+get_range_size(from_end, Stop, _FileInfo)->
+    integer_to_list(-1*Stop);
+
+get_range_size(from_start, StartByte, FileInfo) ->
+    integer_to_list((((FileInfo#file_info.size)-StartByte)));
+
+get_range_size(StartByte, EndByte, _FileInfo) ->
+    integer_to_list((EndByte-StartByte)+1).
+
+parse_ranges("\bytes\=" ++ Ranges)->
+    parse_ranges("bytes\=" ++ Ranges);
+parse_ranges("bytes\=" ++ Ranges)->
+    case string:tokens(Ranges,", ") of
+       [Range] ->
+	   parse_range(Range);
+       [Range1|SplittedRanges]->
+	   {multipart,lists:map(fun parse_range/1,[Range1|SplittedRanges])}
+    end;
+%Bad unit
+parse_ranges(Ranges)->
+    io:format("Bad Ranges : ~p",[Ranges]),
+    error.
+%Parse the range  specification from the request to {Start,End}
+%Start=End : Numreric string | []
+
+parse_range(Range)->
+    format_range(split_range(Range,[],[])).
+format_range({[],BytesFromEnd})->
+    {from_end,-1*(list_to_integer(BytesFromEnd))};
+format_range({StartByte,[]})->    
+    {from_start,list_to_integer(StartByte)};
+format_range({StartByte,EndByte})->        
+    {list_to_integer(StartByte),list_to_integer(EndByte)}.
+%Last case return the splitted range
+split_range([],Current,Other)->
+    {lists:reverse(Other),lists:reverse(Current)};
+
+split_range([$-|Rest],Current,Other)->
+    split_range(Rest,Other,Current);
+
+split_range([N|Rest],Current,End) ->
+    split_range(Rest,[N|Current],End).
+
+send_body(SocketType,Socket,FileDescriptor) ->
+    case file:read(FileDescriptor,?FILE_CHUNK_SIZE) of
+	{ok,Binary} ->
+	    ?DEBUG("send_body -> send another chunk: ~p",[size(Binary)]),
+	    case httpd_socket:deliver(SocketType,Socket,Binary) of
+		socket_closed ->
+		    ?LOG("send_body -> socket closed while sending",[]),
+		    socket_close;
+		_ ->
+		    send_body(SocketType,Socket,FileDescriptor)
+	    end;
+	eof ->
+	    ?DEBUG("send_body -> done with this file",[]),
+	    eof
+    end.



Mime
View raw message