blob: 4c1db20c9d712366ba81bc535c3842b317d10a80 [file] [log] [blame]
#
# Simple web server that routes requests to views based on URLs.
#
require 'wunderbar/sinatra'
require 'wunderbar/bootstrap'
require 'wunderbar/vue'
require 'ruby2js/es2017/strict'
require 'ruby2js/filter/functions'
require 'ruby2js/filter/require'
#require 'erb'
#require 'sanitize'
require 'escape'
require 'whimsy/asf'
require_relative 'helpers'
require_relative 'models/mailbox'
require_relative 'models/safetemp'
require_relative 'models/events'
require_relative 'models/auth'
require_relative 'models/prefs'
# monkey patch mail gem to work around a regression introduced in 2.7.0:
# https://github.com/mikel/mail/pull/1168
module Mail
class Message
def raw_source=(value)
@raw_source = ::Mail::Utilities.to_crlf(value)
end
end
module Utilities
def self.safe_for_line_ending_conversion?(string)
if RUBY_VERSION >= '1.9'
string.ascii_only? or
(string.encoding != Encoding::BINARY and string.valid_encoding?)
else
string.ascii_only?
end
end
end
end
ASF::Mail.configure
set :show_exceptions, true
disable :logging # suppress log of requests to stderr/error.log
def get_pref # common setup
@info = Auth.info({})
@id = @info[:id]
@prefs = Prefs.new()
@domains = @prefs[@id] || []
end
def check_pref
get_pref
# TODO this forces user to choose at least one domain
unless @domains && @domains.length > 0
redirect to('/prefs')
end
pass
end
# Allow direct access to preferences
get '/prefs/?' do
get_pref
_html :prefs
end
# handle the update (easiest to use post from form button)
post '/actions/setprefs' do
get_pref
_html :"actions/setprefs"
end
# force user to use preferences if they have not yet done so
get '*' do check_pref end
post '*' do check_pref end
patch '*' do check_pref end
get '/' do
# Ensure trailing slash is present
redirect to('/') if env['REQUEST_URI'] == env['SCRIPT_NAME']
_html :main
end
# initial list of messages
get '/messages' do # must agree with src in main.html
@mbox = Mailbox.mboxname()
@messages = Mailbox.new(@mbox, @domains).client_headers
@cssmtime = File.mtime('public/secmail.css').to_i
@appmtime = Wunderbar::Asset.convert(File.join(settings.views, 'app.js.rb')).mtime.to_i
_html :messages # must agree with views/*.html.rb
end
# alias for root directory
get '/index.html' do
call env.merge('PATH_INFO' => '/')
end
# list of messages for a month
get %r{/(#{MBOX_RE})/messages} do |mbox|
@mbox = mbox
@messages = Mailbox.new(@mbox, @domains).client_headers
@cssmtime = File.mtime('public/secmail.css').to_i
@appmtime = Wunderbar::Asset.convert(File.join(settings.views, 'app.js.rb')).mtime.to_i
_html :messages # must agree with views/*.html.rb
end
# support for fetching next lot of messages
get %r{/(#{MBOX_RE})} do |mbox|
_json Mailbox.new(mbox, @domains).client_headers
end
# message body for a single message
get %r{/(#{MBOX_RE})/(#{HASH_RE})/} do |mbox, hash|
@message = Mailbox.new(mbox).find(hash)
return [404, {}, 'Message not found or is not accessible'] unless @message
@attachments = @message.attachments
@headers = @message.headers.dup
@headers.delete :attachments
@cssmtime = File.mtime('public/secmail.css').to_i
_html :body # uses view/body.html.rb
end
# posted actions
post '/actions/:file' do
_json :"actions/#{params[:file]}"
end
# update a single message status (:Accept, :Reject, :Spam etc)
patch %r{/(#{MBOX_RE})/(#{HASH_RE})/} do |mbox, hash|
updates = JSON.parse(request.env['rack.input'].read)
success = Mailbox.patch!(mbox, hash, updates)
return [404, {}, 'Message not found or is not accessible or could not be updated'] unless success
[204, {}, '']
end
# header data for a single message
get %r{/(#{MBOX_RE})/(#{HASH_RE})/_headers_} do |mbox, hash|
@headers = Mailbox.new(mbox).headers(hash)
return [404, {}, 'Message not found or is not accessible'] unless @headers
_html :headers # uses view/headers.html.rb
end
# raw data for a single message
get %r{/(#{MBOX_RE})/(#{HASH_RE})/_raw_} do |mbox, hash|
message = Mailbox.new(mbox).find(hash)
return [404, {}, 'Message not found or is not accessible'] unless message
[200, {'Content-Type' => 'text/plain'}, message.raw]
end
# original data for a single message (testing)
get %r{/(#{MBOX_RE})/(#{HASH_RE})/_orig_} do |mbox, hash|
message = Mailbox.new(mbox).orig(hash)
return [404, {}, 'Message not found or is not accessible'] unless message
[200, {'Content-Type' => 'text/plain'}, message]
end
# intercede for potentially dangerous message attachments
get %r{/(#{MBOX_RE})/(#{HASH_RE})/_danger_/(.*?)} do |mbox, hash, name|
message = Mailbox.new(mbox).find(hash)
return [404, {}, 'Message not found or is not accessible'] unless message
@part = message.find(URI.decode(name))
return [404, {}, 'Attachment not found'] unless @part
_html :danger
end
# a specific attachment for a message (e.g. CID)
# WARNING catches anything not handled above!
get %r{/(#{MBOX_RE})/(#{HASH_RE})/(.*?)} do |mbox, hash, name|
message = Mailbox.new(mbox).find(hash)
return [404, {}, 'Message not found or is not accessible'] unless message
part = message.find(URI.decode(name))
return [404, {}, 'Attachment not found'] unless part
[200, {'Content-Type' => part.content_type}, part.body.to_s]
end
# event stream for server sent events (a.k.a EventSource)
get '/events', provides: 'text/event-stream' do
events = Events.new
stream :keep_open do |out|
out.callback {events.close}
loop do
event = events.pop
if Hash === event or Array === event
out << "data: #{JSON.dump(event)}\n\n"
elsif event == :heartbeat
out << ":\n"
elsif event == :exit
out.close
break
else
out << "data: #{event.inspect}\n\n"
end
end
end
end
# catch everything else
get %r{/(.+)} do |req|
[500, {}, "I don't understand the request: #{req}"]
end