aboutsummaryrefslogtreecommitdiff
blob: b6dca40dfdd67ebad2346516b2d5f38a00f4ce4b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
###   Copyright 2004,   Xavier Neys   (neysx@gentoo.org)
# #
# #   This file is part of gorg.
# #
# #   gorg is free software; you can redistribute it and/or modify
# #   it under the terms of the GNU General Public License as published by
# #   the Free Software Foundation; either version 2 of the License, or
# #   (at your option) any later version.
# #
# #   gorg is distributed in the hope that it will be useful,
# #   but WITHOUT ANY WARRANTY; without even the implied warranty of
# #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# #   GNU General Public License for more details.
# #
# #   You should have received a copy of the GNU General Public License
# #   along with Foobar; if not, write to the Free Software
###   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

# Process CGI request, either from cgi or fcgi

require "gorg/base"

module Gorg
  def do_Filter(tmout=30, params=nil)
    # Read STDIN, transform, spit result out
    timeout(tmout) {
      # Give it a few seconds to read it all, then timeout
      xml = STDIN.read
      err, body, filelist = xproc(xml, params, false, true)
      if err["xmlErrLevel"] > 0 then
        STDERR.puts("#{err.collect{|e|e.join(':')}.join("\n")}")
      elsif (body||"").length < 1 then
        # Some transforms can yield empty content
        STDERR.puts("Empty body")
      else
        STDOUT.puts(body)
      end
    }
  rescue Timeout::Error, StandardError =>ex
    # Just spew it out
    STDERR.puts(ex)
  end
  
  def do_CGI(cgi)
    header = Hash.new
    if cgi.path_info.nil? || cgi.env_table["REQUEST_URI"].index("/#{File.basename($0)}/")
      # Sorry, I'm not supposed to be called directly, e.g. /cgi-bin/gorg.cgi/bullshit_from_smartass_skriptbaby
      raise Gorg::Status::Forbidden
    elsif cgi.request_method == "OPTIONS"
      cgi.out('Allow'=>'GET,HEAD'){""}
    elsif cgi.request_method == "HEAD" or cgi.request_method == "GET"
      # lighttp is b0rked despite what they say :(
      # PATH_INFO == "" and PATH_TRANSLATED == nil
      if cgi.path_info.length > 0 then
        # Apache, or any web browser that works
        path_info = cgi.path_info
      else
        # lighttp, use SCRIPT_NAME instead
        path_info = cgi.env_table['SCRIPT_NAME']
      end
      query = Hash.new
      cgi.params.each do |p, v|
        # fcgi 0.9 always provides an Array even with one
        # argument. Gorg only handles one argument, as far as I can
        # tell, so we take the first value in that case.
        value = if v.class == Array
                  v.first
                else
                  v
                end
        query[p] = value.to_s
      end
      # Get DOCUMENT_ROOT from environment
      $Config["root"] = cgi.env_table['DOCUMENT_ROOT']

      xml_file = cgi.path_translated||(cgi.env_table['DOCUMENT_ROOT']+cgi.env_table['SCRIPT_NAME'])
      if not FileTest.file?(xml_file)
        # Should have been checked by apache, check anyway
        raise Gorg::Status::NotFound
      else
        # Process request
        # Parse If-None-Match and If-Modified-Since request header fields if any
        inm=ims=nil
        begin
          inm = split_header_etags(cgi.env_table['HTTP_IF_NONE_MATCH']) if cgi.env_table['HTTP_IF_NONE_MATCH']
          ims = Time.parse(cgi.env_table['HTTP_IF_MODIFIED_SINCE']) if cgi.env_table['HTTP_IF_MODIFIED_SINCE']
          ims = nil if ims > Time.now # Dates later than current must be ignored
        rescue
          # Just ignore ill-formated data
          nil
        end
        if $Config['passthru'] && query["passthru"] && query["passthru"] != "0" then
          # passthru allowed by config and requested by visitor, return file as text/plain
          debug("Passthru granted for #{path_info}")
          mstat = File.stat(xml_file)
          raise Gorg::Status::NotModified.new(mstat) if notModified?(mstat, inm, ims)
          body = IO.read(xml_file)
          header['type'] = 'text/plain'
          # If client accepts gzip encoding and we support it, return gzipped file
          if $Config["zipLevel"] > 0 and ( cgi.accept_encoding =~ /gzip(\s*;\s*q=([0-9\.]+))?/ and ($2||"1") != "0" ) then
            body = gzip(body, $Config["zipLevel"])
            header['Content-Encoding'] = "gzip"
            header['Vary'] = "Accept-Encoding"
          end
        else
          # Get cookies and add them to the parameters
          if $Config["acceptCookies"] then
            # Add cookies to our params
            query.merge!(cookies_to_params(cgi.cookies))
          end

          if $Config["httphost"] then
            # Add HTTP_HOST to stylesheet params
            query["httphost"] = if $Config["httphost"][0] == '*' then
                                  cgi.host||""
                                elsif $Config["httphost"].include?('*') then
                                  $Config["httphost"][0]
                                elsif $Config["httphost"].include?(cgi.host) then
                                  $Config["httphost"][0]
                                else
                                  cgi.host||""
                                end
          end

          xml_query = query.dup # xml_query==params passed to the XSL, query=>metadata in cache
          if $Config["linkParam"] then
            xml_query[$Config["linkParam"]] = path_info
          end

          bodyZ = nil # Compressed version
          body, mstat, extrameta = Cache.hit(path_info, query, inm, ims)
          if body.nil? then
            # Cache miss, process file and cache result
            err, body, filelist, extrameta = xproc(xml_file, xml_query, true)
            if err["xmlErrLevel"] > 0 then
              raise "#{err.collect{|e|e.join(':')}.join('<br/>')}"
            elsif (body||"").length < 1 then
              # Some transforms can yield empty content (handbook?part=9&chap=99)
              # Consider this a 404
              raise Gorg::Status::NotFound
            else
              # Cache the output if all was OK
              mstat, bodyZ = Cache.store(body, path_info, query, filelist, extrameta)
              debug("Cached #{path_info}, mstat=#{mstat.inspect}")
              # Check inm & ims again as they might match if another web node had
              # previously delivered the same data
              if notModified?(mstat, inm, ims) and extrameta.join !~ /set-cookie/i
                raise Gorg::Status::NotModified.new(mstat)
              end
            end
          else
            if $Config["zipLevel"] > 0 then
              bodyZ = body
              body = nil
            end
          end
          # If client accepts gzip encoding and we support it, return gzipped file
          if bodyZ and $Config["zipLevel"] > 0 and ( cgi.accept_encoding =~ /gzip(\s*;\s*q=([0-9\.]+))?/ and ($2||"1") != "0" ) then
            body = bodyZ
            header['Content-Encoding'] = "gzip"
            header['Vary'] = "Accept-Encoding"
          else
            unless body then
              # We need to unzip bodyZ into body, i.e. we cached zipped data but client does not support gzip
              body = gunzip(bodyZ)
            end
          end
          # Add cookies to http header
          cookies = makeCookies(extrameta)
          if cookies then
            header['cookie'] = cookies
          end
          # Add Content-Type to header
          ct = contentType(extrameta)
          if ct then
            # Turn application/xhtml+xml into text/html if browser does not accept it
            if cgi.accept !~ /application\/xhtml\+xml/ and ct =~ /application\/xhtml\+xml(.*)$/ then
              header['type'] = "text/html#{$1}"
            else
              header['type'] = ct
            end
          else
            header['type'] = 'text/plain'
          end
        end
        # Add ETag & Last-Modified http headers
        # NB: it's simply mstat(file.xml) when passthru=1
        if mstat then
          header['ETag'] = makeETag(mstat)
          header['Last-Modified'] = mstat.mtime.httpdate
        end
      end
      cgi.out(header){body} 
    else # Not a HEAD or GET
      raise Gorg::Status::NotAllowed
    end
  rescue => ex
    if ex.respond_to?(:errCode) then
      # One of ours (Gorg::Status::HTTPStatus)
      cgi.out(ex.header){ex.html}
    else
      # Some ruby exceptions occurred, make it a 500
      syserr = Gorg::Status::SysError.new
      cgi.out('Status'=>syserr.errSts){syserr.html(ex)}
      error("do_CGI() failed: #{$!}")
    end
  end
end