diff --git a/Gemfile b/Gemfile index 8d382561..a33eb1e5 100644 --- a/Gemfile +++ b/Gemfile @@ -10,6 +10,7 @@ gem 'sinatra-xsendfile', require: 'sinatra/xsendfile' gem 'puma', require: nil platform :mri do + gem 'magic' # sudo apt-get install file, For OSX: brew install libmagic gem 'pg' gem 'sequel_pg', require: nil gem 'hiredis' diff --git a/Gemfile.lock b/Gemfile.lock index 37a4f27f..2d08b8ee 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -17,10 +17,13 @@ GEM fabrication (2.7.1) faker (1.1.2) i18n (~> 0.5) + ffi (1.4.0) hashie (2.0.5) hiredis (0.4.5) i18n (0.6.4) kgio (2.8.0) + magic (0.2.6) + ffi (>= 0.6.3) metaclass (0.0.1) method_source (0.8.1) minitest (4.7.4) @@ -100,6 +103,7 @@ DEPENDENCIES jdbc-postgres jruby-openssl json + magic minitest minitest-reporters mocha diff --git a/app.rb b/app.rb index 1e8ac06f..2c8b4988 100644 --- a/app.rb +++ b/app.rb @@ -100,11 +100,60 @@ get '/sites/:name/:file' do sites_name_redirect end +get '/site_files/new' do + require_login + slim :'site_files/new' +end + +post '/site_files/upload' do + require_login + @errors = [] + + if params[:newfile] == '' || params[:newfile].nil? + @errors << 'You must select a file to upload.' + halt slim(:'site_files/new') + end + + if params[:newfile][:tempfile].size > Site::MAX_SPACE || (params[:newfile][:tempfile].size + current_site.total_space) > Site::MAX_SPACE + @errors << 'File size must be smaller than available space.' + halt slim(:'site_files/new') + end + + mime_type = Magic.guess_file_mime_type params[:newfile][:tempfile].path + + unless Site::VALID_MIME_TYPES.include?(mime_type) && Site::VALID_EXTENSIONS.include?(File.extname(params[:newfile][:filename]).sub(/^./, '')) + @errors << 'File must me one of the following: HTML, Text, Image (JPG PNG GIF JPEG SVG), JS, CSS, Markdown.' + halt slim(:'site_files/new') + end + + sanitized_filename = params[:newfile][:filename].gsub(/[^a-zA-Z_\-.]/, '') + + FileUtils.mv params[:newfile][:tempfile].path, File.join(site_base_path(current_site.username), sanitized_filename) + flash[:success] = "Successfully uploaded file #{sanitized_filename}." + redirect '/dashboard' +end + +post '/site_files/delete' do + require_login + sanitized_filename = params[:filename].gsub(/[^a-zA-Z_\-.]/, '') + FileUtils.rm File.join(site_base_path(current_site.username), sanitized_filename) + flash[:success] = "Deleted file #{params[:filename]}." + redirect '/dashboard' +end + +get '/terms' do + slim :'terms' +end + +get '/privacy' do + slim :'privacy' +end + def sites_name_redirect path = request.path.gsub "/sites/#{params[:name]}", '' # path += "/#{params[:file]}" unless params[:file].nil? - redirect "http://#{params[:name]}.neocities.org#{path}" + redirect "http://#{params[:name]}.neocities.org#{path}" end def dashboard_if_signed_in diff --git a/environment.rb b/environment.rb index 89c79fb0..abbc201c 100644 --- a/environment.rb +++ b/environment.rb @@ -34,5 +34,10 @@ Sequel.default_timezone = 'UTC' Sequel::Migrator.apply DB, './migrations' Dir.glob('models/*.rb').each {|m| require File.join(DIR_ROOT, "#{m}") } +DB.loggers << Logger.new(STDOUT) if ENV['RACK_ENV'] == 'development' -DB.loggers << Logger.new(STDOUT) if ENV['RACK_ENV'] == 'development' \ No newline at end of file +# If new, throw up a random Server for development. + +if ENV['RACK_ENV'] == 'development' && Server.count == 0 + Server.create ip: '127.0.0.1', slots_available: 999999 +end \ No newline at end of file diff --git a/models/site.rb b/models/site.rb index a4ebda9e..b4a7e40d 100644 --- a/models/site.rb +++ b/models/site.rb @@ -1,4 +1,8 @@ class Site < Sequel::Model + # We might need to include fonts in here.. + VALID_MIME_TYPES = ['text/plain', 'text/html', 'text/css', 'application/javascript', 'image/png', 'image/jpeg', 'image/gif', 'image/svg+xml'] + VALID_EXTENSIONS = %w{ html htm txt text css js jpg jpeg png gif svg md markdown } + MAX_SPACE = 5242880 # 5MB MINIMUM_PASSWORD_LENGTH = 5 USERNAME_REGEX = /[^\w-]/i many_to_one :server @@ -74,4 +78,18 @@ class Site < Sequel::Model errors.add :password, "Password must be at least #{MINIMUM_PASSWORD_LENGTH} characters." end end + + def file_list + Dir.glob(File.join(DIR_ROOT, 'public', 'sites', username, '*')).collect {|p| File.basename(p)}.sort.collect {|sitename| SiteFile.new sitename} + end + + def total_space + space = Dir.glob(File.join(DIR_ROOT, 'public', 'sites', username, '*')).collect {|p| File.size(p)}.inject {|sum,x| sum += x} + space.nil? ? 0 : space + end + + def available_space + remaining = MAX_SPACE - total_space + remaining < 0 ? 0 : remaining + end end \ No newline at end of file diff --git a/models/site_file.rb b/models/site_file.rb new file mode 100644 index 00000000..ac280f17 --- /dev/null +++ b/models/site_file.rb @@ -0,0 +1,8 @@ +class SiteFile + attr_reader :filename, :ext + + def initialize(filename) + @filename = filename + @ext = File.extname(@filename).sub(/^./, '') + end +end \ No newline at end of file diff --git a/public/css/styles.css b/public/css/styles.css index 62aa663d..66648d07 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -13,4 +13,8 @@ textarea, input[type="text"], input[type="password"], input[type="datetime"], input[type="datetime-local"], input[type="date"], input[type="month"], input[type="time"], input[type="week"], input[type="number"], input[type="email"], input[type="url"], input[type="search"], input[type="tel"], input[type="color"], .uneditable-input { color: #000000; +} + +#file_criteria li { + padding-bottom: 20px; } \ No newline at end of file diff --git a/public/font/FontAwesome.otf b/public/font/FontAwesome.otf new file mode 100755 index 00000000..64049bf2 Binary files /dev/null and b/public/font/FontAwesome.otf differ diff --git a/public/font/fontawesome-webfont.eot b/public/font/fontawesome-webfont.eot new file mode 100755 index 00000000..7d81019e Binary files /dev/null and b/public/font/fontawesome-webfont.eot differ diff --git a/public/font/fontawesome-webfont.svg b/public/font/fontawesome-webfont.svg new file mode 100755 index 00000000..ba0afe5e --- /dev/null +++ b/public/font/fontawesome-webfont.svg @@ -0,0 +1,284 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/font/fontawesome-webfont.ttf b/public/font/fontawesome-webfont.ttf new file mode 100755 index 00000000..d4617247 Binary files /dev/null and b/public/font/fontawesome-webfont.ttf differ diff --git a/public/font/fontawesome-webfont.woff b/public/font/fontawesome-webfont.woff new file mode 100755 index 00000000..3c89ae09 Binary files /dev/null and b/public/font/fontawesome-webfont.woff differ diff --git a/views/dashboard.slim b/views/dashboard.slim index 7f22ff0d..4698f4af 100644 --- a/views/dashboard.slim +++ b/views/dashboard.slim @@ -1,4 +1,55 @@ +javascript: + function confirmFileDelete(name) { + $('#deleteFileName').html(name); + $('#deleteConfirmModal').modal(); + } + + function fileDelete() { + $('#deleteFilenameInput').val($('#deleteFileName').html()); + $('#deleteFilenameForm').submit(); + } + .row - .span12 + .span8.offset4 - h1 Dashboard \ No newline at end of file + h1 Your home page + + - current_site.file_list.each do |file| + .row + .span4 + - if file.ext == 'html' || file.ext == 'htm' || file.ext == 'txt' || file.ext == 'js' || file.ext == 'css' || file.ext == 'md' +    #{file.filename} + div style="margin-bottom: 30px" + span + i class="icon-globe"    + + a href="http://#{current_site.username}.neocities.org/#{file.filename}" target="_blank" View
+ span + i class="icon-edit"    + a href="#" Edit with visual editor
+ span + i class="icon-edit"    + a href="#" Edit with text editor
+ span + i class="icon-trash"    + a href="#" onclick="confirmFileDelete('#{file.filename}')" Delete + - else +    #{file.filename} + div style="margin-bottom: 30px" + a href="http://#{current_site.username}.neocities.org/#{file.filename}" target="_blank" View
+ a href="#" onclick="confirmFileDelete('#{file.filename}')" Delete + + Upload New File + +form method="POST" action="/site_files/delete" id="deleteFilenameForm" + input type="hidden" id="deleteFilenameInput" name="filename" + +.modal.hide.fade id="deleteConfirmModal" tabindex="-1" role="dialog" aria-labelledby="deleteConfirmModalLabel" aria-hidden="true" + .modal-header + button.close type="button" data-dismiss="modal" aria-hidden="true" x + h3 id="deleteConfirmModalLabel" Confirm delete of file + .modal-body + p You are about to delete the file . Are you sure? + .modal-footer + button.btn data-dismiss="modal" aria-hidden="true" Cancel + button.btn.btn-danger onclick="fileDelete()" Delete \ No newline at end of file diff --git a/views/privacy.slim b/views/privacy.slim new file mode 100644 index 00000000..50770ab3 --- /dev/null +++ b/views/privacy.slim @@ -0,0 +1,43 @@ +

Our Privacy Policy was last updated and posted on June 3, 2013. It governs the privacy terms of our Website, located at http://neocities.org. Any capitalized terms not defined in our Privacy Policy, have the meaning as specified in our Terms of Service.

+ +

Your Privacy
Neocities follows all legal requirements to protect your privacy. Our Privacy Policy is a legal statement that explains how we may collect information from you, how we may share your information, and how you can limit our sharing of your information. You will see terms in our Privacy Policy that are capitalized. These terms have meanings as described in the Definitions section below.

+ +

Definitions
"Non Personal Information" is information that is not personally identifiable to you and that we automatically collect when you access our Website with a web browser. It may also include publicly available information that is shared between you and others.

+

"Personally Identifiable Information" is non-public information that is personally identifiable to you and obtained in order for us to provide you within our Website. Personally Identifiable Information may include information such as your name, email address, and other related information that you provide to us or that we obtain about you.

+ +

Information We Collect
Generally, you control the amount and type of information you provide to us when using our Website.

+

As a Visitor, you can browse our website to find out more about our Website. You are not required to provide us with any Personally Identifiable Information as a Visitor.

+ +

Computer Information Collected
When you use our Website, we automatically collect certain computer information by the interaction of your mobile phone or web browser with our Website. Such information is typically considered Non Personal Information. We also collect the following:

