uwa_files 0.1

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.
@@ -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: