feat: extend mime with Prism and Mime
This commit is contained in:
1 parent
6825941c0b
commit
464bcda96f
3 files changed
+197
-1
No files matched your search
@@ -0,0 +1,58 @@
|
||||
module Prism
|
||||
module Mime
|
||||
class UnknownMimeType < StandardError; end
|
||||
|
||||
TYPES = {
|
||||
html: 'text/html',
|
||||
json: 'application/json',
|
||||
xml: 'application/xml',
|
||||
text: 'text/plain',
|
||||
csv: 'text/csv',
|
||||
js: 'application/javascript'
|
||||
}.freeze
|
||||
|
||||
EXTENSIONS = TYPES.invert.freeze
|
||||
|
||||
class << self
|
||||
def [](symbol)
|
||||
TYPES.fetch(symbol.to_sym) { raise UnknownMimeType, "unknown mime type #{symbol.inspect}" }
|
||||
end
|
||||
|
||||
def known?(symbol)
|
||||
TYPES.key?(symbol.to_sym)
|
||||
end
|
||||
|
||||
def symbol_for_content_type(content_type)
|
||||
return nil unless content_type
|
||||
base = content_type.split(';').first.to_s.strip
|
||||
EXTENSIONS[base]
|
||||
end
|
||||
|
||||
# Returns the best-matching format symbol for an HTTP Accept header,
|
||||
# honoring q-values and falling back to :html for a bare */*.
|
||||
def best_from_accept(accept_header)
|
||||
return nil if accept_header.nil? || accept_header.strip.empty?
|
||||
|
||||
parse_accept(accept_header).each do |media_type|
|
||||
return :html if media_type == '*/*'
|
||||
symbol = EXTENSIONS[media_type]
|
||||
return symbol if symbol
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_accept(header)
|
||||
entries = header.split(',').filter_map do |part|
|
||||
type, *params = part.strip.split(';').map(&:strip)
|
||||
next if type.nil? || type.empty?
|
||||
quality = 1.0
|
||||
params.each { |p| quality = p.sub('q=', '').to_f if p.start_with?('q=') }
|
||||
[type, quality]
|
||||
end
|
||||
entries.sort_by.with_index { |(_, quality), index| [-quality, index] }.map(&:first)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,117 @@
|
||||
require 'tempfile'
|
||||
|
||||
module Prism
|
||||
# A single uploaded file extracted from a multipart/form-data request.
|
||||
# Backed by a Tempfile so large uploads do not sit fully in memory as
|
||||
# part of the request/params hash (Rails' UploadedFile does the same).
|
||||
class UploadedFile
|
||||
attr_reader :original_filename, :content_type, :tempfile
|
||||
|
||||
def initialize(original_filename:, content_type:, tempfile:)
|
||||
@original_filename = original_filename
|
||||
@content_type = content_type
|
||||
@tempfile = tempfile
|
||||
end
|
||||
|
||||
def path
|
||||
tempfile.path
|
||||
end
|
||||
|
||||
def size
|
||||
tempfile.size
|
||||
end
|
||||
|
||||
def read(*args)
|
||||
tempfile.read(*args)
|
||||
end
|
||||
|
||||
def rewind
|
||||
tempfile.rewind
|
||||
end
|
||||
|
||||
def to_s
|
||||
original_filename.to_s
|
||||
end
|
||||
end
|
||||
|
||||
# Parses `multipart/form-data` request bodies (RFC 2046/7578) into a flat
|
||||
# hash of field name => value, where file parts become UploadedFile
|
||||
# instances and everything else stays a String, mirroring how the rest of
|
||||
# Request#parse_body treats JSON/urlencoded bodies.
|
||||
module Multipart
|
||||
MalformedError = Class.new(StandardError)
|
||||
|
||||
CONTENT_TYPE_RE = %r{\Amultipart/form-data}i.freeze
|
||||
|
||||
module_function
|
||||
|
||||
def multipart?(content_type)
|
||||
!content_type.nil? && CONTENT_TYPE_RE.match?(content_type)
|
||||
end
|
||||
|
||||
def boundary_from(content_type)
|
||||
return nil unless content_type
|
||||
match = content_type.match(/boundary=(?:"([^"]*)"|([^;\s]+))/i)
|
||||
return nil unless match
|
||||
match[1] || match[2]
|
||||
end
|
||||
|
||||
def parse(body, boundary)
|
||||
return Prism::Params.new if boundary.nil? || boundary.empty? || body.nil?
|
||||
|
||||
body = body.dup.force_encoding(Encoding::BINARY)
|
||||
delimiter = "--#{boundary}"
|
||||
pieces = body.split(delimiter)
|
||||
# pieces[0] is the preamble before the first delimiter, pieces[-1] is
|
||||
# the "--\r\n" closing marker (or trailing epilogue) after the last one.
|
||||
pairs = pieces[1..-2].to_a.filter_map do |piece|
|
||||
part = piece.sub(/\A\r\n/, '').sub(/\r\n\z/, '')
|
||||
part.empty? ? nil : part_pair(part)
|
||||
end.compact
|
||||
Prism::Params.nested_from(pairs)
|
||||
end
|
||||
|
||||
def part_pair(part)
|
||||
header_block, separator, content = part.partition("\r\n\r\n")
|
||||
return nil if separator.empty?
|
||||
|
||||
headers = parse_headers(header_block)
|
||||
disposition = headers['content-disposition']
|
||||
return nil unless disposition
|
||||
|
||||
name = extract_token(disposition, 'name')
|
||||
return nil unless name
|
||||
|
||||
filename = extract_token(disposition, 'filename')
|
||||
value =
|
||||
if filename
|
||||
build_uploaded_file(filename, headers['content-type'], content)
|
||||
else
|
||||
content.dup.force_encoding(Encoding::UTF_8)
|
||||
end
|
||||
|
||||
[name, value]
|
||||
end
|
||||
|
||||
def build_uploaded_file(filename, content_type, content)
|
||||
tempfile = Tempfile.new('prism-upload')
|
||||
tempfile.binmode
|
||||
tempfile.write(content)
|
||||
tempfile.rewind
|
||||
UploadedFile.new(original_filename: filename, content_type: content_type, tempfile: tempfile)
|
||||
end
|
||||
|
||||
def parse_headers(block)
|
||||
block.split("\r\n").each_with_object({}) do |line, acc|
|
||||
key, value = line.split(':', 2)
|
||||
next unless key && value
|
||||
acc[key.strip.downcase] = value.strip
|
||||
end
|
||||
end
|
||||
|
||||
def extract_token(header_value, key)
|
||||
match = header_value.match(/#{key}="([^"]*)"/) || header_value.match(/#{key}=([^;\s]+)/)
|
||||
match && match[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
+22
-1
@@ -1,8 +1,29 @@
|
||||
require_relative 'orm/store'
|
||||
require_relative 'orm/transactions'
|
||||
require_relative 'orm/errors'
|
||||
require_relative 'orm/type_casting'
|
||||
require_relative 'orm/attributes'
|
||||
require_relative 'orm/query/predicates'
|
||||
require_relative 'orm/query/condition_node'
|
||||
require_relative 'orm/pagination'
|
||||
require_relative 'orm/serialization'
|
||||
require_relative 'orm/enum'
|
||||
require_relative 'orm/relation'
|
||||
require_relative 'orm/validatable'
|
||||
require_relative 'orm/callbacks'
|
||||
require_relative 'orm/dirty'
|
||||
require_relative 'orm/locking'
|
||||
require_relative 'orm/pessimistic'
|
||||
require_relative 'orm/paranoia'
|
||||
require_relative 'orm/list_position'
|
||||
require_relative 'orm/secure_password'
|
||||
require_relative 'orm/secure_token'
|
||||
require_relative 'orm/token_for'
|
||||
require_relative 'orm/encryption'
|
||||
require_relative 'orm/habtm_collection'
|
||||
require_relative 'orm/n_plus_one_detector'
|
||||
require_relative 'orm/associable'
|
||||
require_relative 'orm/nested_attributes'
|
||||
require_relative 'orm/model'
|
||||
require_relative 'orm/migration'
|
||||
require_relative 'orm/migrator'
|
||||
@@ -23,7 +44,7 @@ module Prism
|
||||
end
|
||||
|
||||
def transaction(&block)
|
||||
store.transaction(&block)
|
||||
store.transaction_with_callbacks(&block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in new issue
Block a user