made form parser easier to use

This commit is contained in:
Yessiest 2023-09-10 20:17:07 +04:00
parent bfc9a3066b
commit bc8750daf7
8 changed files with 95 additions and 45 deletions

View File

@ -2,17 +2,12 @@
$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/lib") $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/lib")
require 'hyde' require 'hyde'
require 'hyde/util/multipart'
require 'hyde/util/header'
app = Hyde::Server.new do app = Hyde::Server.new do
root ENV["PWD"] root ENV["PWD"]
index ["index.html"] index ["index.html"]
post "/" do post "/" do
_, opts = Hyde::Util.parse_value(request.headers["content-type"]) puts form.pretty_inspect if form?
puts Hyde::Util::MultipartParser.new(
request.input, opts["boundary"]
).to_h.pretty_inspect
bounce bounce
end end
serve "*.html" serve "*.html"

View File

@ -12,8 +12,9 @@ app = Hyde::Server.new do
end end
path "important" do path "important" do
preprocess do |req| preprocess do |req|
# Implement logging logic here # Implement logging logic here
puts "Client at #{req.headers['REMOTE_ADDR']} wanted to access something /important!" puts "Client at #{req.headers['remote-addr']} wanted to access something /important!"
end end
get "answer" do get "answer" do
header "content-type", "application/json" header "content-type", "application/json"

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
module Hyde
module DSL
# Methods shared by probes, preprocessors and filters.
module CommonMethods
# Stop execution and generate a boilerplate response with the given code
# @param errorcode [Integer]
# @param backtrace [Array(String), nil]
# @raise [UncaughtThrowError] throws :finish to return back to Server
def die(errorcode, backtrace: nil)
throw :finish, [errorcode].append(
*(@origin.properties["handle.#{errorcode}"] or
@origin.properties["handle.default"]).call(
errorcode,
backtrace: backtrace
)
)
end
# Bounce request to the next handler
# @raise [UncaughtThrowError] throws :break to get out of the callback
def bounce
throw :break
end
end
end
end

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative '../response' require_relative '../response'
require_relative '../util/multipart'
require_relative '../util/header'
module Hyde module Hyde
module DSL module DSL
@ -12,31 +14,11 @@ module Hyde
@origin.request @origin.request
end end
# Stop execution and generate a boilerplate response with the given code
# @param errorcode [Integer]
# @param backtrace [Array(String), nil]
# @raise [UncaughtThrowError] throws :finish to return back to Server
def die(errorcode, backtrace: nil)
throw :finish, [errorcode].append(
*(@origin.properties["handle.#{errorcode}"] or
@origin.properties["handle.default"]).call(
errorcode,
backtrace: backtrace
)
)
end
# Bounce request to the next handler
# @raise [UncaughtThrowError] throws :break to get out of the callback
def bounce
throw :break
end
# Set response status (generate response if one doesn't exist yet) # Set response status (generate response if one doesn't exist yet)
# @param status [Integer] http status code # @param status [Integer] http status code
def status(status) def status(status)
@response = (@response or Hyde::Response.new) @origin.response = (@origin.response or Hyde::Response.new)
@response.status = status @origin.response.status = status
end end
alias code status alias code status
@ -79,6 +61,27 @@ module Hyde
@origin.response.delete_header(key, value) @origin.response.delete_header(key, value)
end end
# Checks if current request has multipart/form-data associated with it
# @return [Boolean]
def form?
value, opts = Hyde::Util.parse_value(request.headers["content-type"])
if value == "multipart/form-data" and
opts["boundary"]
true
else
false
end
end
# Returns formdata
# @return [Hash{String=>(String,Hyde::Util::FormPart)}]
def form
_, opts = Hyde::Util.parse_value(request.headers["content-type"])
Hyde::Util::MultipartParser.new(
request.input, opts["boundary"]
).to_h
end
end end
end end
end end

View File

