vidibus-xss 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 André Pankratz
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,29 @@
1
+ = vidibus-xss
2
+
3
+ This gem is part of the open source SOA framework Vidibus: http://www.vidibus.org
4
+
5
+ TODO: describe
6
+
7
+
8
+ == Installation
9
+
10
+ Add the dependency to the Gemfile of your application:
11
+
12
+ gem "vidibus-xss"
13
+
14
+ Then call bundle install on your console.
15
+
16
+
17
+ == Usage
18
+
19
+ TODO: describe
20
+
21
+
22
+ == Copyright
23
+
24
+ Copyright (c) 2010 Andre Pankratz. See LICENSE for details.
25
+
26
+
27
+ == Thank you!
28
+
29
+ The development of this gem was sponsored by Käuferportal: http://www.kaeuferportal.de
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require "rubygems"
2
+ require "rake"
3
+
4
+ begin
5
+ require "jeweler"
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "vidibus-xss"
8
+ gem.summary = %Q{Drop-in XSS support for remote applications.}
9
+ gem.description = %Q{Drop-in XSS support for remote applications.}
10
+ gem.email = "andre@vidibus.com"
11
+ gem.homepage = "http://github.com/vidibus/vidibus-xss"
12
+ gem.authors = ["Andre Pankratz"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ gem.add_development_dependency "relevance-rcov"
15
+ gem.add_development_dependency "rr"
16
+ gem.add_dependency "rails", ">= 3.0.0.rc"
17
+ gem.add_dependency "nokogiri"
18
+ gem.add_dependency "vidibus-routing_error"
19
+
20
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
21
+ end
22
+ Jeweler::GemcutterTasks.new
23
+ rescue LoadError
24
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
25
+ end
26
+
27
+ require "spec/rake/spectask"
28
+ Spec::Rake::SpecTask.new(:spec) do |spec|
29
+ spec.libs << "lib" << "spec"
30
+ spec.spec_files = FileList["spec/**/*_spec.rb"]
31
+ end
32
+
33
+ Spec::Rake::SpecTask.new(:rcov) do |t|
34
+ t.spec_files = FileList["spec/vidibus/**/*_spec.rb"]
35
+ t.rcov = true
36
+ t.rcov_opts = ["--exclude", "^spec,/gems/"]
37
+ end
38
+
39
+ task :spec => :check_dependencies
40
+ task :default => :spec
41
+
42
+ require "rake/rdoctask"
43
+ Rake::RDocTask.new do |rdoc|
44
+ version = File.exist?("VERSION") ? File.read("VERSION") : ""
45
+ rdoc.rdoc_dir = "rdoc"
46
+ rdoc.title = "vidibus-xss #{version}"
47
+ rdoc.rdoc_files.include("README*")
48
+ rdoc.rdoc_files.include("lib/**/*.rb")
49
+ rdoc.options << "--charset=utf-8"
50
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.4
@@ -0,0 +1,7 @@
1
+ class XssController < ApplicationController
2
+ unloadable
3
+
4
+ def load
5
+ render :path => params[:path], :format => :xss
6
+ end
7
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do |map|
2
+ match "xss/:path" => "xss#load"
3
+ end
@@ -0,0 +1,295 @@
1
+ require "nokogiri"
2
+
3
+ module Vidibus
4
+ module Xss
5
+ module Extensions
6
+ module Controller
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ helper_method :url_for, :xss_request?, :fullpath_url
11
+ #layout :get_layout
12
+
13
+ respond_to :html, :xss
14
+
15
+ rescue_from ActionController::RoutingError, :with => :rescue_404
16
+ end
17
+
18
+ protected
19
+
20
+ # Returns layout for current request format.
21
+ def get_layout(format = nil)
22
+ (xss_request? or format == :xss) ? 'xss.haml' : 'application'
23
+ end
24
+
25
+ #
26
+ # IMPORTANT: restart server to apply modifications.
27
+ def rescue_404
28
+ return if respond_to_options_request
29
+ super
30
+ #render :template => "shared/404", :status => 404
31
+ end
32
+
33
+ # Responds to OPTIONS request.
34
+ # When sending data to foreign domain by AJAX, Firefox will send an OPTIONS request first.
35
+ #
36
+ # == Usage:
37
+ #
38
+ # Set up a catch-all route for handling 404s like this, if you haven't done it already:
39
+ #
40
+ # match "*path" => "application#rescue_404"
41
+ #
42
+ # In ApplicationController, define a method that will be called by this catch-all route:
43
+ #
44
+ # def rescue_404
45
+ # respond_to_options_request
46
+ # end
47
+ #
48
+ def respond_to_options_request
49
+ return unless options_request?
50
+ xss_access_control_headers
51
+ render(:text => "OK", :status => 200) and return true
52
+ end
53
+
54
+ # Returns true if current request is in XSS format.
55
+ def xss_request?
56
+ @is_xss ||= begin
57
+ if request.format == :xss
58
+ true
59
+ elsif request.format == "*/*"
60
+ if env["REQUEST_URI"].match(/[^\?]+\.xss/) # try to detect format by file extension
61
+ true
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ # Returns true if the current request is an OPTIONS request.
68
+ def options_request?
69
+ @is_options_request ||= request.method == "OPTIONS"
70
+ end
71
+
72
+ # Set access control headers to allow cross-domain XMLHttpRequest calls.
73
+ # For more information, see: https://developer.mozilla.org/En/HTTP_access_control
74
+ def xss_access_control_headers
75
+ headers["Access-Control-Allow-Origin"] = "*"
76
+ headers["Access-Control-Allow-Methods"] = "GET, PUT, POST, OPTIONS"
77
+ end
78
+
79
+ def extract_xss_html(dom)
80
+ dom.css('body').first.inner_html
81
+ end
82
+
83
+ # Extracts javascript resources from given DOM object.
84
+ #
85
+ # Usage:
86
+ #
87
+ # dom = Nokogiri::HTML(<html></html>)
88
+ # urls = extract_xss_javascripts(dom)
89
+ #
90
+ def extract_xss_javascripts(dom)
91
+ resources = []
92
+ for resource in dom.css('head script[type="text/javascript"]')
93
+ path = resource.attributes["src"].value
94
+ file = url_for(path, :only_path => false)
95
+ resources << { :type => "text/javascript", :src => file }
96
+ end
97
+ resources
98
+ end
99
+
100
+ # Extracts stylesheet resources from given DOM object.
101
+ #
102
+ # Usage:
103
+ #
104
+ # dom = Nokogiri::HTML(<html></html>)
105
+ # urls = extract_xss_stylesheets(dom)
106
+ #
107
+ def extract_xss_stylesheets(dom)
108
+ resources = []
109
+ for resource in dom.css('head link[type="text/css"]')
110
+ path = resource.attributes["href"].value
111
+ file = url_for(path, :only_path => false)
112
+ media = resource.attributes["media"].value
113
+ resources << { :type => "text/css", :src => file, :media => media }
114
+ end
115
+ resources
116
+ end
117
+
118
+ # Renders given content string to XSS hash of resources and content.
119
+ # If html content is given, the method tries to extract title,
120
+ # stylesheets and javascripts from head and content from body.
121
+ # TODO: Allow script blocks! Add them to body?
122
+ # TODO: Allow style blocks?
123
+ # TODO: Check for html content
124
+ def render_to_xss(content)
125
+ dom = Nokogiri::HTML(content)
126
+ {
127
+ :resources => extract_xss_javascripts(dom) + extract_xss_stylesheets(dom),
128
+ :content => extract_xss_html(dom)
129
+ }
130
+ end
131
+
132
+ # Redirect to a xss url.
133
+ # For POST request, invoke callback action.
134
+ # For GET request, render redirect action directly.
135
+ def redirect_xss(url)
136
+ if request.method == "POST" # request.post? does not work??
137
+ render_xss_callback(:redirect, :to => url)
138
+ else
139
+ render_xss(:redirect => url)
140
+ end
141
+ end
142
+
143
+ # Sends data to callback handler.
144
+ # Inspired by:
145
+ # http://paydrotalks.com/posts/45-standard-json-response-for-rails-and-jquery
146
+ def render_xss_callback(type, options = {})
147
+ unless [ :ok, :redirect, :error ].include?(type)
148
+ raise "Invalid XSS response type: #{type}"
149
+ end
150
+
151
+ data = {
152
+ :status => type,
153
+ :html => nil,
154
+ :message => nil,
155
+ :to => nil
156
+ }.merge(options)
157
+
158
+ render_xss(:callback => data)
159
+ end
160
+
161
+ # Main method for rendering XSS.
162
+ # Renders given XSS resources and content to string and sets it as response_body.
163
+ def render_xss(options = {})
164
+ resources = options.delete(:resources)
165
+ content = options.delete(:content)
166
+ path = options.delete(:get)
167
+ redirect = options.delete(:redirect)
168
+ callback = options.delete(:callback)
169
+
170
+ raise "Please provide :content, :get, :redirect or :callback." unless content or path or redirect or callback
171
+ raise "Please provide either :content to render or :get location to load. Not both." if content and path
172
+
173
+ xss = ""
174
+
175
+ # determine scope
176
+ if !(scope = params[:scope]).blank?
177
+ scope = "$('##{scope}')"
178
+ else
179
+ scope = "$s#{xss_random_string}"
180
+ xss << %(var #{scope}=vidibus.xss.detectScope();)
181
+ end
182
+
183
+ # set host for current scope
184
+ xss << %(vidibus.xss.setHost('#{request.protocol}#{request.host_with_port}',#{scope});)
185
+
186
+ # render load invocations of XSS resources
187
+ if resources and resources.any?
188
+ xss << %(vidibus.loader.load(#{resources.to_json},'#{params[:scope]}');)
189
+ defer = true
190
+ end
191
+
192
+ # render XSS content
193
+ xss_content = begin
194
+ if !content.blank?
195
+ %(vidibus.xss.embed(#{content.escape_xss.to_json},#{scope});)
196
+ elsif path
197
+ %(vidibus.xss.get('#{path}',#{scope});)
198
+ elsif redirect
199
+ %(vidibus.xss.redirect('#{redirect}',#{scope});)
200
+ elsif callback
201
+ %(vidibus.xss.callback(#{callback.to_json},#{scope});)
202
+ end
203
+ end
204
+
205
+ # wait until resources have been loaded, before rendering XSS content
206
+ if defer
207
+ function_name = "rx#{xss_random_string}"
208
+ xss_content = %(var #{function_name}=function(){if(vidibus.loader.complete){#{xss_content}}else{window.setTimeout('#{function_name}()',100);}};#{function_name}();)
209
+ end
210
+ xss << xss_content
211
+ xss_access_control_headers
212
+ self.status = 200 # force success status
213
+ self.response_body = xss
214
+ end
215
+
216
+ # Generates random string for current cycle.
217
+ def xss_random_string
218
+ @xss_random_string ||= begin
219
+ random = ''
220
+ 3.times { random << rand(1000).to_s }
221
+ random
222
+ end
223
+ end
224
+
225
+
226
+ ### Override core extensions
227
+
228
+
229
+ # Bypasses authenticity verification for XSS requests.
230
+ # TODO: Verify authenticity in other ways (Single Sign-on).
231
+ def verify_authenticity_token
232
+ xss_request? || super
233
+ end
234
+
235
+ # Extension of url_for:
236
+ # Transform given relative paths into absolute urls.
237
+ #
238
+ # Usage:
239
+ # url_for("/stylesheets/vidibus.css", :only_path => false)
240
+ #
241
+ def url_for(*args)
242
+ options, special = args
243
+ if options.is_a?(String) and special and special[:only_path] == false
244
+ unless options =~ /\Ahttp/
245
+ options = "#{request.protocol}#{request.host_with_port}#{options}"
246
+ end
247
+ end
248
+ super(options)
249
+ end
250
+
251
+ # Chatches redirect calls for XSS locations.
252
+ # If a XSS location is given, XSS content will be rendered instead of redirecting.
253
+ #
254
+ # == Usage:
255
+ #
256
+ # respond_to do |format|
257
+ # format.xss { redirect_to forms_path }
258
+ # end
259
+ #
260
+ def redirect_to(*args)
261
+ if xss_request?
262
+ redirect_xss(args.first)
263
+ else
264
+ super
265
+ end
266
+ end
267
+
268
+ # Extensions of render method:
269
+ # Renders XSS response if requested
270
+ def render(*args, &block)
271
+ args << options = args.extract_options!
272
+ if xss_request? or options[:format] == :xss
273
+
274
+ # embed xss.get
275
+ if path = options[:path]
276
+ content = render_to_string(:template => "layouts/#{get_layout(:xss)}")
277
+ xss = render_to_xss(content)
278
+ xss[:get] = "/#{path}"
279
+ xss.delete(:content) # Ensure that not content will be embedded!
280
+
281
+ # embed xss content
282
+ else
283
+ content = render_to_string(*args, &block)
284
+ xss = render_to_xss(content)
285
+ end
286
+
287
+ render_xss(xss)
288
+ else
289
+ super(*args, &block)
290
+ end
291
+ end
292
+ end
293
+ end
294
+ end
295
+ end
@@ -0,0 +1,22 @@
1
+ module Vidibus
2
+ module Xss
3
+ module Extensions
4
+ module String
5
+
6
+ # Prepares XSS content for rendering.
7
+ def escape_xss
8
+ regexp = {
9
+ /^\/\/.+$/ => '', # remove comments
10
+ # /\n\s*/ => '', # trim indentation and remove linebreaks
11
+ /\/\/\<!\[CDATA\[(.*?)\/\/\]\]\>/ => "\\1" # remove //<![CDATA[...content...//]]>
12
+ }
13
+ c = clone
14
+ for s, r in regexp
15
+ c.gsub!(s,r)
16
+ end
17
+ c
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end