+ + +

How We Use Your Information
We use the information we receive from you as follows:

+ + +

Links to Other Websites
Our Website may contain links to other websites that are not under our direct control. These websites may have their own policies regarding privacy. We have no control of or responsibility for linked websites and provide these links solely for the convenience and information of our visitors. You access such linked Websites at your own risk. These websites are not subject to this Privacy Policy. You should check the privacy policies, if any, of those individual websites to see how the operators of those third-party websites will utilize your personal information. In addition, these websites may contain a link to Websites of our affiliates. The websites of our affiliates are not subject to this Privacy Policy, and you should check their individual privacy policies to see how the operators of such websites will utilize your personal information.

+ +

Privacy Policy Updates
We reserve the right to modify this Privacy Policy at any time. You should review this Privacy Policy frequently. If we make material changes to this policy, we may notify you on our Website, by a blog post, by email, or by any method we determine. The method we chose is at our sole discretion. We will also change the "Last Updated" date at the beginning of this Privacy Policy. Any changes we make to our Privacy Policy are effective as of this Last Updated date and replace any prior Privacy Policies.

+ +

Questions About Our Privacy Practices or This Privacy Policy
If you have any questions about our Privacy Practices or this Policy, please contact us.

+ +

Privacy Policy by TermsFeed.com

\ No newline at end of file diff --git a/views/site_files/new.slim b/views/site_files/new.slim new file mode 100644 index 00000000..f53d9447 --- /dev/null +++ b/views/site_files/new.slim @@ -0,0 +1,34 @@ +- if @errors + .row + .span8.offset2 + div.alert.alert-error + h3 There were errors, please correct: + - @errors.each do |error| + h5 = error + +.row + .span12.text-center + h1 Upload a new file + +.row + .span12.text-center + form method="POST" action="/site_files/upload" enctype="multipart/form-data" + h4 Select a file from your computer: + h4: input type="file" name="newfile" + p: input.btn.btn-success.btn-large type="submit" value="Upload File" + +.row + .span8.offset2 + h4 Here you can upload a new file for your home page. + h4 It has to be one of the following file types + h5 + ul id="file_criteria" + li HTML (.html, .htm) + li Image (.jpg, .png, .gif, .svg) + li Markdown (.md) + li JavaScript (.js) + li CSS (.css) + h4 If the file already exists, it will be overwritten without warning. + h4 It has to be legal to share this content in the United States. + h4 It must fit into your home page space (5MB). + h4 The file uploader will automatically scrub any characters not matching: a-z A-Z 0-9 _ - . \ No newline at end of file diff --git a/views/terms.slim b/views/terms.slim new file mode 100644 index 00000000..5a7d9380 --- /dev/null +++ b/views/terms.slim @@ -0,0 +1,21 @@ +

