1#--
2# accesslog.rb -- Access log handling utilities
3#
4# Author: IPR -- Internet Programming with Ruby -- writers
5# Copyright (c) 2002 keita yamaguchi
6# Copyright (c) 2002 Internet Programming with Ruby writers
7#
8# $IPR: accesslog.rb,v 1.1 2002/10/01 17:16:32 gotoyuzo Exp $
9
10module WEBrick
11
12  ##
13  # AccessLog provides logging to various files in various formats.
14  #
15  # Multiple logs may be written to at the same time:
16  #
17  #   access_log = [
18  #     [$stderr, WEBrick::AccessLog::COMMON_LOG_FORMAT],
19  #     [$stderr, WEBrick::AccessLog::REFERER_LOG_FORMAT],
20  #   ]
21  #
22  #   server = WEBrick::HTTPServer.new :AccessLog => access_log
23  #
24  # Custom log formats may be defined.  WEBrick::AccessLog provides a subset
25  # of the formatting from Apache's mod_log_config
26  # http://httpd.apache.org/docs/mod/mod_log_config.html#formats.  See
27  # AccessLog::setup_params for a list of supported options
28
29  module AccessLog
30
31    ##
32    # Raised if a parameter such as %e, %i, %o or %n is used without fetching
33    # a specific field.
34
35    class AccessLogError < StandardError; end
36
37    ##
38    # The Common Log Format's time format
39
40    CLF_TIME_FORMAT     = "[%d/%b/%Y:%H:%M:%S %Z]"
41
42    ##
43    # Common Log Format
44
45    COMMON_LOG_FORMAT   = "%h %l %u %t \"%r\" %s %b"
46
47    ##
48    # Short alias for Common Log Format
49
50    CLF                 = COMMON_LOG_FORMAT
51
52    ##
53    # Referer Log Format
54
55    REFERER_LOG_FORMAT  = "%{Referer}i -> %U"
56
57    ##
58    # User-Agent Log Format
59
60    AGENT_LOG_FORMAT    = "%{User-Agent}i"
61
62    ##
63    # Combined Log Format
64
65    COMBINED_LOG_FORMAT = "#{CLF} \"%{Referer}i\" \"%{User-agent}i\""
66
67    module_function
68
69    # This format specification is a subset of mod_log_config of Apache:
70    #
71    # %a:: Remote IP address
72    # %b:: Total response size
73    # %e{variable}:: Given variable in ENV
74    # %f:: Response filename
75    # %h:: Remote host name
76    # %{header}i:: Given request header
77    # %l:: Remote logname, always "-"
78    # %m:: Request method
79    # %{attr}n:: Given request attribute from <tt>req.attributes</tt>
80    # %{header}o:: Given response header
81    # %p:: Server's request port
82    # %{format}p:: The canonical port of the server serving the request or the
83    #              actual port or the client's actual port.  Valid formats are
84    #              canonical, local or remote.
85    # %q:: Request query string
86    # %r:: First line of the request
87    # %s:: Request status
88    # %t:: Time the request was recieved
89    # %T:: Time taken to process the request
90    # %u:: Remote user from auth
91    # %U:: Unparsed URI
92    # %%:: Literal %
93
94    def setup_params(config, req, res)
95      params = Hash.new("")
96      params["a"] = req.peeraddr[3]
97      params["b"] = res.sent_size
98      params["e"] = ENV
99      params["f"] = res.filename || ""
100      params["h"] = req.peeraddr[2]
101      params["i"] = req
102      params["l"] = "-"
103      params["m"] = req.request_method
104      params["n"] = req.attributes
105      params["o"] = res
106      params["p"] = req.port
107      params["q"] = req.query_string
108      params["r"] = req.request_line.sub(/\x0d?\x0a\z/o, '')
109      params["s"] = res.status       # won't support "%>s"
110      params["t"] = req.request_time
111      params["T"] = Time.now - req.request_time
112      params["u"] = req.user || "-"
113      params["U"] = req.unparsed_uri
114      params["v"] = config[:ServerName]
115      params
116    end
117
118    ##
119    # Formats +params+ according to +format_string+ which is described in
120    # setup_params.
121
122    def format(format_string, params)
123      format_string.gsub(/\%(?:\{(.*?)\})?>?([a-zA-Z%])/){
124         param, spec = $1, $2
125         case spec[0]
126         when ?e, ?i, ?n, ?o
127           raise AccessLogError,
128             "parameter is required for \"#{spec}\"" unless param
129           (param = params[spec][param]) ? escape(param) : "-"
130         when ?t
131           params[spec].strftime(param || CLF_TIME_FORMAT)
132         when ?p
133           case param
134           when 'remote'
135             escape(params["i"].peeraddr[1].to_s)
136           else
137             escape(params["p"].to_s)
138           end
139         when ?%
140           "%"
141         else
142           escape(params[spec].to_s)
143         end
144      }
145    end
146
147    ##
148    # Escapes control characters in +data+
149
150    def escape(data)
151      if data.tainted?
152        data.gsub(/[[:cntrl:]\\]+/) {$&.dump[1...-1]}.untaint
153      else
154        data
155      end
156    end
157  end
158end
159