Initial commit
This commit is contained in:
commit
6b75dfd6da
24
LICENSE
Normal file
24
LICENSE
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
Copyright (c) 2021 snow flurry. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in
|
||||||
|
the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||||
|
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||||
|
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||||
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
3
README.md
Normal file
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# aperture - Ruby/CGI script for file uploading
|
||||||
|
|
||||||
|
TODO: docs
|
3
src/Gemfile
Normal file
3
src/Gemfile
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
|
gem 'ffi', '~> 1.9', '>= 1.9.10'
|
125
src/aperture.rb
Normal file
125
src/aperture.rb
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
## TODO: Can we somehow make this dynamic?
|
||||||
|
CONF_PATH = "/etc/aperture.conf"
|
||||||
|
USER_PREFIX = "~"
|
||||||
|
|
||||||
|
require 'cgi'
|
||||||
|
require 'yaml'
|
||||||
|
require_relative 'libmagic'
|
||||||
|
|
||||||
|
$cgi = CGI.new
|
||||||
|
|
||||||
|
# convenience function to print an error to CGI log and quit
|
||||||
|
def die(status, error)
|
||||||
|
puts $cgi.http_header {
|
||||||
|
:type => "text/plain",
|
||||||
|
:status => status
|
||||||
|
}
|
||||||
|
if status == "SERVER_ERROR"
|
||||||
|
$stderr.puts "[aperture:fatal] " + error
|
||||||
|
puts "Something went wrong! Ask the server admin to check the logs for more info."
|
||||||
|
else
|
||||||
|
puts error
|
||||||
|
end
|
||||||
|
exit
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_yaml(path)
|
||||||
|
if not File.file? path
|
||||||
|
die "SERVER_ERROR", "Application not configured"
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
cfg = YAML.load(
|
||||||
|
File.open(path).read
|
||||||
|
)
|
||||||
|
return cfg
|
||||||
|
rescue =>
|
||||||
|
die "SERVER_ERROR", "Config file broken ;_;"
|
||||||
|
end
|
||||||
|
# should be unreachable
|
||||||
|
die "SERVER_ERROR", "get_yaml broken ;_;"
|
||||||
|
end
|
||||||
|
|
||||||
|
# sanitizes string so all that's left is alphanumerics and .
|
||||||
|
def sanitize(str)
|
||||||
|
return str.gsub(/(^\.|[^0-9a-zA-Z\.])/i, '_')
|
||||||
|
end
|
||||||
|
|
||||||
|
# main function
|
||||||
|
def main()
|
||||||
|
# consistency checks for request
|
||||||
|
# if not authenticated, ignore
|
||||||
|
if $cgi.REMOTE_USER.nil? || $cgi.REMOTE_USER.empty?
|
||||||
|
die "FORBIDDEN", ""
|
||||||
|
end
|
||||||
|
|
||||||
|
# even though REMOTE_USER comes from the upstream, don't trust it
|
||||||
|
clean_name = sanitize $cgi.REMOTE_USER
|
||||||
|
|
||||||
|
# Only POST supported for this API
|
||||||
|
if $cgi.REQUEST_METHOD != "POST"
|
||||||
|
die "METHOD_NOT_ALLOWED", "Method not allowed"
|
||||||
|
end
|
||||||
|
|
||||||
|
if $cgi.CONTENT_TYPE != "multipart/form-data"
|
||||||
|
die "NOT_ACCEPTABLE", "Please use multipart/form-data"
|
||||||
|
end
|
||||||
|
|
||||||
|
cfg = get_yaml CONF_PATH
|
||||||
|
|
||||||
|
# make sure expected config entries are there
|
||||||
|
if cfg[:save_path].nil? or cfg[:save_path].empty?
|
||||||
|
die "SERVER_ERROR", "config: save_path not set!"
|
||||||
|
end
|
||||||
|
|
||||||
|
if cfg[:root_url].nil? or cfg[:root_url].empty?
|
||||||
|
die "SERVER_ERROR", "config: root_url not set!"
|
||||||
|
end
|
||||||
|
|
||||||
|
# XXX: configurable prefix?
|
||||||
|
user_dir = cfg[:save_path] + "/" + USER_PREFIX + clean_name
|
||||||
|
|
||||||
|
if not File.directory? user_dir
|
||||||
|
die "SERVER_ERROR", "User directory " + user_dir + " doesn't exist!"
|
||||||
|
end
|
||||||
|
|
||||||
|
# get file data
|
||||||
|
if $cgi.params['sendfile'].nil? || $cgi.params['sendfile'].empty?
|
||||||
|
die "NOT_ACCEPTABLE", "Missing sendfile?"
|
||||||
|
end
|
||||||
|
|
||||||
|
upfile = $cgi.params['sendfile'][0]
|
||||||
|
|
||||||
|
# get the mime type from the file contents, not HTTP
|
||||||
|
lm = LibMagic::Magic.new(cfg[:magic_path])
|
||||||
|
ftype = lm.get_mime_type(upfile)
|
||||||
|
if ftype.match(/^(image|text|audio|video)\/[a-zA-Z0-9\-\.]+$/).nil?
|
||||||
|
die "NOT_ACCEPTABLE", "MIME type " + ftype + " not supported here"
|
||||||
|
end
|
||||||
|
lm.close
|
||||||
|
|
||||||
|
clean_file = sanitize upfile.original_filename
|
||||||
|
full_path = user_dir + "/" + clean_file
|
||||||
|
|
||||||
|
# don't overwrite an existing file unless they asked for it
|
||||||
|
if $cgi.params['overwrite'] != "true" and File.exists? full_path
|
||||||
|
die "NOT_ACCEPTABLE" "File " + clean_file + " already exists!"
|
||||||
|
end
|
||||||
|
|
||||||
|
# finally, store the file
|
||||||
|
File.open(full_path, "w") do |new_file|
|
||||||
|
new_file.write(upfile.read)
|
||||||
|
end
|
||||||
|
|
||||||
|
# print OK and send URL
|
||||||
|
|
||||||
|
$cgi.print $cgi.http_header("status" => "OK",
|
||||||
|
"type" => "text/plain")
|
||||||
|
|
||||||
|
$cgi.print cfg[:root_url] + USER_PREFIX + clean_name + "/" + clean_file
|
||||||
|
end
|
||||||
|
|
||||||
|
main
|
||||||
|
exit 0
|
118
src/libmagic.rb
Normal file
118
src/libmagic.rb
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
require 'ffi'
|
||||||
|
|
||||||
|
module LibMagic
|
||||||
|
extend FFI::Library
|
||||||
|
ffi_lib 'magic'
|
||||||
|
|
||||||
|
## constants
|
||||||
|
MAGIC_NONE = 0x0
|
||||||
|
MAGIC_DEBUG = 0x1
|
||||||
|
MAGIC_SYMLINK = 0x2
|
||||||
|
MAGIC_COMPRESS = 0x4
|
||||||
|
MAGIC_DEVICES = 0x8
|
||||||
|
MAGIC_MIME_TYPE = 0x10
|
||||||
|
MAGIC_CONTINUE = 0x20
|
||||||
|
MAGIC_CHECK = 0x40
|
||||||
|
MAGIC_PRESERVE_ATIME = 0x80
|
||||||
|
MAGIC_RAW = 0x100
|
||||||
|
MAGIC_ERROR = 0x200
|
||||||
|
MAGIC_MIME_ENCODING = 0x400
|
||||||
|
MAGIC_MIME = (MAGIC_MIME_TYPE | MAGIC_MIME_ENCODING)
|
||||||
|
MAGIC_APPLE = 0x800
|
||||||
|
MAGIC_EXTENSION = 0x1000000
|
||||||
|
MAGIC_COMPRESS_TRANSP = 0x2000000
|
||||||
|
|
||||||
|
# error used for handling libmagic errors
|
||||||
|
class MagicError < StandardError
|
||||||
|
def initialize(msg="Unknown error occurred", errno=0)
|
||||||
|
@errno = errno
|
||||||
|
super(msg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Magic
|
||||||
|
# initialize libmagic
|
||||||
|
def initialize(db_path = nil, flags = LibMagic::MAGIC_MIME)
|
||||||
|
# nullptr for default db path
|
||||||
|
dbptr = FFI::Pointer.new 0x0
|
||||||
|
@magic_inst = ffi_magic_open(flags)
|
||||||
|
if @magic_inst.null?
|
||||||
|
raise "magic_open() failed"
|
||||||
|
end
|
||||||
|
|
||||||
|
# now that we have our cookie, clear it on exit
|
||||||
|
at_exit do
|
||||||
|
self.close
|
||||||
|
end
|
||||||
|
|
||||||
|
# load magic database
|
||||||
|
if db_path is not nil
|
||||||
|
dbptr = db_path
|
||||||
|
end
|
||||||
|
|
||||||
|
res = ffi_magic_load(@magic_inst, dbptr)
|
||||||
|
if res != 0
|
||||||
|
self.throw_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Gets MIME type for a given stream
|
||||||
|
def self.get_mime_type(stream)
|
||||||
|
res = nil
|
||||||
|
if stream.fileno.nil? # magic_buffer
|
||||||
|
buf = stream.string
|
||||||
|
buflen = buf.bytesize
|
||||||
|
res = ffi_magic_buffer(@magic_inst, buf, buflen)
|
||||||
|
else # magic_descriptor
|
||||||
|
res = ffi_magic_descriptor(@magic_inst, stream.fileno)
|
||||||
|
end
|
||||||
|
if res.nil? || res.null?
|
||||||
|
self.throw_error
|
||||||
|
else
|
||||||
|
retstr, retfree = FFI::StrPtrConverter.from_native(res, nil)
|
||||||
|
return retstr
|
||||||
|
end
|
||||||
|
# unreachable
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.close()
|
||||||
|
unless @magic_inst.nil?
|
||||||
|
ffi_magic_close(@magic_inst)
|
||||||
|
@magic_inst = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# convenience method for throwing MagicErrors
|
||||||
|
def self.throw_error()
|
||||||
|
if @magic_inst.null?
|
||||||
|
raise LibMagic::MagicError.new
|
||||||
|
else
|
||||||
|
errptr = ffi_magic_error(@magic_inst)
|
||||||
|
errno = ffi_magic_errno(@magic_inst)
|
||||||
|
errstr, errfree = FFI::StrPtrConverter.from_native(errptr, nil)
|
||||||
|
raise LibMagic::MagicError.new(errstr, errno)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
## ffi
|
||||||
|
|
||||||
|
# i/o open/close
|
||||||
|
attach_function :ffi_magic_open, :magic_open, [:int], :pointer
|
||||||
|
attach_function :ffi_magic_close, :magic_close, [:pointer], :void
|
||||||
|
# error info (a la GetLastError/win32)
|
||||||
|
attach_function :ffi_magic_error, :magic_error, [:pointer], :strptr
|
||||||
|
attach_function :ffi_magic_errno, :magic_errno, [:pointer], :int
|
||||||
|
# libmagic flags
|
||||||
|
attach_function :ffi_magic_getflags, :magic_getflags, [:pointer], :int
|
||||||
|
attach_function :ffi_magic_setflags, :magic_setflags, [:pointer, :int], :int
|
||||||
|
# load the libmagic db -- MUST BE DONE BEFORE GETTING MAGIC INFO
|
||||||
|
attach_function :ffi_magic_load, :magic_load, [:pointer, :string], :int
|
||||||
|
# get magic info
|
||||||
|
attach_function :ffi_magic_file, :magic_file, [:pointer, :string], :strptr
|
||||||
|
attach_function :ffi_magic_buffer, :magic_buffer, [:pointer, :pointer, :size_t], :strptr
|
||||||
|
attach_function :ffi_magic_descriptor, :magic_descriptor, [:pointer, :int], :strptr
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue