vidibus-xss 0.1.16 → 0.1.17

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/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