Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 434
- Log:
Fix breakage of graphical revisions by character set conversion of
all data from the CVS viewer script; just parse text/* types.Switch from forced HTTP fetching of related resources to forced
HTTPS fetching, with certificate chain support.
- Author:
- rool
- Date:
- Fri Aug 30 03:16:00 +0100 2013
- Size:
- 7938 Bytes
1 | # RCVSweb - a Ruby On Rails wrapper around the Perl-based FreeBSD |
2 | # version of the CVSweb and Python-based CVShistory. |
3 | # |
4 | # See "http://www.freebsd.org/projects/cvsweb.html" and |
5 | # "http://www.jamwt.com/CVSHistory/" |
6 | # |
7 | # This wrapper was created for the sole purpose of embedding CVSweb |
8 | # output into a Rails-provided layout. This layout is shared between |
9 | # different Rails applications on one host. Using the wrapper means |
10 | # that it is not necessary to create a derived copy of the layout |
11 | # expressed in a form that CVSweb understands - instead, the layout |
12 | # can be used directly. |
13 | # |
14 | # Later extensions to the application gave it the ability to wrap |
15 | # CVShistory output too. |
16 | |
17 | class ApplicationController < ActionController::Base |
18 | |
19 | require 'iconv' |
20 | |
21 | # Hub single sign-on support. |
22 | |
23 | require 'hub_sso_lib' |
24 | include HubSsoLib::Core |
25 | before_filter :hubssolib_beforehand |
26 | after_filter :hubssolib_afterwards |
27 | |
28 | # Turn of session management. |
29 | |
30 | session :off |
31 | |
32 | # The root URL action. |
33 | # |
34 | def index |
35 | redirect_to url_for(:controller => 'rcvsweb', :action => 'run') |
36 | end |
37 | |
38 | private |
39 | |
40 | # Pass the fully qualified pathname of the script that is to be |
41 | # executed and a path prefix from routing (e.g. "/view"). |
42 | |
43 | def capture_script_output(script_location, extra_prefix) |
44 | # Get the request URI in a way that works for FCGI and regular |
45 | # CGI, at least for LigHTTPd. Strip off the root path prefix |
46 | # (location of the Rails application) if present. |
47 | |
48 | uri = @request.env['REQUEST_URI'].dup # NOT a full URI |
49 | uri.slice!(root_path()) |
50 | |
51 | # Split off the query string section, if there is one. |
52 | |
53 | (path_info, query) = uri.split('?') |
54 | |
55 | path_info = URI.decode(path_info) unless path_info.nil? |
56 | query = URI.decode(query) unless query.nil? |
57 | |
58 | # The CGI script expects certain variables to be set up in a |
59 | # certain way. "Slow" CGI does this but FastCGI does not because |
60 | # the script executes under a different process environment |
61 | # entirely, without the benefit of server-set variables. We must |
62 | # therefore emulate the required environment by setting system |
63 | # variables before executing the CVSweb script. |
64 | |
65 | command = '' |
66 | needed = %w( |
67 | HTTP_USER_AGENT HTTP_ACCEPT_ENCODING |
68 | MOD_PERL PATH_INFO |
69 | SCRIPT_NAME SCRIPT_FILENAME |
70 | QUERY_STRING SERVER_PROTOCOL |
71 | SERVER_PORT SERVER_NAME |
72 | ) |
73 | |
74 | needed.each do |key| |
75 | |
76 | # Override certain items where we know we want a particular |
77 | # result, else use a server-set value if there is one. |
78 | |
79 | case key |
80 | when 'SCRIPT_NAME' |
81 | value = root_path().chop + extra_prefix |
82 | when 'SCRIPT_FILENAME' |
83 | value = "#{RAILS_ROOT}/public/dispatch.cgi" |
84 | when 'PATH_INFO' |
85 | value = path_info || '' |
86 | value = '/' + value unless (value[0] == '/') |
87 | value = value[extra_prefix.length..-1] if (value[0..(extra_prefix.length - 1)] == extra_prefix) |
88 | when 'QUERY_STRING' |
89 | value = query |
90 | else |
91 | value = @request.env[key] || '' |
92 | end |
93 | |
94 | # Add the variable initialisation statement to the command string. |
95 | |
96 | command += "#{key}=\"#{value}\" " |
97 | |
98 | end # From needed.each |
99 | |
100 | # Add the CVSweb command to the command string and execute it. |
101 | # Return the output of the command; assume Latin-1 and convert |
102 | # to UTF-8 in passing. |
103 | |
104 | command += "#{script_location}" |
105 | data = `#{command}` |
106 | |
107 | # I searched for nearly two hours through endless documentation about |
108 | # a ridiculous number of classes and methods related to HTTP, but not |
109 | # one single thing just took a string and parsed it as an HTTP response, |
110 | # or even just parsed HTTP headers. |
111 | # |
112 | # There may be a way but I gave up in the face of poor documentation and |
113 | # an excess of often obtuse different ways of doing the same thing over |
114 | # and over, with nothing providing the simple function I wanted. There's |
115 | # a gem that does it, but I don't want extra dependencies - all the HTTP |
116 | # header parsing code is all there, it's just locked away behind a |
117 | # labyrinth of similar named classes and dodgy APIs. |
118 | # |
119 | # All I want is the content type, but reliably! |
120 | # |
121 | # Thus, unreliable hack is forced. |
122 | |
123 | headers = data.split("\r\n\r\n", 2).first || '' |
124 | headers = headers.downcase.split("\r\n") || [] # Don't care about multiline headers here |
125 | parsed = {} |
126 | |
127 | headers.each do | header | |
128 | (key, value) = header.split(':', 2) |
129 | parsed[key] = value.strip |
130 | end |
131 | |
132 | type = parsed['content-type'] |
133 | |
134 | if ( ! type.nil? && type[0..4] == 'text/' ) |
135 | return Iconv.conv("UTF8", "ISO-8859-1", data) |
136 | else |
137 | return data |
138 | end |
139 | end |
140 | |
141 | # Parse script output - pass the raw output data from the script and a |
142 | # filename to use in the event that the output isn't of a recognised type |
143 | # and has to be sent raw to the client. |
144 | |
145 | def parse_script_output(output, filename) |
146 | # The Views expect to use the @output instance variable, so we operate |
147 | # on that from the beginning. |
148 | |
149 | @output = output |
150 | |
151 | # The command should have included HTTP headers; split the two. |
152 | |
153 | pos = @output.index("\r\n\r\n") |
154 | short = true unless pos |
155 | pos = @output.index("\n\n") unless pos |
156 | headstr = @output.slice!(0..pos + (short ? 1 : 3)) if pos |
157 | headers = {} |
158 | |
159 | if (headstr) |
160 | |
161 | # There are indeed some headers. Create a hash from them. |
162 | |
163 | headstr.split(short ? "\n" : "\r\n").each do |str| |
164 | pos = str.index(':') |
165 | headers[str.slice!(0..pos - 1).strip.downcase] = str[1..-1].strip if (pos > 1) |
166 | end |
167 | |
168 | # If we find a Status header with a 300-series code, check for a |
169 | # Location header too. If found, redirect to that location. |
170 | |
171 | if (headers['status']) |
172 | code = headers['status'].to_i |
173 | |
174 | if (code >= 300 and code < 400 and headers['location']) |
175 | redirect_to headers['location'] |
176 | return |
177 | end |
178 | end |
179 | |
180 | # For a content type of 'text/html', render within a View. Otherwise |
181 | # send the data directly without a surrounding template. |
182 | |
183 | if ([ 'text/html', 'text/x-html' ].include? headers['content-type']) |
184 | |
185 | # Almost there - extract a title if we can, and chop off the header |
186 | # and footer (HTML prologue and epilogue) to attempt to produce |
187 | # valid (X)HTML. The version of CVSweb in use at the time of writing |
188 | # always writes body and title container tags in lower case which |
189 | # helps save a bit of effort. |
190 | |
191 | title_tag = @output.slice(/<title.*\/title>/) |
192 | title_tag.gsub!(/<title.*?>/, '') |
193 | @title = title_tag.gsub(/<\/title>/, '') || 'CVS Repository' |
194 | |
195 | # Chop everything from the front of the output string up to the end |
196 | # of the opening body tag, inclusive. |
197 | |
198 | body_tag = @output.slice(/<body.*?>/) |
199 | body_pos = @output.index('<body') |
200 | |
201 | if (body_pos && body_tag && body_tag.length > 0) |
202 | @output.slice!(0..body_pos + body_tag.length - 1) |
203 | end |
204 | |
205 | # Chop off anything after the closing body tag too. |
206 | |
207 | body_pos = @output.index('</body') |
208 | @output.slice!(body_pos..-1) if body_pos |
209 | |
210 | # Render the default layout to send the template-based output. |
211 | |
212 | render :layout => 'default' |
213 | |
214 | else |
215 | |
216 | # Apparently, not HTML; send the data directly. |
217 | |
218 | type = headers['content-type'] || 'application/octet-stream' |
219 | send_data @output, |
220 | :type => type, |
221 | :disposition => 'inline' |
222 | end |
223 | |
224 | else |
225 | |
226 | # CVSweb output had no HTTP header - this is unexpected. We don't |
227 | # understand its output so just send this to the browser as a |
228 | # stream of binary data. |
229 | |
230 | send_data @output, |
231 | :filename => filename, |
232 | :type => 'application/octet-stream' |
233 | end |
234 | end |
235 | end |