vidibus-xss 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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