xfiles 1.6.0

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.
Files changed (3) hide show
  1. checksums.yaml +15 -0
  2. data/bin/xfiles +406 -0
  3. metadata +58 -0
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ Yzk5YzAzY2ViNWU5MzgyYzBlNTVhODAwN2VhMzgxMWVkNWIxMDEwNg==
5
+ data.tar.gz: !binary |-
6
+ MTY5MGQzODU3NWQxNmJmNzRhNmQxODhhMWFmNTVhMjM0MjJiOTU5YQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ Y2FiMWNkOWEwZWU4NDc5OTMwZTQ4MTQxM2E1MzY5MDVlNmQ1MTk5ZDVmZmI2
10
+ MTdjZmRkNjE3ZmFiNDkyYjUxNTlmOTZjMzU5Njc0YmIyYWY4MTNkZGE0N2Ux
11
+ NmQwNTc0ZGUwZDI4ODUyYmM3YjJkZWY0MjZiMjNjY2Y3Njg5MmQ=
12
+ data.tar.gz: !binary |-
13
+ YTc1NzNiZTk4N2RjY2ZlNWY5OGU5MDBiN2VjYzA3NzM0Y2QxOWIxMmI2M2U1
14
+ NGRjZTc2YmQyM2VkNjJhNGYyYTJkNGRhZDg3Mzc0MjRiZmJlY2VjOGJmZDQ4
15
+ ZDFkNmE2MmJlMmM2NmJhZTcwZGZjMTNkMWRlZDBmNDYxNTY0N2U=
@@ -0,0 +1,406 @@
1
+ #!/usr/bin/env ruby
2
+ VERSION="1.6"
3
+ require 'socket'
4
+ require 'ostruct'
5
+ require 'rubygems'
6
+ require 'optparse'
7
+
8
+ #colorization
9
+ class String
10
+ def colorize(color_code) "\e[#{color_code}m#{self}\e[0m" end
11
+ def red() colorize(31) end
12
+ def lred() colorize(91) end
13
+ def green() colorize(32) end
14
+ def lgreen() colorize(92) end
15
+ def orange() colorize('38;5;208') end
16
+ def yellow() colorize(93) end
17
+ def blue() colorize(34) end
18
+ def pink() colorize(35) end
19
+ end
20
+
21
+ class XFilesConfig < OpenStruct
22
+ def timeout?
23
+ !(self.timeout_state == false)
24
+ end
25
+ def timeout
26
+ return self.timeout_override if self.timeout_override
27
+ self.secure? ? self.default_secure_timeout : self.default_insecure_timeout
28
+ end
29
+ def disable_timeout
30
+ self.timeout_state = false
31
+ end
32
+ def timeout=(seconds)
33
+ self.timeout_override=seconds
34
+ end
35
+ def secure?
36
+ !!(self.secure && self.secure.to_sym != :none)
37
+ end
38
+ def overwrite?
39
+ !!self.overwrite
40
+ end
41
+ def versioned?
42
+ !!self.versioned
43
+ end
44
+ end
45
+
46
+ config = XFilesConfig.new(
47
+ :secure=>:access_code,
48
+ :default_insecure_timeout=>2*60,
49
+ :default_secure_timeout=>30*60,
50
+ :overwrite=>false,
51
+ :versioned=>false,
52
+ :name=>'xFiles Server'
53
+ )
54
+
55
+ optparse = OptionParser.new do |opts|
56
+ opts.banner="Usage: #{File.basename($0)} [ACCESS-CODE] [OPTIONS]"
57
+ opts.program_name=config.name
58
+ opts.release=VERSION
59
+ opts.separator ''
60
+ opts.separator " Access options:"
61
+ opts.on('--no-auth', 'Do not require authorization') do
62
+ config.secure = nil
63
+ end
64
+ opts.on('--timeout=SECONDS', Integer, "Overrides default timeout") do |seconds|
65
+ config.timeout=seconds.to_i
66
+ end
67
+ opts.on('--no-timeout', "Do not use timeout") do
68
+ config.disable_timeout
69
+ end
70
+ opts.separator ''
71
+ opts.separator " Upload options:"
72
+ opts.on('--version-uploads', "Add version number to pre-existing files") do
73
+ config.versioned = true
74
+ end
75
+ opts.on('--overwrite', "Overwrite pre-existing files") do
76
+ config.overwrite=true
77
+ end
78
+ opts.separator ''
79
+ opts.on('--debug') do
80
+ require 'debugger'
81
+ Debugger.settings[:autoeval]=true
82
+ Debugger.settings[:autolist]=1
83
+ Debugger.settings[:listsize]=20
84
+ end
85
+
86
+ opts.on('-v', '--version', 'Show version') do
87
+ puts VERSION
88
+ exit
89
+ end
90
+ opts.separator ''
91
+ end
92
+
93
+ optparse.parse!
94
+ if config.secure? && (ARGV[0].nil? || ARGV[0] !~ /[_0-9a-zA-Z]+/)
95
+ puts "First argument must be an access code".orange
96
+ puts
97
+ exit
98
+ end
99
+
100
+ ACCESS_CODE||=ARGV[0]
101
+ start_message=["Starting #{config.name} with".green]
102
+ start_message << 'NO ACCESS CODE'.orange unless config.secure?
103
+ start_message << 'access code: '.green + ACCESS_CODE.orange if config.secure?
104
+ puts start_message.join(' ')
105
+ unless config.secure?
106
+ puts "WARNING: Anyone can download or upload files without an access code".orange
107
+ end
108
+
109
+ addresses = Socket.ip_address_list.map{|a| a.ip_address}.select do |ip|
110
+ ip =~ /^([0-9]{1,3}\.){3}[0-9]+$/ && ip != "127.0.0.1"
111
+ end.map{|ip| "http://#{ip}:2021" }
112
+ puts "#{config.name} available at: ".green + addresses.join(', ').yellow
113
+
114
+ def seconds_by_unit(seconds)
115
+ case
116
+ when seconds/60 == 0 then "#{seconds} seconds"
117
+ when seconds/3600 == 0 then "%0.1f minutes" % (seconds*1.0/60)
118
+ when seconds/86400 == 0 then "%0.1f hours" % (seconds*1.0/3600)
119
+ else "%0.1f days" % (seconds*1.0/86400)
120
+ end.sub(/\.0+\s/, ' ')
121
+ end
122
+
123
+ # -- START SINATRA
124
+ require 'sinatra'
125
+
126
+ if config.timeout?
127
+ t=Thread.new(config.timeout) do |seconds|
128
+ text = seconds_by_unit(seconds)
129
+ puts "Server will live for #{text}".green
130
+ sleep(seconds)
131
+ puts "Server has timed out after #{text}. Ending session now.".orange
132
+ Process.kill 'TERM', Process.pid
133
+ end
134
+ end
135
+
136
+ # -- Sinatra Controller code starts here
137
+
138
+ use Rack::Session::Cookie, :key => 'xfile_server',
139
+ :path => '/',
140
+ :expire_after => 3600, # In seconds
141
+ :secret => 'qridufkasnvudksursnfjsdl'
142
+
143
+ #set :environment, 'production'
144
+ set :port, 2021
145
+ set :bind, "0.0.0.0"
146
+ enable :sessions
147
+
148
+ FLASH={}
149
+ def clear_flash
150
+ FLASH.clear
151
+ end
152
+
153
+ def flash
154
+ FLASH
155
+ end
156
+
157
+ configure do
158
+ mime_type :css, 'text/css'
159
+ end
160
+
161
+ before do
162
+ expires 0, :public, :must_revalidate
163
+ end
164
+
165
+ get "/" do
166
+ @hostname=Socket.gethostname
167
+ @config = config
168
+ @pwd=Dir.pwd
169
+ @files=Dir.glob('*').select{|fn| File.file?(fn) }.sort
170
+ @authorized=session[:access_code] && session[:access_code] == ACCESS_CODE
171
+ #haml :upload_haml
172
+ output = if config.secure? && !@authorized
173
+ erb :access_erb
174
+ else
175
+ erb :upload_erb
176
+ end
177
+ clear_flash
178
+ output
179
+ end
180
+
181
+ get "/css/style.css" do
182
+ content_type :css
183
+ erb :style
184
+ end
185
+
186
+ get "/download/:filename" do |filename|
187
+ if File.exist?(filename)
188
+ puts "Sent file '#{filename}' to #{request.ip}".yellow
189
+ send_file filename, :filename=>filename, :type=>"application/octet-stream"
190
+ else
191
+ redirect "/"
192
+ end
193
+ end
194
+
195
+ post "/access" do
196
+ if params[:code] == ACCESS_CODE
197
+ session[:access_code] = ACCESS_CODE
198
+ puts "Access granted to #{request.ip}".green
199
+ else
200
+ flash[:error]='Incorrect access code!'
201
+ puts "Access denied to #{request.ip}".orange
202
+ end
203
+ redirect "/"
204
+ end
205
+
206
+ get "/access" do
207
+ redirect "/"
208
+ end
209
+
210
+ get "/upload" do
211
+ redirect "/"
212
+ end
213
+
214
+ def versioned_filename(filename, version)
215
+ parts = filename.split(/\./)
216
+ parts[0] = parts[0] + "(v#{version})"
217
+ parts.join('.')
218
+ end
219
+ def unversioned_filename(filename)
220
+ filename.sub(/(\w)\(v\d+\)(\.|$)/, '\1\2')
221
+ end
222
+
223
+ def find_free_version_number(filename)
224
+ version = 1
225
+ while File.exist?(versioned_filename(filename, version)) do
226
+ version += 1;
227
+ end
228
+ version
229
+ end
230
+
231
+ def advance_versions(filename, version)
232
+ moveto = versioned_filename(filename, version)
233
+ version.downto(2) do |v|
234
+ orig = versioned_filename(filename, v-1)
235
+ File.rename(orig, moveto) if File.exist?(orig)
236
+ moveto = orig
237
+ end
238
+ File.rename(filename, moveto) if File.exist?(filename)
239
+ end
240
+
241
+ def file_size(filename)
242
+ size = File.size(filename)*1.0
243
+ for index in 0..3
244
+ break if size < 1024
245
+ size /= 1024
246
+ end
247
+ "%.2f %s" % [size, %w(bytes KB MB GB)[index]]
248
+ end
249
+
250
+ post "/upload" do
251
+ if params['file'] && params['file'][:filename] && !params['file'][:filename].empty?
252
+ filename=params['file'][:filename]
253
+ unless config.overwrite?
254
+ if config.versioned?
255
+ filename = unversioned_filename(filename)
256
+ free_version = find_free_version_number(filename)
257
+ advance_versions(filename, free_version)
258
+ else
259
+ index=0
260
+ while File.exist?(filename) do
261
+ index+=1
262
+ filename=params['file'][:filename].sub(/\./, "(#{index}).")
263
+ end
264
+ end
265
+ end
266
+ File.open(filename, "w") {|f| f.write(params['file'][:tempfile].read) }
267
+ puts "Received file '#{filename}' (#{file_size(filename)}) from #{request.ip}".yellow
268
+ end
269
+ redirect "/"
270
+ end
271
+
272
+
273
+ __END__
274
+
275
+ @@ style
276
+
277
+ body {
278
+ margin: 0;
279
+ padding: 0;
280
+ }
281
+
282
+ #page {
283
+ margin: 0;
284
+ padding-bottom: 65px;
285
+ }
286
+ #header {
287
+ margin-bottom: 15px;
288
+ width: 100%;
289
+ background-color: lightblue;
290
+ }
291
+ #directory {
292
+ margin-top: 25px;
293
+ }
294
+ h3 {
295
+ margin-top: 0;
296
+ margin-bottom: 2px;
297
+ font-weight: normal;
298
+ }
299
+ hr {
300
+ margin-top: 0;
301
+ margin-bottom: 5px;
302
+ }
303
+ #files {
304
+ font-weight: bold;
305
+ }
306
+ form#upload {
307
+ margin-top: 20px;
308
+ }
309
+ #page>h1, #page>h3, #page>form, #page>ul, #page>p {
310
+ padding-left: 20px;
311
+ }
312
+ #page>ul {
313
+ list-style-type: none;
314
+ }
315
+
316
+ @@ access_erb
317
+
318
+ <html>
319
+ <head>
320
+ <title><%= @config.name %> -- Access</title>
321
+ <link rel='stylesheet' type='text/css' href='/css/style.css'/>
322
+ </head>
323
+ <body>
324
+ <div id='page'>
325
+ <h1 id='header'><%= @config.name %> on <%=@hostname %></h1>
326
+ <h3 id='directory'>Location: <%=@pwd.gsub(/[^\/]/, '*') %></h3><hr/>
327
+ <br/>
328
+ <form id='access' method="post" action='/access'>
329
+ <% if flash[:error] %>
330
+ <p style='color: red'><%=flash[:error] %></p>
331
+ <% end %>
332
+ Access Code: <input type='text' name='code' autofocus /><br/>
333
+ <input type='submit' value='Access'/>
334
+ </form>
335
+ <br/>
336
+ <hr/>
337
+ </div>
338
+ </body>
339
+ </html>
340
+
341
+ @@ upload_erb
342
+
343
+ <html>
344
+ <head>
345
+ <title><%= @config.name %></title>
346
+ <link rel='stylesheet' type='text/css' href='/css/style.css'/>
347
+ <meta http-equiv='refresh' content="60;url=/" />
348
+ <script type='text/javascript'>
349
+ function close_confirm(elem) {
350
+ elem.parentNode.style.visibility='hidden';
351
+ stop_propagation();
352
+ }
353
+ function hide_all_confirms() {
354
+ targets = document.getElementsByClassName('confirm');
355
+ for(i=0; i<targets.length; i++) {
356
+ targets[i].style.visibility='hidden';
357
+ }
358
+ }
359
+ function stop_propagation() {
360
+ if (window.event) {
361
+ if (window.event.stopPropagation) {
362
+ window.event.stopPropagation();
363
+ } else {
364
+ window.event.cancelBubble = true;
365
+ }
366
+ }
367
+ }
368
+ function show_sibling(elem) {
369
+ target = elem.parentNode.getElementsByTagName("div")[0];
370
+ if (target.style.visibility == 'visible') {
371
+ target.style.visibility = 'hidden';
372
+ } else {
373
+ hide_all_confirms();
374
+ target.style.visibility='visible';
375
+ }
376
+ stop_propagation();
377
+ }
378
+ </script>
379
+ </head>
380
+ <body onclick="hide_all_confirms();">
381
+ <div id='page'>
382
+ <h1 id='header'><%= @config.name %> on <%=@hostname %></h1>
383
+ <h3 id='directory'>Location: <%=@pwd %></h3><hr/>
384
+ <% if @files.empty? %>
385
+ <p>No files in directory</p>
386
+ <% else %>
387
+ <ul id='files'><% @files.each do |file| %>
388
+ <li>
389
+ <a href='' onclick="show_sibling(this); return false;"><%=file %></a>
390
+ <div class='confirm' style='visibility: hidden; display: inline-block;'>
391
+ &nbsp; <==&nbsp; download?&nbsp;
392
+ <a href="/download/<%=file %>" onclick="close_confirm(this); return true;">Yes</a>
393
+ <a href='' onclick="close_confirm(this); return false;">No</a>
394
+ </div>
395
+ </li>
396
+ <% end %></ul>
397
+ <% end %><hr/>
398
+ <form id='upload' method="post" action='/upload' enctype='multipart/form-data'>
399
+ <h3>Upload File</h3>
400
+ <input type='file' name='file'/><br/>
401
+ <input type='submit' value='Upload'/>
402
+ </form>
403
+ </div>
404
+ </body>
405
+ </html>
406
+
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xfiles
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.6.0
5
+ platform: ruby
6
+ authors:
7
+ - R2dR
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-07-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sinatra
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.4.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.4.3
27
+ description: Easy file transfer web interface built on Sinatra
28
+ email: github@r2dr.com
29
+ executables:
30
+ - xfiles
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - bin/xfiles
35
+ homepage: http://github.com/r2dr/xfiles
36
+ licenses: []
37
+ metadata: {}
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubyforge_project:
54
+ rubygems_version: 2.0.6
55
+ signing_key:
56
+ specification_version: 4
57
+ summary: Sinatra file transfer web interface
58
+ test_files: []