@ -4,10 +4,20 @@ require_relative 'pattern_matching'
require_relative 'node' require_relative 'node'
require_relative 'dsl/path_constructors' require_relative 'dsl/path_constructors'
require_relative 'dsl/path_methods' require_relative 'dsl/path_methods'
require_relative 'dsl/common_methods'
require_relative 'util/lookup' require_relative 'util/lookup'
module Hyde module Hyde
# Protected interface that provides DSL context for setup block. # Binding that provides execution context for filters and preprocessors.
class ProcessorBinding
include Hyde::DSL::CommonMethods
def initialize(path)
@origin = path
end
end
# Binding that provides execution context for path setup block.
class PathBinding class PathBinding
include Hyde::DSL::PathConstructors include Hyde::DSL::PathConstructors
include Hyde::DSL::PathMethods include Hyde::DSL::PathMethods
@ -19,6 +29,7 @@ module Hyde
# Primary building block of request navigation. # Primary building block of request navigation.
class Path < Hyde::Node class Path < Hyde::Node
ProcBinding = Hyde::ProcessorBinding
Binding = Hyde::PathBinding Binding = Hyde::PathBinding
# @param path [Object] Object to generate {Hyde::Pattern} from # @param path [Object] Object to generate {Hyde::Pattern} from
@ -32,9 +43,10 @@ module Hyde
@preprocessors = [] @preprocessors = []
@postprocessors = [] @postprocessors = []
@filters = [] @filters = []
# Bindings setup
binding = Binding.new(self) binding = self.class::Binding.new(self)
binding.instance_exec(&setup) binding.instance_exec(&setup)
@procbinding = self.class::ProcBinding.new(self)
end end
# Method callback on successful request navigation. # Method callback on successful request navigation.
@ -92,7 +104,7 @@ module Hyde
# @return [Boolean] true if request passed all filters # @return [Boolean] true if request passed all filters
def run_filters(request) def run_filters(request)
@filters.each do |filter| @filters.each do |filter|
return false if filter.call(request).is_a? FalseClass return false unless @procbinding.instance_exec(request, &filter)
end end
true true
end end
@ -101,7 +113,7 @@ module Hyde
# @param request [Hyde::Request] # @param request [Hyde::Request]
def run_preprocessors(request) def run_preprocessors(request)
@preprocessors.each do |preproc| @preprocessors.each do |preproc|
preproc.call(request) @procbinding.instance_exec(request, &preproc)
end end
end end

View File

@ -1,12 +1,16 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative "../dsl/probe_methods" require_relative "../dsl/probe_methods"
require_relative "../dsl/common_methods"
module Hyde module Hyde
# Binding that provides execution context for Probes.
class ProbeBinding class ProbeBinding
include Hyde::DSL::ProbeMethods
include Hyde::DSL::CommonMethods
def initialize(origin) def initialize(origin)
@origin = origin @origin = origin
end end
include Hyde::DSL::ProbeMethods
end end
end end

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'cgi/escape'
module Hyde module Hyde
module Util module Util
# HTTP status codes and descriptions # HTTP status codes and descriptions
@ -40,6 +42,7 @@ module Hyde
415 => 'Unsupported Media Type', 415 => 'Unsupported Media Type',
416 => 'Request Range Not Satisfiable', 416 => 'Request Range Not Satisfiable',
417 => 'Expectation Failed', 417 => 'Expectation Failed',
418 => "I'm a teapot",
422 => 'Unprocessable Entity', 422 => 'Unprocessable Entity',
423 => 'Locked', 423 => 'Locked',
424 => 'Failed Dependency', 424 => 'Failed Dependency',
@ -65,11 +68,14 @@ module Hyde
# @param str [String] # @param str [String]
# @return [String] # @return [String]
def self.escape_html(str) def self.escape_html(str)
str.gsub("&", "&amp;") CGI.escapeHTML(str)
.gsub("<", "&lt;") end
.gsub(">", "&gt;")
.gsub("\"", "&quot;") # Return string with unescaped HTML entities.
.gsub("'", "&#39;") # @param str [String]
# @return [String]
def self.unescape_html(str)
CGI.unescapeHTML(str)
end end
# rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/MethodLength

View File

@ -5,6 +5,7 @@ require_relative 'sorting'
module Hyde module Hyde
module Util module Util
# TODO: encoding support????
# Query string parser # Query string parser
class Query class Query
include Hyde::Util::ParserCommon include Hyde::Util::ParserCommon
@ -16,13 +17,13 @@ module Hyde
# Shallow query parser (does not do PHP-like array keys) # Shallow query parser (does not do PHP-like array keys)
# @return [Hash] # @return [Hash]
def parse_shallow def parse_shallow
URI.decode_www_form(@query, Encoding::UTF_8) URI.decode_www_form(@query)
.sort_by { |array| array[0] } .sort_by { |array| array[0] }
.to_h .to_h
end end
# Better(tm) query parser with # Better(tm) query parser.
# Returns a hash with arrays # Returns a hash with arrays.
# Key semantics: # Key semantics:
# #
# - `key=value` creates a key value pair # - `key=value` creates a key value pair
@ -30,10 +31,10 @@ module Hyde
# - `key[index]=value` sets `value` at `index` of array named `key` # - `key[index]=value` sets `value` at `index` of array named `key`
# @return [Hash] # @return [Hash]
def parse def parse
construct_deep_hash(URI.decode_www_form(@query, Encoding::UTF_8)) construct_deep_hash(URI.decode_www_form(@query))
end end
# Get key from query # Get key from query.
# @param key [String] # @param key [String]
# @return [String,Array] # @return [String,Array]
def [](key) def [](key)