ibg 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +14 -0
- data/.travis.yml +7 -0
- data/Dockerfile +47 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +22 -0
- data/README.md +72 -0
- data/Rakefile +6 -0
- data/bin/iceberg-sync +137 -0
- data/bin/iceberg-web +8 -0
- data/config.ru +2 -0
- data/examples/docker/default.conf +38 -0
- data/examples/s3/credentials +3 -0
- data/ibg.gemspec +27 -0
- data/lib/ibg/storage.rb +96 -0
- data/lib/ibg/version.rb +3 -0
- data/lib/ibg/web.rb +213 -0
- data/lib/ibg.rb +185 -0
- data/public/bootstrap-filestyle.min.js +1 -0
- data/public/carousel.css +134 -0
- data/public/favicon.ico +0 -0
- data/public/humans.txt +2 -0
- data/public/index.js +88 -0
- data/public/jquery-2.1.3.min.js +4 -0
- data/public/robots.txt +5 -0
- data/public/show.js +9 -0
- data/public/starter-template.css +21 -0
- data/public/style.css +4 -0
- data/public/tripcode.js +53 -0
- data/public/white300x32.png +0 -0
- data/test/test_entry.rb +15 -0
- data/views/_navbar.haml +9 -0
- data/views/container.haml +8 -0
- data/views/index.haml +246 -0
- data/views/show.haml +93 -0
- data/views/tripcode.haml +56 -0
- metadata +180 -0
data/lib/ibg/web.rb
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
require 'haml'
|
4
|
+
require 'sinatra/base'
|
5
|
+
require 'nkf'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'base64'
|
8
|
+
require 'json'
|
9
|
+
|
10
|
+
module Iceberg
|
11
|
+
|
12
|
+
class WebApp < Sinatra::Base
|
13
|
+
|
14
|
+
ICEBERG_HOME = File.dirname(__FILE__) + '/../../'
|
15
|
+
set :protection, :except => :frame_options
|
16
|
+
set :public_folder, ICEBERG_HOME + 'public'
|
17
|
+
set :views, ICEBERG_HOME + 'views'
|
18
|
+
|
19
|
+
FORBIDDEN_CHARS = " #<>:\\/*?\"|&',;`"
|
20
|
+
|
21
|
+
enable :sessions
|
22
|
+
|
23
|
+
helpers do
|
24
|
+
|
25
|
+
include Rack::Utils; alias_method :h, :escape_html
|
26
|
+
def partial(template, options = {})
|
27
|
+
options = options.merge({:layout => false})
|
28
|
+
template = "_#{template.to_s}".to_sym
|
29
|
+
haml(template, options)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
CONTENT_TYPES = {
|
35
|
+
:html => 'text/html',
|
36
|
+
:css => 'text/css',
|
37
|
+
:js => 'application/javascript',
|
38
|
+
:txt => 'text/plain',
|
39
|
+
}
|
40
|
+
|
41
|
+
before do
|
42
|
+
request_uri = case request.env['REQUEST_URI'].split('?')[0]
|
43
|
+
when /\.css$/ ; :css
|
44
|
+
when /\.js$/ ; :js
|
45
|
+
when /\.txt$/ ; :txt
|
46
|
+
else :html
|
47
|
+
end
|
48
|
+
content_type CONTENT_TYPES[request_uri], :charset => 'utf-8'
|
49
|
+
response.headers['Cache-Control'] = 'no-cache'
|
50
|
+
end
|
51
|
+
|
52
|
+
get '/' do
|
53
|
+
filemax = SETTING['local']['filemax']
|
54
|
+
recentfiles = REDIS.lrange(IBDB_RECENT, 0, filemax)
|
55
|
+
tripcodelist = REDIS.smembers(IBDB_TRIPCODE_SET)
|
56
|
+
uploaded = session[:uploaded]
|
57
|
+
session[:uploaded] = nil
|
58
|
+
maxfilesize = SETTING['local']['maxfilesize']
|
59
|
+
recentpeers = REDIS.lrange(IBDB_RECENT_PEERS, 0, 10).map do |peer|
|
60
|
+
digest = Iceberg.ip2digest(peer)
|
61
|
+
peerinfo = JSON.parse(REDIS.hget(IBDB_PEERS, digest) || '{}')
|
62
|
+
[digest, peerinfo['download']]
|
63
|
+
end
|
64
|
+
haml :index, :locals => { :recentfiles => recentfiles,
|
65
|
+
:uploaded => uploaded, :tripcodelist => tripcodelist,
|
66
|
+
:filemax => filemax, :maxfilesize => maxfilesize,
|
67
|
+
:recentpeers => recentpeers }
|
68
|
+
end
|
69
|
+
|
70
|
+
get '/api/v1/recentfiles' do
|
71
|
+
filemax = SETTING['local']['filemax']
|
72
|
+
recentfiles = REDIS.lrange(IBDB_RECENT, 0, filemax)
|
73
|
+
rv = { :recentfiles => recentfiles, :filemax => filemax }
|
74
|
+
rv.to_json + "\n"
|
75
|
+
end
|
76
|
+
|
77
|
+
get '/api/v1/tripcodelist' do
|
78
|
+
tripcodelist = REDIS.smembers(IBDB_TRIPCODE_SET)
|
79
|
+
rv = { :tripcodelist => tripcodelist }
|
80
|
+
rv.to_json + "\n"
|
81
|
+
end
|
82
|
+
|
83
|
+
post '/upload' do
|
84
|
+
f = params[:file]
|
85
|
+
redirect '/' if f.nil? # TODO error
|
86
|
+
path = f[:tempfile].path
|
87
|
+
begin
|
88
|
+
filemax = SETTING['local']['filemax']
|
89
|
+
maxfilesize = SETTING['local']['maxfilesize']
|
90
|
+
rv = Iceberg.upload(path, params[:tripkey], maxfilesize, filemax)
|
91
|
+
origname = f[:filename]
|
92
|
+
origname = File.basename(origname)
|
93
|
+
origname = NKF.nkf("-w", origname) # TODO i18n
|
94
|
+
origname = origname.tr(FORBIDDEN_CHARS, "_")
|
95
|
+
rv[:name] = origname
|
96
|
+
session[:uploaded] = rv
|
97
|
+
rescue => x
|
98
|
+
p x
|
99
|
+
end
|
100
|
+
redirect '/'
|
101
|
+
end
|
102
|
+
|
103
|
+
post '/api/v1/upload' do
|
104
|
+
begin
|
105
|
+
f = params[:file]
|
106
|
+
raise if f.nil?
|
107
|
+
path = f[:tempfile].path
|
108
|
+
filemax = SETTING['local']['filemax']
|
109
|
+
maxfilesize = SETTING['local']['maxfilesize']
|
110
|
+
rv = Iceberg.upload(path, params[:tripkey], maxfilesize, filemax)
|
111
|
+
rescue => x
|
112
|
+
rv = { :error => x.to_s }
|
113
|
+
end
|
114
|
+
content_type CONTENT_TYPES[:js], :charset => 'utf-8'
|
115
|
+
rv.to_json + "\n"
|
116
|
+
end
|
117
|
+
|
118
|
+
post '/api/v1/uploadraw' do
|
119
|
+
begin
|
120
|
+
f = params[:file]
|
121
|
+
raise if f.nil?
|
122
|
+
path = f[:tempfile].path
|
123
|
+
origname = f[:filename] # TODO check SHA-1
|
124
|
+
filemax = SETTING['local']['filemax']
|
125
|
+
maxfilesize = SETTING['local']['maxfilesize']
|
126
|
+
rv = Iceberg.uploadraw(path, maxfilesize, filemax)
|
127
|
+
rescue => x
|
128
|
+
rv = { :error => x.to_s }
|
129
|
+
end
|
130
|
+
content_type CONTENT_TYPES[:js], :charset => 'utf-8'
|
131
|
+
rv.to_json + "\n"
|
132
|
+
end
|
133
|
+
|
134
|
+
post '/uploadtext' do
|
135
|
+
# TODO
|
136
|
+
text = params[:text]
|
137
|
+
title = params[:title]
|
138
|
+
begin
|
139
|
+
filemax = SETTING['local']['filemax']
|
140
|
+
maxfilesize = SETTING['local']['maxfilesize']
|
141
|
+
rv = Iceberg.upload(nil, params[:tripkey], maxfilesize, filemax, text)
|
142
|
+
origname = "#{title}.txt"
|
143
|
+
origname = origname.tr(FORBIDDEN_CHARS, "_")
|
144
|
+
rv[:name] = origname
|
145
|
+
session[:uploaded] = rv
|
146
|
+
rescue => x
|
147
|
+
p x
|
148
|
+
end
|
149
|
+
redirect '/'
|
150
|
+
end
|
151
|
+
|
152
|
+
get '/show/:name' do
|
153
|
+
name = params[:name]
|
154
|
+
filename = params[:filename]
|
155
|
+
hexdigest = params[:digest]
|
156
|
+
b = Storage.new
|
157
|
+
o = b.getobject(name)
|
158
|
+
ex = o.exists?
|
159
|
+
size = ex ? o.content_length : nil
|
160
|
+
haml :show, :locals => {:name => name, :filename => filename,
|
161
|
+
:hexdigest => hexdigest, :filesize => size,
|
162
|
+
:exists => o.exists?}
|
163
|
+
end
|
164
|
+
|
165
|
+
get '/container/:name' do
|
166
|
+
name = params[:name]
|
167
|
+
hexdigest = params[:digest]
|
168
|
+
haml :container, :locals => { :name => name, :hexdigest => hexdigest }
|
169
|
+
end
|
170
|
+
|
171
|
+
['/download/:name', '/api/v1/download/:name'].each do |path|
|
172
|
+
get path do
|
173
|
+
name = params[:name]
|
174
|
+
filename = params[:filename]
|
175
|
+
hexdigest = params[:digest]
|
176
|
+
ctype, disp, file, cipher = Iceberg.download(name, filename, hexdigest)
|
177
|
+
error 404 unless file.exists?
|
178
|
+
if ctype == 'text/plain'
|
179
|
+
content_type ctype, :charset => 'utf-8'
|
180
|
+
else
|
181
|
+
content_type ctype
|
182
|
+
end
|
183
|
+
if filename
|
184
|
+
response.headers['Content-Disposition'] =
|
185
|
+
"#{disp}; filename=\"#{filename}\""
|
186
|
+
end
|
187
|
+
stream do |out|
|
188
|
+
begin
|
189
|
+
file.read do |data|
|
190
|
+
data = cipher.update(data) if cipher
|
191
|
+
out << data
|
192
|
+
sleep 0.1 # TODO
|
193
|
+
end
|
194
|
+
out << cipher.final if cipher
|
195
|
+
rescue => x
|
196
|
+
p x
|
197
|
+
end
|
198
|
+
Iceberg.recordip(request.ip)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
get '/tripcode/:tripcode' do
|
204
|
+
tripcode = params[:tripcode]
|
205
|
+
files = REDIS.smembers(IBDB_TRIPCODE + tripcode)
|
206
|
+
fund = REDIS.get(IBDB_TRIPCODE_FUND + tripcode)
|
207
|
+
haml :tripcode, :locals => { :tripcode => tripcode, :files => files,
|
208
|
+
:fund => fund }
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
data/lib/ibg.rb
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'redis'
|
5
|
+
require 'yaml'
|
6
|
+
require 'openssl'
|
7
|
+
require "ibg/version"
|
8
|
+
require 'ibg/storage'
|
9
|
+
require 'ibg/web'
|
10
|
+
|
11
|
+
module Iceberg
|
12
|
+
|
13
|
+
HOME_DIR = ENV['HOME']
|
14
|
+
SETTING_DIR = File.join(HOME_DIR, '.iceberg')
|
15
|
+
SETTING_FILE = File.join(SETTING_DIR, 'settings.yaml')
|
16
|
+
unless File.exist?(SETTING_DIR)
|
17
|
+
FileUtils.mkdir SETTING_DIR
|
18
|
+
end
|
19
|
+
unless File.exist?(SETTING_FILE)
|
20
|
+
open(SETTING_FILE, 'w') do |fd|
|
21
|
+
setting = {
|
22
|
+
'local' => {
|
23
|
+
'download' => File.join(SETTING_DIR, 'download'),
|
24
|
+
'filemax' => 200,
|
25
|
+
'maxfilesize' => 20 * 1024 * 1024, # 20 MiB
|
26
|
+
'demourl' => '/show/3f636ca05f41c4a6dfd5f8cbc7a9dc0125b9a9b7?' +
|
27
|
+
'digest=cafcc2df4c3998ba5ab94b5262ef3369502488f7&' +
|
28
|
+
'filename=jBRA8.webm', # Big Buck Bunny
|
29
|
+
'cdn' => 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1',
|
30
|
+
'salt' => rand.to_s,
|
31
|
+
's3bucket' => false,
|
32
|
+
},
|
33
|
+
}
|
34
|
+
fd.puts(YAML.dump(setting))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
SETTING = YAML.load(File.read(SETTING_FILE))
|
39
|
+
|
40
|
+
IBDB_RECENT = 'iceberg:recent'
|
41
|
+
IBDB_TRIPCODE = 'iceberg:tripcode:'
|
42
|
+
IBDB_TRIPCODE_SET = 'iceberg:tripcode:set'
|
43
|
+
IBDB_TRIPCODE_FUND = 'iceberg:tripcode:fund:'
|
44
|
+
IBDB_RECENT_PEERS = 'iceberg:recentpeers'
|
45
|
+
IBDB_PEERS = 'iceberg:peers'
|
46
|
+
|
47
|
+
ALGORITHM = 'AES-128-CBC'
|
48
|
+
REDIS = if ENV['DB_PORT_6379_TCP_PORT']
|
49
|
+
host = ENV['DB_PORT_6379_TCP_ADDR']
|
50
|
+
port = ENV['DB_PORT_6379_TCP_PORT'].to_i
|
51
|
+
Redis.new(:host => host, :port => port)
|
52
|
+
else
|
53
|
+
Redis.new
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.uploadimpl2(filemax, encdigest)
|
57
|
+
n = REDIS.lpush(IBDB_RECENT, encdigest)
|
58
|
+
tripcodelist = REDIS.smembers(IBDB_TRIPCODE_SET) || []
|
59
|
+
while n.size > filemax
|
60
|
+
n = REDIS.llen(IBDB_RECENT)
|
61
|
+
dropdigest = REDIS.rpop(IBDB_RECENT)
|
62
|
+
b = Iceberg::Storage.new
|
63
|
+
dropfile = b.getobject(dropdigest)
|
64
|
+
dropsize = dropfile.content_length / (1024 * 1024) # MiB
|
65
|
+
dropsize = 1 if dropsize <= 0 # TODO handle under 1 MiB
|
66
|
+
found = false
|
67
|
+
tripcodelist.each do |tripcode|
|
68
|
+
next unless REDIS.sismember(IBDB_TRIPCODE + tripcode, dropdigest)
|
69
|
+
v = REDIS.get(IBDB_TRIPCODE_FUND + tripcode)
|
70
|
+
if v.to_i > 0
|
71
|
+
REDIS.decrby(IBDB_TRIPCODE_FUND + tripcode, dropsize)
|
72
|
+
found = true
|
73
|
+
break
|
74
|
+
end
|
75
|
+
end
|
76
|
+
unless found
|
77
|
+
# TODO do not remove small files (for now)
|
78
|
+
dropfile.delete if dropfile.content_length > 64 * 1024 # 64 KiB
|
79
|
+
break
|
80
|
+
end
|
81
|
+
REDIS.lpush(IBDB_RECENT, dropdigest)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.upload(path, tripkey, filesize, filemax, text = nil)
|
86
|
+
tripkey = nil if tripkey.empty?
|
87
|
+
if path
|
88
|
+
raise 'size over' if File.new(path).size > filesize
|
89
|
+
alldata = File.open(path, 'rb'){|fd| fd.read}
|
90
|
+
else
|
91
|
+
raise 'size over' if text.size > filesize # TODO convert to binary size
|
92
|
+
alldata = text
|
93
|
+
end
|
94
|
+
digest = Digest::SHA1.digest(alldata)
|
95
|
+
hexdigest = digest.unpack('H*')[0]
|
96
|
+
cipher = OpenSSL::Cipher::Cipher.new(ALGORITHM).encrypt
|
97
|
+
cipher.key = digest[0, 16]
|
98
|
+
cipher.iv = digest[4, 16]
|
99
|
+
encdata = cipher.update(alldata) + cipher.final
|
100
|
+
encdigest = Digest::SHA1.hexdigest(encdata)
|
101
|
+
|
102
|
+
b = Iceberg::Storage.new
|
103
|
+
dest = b.getobject(encdigest)
|
104
|
+
unless dest.exists?
|
105
|
+
dest.write(encdata)
|
106
|
+
dest.close rescue nil # TODO
|
107
|
+
uploadimpl2(filemax, encdigest)
|
108
|
+
end
|
109
|
+
if tripkey
|
110
|
+
tripcode = Base64.encode64(Digest::SHA1.digest(tripkey))[0, 12]
|
111
|
+
tripcode = tripcode.tr('/', '.')
|
112
|
+
REDIS.sadd(IBDB_TRIPCODE_SET, tripcode)
|
113
|
+
REDIS.sadd(IBDB_TRIPCODE + tripcode, encdigest)
|
114
|
+
# TODO test (initial bonus)
|
115
|
+
v = REDIS.get(IBDB_TRIPCODE_FUND + tripcode)
|
116
|
+
if v
|
117
|
+
REDIS.incrby(IBDB_TRIPCODE_FUND + tripcode, 1)
|
118
|
+
else
|
119
|
+
REDIS.set(IBDB_TRIPCODE_FUND + tripcode, 2)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
{
|
123
|
+
:digest => hexdigest,
|
124
|
+
:encdigest => encdigest,
|
125
|
+
:tripcode => tripcode,
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.uploadraw(path, filesize, filemax)
|
130
|
+
raise 'size over' if File.new(path).size > filesize
|
131
|
+
encdata = File.open(path, 'rb'){|fd| fd.read} # TODO large file
|
132
|
+
encdigest = Digest::SHA1.hexdigest(encdata)
|
133
|
+
# TODO check filename
|
134
|
+
b = Iceberg::Storage.new
|
135
|
+
dest = b.getobject(encdigest)
|
136
|
+
unless dest.exists?
|
137
|
+
dest.write(encdata)
|
138
|
+
dest.close rescue nil # TODO
|
139
|
+
uploadimpl2(filemax, encdigest)
|
140
|
+
end
|
141
|
+
{
|
142
|
+
}
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.download(name, filename, hexdigest)
|
146
|
+
ctype, disp = case filename
|
147
|
+
when /\.jpg$/ ; ['image/jpeg', 'inline']
|
148
|
+
when /\.png$/ ; ['image/png', 'inline']
|
149
|
+
when /\.gif$/ ; ['image/gif', 'inline']
|
150
|
+
when /\.mp3$/ ; ['audio/mpeg', 'inline']
|
151
|
+
when /\.ogg$/ ; ['audio/ogg', 'inline']
|
152
|
+
when /\.flac$/ ; ['audio/flac', 'inline']
|
153
|
+
when /\.webm$/ ; ['video/webm', 'inline']
|
154
|
+
when /\.txt$/ ; ['text/plain', 'inline']
|
155
|
+
else ['application/octet-stream', 'attachment']
|
156
|
+
end
|
157
|
+
if hexdigest
|
158
|
+
digest = [hexdigest].pack('H*')
|
159
|
+
cipher = OpenSSL::Cipher::Cipher.new(ALGORITHM).decrypt
|
160
|
+
cipher.key = digest[0, 16]
|
161
|
+
cipher.iv = digest[4, 16]
|
162
|
+
end
|
163
|
+
b = Iceberg::Storage.new
|
164
|
+
file = b.getobject(name)
|
165
|
+
[ctype, disp, file, cipher]
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.ip2digest(ip)
|
169
|
+
salt = SETTING['local']['salt'] || ''
|
170
|
+
Digest::SHA1.hexdigest(salt + ip)
|
171
|
+
end
|
172
|
+
|
173
|
+
def self.recordip(ip)
|
174
|
+
info = JSON.parse(REDIS.hget(IBDB_PEERS, ip2digest(ip)) || '{}')
|
175
|
+
info['download'] ||= 0
|
176
|
+
info['download'] += 1
|
177
|
+
REDIS.hset(IBDB_PEERS, ip2digest(ip), info.to_json)
|
178
|
+
REDIS.lrem(IBDB_RECENT_PEERS, 1, ip)
|
179
|
+
REDIS.lpush(IBDB_RECENT_PEERS, ip)
|
180
|
+
if REDIS.llen(IBDB_RECENT_PEERS) > 10 # TODO
|
181
|
+
REDIS.rpop(IBDB_RECENT_PEERS)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
(function(c){var b=function(d,e){this.options=e;this.$elementFilestyle=[];this.$element=c(d)};b.prototype={clear:function(){this.$element.val("");this.$elementFilestyle.find(":text").val("");this.$elementFilestyle.find(".badge").remove()},destroy:function(){this.$element.removeAttr("style").removeData("filestyle").val("");this.$elementFilestyle.remove()},disabled:function(d){if(d===true){if(!this.options.disabled){this.$element.attr("disabled","true");this.$elementFilestyle.find("label").attr("disabled","true");this.options.disabled=true}}else{if(d===false){if(this.options.disabled){this.$element.removeAttr("disabled");this.$elementFilestyle.find("label").removeAttr("disabled");this.options.disabled=false}}else{return this.options.disabled}}},buttonBefore:function(d){if(d===true){if(!this.options.buttonBefore){this.options.buttonBefore=true;if(this.options.input){this.$elementFilestyle.remove();this.constructor();this.pushNameFiles()}}}else{if(d===false){if(this.options.buttonBefore){this.options.buttonBefore=false;if(this.options.input){this.$elementFilestyle.remove();this.constructor();this.pushNameFiles()}}}else{return this.options.buttonBefore}}},icon:function(d){if(d===true){if(!this.options.icon){this.options.icon=true;this.$elementFilestyle.find("label").prepend(this.htmlIcon())}}else{if(d===false){if(this.options.icon){this.options.icon=false;this.$elementFilestyle.find(".glyphicon").remove()}}else{return this.options.icon}}},input:function(e){if(e===true){if(!this.options.input){this.options.input=true;if(this.options.buttonBefore){this.$elementFilestyle.append(this.htmlInput())}else{this.$elementFilestyle.prepend(this.htmlInput())}this.$elementFilestyle.find(".badge").remove();this.pushNameFiles();this.$elementFilestyle.find(".group-span-filestyle").addClass("input-group-btn")}}else{if(e===false){if(this.options.input){this.options.input=false;this.$elementFilestyle.find(":text").remove();var d=this.pushNameFiles();if(d.length>0&&this.options.badge){this.$elementFilestyle.find("label").append(' <span class="badge">'+d.length+"</span>")}this.$elementFilestyle.find(".group-span-filestyle").removeClass("input-group-btn")}}else{return this.options.input}}},size:function(d){if(d!==undefined){var f=this.$elementFilestyle.find("label"),e=this.$elementFilestyle.find("input");f.removeClass("btn-lg btn-sm");e.removeClass("input-lg input-sm");if(d!="nr"){f.addClass("btn-"+d);e.addClass("input-"+d)}}else{return this.options.size}},buttonText:function(d){if(d!==undefined){this.options.buttonText=d;this.$elementFilestyle.find("label span").html(this.options.buttonText)}else{return this.options.buttonText}},buttonName:function(d){if(d!==undefined){this.options.buttonName=d;this.$elementFilestyle.find("label").attr({"class":"btn "+this.options.buttonName})}else{return this.options.buttonName}},iconName:function(d){if(d!==undefined){this.$elementFilestyle.find(".glyphicon").attr({"class":".glyphicon "+this.options.iconName})}else{return this.options.iconName}},htmlIcon:function(){if(this.options.icon){return'<span class="glyphicon '+this.options.iconName+'"></span> '}else{return""}},htmlInput:function(){if(this.options.input){return'<input type="text" class="form-control '+(this.options.size=="nr"?"":"input-"+this.options.size)+'" disabled> '}else{return""}},pushNameFiles:function(){var d="",f=[];if(this.$element[0].files===undefined){f[0]={name:this.$element[0]&&this.$element[0].value}}else{f=this.$element[0].files}for(var e=0;e<f.length;e++){d+=f[e].name.split("\\").pop()+", "}if(d!==""){this.$elementFilestyle.find(":text").val(d.replace(/\, $/g,""))}else{this.$elementFilestyle.find(":text").val("")}return f},constructor:function(){var h=this,f="",g=h.$element.attr("id"),d=[],i="",e;if(g===""||!g){g="filestyle-"+c(".bootstrap-filestyle").length;h.$element.attr({id:g})}i='<span class="group-span-filestyle '+(h.options.input?"input-group-btn":"")+'"><label for="'+g+'" class="btn '+h.options.buttonName+" "+(h.options.size=="nr"?"":"btn-"+h.options.size)+'" '+(h.options.disabled?'disabled="true"':"")+">"+h.htmlIcon()+h.options.buttonText+"</label></span>";f=h.options.buttonBefore?i+h.htmlInput():h.htmlInput()+i;h.$elementFilestyle=c('<div class="bootstrap-filestyle input-group">'+f+"</div>");h.$elementFilestyle.find(".group-span-filestyle").attr("tabindex","0").keypress(function(j){if(j.keyCode===13||j.charCode===32){h.$elementFilestyle.find("label").click();return false}});h.$element.css({position:"absolute",clip:"rect(0px 0px 0px 0px)"}).attr("tabindex","-1").after(h.$elementFilestyle);if(h.options.disabled){h.$element.attr("disabled","true")}h.$element.change(function(){var j=h.pushNameFiles();if(h.options.input==false&&h.options.badge){if(h.$elementFilestyle.find(".badge").length==0){h.$elementFilestyle.find("label").append(' <span class="badge">'+j.length+"</span>")}else{if(j.length==0){h.$elementFilestyle.find(".badge").remove()}else{h.$elementFilestyle.find(".badge").html(j.length)}}}else{h.$elementFilestyle.find(".badge").remove()}});if(window.navigator.userAgent.search(/firefox/i)>-1){h.$elementFilestyle.find("label").click(function(){h.$element.click();return false})}}};var a=c.fn.filestyle;c.fn.filestyle=function(e,d){var f="",g=this.each(function(){if(c(this).attr("type")==="file"){var j=c(this),h=j.data("filestyle"),i=c.extend({},c.fn.filestyle.defaults,e,typeof e==="object"&&e);if(!h){j.data("filestyle",(h=new b(this,i)));h.constructor()}if(typeof e==="string"){f=h[e](d)}}});if(typeof f!==undefined){return f}else{return g}};c.fn.filestyle.defaults={buttonText:"Choose file",iconName:"glyphicon-folder-open",buttonName:"btn-default",size:"nr",input:true,badge:true,icon:true,buttonBefore:false,disabled:false};c.fn.filestyle.noConflict=function(){c.fn.filestyle=a;return this};c(function(){c(".filestyle").each(function(){var e=c(this),d={input:e.attr("data-input")==="false"?false:true,icon:e.attr("data-icon")==="false"?false:true,buttonBefore:e.attr("data-buttonBefore")==="true"?true:false,disabled:e.attr("data-disabled")==="true"?true:false,size:e.attr("data-size"),buttonText:e.attr("data-buttonText"),buttonName:e.attr("data-buttonName"),iconName:e.attr("data-iconName"),badge:e.attr("data-badge")==="false"?false:true};e.filestyle(d)})})})(window.jQuery);
|
data/public/carousel.css
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
/* GLOBAL STYLES
|
2
|
+
-------------------------------------------------- */
|
3
|
+
/* Padding below the footer and lighter body text */
|
4
|
+
|
5
|
+
body {
|
6
|
+
padding-bottom: 40px;
|
7
|
+
color: #5a5a5a;
|
8
|
+
}
|
9
|
+
|
10
|
+
|
11
|
+
/* CUSTOMIZE THE NAVBAR
|
12
|
+
-------------------------------------------------- */
|
13
|
+
|
14
|
+
/* Special class on .container surrounding .navbar, used for positioning it into place. */
|
15
|
+
.navbar-wrapper {
|
16
|
+
position: absolute;
|
17
|
+
top: 0;
|
18
|
+
right: 0;
|
19
|
+
left: 0;
|
20
|
+
z-index: 20;
|
21
|
+
}
|
22
|
+
|
23
|
+
/* Flip around the padding for proper display in narrow viewports */
|
24
|
+
.navbar-wrapper > .container {
|
25
|
+
padding-right: 0;
|
26
|
+
padding-left: 0;
|
27
|
+
}
|
28
|
+
.navbar-wrapper .navbar {
|
29
|
+
padding-right: 15px;
|
30
|
+
padding-left: 15px;
|
31
|
+
}
|
32
|
+
.navbar-wrapper .navbar .container {
|
33
|
+
width: auto;
|
34
|
+
}
|
35
|
+
|
36
|
+
|
37
|
+
/* CUSTOMIZE THE CAROUSEL
|
38
|
+
-------------------------------------------------- */
|
39
|
+
|
40
|
+
/* Carousel base class */
|
41
|
+
.carousel {
|
42
|
+
height: 400px;
|
43
|
+
margin-bottom: 60px;
|
44
|
+
}
|
45
|
+
/* Since positioning the image, we need to help out the caption */
|
46
|
+
.carousel-caption {
|
47
|
+
z-index: 10;
|
48
|
+
}
|
49
|
+
|
50
|
+
/* Declare heights because of positioning of img element */
|
51
|
+
.carousel .item {
|
52
|
+
height: 400px;
|
53
|
+
background-color: #777;
|
54
|
+
}
|
55
|
+
.carousel-inner > .item > img {
|
56
|
+
position: absolute;
|
57
|
+
top: 0;
|
58
|
+
left: 0;
|
59
|
+
min-width: 100%;
|
60
|
+
height: 400px;
|
61
|
+
}
|
62
|
+
|
63
|
+
|
64
|
+
/* MARKETING CONTENT
|
65
|
+
-------------------------------------------------- */
|
66
|
+
|
67
|
+
/* Center align the text within the three columns below the carousel */
|
68
|
+
.marketing .col-lg-4 {
|
69
|
+
margin-bottom: 20px;
|
70
|
+
text-align: center;
|
71
|
+
}
|
72
|
+
.marketing h2 {
|
73
|
+
font-weight: normal;
|
74
|
+
}
|
75
|
+
.marketing .col-lg-4 p {
|
76
|
+
margin-right: 10px;
|
77
|
+
margin-left: 10px;
|
78
|
+
}
|
79
|
+
|
80
|
+
|
81
|
+
/* Featurettes
|
82
|
+
------------------------- */
|
83
|
+
|
84
|
+
.featurette-divider {
|
85
|
+
margin: 80px 0; /* Space out the Bootstrap <hr> more */
|
86
|
+
}
|
87
|
+
|
88
|
+
/* Thin out the marketing headings */
|
89
|
+
.featurette-heading {
|
90
|
+
font-weight: 300;
|
91
|
+
line-height: 1;
|
92
|
+
letter-spacing: -1px;
|
93
|
+
}
|
94
|
+
|
95
|
+
|
96
|
+
/* RESPONSIVE CSS
|
97
|
+
-------------------------------------------------- */
|
98
|
+
|
99
|
+
@media (min-width: 768px) {
|
100
|
+
/* Navbar positioning foo */
|
101
|
+
.navbar-wrapper {
|
102
|
+
margin-top: 20px;
|
103
|
+
}
|
104
|
+
.navbar-wrapper .container {
|
105
|
+
padding-right: 15px;
|
106
|
+
padding-left: 15px;
|
107
|
+
}
|
108
|
+
.navbar-wrapper .navbar {
|
109
|
+
padding-right: 0;
|
110
|
+
padding-left: 0;
|
111
|
+
}
|
112
|
+
|
113
|
+
/* The navbar becomes detached from the top, so we round the corners */
|
114
|
+
.navbar-wrapper .navbar {
|
115
|
+
border-radius: 4px;
|
116
|
+
}
|
117
|
+
|
118
|
+
/* Bump up size of carousel content */
|
119
|
+
.carousel-caption p {
|
120
|
+
margin-bottom: 20px;
|
121
|
+
font-size: 21px;
|
122
|
+
line-height: 1.4;
|
123
|
+
}
|
124
|
+
|
125
|
+
.featurette-heading {
|
126
|
+
font-size: 50px;
|
127
|
+
}
|
128
|
+
}
|
129
|
+
|
130
|
+
@media (min-width: 992px) {
|
131
|
+
.featurette-heading {
|
132
|
+
margin-top: 120px;
|
133
|
+
}
|
134
|
+
}
|
data/public/favicon.ico
ADDED
Binary file
|
data/public/humans.txt
ADDED
data/public/index.js
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
$(function(){
|
2
|
+
var encdigest = $('#encdigest').val();
|
3
|
+
if (encdigest) {
|
4
|
+
var name = $('#name').val();
|
5
|
+
var digest = $('#digest').val();
|
6
|
+
var tripcode = $('#tripcode').val();
|
7
|
+
localStorage.setItem(encdigest + ':name', name);
|
8
|
+
localStorage.setItem(encdigest + ':digest', digest);
|
9
|
+
if (tripcode) {
|
10
|
+
var list = localStorage.getItem('tripcodelist');
|
11
|
+
if (list) {
|
12
|
+
if (list.indexOf(tripcode) < 0) {
|
13
|
+
localStorage.setItem('tripcodelist', list + ',' + tripcode);
|
14
|
+
}
|
15
|
+
}
|
16
|
+
else {
|
17
|
+
localStorage.setItem('tripcodelist', tripcode);
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
$('.files').each(function(i, x) {
|
22
|
+
var y = $(x);
|
23
|
+
var id = y.attr('id');
|
24
|
+
var digest = localStorage.getItem(id + ':digest');
|
25
|
+
if (digest) {
|
26
|
+
var name = localStorage.getItem(id + ':name');
|
27
|
+
var href = y.attr('href');
|
28
|
+
href = href + '?digest=' + digest + '&filename=' + name;
|
29
|
+
y.attr('href', href);
|
30
|
+
y.html(name);
|
31
|
+
}
|
32
|
+
else {
|
33
|
+
y.hide();
|
34
|
+
}
|
35
|
+
});
|
36
|
+
$('.tripcodelist').each(function(i, x) {
|
37
|
+
var y = $(x);
|
38
|
+
var id = y.attr('id');
|
39
|
+
var tripcodelist = localStorage.getItem('tripcodelist');
|
40
|
+
var star = '<span class="glyphicon glyphicon-star" aria-hidden="true">' +
|
41
|
+
'</span>';
|
42
|
+
var html = y.html();
|
43
|
+
var tagname = localStorage.getItem(id + ':title');
|
44
|
+
tagname = tagname ? tagname : ('Untitled Tag ' + id);
|
45
|
+
html = tagname;
|
46
|
+
if (tripcodelist && tripcodelist.indexOf(id) >= 0) {
|
47
|
+
html = star + html;
|
48
|
+
}
|
49
|
+
y.html(html);
|
50
|
+
});
|
51
|
+
$('#showkeys').click(function () {
|
52
|
+
var metadata = ''
|
53
|
+
var sepa = ''
|
54
|
+
for (var i = 0; i < localStorage.length; i++) {
|
55
|
+
var k = localStorage.key(i);
|
56
|
+
metadata = metadata + sepa + k + '=' + localStorage.getItem(k);
|
57
|
+
sepa = '\n';
|
58
|
+
}
|
59
|
+
$('#metadata').val(metadata);
|
60
|
+
});
|
61
|
+
$('#apply').click(function () {
|
62
|
+
var metadata = $('#metadata').val();
|
63
|
+
$(metadata.split('\n')).each(function (i, x) {
|
64
|
+
var kv = x.split('=');
|
65
|
+
var k = kv[0];
|
66
|
+
var v = kv[1];
|
67
|
+
if (v) {
|
68
|
+
if (v.length == 0) {
|
69
|
+
localStorage.removeItem(k);
|
70
|
+
}
|
71
|
+
else {
|
72
|
+
localStorage.setItem(k, v);
|
73
|
+
}
|
74
|
+
}
|
75
|
+
});
|
76
|
+
location.href = '/';
|
77
|
+
});
|
78
|
+
$('#deleteall').click(function () {
|
79
|
+
if (confirm('Are you sure?')) {
|
80
|
+
localStorage.clear();
|
81
|
+
}
|
82
|
+
});
|
83
|
+
$(":file").filestyle({input: false, icon: true, size: 'lg'});
|
84
|
+
$('#uploadtabs a').click(function (e) {
|
85
|
+
e.preventDefault()
|
86
|
+
$(this).tab('show')
|
87
|
+
});
|
88
|
+
});
|