%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 2008-2021. All Rights Reserved.
%% 
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%% 
%% %CopyrightEnd%
%%
%%
-module(httpd_log).

-include("httpd.hrl").

-export([access_entry/8, error_entry/5, error_report_entry/5, 
	 security_entry/5]).


%%%=========================================================================
%%%  Internal Application API 
%%%=========================================================================

-spec access_entry(Log        :: term(), % Id of the log
		   NoLog      :: term(), % What to return when no log is found
		   Info       :: #mod{},
		   RFC931     :: string(),
		   AuthUser   :: string(), 
		   Date       :: string(), 
		   StatusCode :: pos_integer(),
		   Size       :: 0 | pos_integer() | string()) ->
			  {Log :: atom() | pid(), Entry :: string()} | term() .

%% Somethime the size in the form of the content_length is put here, which
%% is actually in the form of a string
%% So it can either be the size as an integer, the size as a string
%% or, worst case scenario, bytes.
access_entry(Log, NoLog, Info, RFC931, AuthUser, Date, StatusCode, 
	     SizeStrOrBytes) 
  when is_list(SizeStrOrBytes) ->
    Size = 
	case (catch list_to_integer(SizeStrOrBytes)) of
	    I when is_integer(I) ->
		%% This is from using the content_length (which is a string)
		I;
	    _ ->
		%% This is better than nothing
		erlang:iolist_size(SizeStrOrBytes)
	end,
    access_entry(Log, NoLog, Info, RFC931, AuthUser, Date, StatusCode, Size);
access_entry(Log, NoLog, 
	     #mod{config_db     = ConfigDB, 
		  init_data     = #init_data{peername = {_, RemoteHost}}, 
		  request_line  = RequestLine,
		  parsed_header = Headers}, 
	     RFC931, AuthUser, Date, StatusCode, Size) ->
    MakeEntry = 
	fun() ->
		do_access_entry(ConfigDB, Headers, RequestLine, 
				RemoteHost, RFC931, AuthUser,
				Date, StatusCode, Size)
	end,
    log_entry(Log, NoLog, ConfigDB, MakeEntry).


-spec error_entry(Log    :: term(), % Id of the log
		  NoLog  :: term(), % What to return when no log is found
		  Info   :: #mod{},
		  Date   :: string(), 
		  Reason :: term()) ->
			 {Log :: atom() | pid(), Entry :: string()} | term().

error_entry(Log, NoLog, 
	    #mod{config_db   = ConfigDB,
		 init_data   = #init_data{peername = {_, RemoteHost}}, 
		 request_uri = URI}, Date, Reason) ->
    MakeEntry = 
	fun() ->
		do_error_entry(ConfigDB, RemoteHost, URI, Date, Reason)	
	end,
    log_entry(Log, NoLog, ConfigDB, MakeEntry).


-spec error_report_entry(Log      :: term(), 
			 NoLog    :: term(), 
			 ConfigDB :: term(),
			 Date     :: string(), 
			 ErrroStr :: string()) ->
    {Log :: atom() | pid(), Entry :: string()} | term().

error_report_entry(Log, NoLog, ConfigDb, Date, ErrorStr) ->
    MakeEntry = fun() -> io_lib:format("[~s], ~s~n", [Date, ErrorStr]) end,
    log_entry(Log, NoLog, ConfigDb, MakeEntry).


-spec security_entry(Log      :: term(), 
		     NoLog    :: term(), 
		     ConfigDB :: term(),
		     Date     :: string(), 
		     Reason   :: term()) ->
    {Log :: atom() | pid(), Entry :: string()} | term().

security_entry(Log, NoLog, #mod{config_db = ConfigDB}, Date, Reason) ->
    MakeEntry = fun() -> io_lib:format("[~s] ~s~n", [Date, Reason]) end,
    log_entry(Log, NoLog, ConfigDB, MakeEntry).


log_entry(Log, NoLog, ConfigDb, MakeEntry) when is_function(MakeEntry) ->
    case httpd_util:lookup(ConfigDb, Log) of
	undefined ->
	    NoLog;
	LogRef ->
	    {LogRef, MakeEntry()}
    end.
   
    
%%%========================================================================
%%% Internal functions
%%%========================================================================

do_access_entry(ConfigDB, Headers, RequestLine,
		RemoteHost, RFC931, AuthUser, Date, StatusCode,
		Size) ->
    case httpd_util:lookup(ConfigDB, log_format, common) of
	common ->
	    lists:flatten(io_lib:format("~s ~s ~s [~s] \"~s\" ~w ~w~n",
			  [RemoteHost, RFC931, AuthUser, Date,
			   RequestLine, 
			   StatusCode, Size]));
	combined ->
	    Referer = 
		proplists:get_value("referer", Headers, "-"),
	    UserAgent = 
		proplists:get_value("user-agent", 
				    Headers, "-"),
	    io_lib:format("~s ~s ~s [~s] \"~s\" ~w ~w ~s ~s~n",
			  [RemoteHost, RFC931, AuthUser, Date,
			   RequestLine, StatusCode, Size, 
			   Referer, UserAgent])
    end.


do_error_entry(ConfigDB, RemoteHost, undefined, Date, Reason) ->
     case httpd_util:lookup(ConfigDB, error_log_format, pretty) of
	pretty ->
	   io_lib:format("[~s] server crash for ~s, reason: ~n~p~n~n", 
			 [Date, RemoteHost, Reason]);
	compact ->
	   io_lib:format("[~s] server crash for ~s, reason: ~w~n", 
			 [Date, RemoteHost, Reason])
	     
    end;
    
do_error_entry(ConfigDB, RemoteHost, URI, Date, Reason) ->
    case httpd_util:lookup(ConfigDB, error_log_format, pretty) of
	pretty ->
	   io_lib:format("[~s] access to ~s failed for ~s, reason: ~n~p~n",
			 [Date, URI, RemoteHost, Reason]);
	compact ->
	   io_lib:format( "[~s] access to ~s failed for ~s, reason: ~w~n", 
			 [Date, URI, RemoteHost, Reason])	     
    end.
