From bc8750daf7be3d1f34d9fb88f784caf366385202 Mon Sep 17 00:00:00 2001 From: Yessiest Date: Sun, 10 Sep 2023 20:17:07 +0400 Subject: [PATCH] made form parser easier to use --- examples/form/form.ru | 7 +---- examples/logging.ru | 3 ++- lib/hyde/dsl/common_methods.rb | 28 ++++++++++++++++++++ lib/hyde/dsl/probe_methods.rb | 47 ++++++++++++++++++---------------- lib/hyde/path.rb | 22 ++++++++++++---- lib/hyde/probe/binding.rb | 6 ++++- lib/hyde/util/html.rb | 16 ++++++++---- lib/hyde/util/query.rb | 11 ++++---- 8 files changed, 95 insertions(+), 45 deletions(-) create mode 100644 lib/hyde/dsl/common_methods.rb diff --git a/examples/form/form.ru b/examples/form/form.ru index ccb7806..26c57b3 100644 --- a/examples/form/form.ru +++ b/examples/form/form.ru @@ -2,17 +2,12 @@ $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/lib") require 'hyde' -require 'hyde/util/multipart' -require 'hyde/util/header' app = Hyde::Server.new do root ENV["PWD"] index ["index.html"] post "/" do - _, opts = Hyde::Util.parse_value(request.headers["content-type"]) - puts Hyde::Util::MultipartParser.new( - request.input, opts["boundary"] - ).to_h.pretty_inspect + puts form.pretty_inspect if form? bounce end serve "*.html" diff --git a/examples/logging.ru b/examples/logging.ru index 42e2cc7..e853594 100644 --- a/examples/logging.ru +++ b/examples/logging.ru @@ -12,8 +12,9 @@ app = Hyde::Server.new do end path "important" do preprocess do |req| + # 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 get "answer" do header "content-type", "application/json" diff --git a/lib/hyde/dsl/common_methods.rb b/lib/hyde/dsl/common_methods.rb new file mode 100644 index 0000000..5a45c36 --- /dev/null +++ b/lib/hyde/dsl/common_methods.rb @@ -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 diff --git a/lib/hyde/dsl/probe_methods.rb b/lib/hyde/dsl/probe_methods.rb index 5d113c7..30d076d 100644 --- a/lib/hyde/dsl/probe_methods.rb +++ b/lib/hyde/dsl/probe_methods.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true require_relative '../response' +require_relative '../util/multipart' +require_relative '../util/header' module Hyde module DSL @@ -12,31 +14,11 @@ module Hyde @origin.request 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) # @param status [Integer] http status code def status(status) - @response = (@response or Hyde::Response.new) - @response.status = status + @origin.response = (@origin.response or Hyde::Response.new) + @origin.response.status = status end alias code status @@ -79,6 +61,27 @@ module Hyde @origin.response.delete_header(key, value) 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 diff --git a/lib/hyde/path.rb b/lib/hyde/path.rb index 67798da..3660b09 100644 --- a/lib/hyde/path.rb +++ b/lib/hyde/path.rb @@ -4,10 +4,20 @@ require_relative 'pattern_matching' require_relative 'node' require_relative 'dsl/path_constructors' require_relative 'dsl/path_methods' +require_relative 'dsl/common_methods' require_relative 'util/lookup' 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 include Hyde::DSL::PathConstructors include Hyde::DSL::PathMethods @@ -19,6 +29,7 @@ module Hyde # Primary building block of request navigation. class Path < Hyde::Node + ProcBinding = Hyde::ProcessorBinding Binding = Hyde::PathBinding # @param path [Object] Object to generate {Hyde::Pattern} from @@ -32,9 +43,10 @@ module Hyde @preprocessors = [] @postprocessors = [] @filters = [] - - binding = Binding.new(self) + # Bindings setup + binding = self.class::Binding.new(self) binding.instance_exec(&setup) + @procbinding = self.class::ProcBinding.new(self) end # Method callback on successful request navigation. @@ -92,7 +104,7 @@ module Hyde # @return [Boolean] true if request passed all filters def run_filters(request) @filters.each do |filter| - return false if filter.call(request).is_a? FalseClass + return false unless @procbinding.instance_exec(request, &filter) end true end @@ -101,7 +113,7 @@ module Hyde # @param request [Hyde::Request] def run_preprocessors(request) @preprocessors.each do |preproc| - preproc.call(request) + @procbinding.instance_exec(request, &preproc) end end diff --git a/lib/hyde/probe/binding.rb b/lib/hyde/probe/binding.rb index 334d49b..1d0fb79 100644 --- a/lib/hyde/probe/binding.rb +++ b/lib/hyde/probe/binding.rb @@ -1,12 +1,16 @@ # frozen_string_literal: true require_relative "../dsl/probe_methods" +require_relative "../dsl/common_methods" module Hyde + # Binding that provides execution context for Probes. class ProbeBinding + include Hyde::DSL::ProbeMethods + include Hyde::DSL::CommonMethods + def initialize(origin) @origin = origin end - include Hyde::DSL::ProbeMethods end end diff --git a/lib/hyde/util/html.rb b/lib/hyde/util/html.rb index 867815f..6d8a648 100644 --- a/lib/hyde/util/html.rb +++ b/lib/hyde/util/html.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'cgi/escape' + module Hyde module Util # HTTP status codes and descriptions @@ -40,6 +42,7 @@ module Hyde 415 => 'Unsupported Media Type', 416 => 'Request Range Not Satisfiable', 417 => 'Expectation Failed', + 418 => "I'm a teapot", 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', @@ -65,11 +68,14 @@ module Hyde # @param str [String] # @return [String] def self.escape_html(str) - str.gsub("&", "&") - .gsub("<", "<") - .gsub(">", ">") - .gsub("\"", """) - .gsub("'", "'") + CGI.escapeHTML(str) + end + + # Return string with unescaped HTML entities. + # @param str [String] + # @return [String] + def self.unescape_html(str) + CGI.unescapeHTML(str) end # rubocop:disable Metrics/MethodLength diff --git a/lib/hyde/util/query.rb b/lib/hyde/util/query.rb index 24858f0..fd23ab8 100644 --- a/lib/hyde/util/query.rb +++ b/lib/hyde/util/query.rb @@ -5,6 +5,7 @@ require_relative 'sorting' module Hyde module Util + # TODO: encoding support???? # Query string parser class Query include Hyde::Util::ParserCommon @@ -16,13 +17,13 @@ module Hyde # Shallow query parser (does not do PHP-like array keys) # @return [Hash] def parse_shallow - URI.decode_www_form(@query, Encoding::UTF_8) + URI.decode_www_form(@query) .sort_by { |array| array[0] } .to_h end - # Better(tm) query parser with - # Returns a hash with arrays + # Better(tm) query parser. + # Returns a hash with arrays. # Key semantics: # # - `key=value` creates a key value pair @@ -30,10 +31,10 @@ module Hyde # - `key[index]=value` sets `value` at `index` of array named `key` # @return [Hash] def parse - construct_deep_hash(URI.decode_www_form(@query, Encoding::UTF_8)) + construct_deep_hash(URI.decode_www_form(@query)) end - # Get key from query + # Get key from query. # @param key [String] # @return [String,Array] def [](key)