ibg 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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);
@@ -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
+ }
Binary file
data/public/humans.txt ADDED
@@ -0,0 +1,2 @@
1
+ Developer: https://twitter.com/ohac
2
+ GitHub: https://github.com/ohac/iceberg
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
+ });