This web page represents a legal document that serves as our Terms of Service and it governs the legal terms of our website, http://neocities.org, sub-domains, and any associated web-based and mobile applications (collectively, "Website"), as owned and operated by Neocities.

+

Capitalized terms, unless otherwise defined, have the meaning specified within the Definitions section below. This Terms of Service, along with our Privacy Policy, any mobile license agreement, and other posted guidelines within our Website, collectively "Legal Terms", constitute the entire and only agreement between you and Neocities, and supersede all other agreements, representations, warranties and understandings with respect to our Website and the subject matter contained herein. We may amend our Legal Terms at any time without specific notice to you. The latest copies of our Legal Terms will be posted on our Website, and you should review all Legal Terms prior to using our Website. After any revisions to our Legal Terms are posted, you agree to be bound to any such changes to them. Therefore, it is important for you to periodically review our Legal Terms to make sure you still agree to them.

+

By using our Website, you agree to fully comply with and be bound by our Legal Terms. Please review them carefully. If you do not accept our Legal Terms, do not access and use our Website. If you have already accessed our Website and do not accept our Legal Terms, you should immediately discontinue use of our Website.

+

The last update to our Terms of Service was posted on June 3, 2013.

+ +

Definitions
The terms "us" or "we" or "our" refers to Neocities, the owner of the Website.

+

A "Visitor" is someone who merely browses our Website, but has not registered as Member.

+

A "Member" is an individual that has registered with us to use our Service.

+

Our "Service" represents the collective functionality and features as offered through our Website to our Members.

+

A "User" is a collective identifier that refers to either a Visitor or a Member.

+

All text, information, graphics, audio, video, and data offered through our Website are collectively known as our "Content".

+ +

Legal Compliance
You agree to comply with all applicable domestic and international laws, statutes, ordinances, and regulations regarding your use of our Website. Neocities reserves the right to investigate complaints or reported violations of our Legal Terms and to take any action we deem appropriate, including but not limited to canceling your Member account, reporting any suspected unlawful activity to law enforcement officials, regulators, or other third parties and disclosing any information necessary or appropriate to such persons or entities relating to your profile, email addresses, usage history, posted materials, IP addresses and traffic information, as allowed under our Privacy Policy.

+ +

Intellectual Property
Our Website may contain our service marks or trademarks as well as those of our hosted sites, affiliates or other companies, in the form of words, graphics, and logos. Your use of our Website does not constitute any right or license for you to use such service marks/trademarks, without the prior written permission of the corresponding service mark/trademark owner.

+ +

Links to Other Websites
Our Website may contain links to third party websites. These links are provided solely as a convenience to you. By linking to these websites, we do not create or have an affiliation with, or sponsor such third party websites. The inclusion of links within our Website does not constitute any endorsement, guarantee, warranty, or recommendation of such third party websites. Neocities has no control over the legal documents and privacy practices of third party websites; as such, you access any such third party websites at your own risk.

+ +

General Terms
Our Legal Terms shall be treated as though it were executed and performed in Oregon, United States, and shall be governed by and construed in accordance with the laws of Oregon, United States, without regard to conflict of law principles. In addition, you agree to submit to the personal jurisdiction and venue of such courts. Any cause of action by you with respect to our Website, must be instituted within one (1) year after the cause of action arose or be forever waived and barred. Should any part of our Legal Terms be held invalid or unenforceable, that portion shall be construed consistent with applicable law and the remaining portions shall remain in full force and effect. To the extent that any Content in our Website conflicts or is inconsistent with our Legal Terms, our Legal Terms shall take precedence. Our failure to enforce any provision of our Legal Terms shall not be deemed a waiver of such provision nor of the right to enforce such provision. The rights of Neocities under our Legal Terms shall survive the termination of our Legal Terms.

+ +

Terms of Service by TermsFeed.com

\ No newline at end of file