uwa_files 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ require 'uwa'
2
+ require 'digest/sha1'
3
+ require 'json'
4
+ require 'fileutils'
5
+
6
+ module UWA
7
+ module Widget
8
+ class Files < UWA::Handler
9
+ NAME = 'uwa_files'
10
+ VERSION = '0.1'
11
+ COPYRIGHT = 'Copyright (C) 2007 Florent Solt'
12
+ DESC = 'UWA Files widget'
13
+ AUTHOR = 'Florent Solt'
14
+ EMAIL = 'florent@solt.biz'
15
+ HOMEPAGE = 'http://gnetvibes.rubyforge.org'
16
+ LICENSE = 'BSD'
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,109 @@
1
+ require 'uwa_files/config'
2
+
3
+ module UWA
4
+ module Widget
5
+ class Files
6
+ def initialize
7
+ super
8
+ @author = AUTHOR
9
+ @title = 'My Files'
10
+ @icon = 'http://www.netvibes.com/img/icons/folder.gif'
11
+ @tokens = {}
12
+ @preferences << {:name => :login, :type => :text, :label => 'Login'}
13
+ @preferences << {:name => :password, :type => :text, :label => 'Password'}
14
+ @start = '/tmp'
15
+ @users = {}
16
+ end
17
+
18
+ def login_token
19
+ token = new_token
20
+ @tokens[token] = {:login => query['login'], :auth => false, :files => {}}
21
+ self << token
22
+ end
23
+
24
+ def login
25
+ token = query['token']
26
+ login = @tokens[token][:login]
27
+ if Digest::SHA1.new(token + @users[login].to_s).hexdigest == query['digest']
28
+ UWA::Server.log(:info, "Login successful for \"#{login}\"")
29
+ self << token
30
+ @tokens[token][:auth] = true
31
+ else
32
+ UWA::Server.log(:warning, "Login failed for \"#{login}\"")
33
+ end
34
+ end
35
+
36
+ def files
37
+ return unless auth?
38
+ answer = {:type => nil, :data => []}
39
+ target = @token[:files][query['target']] || @start
40
+
41
+ if File.directory?(target)
42
+ @token[:files] = {} # Security: remove all unused tokens
43
+ answer[:type] = :dir
44
+ files = Dir[File.join(target, '*')].sort
45
+ files.unshift(File.join(target, '..'))
46
+ files.unshift(File.join(target, '.'))
47
+ files.each do |f|
48
+ ftoken = new_token
49
+ @token[:files][ftoken] = File.expand_path(f)
50
+ answer[:data] << {
51
+ :name => File.basename(f),
52
+ :type => File.directory?(f) ? :dir : :file,
53
+ :token => ftoken }
54
+ answer[:data].last[:path] = File.expand_path(f) if f[-1] == 46 # the dot dir
55
+ end
56
+ end
57
+ self << JSON.unparse(answer)
58
+ end
59
+
60
+ def download
61
+ return unless auth?
62
+ target = @token[:files][query['target']]
63
+ unless target.nil?
64
+ send_file(target)
65
+ else
66
+ response.status = '403'
67
+ self << 'Forbidden'
68
+ end
69
+ @token[:files].delete(query['target']) # Security: remove used file token
70
+ end
71
+
72
+ def upload
73
+ return unless auth?
74
+ target = @token[:files][query['target']]
75
+ File.open(File.join(target, query['file']['filename'] ), 'w') do |fd|
76
+ fd.write query['file']['tempfile'].read
77
+ end
78
+ self << "File #{query['file']['filename'].inspect} uploaded"
79
+ end
80
+
81
+ def delete
82
+ return unless auth?
83
+ target = @token[:files][query['target']]
84
+ FileUtils.rm_rf(target)
85
+ end
86
+
87
+ def rename
88
+ return unless auth?
89
+ target = @token[:files][query['target']]
90
+ new = query['new']
91
+ File.rename(target, File.join(File.dirname(target), File.basename(new)))
92
+ end
93
+
94
+ private
95
+
96
+ def auth?
97
+ @token = query['token']
98
+ @token = @tokens[@token]
99
+ auth = @token[:auth] rescue false
100
+ UWA::Server.log(:warning, "Auth failed for token #{query['token'].inspect}") if not auth
101
+ auth
102
+ end
103
+
104
+ def new_token
105
+ Digest::SHA1.new(Time.now.to_s + rand(0xffffffff).to_s).hexdigest
106
+ end
107
+ end
108
+ end
109
+ end
Binary file
data/resources/del.png ADDED
Binary file
Binary file
Binary file
@@ -0,0 +1,201 @@
1
+
2
+ widget.onLoad = function() {
3
+ widget.files = widget.createElement('ul');
4
+ widget.files.className = 'uwa_files_list';
5
+ widget.footer = widget.createElement('div');
6
+ widget.footer.className = 'uwa_files_footer';
7
+
8
+ // Form creation
9
+ widget.form = widget.createElement('form');
10
+ widget.form.method = 'POST';
11
+ widget.form.enctype = 'multipart/form-data';
12
+ widget.form.onsubmit = function() {
13
+ this.action = this.url + '&target=' + widget.current;
14
+ return Uploader.submit(this, {
15
+ onStart : widget.setFooterUpload,
16
+ onComplete : function(msg) {
17
+ widget.setFooterForm(msg);
18
+ widget.onRefresh();
19
+ }});
20
+ }.bind(widget.form);
21
+ widget.file = widget.createElement('input');
22
+ widget.file.type = 'file';
23
+ widget.file.name = 'file';
24
+ widget.form.appendChild(widget.file);
25
+ var submit = widget.createElement('input');
26
+ submit.type = 'submit';
27
+ submit.value = 'Upload';
28
+ widget.form.appendChild(submit);
29
+ widget.footer.appendChild(widget.form);
30
+
31
+ // Message creation
32
+ widget.indicator = widget.createElement('img');
33
+ widget.indicator.src = widget.remoteUrl + 'resources/ajax.gif';
34
+ widget.footer.appendChild(widget.indicator);
35
+ widget.message = widget.createElement('div');
36
+ widget.message.className = 'uwa_files_message';
37
+ widget.message.setStyle('display', 'none');
38
+ widget.footer.appendChild(widget.message);
39
+
40
+ login = widget.getValue('login');
41
+ widget.remoteText('login_token?login=' + encodeURIComponent(login),
42
+ widget.onLoginToken);
43
+ }
44
+
45
+ widget.onLoginToken = function(token) {
46
+ digest = sha1(token + sha1(widget.getValue('password')));
47
+ widget.remoteText('login?token=' + token + '&digest=' + digest,
48
+ widget.onLogin);
49
+ }
50
+
51
+ widget.onLogin = function(token) {
52
+ if (token) {
53
+ widget.token = token;
54
+ widget.setBody(widget.files);
55
+ widget.body.appendChild(widget.footer);
56
+ widget.form.url = widget.remoteUrl + 'upload?token=' + widget.token;
57
+ widget.setFooterForm(null);
58
+ widget.refreshDir(null);
59
+ widget.onRefresh = function() {
60
+ widget.refreshDir(widget.current);
61
+ };
62
+ } else {
63
+ widget.setBody('Login failed !')
64
+ }
65
+ }
66
+
67
+ widget.setFooterForm = function(msg) {
68
+ widget.file.value = '';
69
+ widget.form.setStyle('display', 'block');
70
+ widget.indicator.setStyle('display', 'none');
71
+ if (msg) {
72
+ widget.message.setText(msg);
73
+ widget.message.setStyle('display', 'block');
74
+ } else {
75
+ widget.message.setStyle('display', 'none');
76
+ }
77
+ }
78
+
79
+ widget.setFooterUpload = function() {
80
+ widget.form.setStyle('display', 'none');
81
+ var filename = widget.file.value.split(/[\/\\]/);
82
+ widget.message.setText('Uploading ' + filename[filename.length-1]);
83
+ widget.message.setStyle('display', 'block');
84
+ widget.indicator.setStyle('display', 'block');
85
+ return true;
86
+ }
87
+
88
+ widget.refreshDir = function(target) {
89
+ widget.remoteJson('files?token=' + widget.token + '&target=' + encodeURIComponent(target || ''),
90
+ widget.onFiles);
91
+ }
92
+
93
+ widget.onFiles = function(answer) {
94
+ switch(answer.type) {
95
+ case 'dir':
96
+ widget.files.empty();
97
+ widget.current = answer.data[0].token;
98
+ var li = widget.createElement('li');
99
+ li.className = 'parent';
100
+
101
+ var a = widget.createElement('a');
102
+ a.className = 'parent';
103
+ a.href = '#';
104
+ a.token = answer.data[1].token;
105
+ a.onclick = function() {
106
+ widget.refreshDir(this.token);
107
+ return false;
108
+ }.bind(a);
109
+
110
+ var img = widget.createElement('img');
111
+ img.src = widget.remoteUrl + 'resource/parent.gif';
112
+ a.appendChild(img);
113
+
114
+ li.appendChild(a);
115
+ li.appendText(answer.data[0].path);
116
+ widget.files.appendChild(li);
117
+
118
+ for (var i = 2; i < answer.data.length; i++) {
119
+ widget.files.appendChild(widget.printLink(answer.data[i]))
120
+ }
121
+ break;
122
+
123
+ default:
124
+ alert('Answer type :' + answer.type + ' not supported');
125
+ break;
126
+ }
127
+ }
128
+
129
+ widget.printLink = function(file) {
130
+ var li = widget.createElement('li');
131
+ var a = widget.createElement('a');
132
+ var action = null;
133
+ var img = null;
134
+
135
+ // del action
136
+ action = widget.createElement('a');
137
+ action.href = '#';
138
+ action.className = 'action';
139
+ img = widget.createElement('img');
140
+ img.src = widget.remoteUrl + 'resources/del.png';
141
+ action.appendChild(img);
142
+ action.onclick = function() {
143
+ if (confirm('Are you sure you want to delete "' + this.firstChild.nodeValue + '" ? ')) {
144
+ widget.remoteText('delete?token=' + widget.token + '&target=' + this.token,
145
+ function() { widget.refreshDir(widget.current); });
146
+ }
147
+ return false;
148
+ }.bind(a);
149
+ li.appendChild(action);
150
+
151
+ // rename action
152
+ action = widget.createElement('a');
153
+ action.href = '#';
154
+ action.className = 'action';
155
+ img = widget.createElement('img');
156
+ img.src = widget.remoteUrl + 'resources/rename.png';
157
+ action.appendChild(img);
158
+ action.onclick = function() {
159
+ var li = this.parentNode;
160
+ var input = widget.createElement('input');
161
+ input.type = 'text';
162
+ input.token = this.token;
163
+ input.value = this.firstChild.nodeValue;
164
+ Event.observe(input, 'keyup', function(ev) {
165
+ if (ev.keyCode == 27) { // Esc
166
+ widget.refreshDir(widget.current);
167
+ } else if (ev.keyCode == 13) { // Return
168
+ this.blur();
169
+ }
170
+ }.bind(input));
171
+ input.onblur = function() {
172
+ widget.remoteText('rename?token=' + widget.token +
173
+ '&target=' + this.token +
174
+ '&new=' + encodeURIComponent(this.value),
175
+ function() { widget.refreshDir(widget.current); });
176
+ }.bind(input);
177
+ li.setContent(input);
178
+ input.focus();
179
+ }.bind(a);
180
+ li.appendChild(action);
181
+
182
+ // download action
183
+ a.token = file.token;
184
+ a.className = 'file';
185
+ if (file.type == 'dir') {
186
+ img = 'http://www.netvibes.com/img/icons/folder.gif';
187
+ a.href = '#';
188
+ a.onclick = function() {
189
+ widget.refreshDir(this.token);
190
+ return false;
191
+ }.bind(a);
192
+ } else {
193
+ img = 'http://www.netvibes.com/img/icons/page_white.gif';
194
+ a.href = widget.remoteUrl + 'download?token=' + widget.token + '&target=' + file.token;
195
+ }
196
+ li.setStyle('backgroundImage', 'url(' + img +')');
197
+ a.appendText(file.name);
198
+ li.appendChild(a);
199
+ return li;
200
+ }
201
+
data/resources/sha1.js ADDED
@@ -0,0 +1,99 @@
1
+ function sha1(msg)
2
+ {
3
+ //
4
+ // function 'f' [4.1.1]
5
+ //
6
+ function f(s, x, y, z)
7
+ {
8
+ switch (s) {
9
+ case 0: return (x & y) ^ (~x & z);
10
+ case 1: return x ^ y ^ z;
11
+ case 2: return (x & y) ^ (x & z) ^ (y & z);
12
+ case 3: return x ^ y ^ z;
13
+ }
14
+ }
15
+
16
+ //
17
+ // rotate left (circular left shift) value x by n positions [3.2.5]
18
+ //
19
+ function ROTL(x, n)
20
+ {
21
+ return (x<<n) | (x>>>(32-n));
22
+ }
23
+
24
+ // constants [4.2.1]
25
+ var K = [0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6];
26
+
27
+ // PREPROCESSING
28
+
29
+ msg += String.fromCharCode(0x80); // add trailing '1' bit to string [5.1.1]
30
+
31
+ // convert string msg into 512-bit/16-integer blocks arrays of ints [5.2.1]
32
+ var l = Math.ceil(msg.length/4) + 2; // long enough to contain msg plus 2-word length
33
+ var N = Math.ceil(l/16); // in N 16-int blocks
34
+ var M = new Array(N);
35
+ for (var i=0; i<N; i++) {
36
+ M[i] = new Array(16);
37
+ for (var j=0; j<16; j++) { // encode 4 chars per integer, big-endian encoding
38
+ M[i][j] = (msg.charCodeAt(i*64+j*4)<<24) | (msg.charCodeAt(i*64+j*4+1)<<16) |
39
+ (msg.charCodeAt(i*64+j*4+2)<<8) | (msg.charCodeAt(i*64+j*4+3));
40
+ }
41
+ }
42
+ // add length (in bits) into final pair of 32-bit integers (big-endian) [5.1.1]
43
+ // note: most significant word would be ((len-1)*8 >>> 32, but since JS converts
44
+ // bitwise-op args to 32 bits, we need to simulate this by arithmetic operators
45
+ M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14])
46
+ M[N-1][15] = ((msg.length-1)*8) & 0xffffffff;
47
+
48
+ // set initial hash value [5.3.1]
49
+ var H0 = 0x67452301;
50
+ var H1 = 0xefcdab89;
51
+ var H2 = 0x98badcfe;
52
+ var H3 = 0x10325476;
53
+ var H4 = 0xc3d2e1f0;
54
+
55
+ // HASH COMPUTATION [6.1.2]
56
+
57
+ var W = new Array(80); var a, b, c, d, e;
58
+ for (var i=0; i<N; i++) {
59
+
60
+ // 1 - prepare message schedule 'W'
61
+ for (var t=0; t<16; t++) W[t] = M[i][t];
62
+ for (var t=16; t<80; t++) W[t] = ROTL(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1);
63
+
64
+ // 2 - initialise five working variables a, b, c, d, e with previous hash value
65
+ a = H0; b = H1; c = H2; d = H3; e = H4;
66
+
67
+ // 3 - main loop
68
+ for (var t=0; t<80; t++) {
69
+ var s = Math.floor(t/20); // seq for blocks of 'f' functions and 'K' constants
70
+ var T = (ROTL(a,5) + f(s,b,c,d) + e + K[s] + W[t]) & 0xffffffff;
71
+ e = d;
72
+ d = c;
73
+ c = ROTL(b, 30);
74
+ b = a;
75
+ a = T;
76
+ }
77
+
78
+ // 4 - compute the new intermediate hash value
79
+ H0 = (H0+a) & 0xffffffff; // note 'addition modulo 2^32'
80
+ H1 = (H1+b) & 0xffffffff;
81
+ H2 = (H2+c) & 0xffffffff;
82
+ H3 = (H3+d) & 0xffffffff;
83
+ H4 = (H4+e) & 0xffffffff;
84
+ }
85
+
86
+ return H0.toHexStr() + H1.toHexStr() + H2.toHexStr() + H3.toHexStr() + H4.toHexStr();
87
+ }
88
+
89
+ //
90
+ // extend Number class with a tailored hex-string method
91
+ // (note toString(16) is implementation-dependant, and
92
+ // in IE returns signed numbers when used on full words)
93
+ //
94
+ Number.prototype.toHexStr = function()
95
+ {
96
+ var s="", v;
97
+ for (var i=7; i>=0; i--) { v = (this>>>(i*4)) & 0xf; s += v.toString(16); }
98
+ return s;
99
+ }
@@ -0,0 +1,82 @@
1
+ .uwa_files_list {
2
+ display: block;
3
+ margin: 2px;
4
+ padding: 2px;
5
+ }
6
+
7
+ .uwa_files_list LI {
8
+ margin: 2px;
9
+ padding-left: 20px;
10
+ background-repeat: no-repeat;
11
+ background-position: left center;
12
+ line-height: 16px;
13
+ height: 16px;
14
+ clear: both;
15
+ }
16
+
17
+ .uwa_files_list LI INPUT {
18
+ width: 80%;
19
+ border: 1px inset #999;
20
+ }
21
+
22
+ /* This prevent word wrapping */
23
+ .uwa_files_list LI A.file {
24
+ display: block;
25
+ float: left;
26
+ overflow: hidden;
27
+ white-space: nowrap;
28
+ margin: 0px;
29
+ padding: 0px;
30
+ line-height: 16px;
31
+ }
32
+
33
+ .uwa_files_list IMG {
34
+ vertical-align: bottom;
35
+ margin-right: 4px;
36
+ }
37
+
38
+ .uwa_files_list LI A.action {
39
+ display: block;
40
+ float: right;
41
+ }
42
+
43
+ .uwa_files_list A.action IMG {
44
+ margin: 0px 2px;
45
+ padding: 0px;
46
+ }
47
+
48
+ .uwa_files_list A {
49
+ border: none;
50
+ }
51
+
52
+ .uwa_files_footer {
53
+ clear: both;
54
+ background-color: #eee;
55
+ padding: 4px;
56
+ }
57
+
58
+ .uwa_files_list .parent {
59
+ display: block;
60
+ list-style-type: none;
61
+ background: none;
62
+ background-color: #eee;
63
+ line-height: 16px;
64
+ padding: 2px;
65
+ }
66
+ .uwa_files_list .parent A {
67
+ float: right;
68
+ display: block;
69
+ margin: 0px;
70
+ padding: 0px;
71
+ }
72
+
73
+ .uwa_files_message {
74
+ font-style: italic;
75
+ padding: 4px;
76
+ }
77
+
78
+ .uwa_files_footer IMG {
79
+ display: block;
80
+ float: right;
81
+ margin: 3px;
82
+ }
@@ -0,0 +1,46 @@
1
+ /*
2
+ * Inpired by http://www.webtoolkit.info/ajax-file-upload.html
3
+ */
4
+
5
+ Uploader = {}
6
+
7
+ Uploader.submit = function(form, options) {
8
+ var iframe = widget.createElement('iframe');
9
+ iframe.setStyle('display', 'none');
10
+ iframe.src = 'about:blank';
11
+ iframe.id = 'uploader' + Math.floor(Math.random() * 100000);
12
+ iframe.name = iframe.id;
13
+ iframe.onload = function() {
14
+ Uploader.loaded(this);
15
+ }.bind(iframe);
16
+ form.appendChild(iframe);
17
+ if (options && typeof(options.onComplete) == 'function') {
18
+ iframe.onComplete = options.onComplete;
19
+ }
20
+ form.target = iframe.name;
21
+
22
+ if (options && typeof(options.onStart) == 'function') {
23
+ return options.onStart();
24
+ } else {
25
+ return true;
26
+ }
27
+ }
28
+
29
+ Uploader.loaded = function(iframe) {
30
+ if (iframe.contentDocument) {
31
+ var doc = iframe.contentDocument;
32
+ } else if (iframe.contentWindow) {
33
+ var doc = iframe.contentWindow.document;
34
+ }
35
+
36
+ if (doc.location.href == "about:blank") {
37
+ return;
38
+ }
39
+
40
+ if (typeof(iframe.onComplete) == 'function') {
41
+ iframe.onComplete(doc.body.innerHTML);
42
+ }
43
+
44
+ // iframe.parentNode.removeChild(iframe);
45
+ // delete iframe;
46
+ }
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.2
3
+ specification_version: 1
4
+ name: uwa_files
5
+ version: !ruby/object:Gem::Version
6
+ version: "0.1"
7
+ date: 2007-04-12 00:00:00 +02:00
8
+ summary: UWA Files widget
9
+ require_paths:
10
+ - lib
11
+ email: florent@solt.biz
12
+ homepage: http://gnetvibes.rubyforge.org
13
+ rubyforge_project:
14
+ description:
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Florent Solt
31
+ files:
32
+ - lib/uwa_files/widget.rb
33
+ - lib/uwa_files/config.rb
34
+ - resources/parent.gif
35
+ - resources/script.js
36
+ - resources/sha1.js
37
+ - resources/style.css
38
+ - resources/upload.js
39
+ - resources/ajax.gif
40
+ - resources/del.png
41
+ - resources/rename.png
42
+ test_files: []
43
+
44
+ rdoc_options: []
45
+
46
+ extra_rdoc_files: []
47
+
48
+ executables: []
49
+
50
+ extensions: []
51
+
52
+ requirements: []
53
+
54
+ dependencies:
55
+ - !ruby/object:Gem::Dependency
56
+ name: uwa
57
+ version_requirement:
58
+ version_requirements: !ruby/object:Gem::Version::Requirement
59
+ requirements:
60
+ - - ">"
61
+ - !ruby/object:Gem::Version
62
+ version: 0.0.0
63
+ version: