merged cont.
[opensuse:yast-rest-service.git] / webservice / lib / yast / rack / response_validator.rb
1 # Copyright (c) 2010 Novell Inc.
2 #
3 # Based on
4 # http://github.com/accuser/flonch/blob/master/lib/response_validator.rb
5 # Copyright (c) 2009 Matthew Gibbons <mhgibbons@me.com>
6
7 # Permission is hereby granted, free of charge, to any person obtaining a copy
8 # of this software and associated documentation files (the "Software"), to deal
9 # in the Software without restriction, including without limitation the rights
10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 # copies of the Software, and to permit persons to whom the Software is
12 # furnished to do so, subject to the following conditions:
13 #
14 # The above copyright notice and this permission notice shall be included in
15 # all copies or substantial portions of the Software.
16 #
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 # THE SOFTWARE.
24 #
25 require 'tidy'
26
27 module YaST
28   module Rack
29     
30     class ResponseValidator
31       include ViewHelpers::HtmlHelper
32       
33       DEFAULT_TIDY_OPTS = {
34         'char-encoding'     => 'utf8',
35         'indent'            => true,
36         'indent-spaces'     => 2,
37         'tidy-mark'         => false,
38         'wrap'              => 0
39       }
40   
41       attr_accessor :options
42   
43       def initialize(app, options = {})
44         options.stringify_keys!
45         options.reverse_merge! DEFAULT_TIDY_OPTS
46     
47         self.options = options
48     
49         @app = app  
50       end
51   
52       def call(env)
53         status, headers, response = @app.call(env)
54
55         if should_validate? headers, env
56           response = ::Rack::Response.new(validate(response.body, :partial => xhr?(env)), status, headers)
57           response.finish
58         else
59           [ status, headers, response ]
60         end
61       end
62   
63       private
64
65       # whether it is an ajax request
66       def xhr?(env)
67         env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" # RORSCAN_ITL
68       end
69       
70       def should_validate?(headers, env)
71         headers && headers["Content-Type"] && headers["Content-Type"].include?("text/html") && !env["QUERY_STRING"].include?("tidy=no")
72       end
73
74       def validate(content, opts={}) #:nodoc:
75         cooked_content = content
76         if opts[:partial]
77           # insert basic boilerplate that makes a partial page pass thru Tidy
78           first_line = content.each_line.to_a.first || ""
79           if first_line.include?("DOCTYPE")
80             doctype = first_line
81           else
82             doctype = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
83           end
84           # BTW, do not insert newlines here, otherwise line numbers will
85           # not match in the Tidy report
86           cooked_content = "#{doctype}\n<html><head><title>tidy</title></head><body>#{content}</body></html>"
87         end
88
89         Tidy.open(self.options) do |tidy|
90           tidy.clean(cooked_content)
91           unless tidy.errors.empty?
92               content << present_errors(tidy.errors)
93           end
94         end
95         content
96       end
97       
98       def present_errors(s)
99         "<div class='ui-state-error'><p><b>HTML errors:</b></p><pre>#{html_escape(s)}</pre></div>"
100       end
101
102       HTML_ESCAPE       =       { 
103         '&' => '&amp;', 
104         '>' => '&gt;', 
105         '<' => '&lt;', 
106         '"' => '&quot;' 
107       }
108     
109       def html_escape(s)
110         s.to_s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] }
111       end
112     end
113
114   end
115 end