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 +1 -1
- data/lib/vidibus/xss/extensions/controller.rb +1 -1
- data/public/javascripts/vidibus.js +10 -17
- data/public/javascripts/vidibus.loader.js +180 -177
- data/public/javascripts/vidibus.xss.js +270 -278
- data/vidibus-xss.gemspec +2 -2
- metadata +4 -4
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.17
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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 (
|
|
55
|
-
data
|
|
56
|
-
|
|
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:
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Loads resources in queue.
|
|
92
|
+
*/
|
|
93
|
+
loadQueue: function(start) {
|
|
92
94
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
// Reduce queue if this method is called as callback.
|
|
96
|
+
if(start !== true) {
|
|
97
|
+
xssLoader.queue.shift();
|
|
98
|
+
}
|
|
97
99
|
|
|
98
|
-
|
|
100
|
+
var resource = xssLoader.queue[0];
|
|
99
101
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
element.
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Appends given element to document head.
|
|
184
|
+
*/
|
|
185
|
+
appendToHead: function(element) {
|
|
186
|
+
$("head")[0].appendChild(element);
|
|
152
187
|
}
|
|
153
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
268
|
+
var host = this.getHost($scope),
|
|
269
|
+
scope = $scope.attr('id'),
|
|
270
|
+
url = this.getUrl(path, host);
|
|
267
271
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
277
|
+
// add cache buster
|
|
278
|
+
if (uncached) {
|
|
279
|
+
var d = new Date();
|
|
280
|
+
params.push(d.getTime());
|
|
281
|
+
}
|
|
276
282
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
283
|
+
// append params
|
|
284
|
+
if (params.length) {
|
|
285
|
+
if (url.indexOf('?') === -1) { url += '?'; }
|
|
286
|
+
url += params.join('&');
|
|
287
|
+
}
|
|
282
288
|
|
|
283
|
-
|
|
284
|
-
if (params.length) {
|
|
285
|
-
if (url.indexOf('?') === -1) { url += '?'; }
|
|
286
|
-
url += params.join('&');
|
|
289
|
+
return url;
|
|
287
290
|
}
|
|
291
|
+
};
|
|
288
292
|
|
|
289
|
-
|
|
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
|
-
*
|
|
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.
|
|
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-
|
|
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:
|
|
4
|
+
hash: 57
|
|
5
5
|
prerelease: false
|
|
6
6
|
segments:
|
|
7
7
|
- 0
|
|
8
8
|
- 1
|
|
9
|
-
-
|
|
10
|
-
version: 0.1.
|
|
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-
|
|
18
|
+
date: 2011-01-18 00:00:00 +01:00
|
|
19
19
|
default_executable:
|
|
20
20
|
dependencies:
|
|
21
21
|
- !ruby/object:Gem::Dependency
|