vidibus-xss 0.1.16 → 0.1.17

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.16
1
+ 0.1.17
@@ -208,7 +208,7 @@ module Vidibus
208
208
 
209
209
  # determine scope
210
210
  if !(scope = params[:scope]).blank?
211
- scope = "$('##{scope}')"
211
+ scope = "jQuery('##{scope}')"
212
212
  else
213
213
  scope = "$s#{xss_random_string}"
214
214
  xss << %(var #{scope}=vidibus.xss.detectScope();)
@@ -1,11 +1,5 @@
1
- // if (typeof console == undefined) {
2
- // console = {};
3
- // }
4
-
5
-
6
1
  var vidibus = {};
7
-
8
- jQuery(function ($) {
2
+ (function($) {
9
3
 
10
4
  /**
11
5
  * Assign global cross-site request forgery protection variables.
@@ -28,14 +22,13 @@ jQuery(function ($) {
28
22
  if(vidibus.csrf.param && vidibus.csrf.token) {
29
23
  vidibus.csrf.data[vidibus.csrf.param] = encodeURIComponent(vidibus.csrf.token);
30
24
  }
31
- });
32
-
25
+ }(jQuery));
33
26
 
34
27
  /**
35
28
  * Implement ajax handler.
36
29
  * This is the default handler provided in rails.js with some extensions.
37
30
  */
38
- $(function($) {
31
+ (function($) {
39
32
  $.fn.extend({
40
33
 
41
34
  /**
@@ -43,24 +36,24 @@ $(function($) {
43
36
  */
44
37
  callAjax: function(url, method, data) {
45
38
  var el = this,
46
- method = method || el.attr('method') || el.attr('data-method') || 'GET',
39
+ meth = method || el.attr('method') || el.attr('data-method') || 'GET',
47
40
  dataType = el.attr('data-type') || 'script';
48
- if (!url) url = el.attr('action') || el.attr('href') || el.attr('data-url');
41
+ if (!url) {url = el.attr('action') || el.attr('href') || el.attr('data-url');}
49
42
  if (url === undefined) {
50
43
  throw "No URL specified for remote call (action or href must be present).";
51
44
  } else {
52
45
  if (el.triggerAndReturn('ajax:before')) {
53
46
  data = data || el.is('form') ? el.serializeArray() : {};
54
- if (method == 'delete') {
55
- data['_method'] = method;
56
- method = 'POST';
47
+ if (meth === 'delete') {
48
+ data._method = meth;
49
+ meth = 'POST';
57
50
  }
58
51
  $.extend(data, vidibus.csrf.data());
59
52
  $.ajax({
60
53
  url: url,
61
54
  data: data,
62
55
  dataType: dataType,
63
- type: method.toUpperCase(),
56
+ type: meth.toUpperCase(),
64
57
  beforeSend: function(xhr) {
65
58
  el.trigger('ajax:loading', xhr);
66
59
  },
@@ -79,4 +72,4 @@ $(function($) {
79
72
  }
80
73
  }
81
74
  });
82
- });
75
+ }(jQuery));
@@ -1,191 +1,194 @@
1
1
  // Basic loader for stylesheets and javascripts.
2
- var xssLoader = {
3
-
4
- complete: true, // indicates that loading has been finished
5
- queue: [], // holds resources that are queued to load
6
- loading: undefined, // holds resource that is currently being loaded
7
- preloaded: undefined, // holds resources that are included in consumer base file
8
- loaded: {}, // holds resources that are currently loaded
9
- unused: {}, // holds resources that are loaded, but not required anymore
10
-
11
- /**
12
- * Load resources.
13
- */
14
- load: function(resources, scope) {
15
- this.initStaticResources();
16
- this.complete = false;
17
- this.unused = jQuery.extend({}, this.loaded); // clone
18
-
19
- $(resources).each(function() {
20
- var resource = this,
21
- src = resource.src,
22
- name = xssLoader.resourceName(src);
23
-
24
- resource.name = name;
25
- resource.scopes = {};
26
- resource.scopes[scope] = true;
27
-
28
- // remove current file, because it is used
29
- delete xssLoader.unused[name];
2
+ var xssLoader;
3
+ (function($) {
4
+ xssLoader = {
5
+
6
+ complete: true, // indicates that loading has been finished
7
+ queue: [], // holds resources that are queued to load
8
+ loading: undefined, // holds resource that is currently being loaded
9
+ preloaded: undefined, // holds resources that are included in consumer base file
10
+ loaded: {}, // holds resources that are currently loaded
11
+ unused: {}, // holds resources that are loaded, but not required anymore
12
+
13
+ /**
14
+ * Load resources.
15
+ */
16
+ load: function(resources, scope) {
17
+ this.initStaticResources();
18
+ this.complete = false;
19
+ this.unused = jQuery.extend({}, this.loaded); // clone
20
+
21
+ $(resources).each(function() {
22
+ var resource = this,
23
+ src = resource.src,
24
+ name = xssLoader.resourceName(src);
25
+
26
+ resource.name = name;
27
+ resource.scopes = {};
28
+ resource.scopes[scope] = true;
29
+
30
+ // remove current file, because it is used
31
+ delete xssLoader.unused[name];
32
+
33
+ // skip files that have already been loaded
34
+ if (xssLoader.loaded[name]) {
35
+ xssLoader.loaded[name].scopes[scope] = true; // add current scope
36
+ return; // continue
37
+ } else if (xssLoader.preloaded[name]) {
38
+ return; // continue
39
+ }
30
40
 
31
- // skip files that have already been loaded
32
- if (xssLoader.loaded[name]) {
33
- xssLoader.loaded[name].scopes[scope] = true; // add current scope
34
- return; // continue
35
- } else if (xssLoader.preloaded[name]) {
36
- return; // continue
37
- }
41
+ xssLoader.loaded[name] = resource;
42
+ switch (resource.type) {
43
+
44
+ // load css file directly
45
+ case 'text/css':
46
+ var element = document.createElement("link");
47
+ element.rel = 'stylesheet';
48
+ element.href = src;
49
+ element.media = resource.media || 'all';
50
+ element.type = 'text/css';
51
+ xssLoader.appendToHead(element);
52
+ break;
53
+
54
+ // push script file to loading queue
55
+ case 'text/javascript':
56
+ xssLoader.queue.push(resource);
57
+ break;
58
+
59
+ default: console.log('xssLoader.load: unsupported resource type: '+resource.type);
60
+ }
61
+ });
38
62
 
39
- xssLoader.loaded[name] = resource;
40
- switch (resource.type) {
41
-
42
- // load css file directly
43
- case 'text/css':
44
- var element = document.createElement("link");
45
- element.rel = 'stylesheet';
46
- element.href = src;
47
- element.media = resource.media || 'all';
48
- element.type = 'text/css';
49
- xssLoader.appendToHead(element);
50
- break;
51
-
52
- // push script file to loading queue
53
- case 'text/javascript':
54
- xssLoader.queue.push(resource);
55
- break;
56
-
57
- default: console.log('xssLoader.load: unsupported resource type: '+resource.type);
63
+ this.loadQueue(true);
64
+ this.unloadUnused(scope);
65
+ },
66
+
67
+ /**
68
+ * Returns file name of resource.
69
+ */
70
+ resourceName: function(url) {
71
+ return url.match(/\/([^\/\?]+)(\?.*)*$/)[1];
72
+ },
73
+
74
+ /**
75
+ * Returns list of static resources.
76
+ */
77
+ initStaticResources: function() {
78
+ if (xssLoader.preloaded === undefined) {
79
+ xssLoader.preloaded = {};
80
+ var $resource, src, name;
81
+ $('script[src],link[href]',$('head')).each(function() {
82
+ $resource = $(this);
83
+ src = $resource.attr('src') || $resource.attr('href');
84
+ name = xssLoader.resourceName(src);
85
+ xssLoader.preloaded[name] = src;
86
+ });
58
87
  }
59
- });
60
-
61
- this.loadQueue(true);
62
- this.unloadUnused(scope);
63
- },
64
-
65
- /**
66
- * Returns file name of resource.
67
- */
68
- resourceName: function(url) {
69
- return url.match(/\/([^\/\?]+)(\?.*)*$/)[1];
70
- },
71
-
72
- /**
73
- * Returns list of static resources.
74
- */
75
- initStaticResources: function() {
76
- if (xssLoader.preloaded === undefined) {
77
- xssLoader.preloaded = {};
78
- var $resource, src, name;
79
- $('script[src],link[href]',$('head')).each(function() {
80
- $resource = $(this);
81
- src = $resource.attr('src') || $resource.attr('href');
82
- name = xssLoader.resourceName(src);
83
- xssLoader.preloaded[name] = src;
84
- });
85
- }
86
- },
88
+ },
87
89
 
88
- /**
89
- * Loads resources in queue.
90
- */
91
- loadQueue: function(start) {
90
+ /**
91
+ * Loads resources in queue.
92
+ */
93
+ loadQueue: function(start) {
92
94
 
93
- // Reduce queue if this method is called as callback.
94
- if(start !== true) {
95
- xssLoader.queue.shift();
96
- }
95
+ // Reduce queue if this method is called as callback.
96
+ if(start !== true) {
97
+ xssLoader.queue.shift();
98
+ }
97
99
 
98
- var resource = xssLoader.queue[0];
100
+ var resource = xssLoader.queue[0];
99
101
 
100
- // return if file is currently loading
101
- if (resource) {
102
- if (resource === xssLoader.loading) {
103
- // console.log('CURRENTLY LOADING: '+resource.src);
104
- return;
102
+ // return if file is currently loading
103
+ if (resource) {
104
+ if (resource === xssLoader.loading) {
105
+ // console.log('CURRENTLY LOADING: '+resource.src);
106
+ return;
107
+ }
108
+ xssLoader.loading = resource;
109
+ xssLoader.loadScript(resource.src, xssLoader.loadQueue);
110
+ } else {
111
+ xssLoader.loading = undefined;
112
+ xssLoader.complete = true;
105
113
  }
106
- xssLoader.loading = resource;
107
- xssLoader.loadScript(resource.src, xssLoader.loadQueue);
108
- } else {
109
- xssLoader.loading = undefined;
110
- xssLoader.complete = true;
111
- }
112
- },
113
-
114
- /**
115
- * Loads script src.
116
- */
117
- loadScript: function(src, callback) {
118
- var element = document.createElement("script");
119
- if (element.addEventListener) {
120
- element.addEventListener("load", callback, false);
121
- } else {
122
- // IE
123
- element.onreadystatechange = function() {
124
- if (this.readyState === 'loaded') {
125
- callback.call(this);
114
+ },
115
+
116
+ /**
117
+ * Loads script src.
118
+ */
119
+ loadScript: function(src, callback) {
120
+ var element = document.createElement("script");
121
+ if (element.addEventListener) {
122
+ element.addEventListener("load", callback, false);
123
+ } else {
124
+ // IE
125
+ element.onreadystatechange = function() {
126
+ if (this.readyState === 'loaded') {
127
+ callback.call(this);
128
+ }
129
+ };
130
+ }
131
+ element.type = 'text/javascript';
132
+ element.src = src;
133
+ xssLoader.appendToHead(element);
134
+ element = null;
135
+ },
136
+
137
+ /**
138
+ * Detects unused resources and removes them.
139
+ */
140
+ unloadUnused: function(scope) {
141
+ var name, resources = [];
142
+ for(name in xssLoader.unused) {
143
+ if (xssLoader.unused.hasOwnProperty(name)) {
144
+ // Remove dependency for given scope.
145
+ if (xssLoader.unused[name].scopes[scope]) {
146
+ delete xssLoader.unused[name].scopes[scope];
147
+ }
148
+
149
+ // Unload resource if it has no dependencies left.
150
+ if ($.isEmptyObject(xssLoader.unused[name].scopes)) {
151
+ resources.push(xssLoader.unused[name]);
152
+ }
126
153
  }
127
- };
128
- }
129
- element.type = 'text/javascript';
130
- element.src = src;
131
- xssLoader.appendToHead(element);
132
- element = null;
133
- },
134
-
135
- /**
136
- * Detects unused resources and removes them.
137
- */
138
- unloadUnused: function(scope) {
139
- var name, resources = [];
140
- for(name in xssLoader.unused) {
141
- if (xssLoader.unused.hasOwnProperty(name)) {
142
- // Remove dependency for given scope.
143
- if (xssLoader.unused[name].scopes[scope]) {
144
- delete xssLoader.unused[name].scopes[scope];
154
+ }
155
+ xssLoader.unload(resources);
156
+ xssLoader.unused = {};
157
+ },
158
+
159
+ /**
160
+ * Removes resources given in list.
161
+ */
162
+ unload: function(resources) {
163
+ var src, resource;
164
+ $(resources).each(function() {
165
+ resource = this;
166
+ src = resource.src;
167
+ switch (resource.type) {
168
+ case "text/css":
169
+ $('link[href="'+src+'"]').remove();
170
+ break;
171
+
172
+ case "text/javascript":
173
+ $('script[src="'+src+'"]').remove();
174
+ break;
175
+
176
+ default: console.log('xssLoader.unload: unsupported resource type: '+resource.type);
145
177
  }
178
+ delete xssLoader.loaded[resource.name];
179
+ });
180
+ },
146
181
 
147
- // Unload resource if it has no dependencies left.
148
- if ($.isEmptyObject(xssLoader.unused[name].scopes)) {
149
- resources.push(xssLoader.unused[name]);
150
- }
151
- }
182
+ /**
183
+ * Appends given element to document head.
184
+ */
185
+ appendToHead: function(element) {
186
+ $("head")[0].appendChild(element);
152
187
  }
153
- xssLoader.unload(resources);
154
- xssLoader.unused = {};
155
- },
156
-
157
- /**
158
- * Removes resources given in list.
159
- */
160
- unload: function(resources) {
161
- var src, resource;
162
- $(resources).each(function() {
163
- resource = this;
164
- src = resource.src;
165
- switch (resource.type) {
166
- case "text/css":
167
- $('link[href="'+src+'"]').remove();
168
- break;
169
-
170
- case "text/javascript":
171
- $('script[src="'+src+'"]').remove();
172
- break;
173
-
174
- default: console.log('xssLoader.unload: unsupported resource type: '+resource.type);
175
- }
176
- delete xssLoader.loaded[resource.name];
177
- });
178
- },
179
-
180
- /**
181
- * Appends given element to document head.
182
- */
183
- appendToHead: function(element) {
184
- $("head")[0].appendChild(element);
185
- }
186
- };
188
+ };
187
189
 
188
- // Maintain compatibility
189
- if (typeof vidibus !== "undefined") {
190
- vidibus.loader = xssLoader;
191
- }
190
+ // Maintain compatibility
191
+ if (typeof vidibus !== "undefined") {
192
+ vidibus.loader = xssLoader;
193
+ }
194
+ }(jQuery));
@@ -13,290 +13,287 @@
13
13
  // } else {
14
14
  // $.ajax({...});
15
15
  // }
16
-
17
- vidibus.xss = {
18
- initialized: {}, // holds true for every scope that has been initialized
19
- fileExtension: 'xss', // use 'xss' as file extension
20
- loadedUrls: {}, // store urls currently loaded in each scope
21
- pathPrefix: '', // set a fixed prefix for all paths
22
-
23
- ready: function() {
24
- // TODO: implement real event handler
25
- },
26
-
27
- /**
28
- * Detects scope of script block to be executed.
29
- * Must be called from embedding page.
30
- */
31
- detectScope: function() {
32
- document.write('<div id="scopeDetector"></div>');
33
- var $detector = $("#scopeDetector");
34
- var $scope = $detector.parent();
35
- $detector.remove();
36
- return $scope;
37
- },
38
-
39
- /**
40
- * Usage:
41
- * vidibus.xss.embed('<div>Some HTML</div>', $('#scope') [, ".some element"]);
42
- */
43
- embed: function(html, $scope, selector) {
44
- html = this.transformPaths(html, $scope); // Transform local paths before embedding html into page!
45
- if (selector) {
46
- $(selector, $scope).html(html);
47
- } else {
48
- $scope.html(html);
49
- }
50
- this.setUrls($scope);
51
- this.setActions($scope);
52
- this.ready();
53
- },
54
-
55
- /**
56
- * Calls given path for given scope. If a third parameter is set to true,
57
- * the location will be loaded, even if it is currently loaded.
58
- *
59
- * Usage:
60
- * vidibus.xss.get('path', $('#scope') [, true]);
61
- *
62
- * If no host is provided, host of scope will be used.
63
- */
64
- get: function(path, $scope, reload) {
65
- // escape query parts
66
- path = path.replace("?", "%3F").replace("&", "%26").replace("=", "%3D");
67
-
68
- // remove path prefix
69
- if (typeof xssPathPrefix !== "undefined" && xssPathPrefix) {
70
- path = path.replace(xssPathPrefix,'');
71
- }
72
-
73
- var scopeId = $scope[0].id,
74
- params = scopeId+'='+this.getPath(path),
75
- keepCurrentLocation = this.initialized[scopeId] ? 0 : 1,
76
- location = $.param.fragment(String(document.location), params, keepCurrentLocation),
77
- reloadScope = {};
78
-
79
- this.initialized[scopeId] = true;
80
- window.location.href = location; // write history
81
- reloadScope[scopeId] = reload;
82
- this.loadUrl(location, reloadScope);
83
- },
84
-
85
- /**
86
- * Redirect to given path while forcing reloading.
87
- */
88
- redirect: function(path, $scope) {
89
- this.get(path, $scope, true);
90
- },
91
-
92
- /**
93
- * Handles callback action.
94
- *
95
- * Requires data.status:
96
- * redirect, TODO: ok, error
97
- *
98
- * Accepts actions depending on status:
99
- * (redirect) to: Performs redirect to location.
100
- */
101
- callback: function(data, $scope) {
102
- if (data.status === "redirect") {
103
- this.redirect(data.to, $scope);
104
- }
105
- },
106
-
107
- /**
108
- * Sets host for given scope.
109
- */
110
- setHost: function(host, $scope) {
111
- $scope.attr('data-host', host);
112
- },
113
-
114
- /**
115
- * Returns host for given scope.
116
- */
117
- getHost: function($scope) {
118
- return $scope.attr('data-host');
119
- },
120
-
121
- /**
122
- * Turns given path into an absolute url.
123
- */
124
- getUrl: function(path, host) {
125
- if (path.match(/https?:\/\//)) { return path; }
126
- return host + path;
127
- },
128
-
129
- /**
130
- * Returns relative path from url.
131
- */
132
- getPath: function(url) {
133
- return url.replace(/https?:\/\/[^\/]+/,'');
134
- },
135
-
136
- /**
137
- * Rewrites links to absolute urls.
138
- */
139
- setUrls: function($scope) {
140
- var host = this.getHost($scope);
141
-
142
- // Rewrite links
143
- $('a[href]:not([href^=http])', $scope).each(function(e) {
144
- var href = $(this).attr('href');
145
- $(this).attr('href', vidibus.xss.getUrl(href, host));
146
- });
147
-
148
- // Rewrite forms
149
- $('form[action]', $scope).each(function(e) {
150
- var action = $(this).attr('action');
151
- $(this).attr('href', vidibus.xss.getUrl(action, host));
152
- });
153
- },
154
-
155
- /**
156
- * Set xss actions for interactive elements.
157
- */
158
- setActions: function($scope) {
159
- var host = this.getHost($scope);
160
-
161
- // Set action for GET links
162
- // TODO: Allow links to be flagged as "external"
163
- $('a[href^='+host+']:not([data-method],[data-remote])', $scope).bind('click.xss', function(e) {
164
- var href = $(this).attr('href');
165
- vidibus.xss.get(href, $scope);
166
- e.preventDefault();
167
- });
168
-
169
- // Set action non-GET links
170
- // TODO: Remove bindings from links that match current host only: a[data-method][href^='+host+']:not([data-remote])
171
- $('a[data-method]:not([data-remote])').die('click').unbind('click'); // remove bindings
172
- $('a[data-method][href^='+host+']:not([data-remote])', $scope).click(function(e) {
173
- var $link = $(this),
174
- path = $link.attr('href'),
175
- url = vidibus.xss.buildUrl(path, $scope);
176
- $(this).callAjax(url);
177
- e.preventDefault();
178
- });
179
-
180
- // Set form action
181
- $('form[action]').die('submit').unbind('submit'); // remove bindings
182
- $('form[action][href^='+host+']', $scope).submit(function(e) {
183
- var $form = $(this),
184
- path = $form.attr('action');
185
- url = vidibus.xss.buildUrl(path, $scope);
186
- $(this).callAjax(url);
187
- return false;
188
- });
189
- },
190
-
191
- /**
192
- * Modifies paths within given html string.
193
- * This method must be called before embedding html snippet into the page
194
- * to avoid loading errors from invalid relative paths.
195
- */
196
- transformPaths: function(html, $scope) {
197
- var match, url;
198
- while (true) {
199
- match = html.match(/src="((?!http)[^"]+)"/);
200
- if (!match) {
201
- break;
16
+ (function($) {
17
+ vidibus.xss = {
18
+ initialized: {}, // holds true for every scope that has been initialized
19
+ fileExtension: 'xss', // use 'xss' as file extension
20
+ loadedUrls: {}, // store urls currently loaded in each scope
21
+ pathPrefix: '', // set a fixed prefix for all paths
22
+
23
+ ready: function() {
24
+ // TODO: implement real event handler
25
+ },
26
+
27
+ /**
28
+ * Detects scope of script block to be executed.
29
+ * Must be called from embedding page.
30
+ */
31
+ detectScope: function() {
32
+ document.write('<div id="scopeDetector"></div>');
33
+ var $detector = $("#scopeDetector");
34
+ var $scope = $detector.parent();
35
+ $detector.remove();
36
+ return $scope;
37
+ },
38
+
39
+ /**
40
+ * Usage:
41
+ * vidibus.xss.embed('<div>Some HTML</div>', $('#scope') [, ".some element"]);
42
+ */
43
+ embed: function(html, $scope, selector) {
44
+ html = this.transformPaths(html, $scope); // Transform local paths before embedding html into page!
45
+ if (selector) {
46
+ $(selector, $scope).html(html);
47
+ } else {
48
+ $scope.html(html);
49
+ }
50
+ this.setUrls($scope);
51
+ this.setActions($scope);
52
+ this.ready();
53
+ },
54
+
55
+ /**
56
+ * Calls given path for given scope. If a third parameter is set to true,
57
+ * the location will be loaded, even if it is currently loaded.
58
+ *
59
+ * Usage:
60
+ * vidibus.xss.get('path', $('#scope') [, true]);
61
+ *
62
+ * If no host is provided, host of scope will be used.
63
+ */
64
+ get: function(path, $scope, reload) {
65
+ // escape query parts
66
+ path = path.replace("?", "%3F").replace("&", "%26").replace("=", "%3D");
67
+
68
+ // remove path prefix
69
+ if (typeof xssPathPrefix !== "undefined" && xssPathPrefix) {
70
+ path = path.replace(xssPathPrefix,'');
202
71
  }
203
- url = vidibus.xss.buildUrl(match[1], $scope);
204
- html = html.replace(match[0], 'src="'+url+'"');
205
- }
206
- return html;
207
- },
208
72
 
209
- /**
210
- * Load XSS sources from given url.
211
- * If url is empty, the current location will be used.
212
- */
213
- loadUrl: function(url, reload) {
214
- var scope, $scope, path, loaded, params = $.deparam.fragment();
215
- for(scope in params) {
216
- if (params.hasOwnProperty(scope)) {
217
- path = params[scope];
218
-
219
- // don't reload locations that have already been loaded
220
- loaded = this.loadedUrls[scope];
221
- if((!reload || !reload[scope]) && loaded && loaded === path) {
222
- continue;
73
+ var scopeId = $scope[0].id,
74
+ params = scopeId+'='+this.getPath(path),
75
+ keepCurrentLocation = this.initialized[scopeId] ? 0 : 1,
76
+ location = $.param.fragment(String(document.location), params, keepCurrentLocation),
77
+ reloadScope = {};
78
+
79
+ this.initialized[scopeId] = true;
80
+ window.location.href = location; // write history
81
+ reloadScope[scopeId] = reload;
82
+ this.loadUrl(location, reloadScope);
83
+ },
84
+
85
+ /**
86
+ * Redirect to given path while forcing reloading.
87
+ */
88
+ redirect: function(path, $scope) {
89
+ this.get(path, $scope, true);
90
+ },
91
+
92
+ /**
93
+ * Handles callback action.
94
+ *
95
+ * Requires data.status:
96
+ * redirect, TODO: ok, error
97
+ *
98
+ * Accepts actions depending on status:
99
+ * (redirect) to: Performs redirect to location.
100
+ */
101
+ callback: function(data, $scope) {
102
+ if (data.status === "redirect") {
103
+ this.redirect(data.to, $scope);
104
+ }
105
+ },
106
+
107
+ /**
108
+ * Sets host for given scope.
109
+ */
110
+ setHost: function(host, $scope) {
111
+ $scope.attr('data-host', host);
112
+ },
113
+
114
+ /**
115
+ * Returns host for given scope.
116
+ */
117
+ getHost: function($scope) {
118
+ return $scope.attr('data-host');
119
+ },
120
+
121
+ /**
122
+ * Turns given path into an absolute url.
123
+ */
124
+ getUrl: function(path, host) {
125
+ if (path.match(/^(https?:\/\/|#)/)) { return path; }
126
+ return host + path;
127
+ },
128
+
129
+ /**
130
+ * Returns relative path from url.
131
+ */
132
+ getPath: function(url) {
133
+ return url.replace(/https?:\/\/[^\/]+/,'');
134
+ },
135
+
136
+ /**
137
+ * Rewrites links to absolute urls.
138
+ */
139
+ setUrls: function($scope) {
140
+ var host = this.getHost($scope);
141
+
142
+ // Rewrite links
143
+ $('a[href]:not([href^=http])', $scope).each(function(e) {
144
+ var href = $(this).attr('href');
145
+ $(this).attr('href', vidibus.xss.getUrl(href, host));
146
+ });
147
+
148
+ // Rewrite forms
149
+ $('form[action]', $scope).each(function(e) {
150
+ var action = $(this).attr('action');
151
+ $(this).attr('href', vidibus.xss.getUrl(action, host));
152
+ });
153
+ },
154
+
155
+ /**
156
+ * Set xss actions for interactive elements.
157
+ */
158
+ setActions: function($scope) {
159
+ var host = this.getHost($scope);
160
+
161
+ // Set action for GET links
162
+ // TODO: Allow links to be flagged as "external"
163
+ $('a[href^='+host+']:not([data-method],[data-remote])', $scope).bind('click.xss', function(e) {
164
+ var href = $(this).attr('href');
165
+ vidibus.xss.get(href, $scope);
166
+ e.preventDefault();
167
+ });
168
+
169
+ // Set action non-GET links
170
+ // TODO: Remove bindings from links that match current host only: a[data-method][href^='+host+']:not([data-remote])
171
+ $('a[data-method]:not([data-remote])').die('click').unbind('click'); // remove bindings
172
+ $('a[data-method][href^='+host+']:not([data-remote])', $scope).click(function(e) {
173
+ var $link = $(this),
174
+ path = $link.attr('href'),
175
+ url = vidibus.xss.buildUrl(path, $scope);
176
+ $(this).callAjax(url);
177
+ e.preventDefault();
178
+ });
179
+
180
+ // Set form action
181
+ $('form[action]').die('submit').unbind('submit'); // remove bindings
182
+ $('form[action][href^='+host+']', $scope).submit(function(e) {
183
+ var $form = $(this),
184
+ path = $form.attr('action');
185
+ url = vidibus.xss.buildUrl(path, $scope);
186
+ $(this).callAjax(url);
187
+ return false;
188
+ });
189
+ },
190
+
191
+ /**
192
+ * Modifies paths within given html string.
193
+ * This method must be called before embedding html snippet into the page
194
+ * to avoid loading errors from invalid relative paths.
195
+ */
196
+ transformPaths: function(html, $scope) {
197
+ var match, url;
198
+ while (true) {
199
+ match = html.match(/src="((?!http)[^"]+)"/);
200
+ if (!match) {
201
+ break;
223
202
  }
224
-
225
- $scope = $('#'+scope);
226
- if($scope[0] === undefined) {
227
- console.log('Scope not found: '+scope);
228
- } else {
229
- this.loadedUrls[scope] = path;
230
- this.loadData(path, $scope);
203
+ url = vidibus.xss.buildUrl(match[1], $scope);
204
+ html = html.replace(match[0], 'src="'+url+'"');
205
+ }
206
+ return html;
207
+ },
208
+
209
+ /**
210
+ * Load XSS sources from given url.
211
+ * If url is empty, the current location will be used.
212
+ */
213
+ loadUrl: function(url, reload) {
214
+ var scope, $scope, path, loaded, params = $.deparam.fragment();
215
+ for(scope in params) {
216
+ if (params.hasOwnProperty(scope)) {
217
+ path = params[scope];
218
+
219
+ // don't reload locations that have already been loaded
220
+ loaded = this.loadedUrls[scope];
221
+ if((!reload || !reload[scope]) && loaded && loaded === path) {
222
+ continue;
223
+ }
224
+
225
+ $scope = $('#'+scope);
226
+ if($scope[0] === undefined) {
227
+ console.log('Scope not found: '+scope);
228
+ } else {
229
+ this.loadedUrls[scope] = path;
230
+ this.loadData(path, $scope);
231
+ }
231
232
  }
232
233
  }
233
- }
234
- },
235
-
236
- /**
237
- * Load relative path into given scope.
238
- * Transforms scope host and path into a XSS location.
239
- */
240
- loadData: function(path, $scope) {
241
- var url = this.buildUrl(path, $scope, true);
242
- $.ajax({
243
- url: url,
244
- data: [],
245
- dataType: 'script',
246
- type: 'GET'
247
- });
248
- },
234
+ },
235
+
236
+ /**
237
+ * Load relative path into given scope.
238
+ * Transforms scope host and path into a XSS location.
239
+ */
240
+ loadData: function(path, $scope) {
241
+ var url = this.buildUrl(path, $scope, true);
242
+ $.ajax({
243
+ url: url,
244
+ data: [],
245
+ dataType: 'script',
246
+ type: 'GET'
247
+ });
248
+ },
249
+
250
+ /**
251
+ * Tranforms local paths to absolute ones.
252
+ */
253
+ buildUrl: function(path, $scope, uncached) {
254
+ var parts = path.split("?");
255
+ path = parts[0];
256
+ params = parts[1];
257
+ if(params) {
258
+ params = params.split("&");
259
+ } else {
260
+ params = [];
261
+ }
249
262
 
250
- /**
251
- * Tranforms local paths to absolute ones.
252
- */
253
- buildUrl: function(path, $scope, uncached) {
254
- var parts = path.split("?");
255
- path = parts[0];
256
- params = parts[1];
257
- if(params) {
258
- params = params.split("&");
259
- } else {
260
- params = [];
261
- }
263
+ // prepend path prefix
264
+ if (typeof xssPathPrefix !== "undefined" && xssPathPrefix && !path.match(xssPathPrefix)) {
265
+ path = xssPathPrefix + path;
266
+ }
262
267
 
263
- // prepend path prefix
264
- if (typeof xssPathPrefix !== "undefined" && xssPathPrefix && !path.match(xssPathPrefix)) {
265
- path = xssPathPrefix + path;
266
- }
268
+ var host = this.getHost($scope),
269
+ scope = $scope.attr('id'),
270
+ url = this.getUrl(path, host);
267
271
 
268
- var host = this.getHost($scope),
269
- scope = $scope.attr('id'),
270
- url = this.getUrl(path, host);
272
+ if (!url.match(/\.[a-z]+((\?|\#).*)?$/)) { url += '.xss'; }
273
+ if (url.indexOf('.xss') > -1 && params.toString().indexOf('scope='+scope) === -1) {
274
+ params.push('scope='+scope);
275
+ }
271
276
 
272
- if (!url.match(/\.[a-z]+((\?|\#).*)?$/)) { url += '.xss'; }
273
- if (url.indexOf('.xss') > -1 && params.toString().indexOf('scope='+scope) === -1) {
274
- params.push('scope='+scope);
275
- }
277
+ // add cache buster
278
+ if (uncached) {
279
+ var d = new Date();
280
+ params.push(d.getTime());
281
+ }
276
282
 
277
- // add cache buster
278
- if (uncached) {
279
- var d = new Date();
280
- params.push(d.getTime());
281
- }
283
+ // append params
284
+ if (params.length) {
285
+ if (url.indexOf('?') === -1) { url += '?'; }
286
+ url += params.join('&');
287
+ }
282
288
 
283
- // append params
284
- if (params.length) {
285
- if (url.indexOf('?') === -1) { url += '?'; }
286
- url += params.join('&');
289
+ return url;
287
290
  }
291
+ };
288
292
 
289
- return url;
290
- }
291
- };
292
-
293
- /**
294
- * Detect changes to document's location.
295
- *
296
- */
297
- $(function($){
298
-
299
- // Detect changes of document.location and trigger loading.
293
+ /**
294
+ * Detect changes to document's location.
295
+ *
296
+ */
300
297
  $(window).bind('hashchange', function(e) {
301
298
  if (xssLoader.complete) {
302
299
  vidibus.xss.loadUrl();
@@ -308,12 +305,7 @@ $(function($){
308
305
  // to trigger the event now, to handle the hash the page may have
309
306
  // loaded with.
310
307
  $(window).trigger('hashchange');
311
- });
312
308
 
313
- /**
314
- * Extend XHR
315
- */
316
- $(function($) {
317
309
 
318
310
  /**
319
311
  * Extend xhr object to send credentials and force XMLHttpRequest.
@@ -328,7 +320,7 @@ $(function($) {
328
320
  };
329
321
 
330
322
  /**
331
- * Extends xhr on beforeSend by binding to Rails' ajax:loading event.
323
+ * Extend xhr on beforeSend by binding to Rails' ajax:loading event.
332
324
  */
333
325
  $("body").bind('ajax:loading', function(e, xhr) {
334
326
  extendXhr(xhr);
@@ -342,4 +334,4 @@ $(function($) {
342
334
  $.ajaxSettings.beforeSend = function(xhr) {
343
335
  extendXhr(xhr);
344
336
  };
345
- });
337
+ }(jQuery));
data/vidibus-xss.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{vidibus-xss}
8
- s.version = "0.1.16"
8
+ s.version = "0.1.17"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Andre Pankratz"]
12
- s.date = %q{2011-01-11}
12
+ s.date = %q{2011-01-18}
13
13
  s.description = %q{Drop-in XSS support for remote applications.}
14
14
  s.email = %q{andre@vidibus.com}
15
15
  s.extra_rdoc_files = [
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vidibus-xss
3
3
  version: !ruby/object:Gem::Version
4
- hash: 59
4
+ hash: 57
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 16
10
- version: 0.1.16
9
+ - 17
10
+ version: 0.1.17
11
11
  platform: ruby
12
12
  authors:
13
13
  - Andre Pankratz
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-01-11 00:00:00 +01:00
18
+ date: 2011-01-18 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency