landline 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -96,7 +96,7 @@ module Landline
96
96
  end
97
97
  end
98
98
 
99
- attr_accessor :status, :headers, :body
99
+ attr_accessor :status, :headers, :body, :cookies
100
100
 
101
101
  # Ensure response correctness
102
102
  # @param obj [String, Array, Landline::Response]
@@ -11,39 +11,64 @@ module Landline
11
11
  # A specialized path that can be used directly as a Rack application.
12
12
  class Server < Landline::Path
13
13
  Context = ServerContext
14
-
15
14
  # @param parent [Landline::Node, nil] Parent object to inherit properties to
16
15
  # @param setup [#call] Setup block
17
- def initialize(parent: nil, **args, &setup)
18
- super("", parent: nil, **args, &setup)
16
+ def initialize(passthrough = nil, parent: nil, **opts, &setup)
17
+ super("", parent: nil, **opts, &setup)
19
18
  return if parent
20
19
 
20
+ @passthrough = passthrough
21
+ setup_properties(parent: nil, **opts)
22
+ end
23
+
24
+ # Rack ingress point.
25
+ # @param env [Hash]
26
+ # @return [Array(Integer,Hash,Array)]
27
+ def call(env)
28
+ request = Landline::Request.new(env)
29
+
30
+ response = handle_jumps(request)
31
+ request.run_postprocessors(response)
32
+ resp = response.finalize
33
+ if resp[1][:"x-cascade"] and resp[0] == 404 and @passthrough
34
+ @passthrough.call(request.env)
35
+ else
36
+ resp
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ # Catch internal jumps
43
+ def handle_jumps(request)
44
+ response = Response.convert(catch(:finish) do
45
+ go(request)
46
+ end)
47
+ while response and
48
+ response.status == 307 and
49
+ response.headers.include? :"x-internal-jump"
50
+ response = Response.convert(catch(:finish) do
51
+ go(request)
52
+ end)
53
+ end
54
+ response
55
+ end
56
+
57
+ # Inititalization block for property setup
58
+ def setup_properties(*_args, **_opts)
21
59
  {
22
60
  "index" => [],
23
61
  "handle.default" => proc do |code, backtrace: nil|
24
62
  page = Landline::Util.default_error_page(code, backtrace)
25
63
  headers = {
26
64
  "content-length": page.bytesize,
27
- "content-type": "text/html"
65
+ "content-type": "text/html",
66
+ "x-cascade": true
28
67
  }
29
68
  [headers, page]
30
69
  end,
31
70
  "path" => "/"
32
71
  }.each { |k, v| @properties[k] = v unless @properties[k] }
33
72
  end
34
-
35
- # Rack ingress point.
36
- # This should not be called under any circumstances twice in the same application,
37
- # although server nesting for the purpose of creating virtual hosts is allowed.
38
- # @param env [Hash]
39
- # @return [Array(Integer,Hash,Array)]
40
- def call(env)
41
- request = Landline::Request.new(env)
42
- response = catch(:finish) do
43
- go(request)
44
- end
45
- request.run_postprocessors(response)
46
- Response.convert(response).finalize
47
- end
48
73
  end
49
74
  end
@@ -4,9 +4,16 @@ require_relative 'parseutils'
4
4
  require_relative 'errors'
5
5
  require 'date'
6
6
  require 'openssl'
7
+ require 'base64'
7
8
  HeaderRegexp = Landline::Util::HeaderRegexp
8
9
  ParserCommon = Landline::Util::ParserCommon
9
10
 
11
+ if RUBY_ENGINE == 'jruby' # fix for JRuby
12
+ OpenSSL::HMAC.define_singleton_method(:base64digest) do |*args|
13
+ Base64.strict_encode64(OpenSSL::HMAC.digest(*args)).strip
14
+ end
15
+ end
16
+
10
17
  module Landline
11
18
  # Utility class for handling cookies
12
19
  class Cookie
@@ -22,11 +29,11 @@ module Landline
22
29
  # @option params [String, Date] "expires"
23
30
  # @raise Landline::ParsingError invalid cookie parameters
24
31
  def initialize(key, value, params = {})
25
- unless key.match? HeaderRegexp::COOKIE_NAME
32
+ unless key.match?(/\A#{HeaderRegexp::COOKIE_NAME}\z/o)
26
33
  raise Landline::ParsingError, "invalid cookie key: #{key}"
27
34
  end
28
35
 
29
- unless value.match? HeaderRegexp::COOKIE_VALUE
36
+ unless value.match?(/\A#{HeaderRegexp::COOKIE_VALUE}\z/o)
30
37
  raise Landline::ParsingError, "invalid cookie value: #{value}"
31
38
  end
32
39
 
@@ -81,7 +88,7 @@ module Landline
81
88
  # @param sep [String] Hash separator
82
89
  # @return [Boolean] whether value is signed and valid
83
90
  def verify(key, algorithm: "sha256", sep: "&")
84
- val, sig = @value.match(/\A(.*)#{sep}([A-Za-z0-9+\/=]+)\Z/).to_a[1..]
91
+ val, sig = @value.match(/\A(.*)#{sep}([A-Za-z0-9+\/=]+)\z/).to_a[1..]
85
92
  return false unless val and sig
86
93
 
87
94
  sig == ::OpenSSL::HMAC.base64digest(algorithm, key, val)
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'json'
5
+ require 'base64'
6
+
7
+ if RUBY_ENGINE == 'jruby' # fix for JRuby
8
+ OpenSSL::HMAC.define_singleton_method(:base64digest) do |*args|
9
+ Base64.strict_encode64(OpenSSL::HMAC.digest(*args)).strip
10
+ end
11
+ end
12
+
13
+ module Landline
14
+ module Util
15
+ # JSON Web Token construction class
16
+ class JWT
17
+ ALGO = {
18
+ "HS256" => proc do |data, secret|
19
+ Base64.urlsafe_encode64(
20
+ OpenSSL::HMAC.digest("SHA256", secret, data)
21
+ ).gsub('=', '')
22
+ end,
23
+ "HS384" => proc do |data, secret|
24
+ Base64.urlsafe_encode64(
25
+ OpenSSL::HMAC.digest("SHA384", secret, data)
26
+ ).gsub('=', '')
27
+ end,
28
+ "HS512" => proc do |data, secret|
29
+ Base64.urlsafe_encode64(
30
+ OpenSSL::HMAC.digest("SHA512", secret, data)
31
+ ).gsub('=', '')
32
+ end
33
+ }.freeze
34
+
35
+ # Create a new JWT token wrapper
36
+ # @param data [Hash, Array] JSON-formattable data
37
+ # @param halgo [String] Name of the hash algorithm to use
38
+ def initialize(data, halgo = "HS256")
39
+ unless ALGO.include? halgo
40
+ raise StandardError, "hash algorithm #{halgo} not supported"
41
+ end
42
+
43
+ @halgo = halgo
44
+ @data = data
45
+ end
46
+
47
+ # Construct a string representation of the current token
48
+ # @param key [String]
49
+ # @return [String]
50
+ def make(key)
51
+ jsonheader = {
52
+ "alg": @halgo,
53
+ "typ": "JWT"
54
+ }.to_json
55
+ jsondata = @data.to_json
56
+ data = "#{base64(jsonheader)}.#{base64(jsondata)}"
57
+ "#{data}.#{ALGO[@halgo].call(data, key)}"
58
+ end
59
+
60
+ # Construct an object from string
61
+ # @param input [String]
62
+ # @param key [String]
63
+ # @return [JWT, nil] returns nil if verification couldn't complete
64
+ def self.from_string(input, key)
65
+ halgoj, dataj, sig = input.split(".")
66
+ halgo = JSON.parse(Base64.urlsafe_decode64(halgoj))["alg"]
67
+ return nil unless ALGO.include? halgo
68
+ return nil if ALGO[halgo].call("#{halgoj}.#{dataj}", key) != sig
69
+
70
+ new(JSON.parse(Base64.urlsafe_decode64(dataj)), halgo)
71
+ end
72
+
73
+ attr_accessor :data
74
+
75
+ private
76
+
77
+ def base64(data)
78
+ Base64.urlsafe_encode64(data).gsub("=", "")
79
+ end
80
+ end
81
+ end
82
+ end
@@ -33,5 +33,19 @@ module Landline
33
33
 
34
34
  attr_accessor :parent
35
35
  end
36
+
37
+ # Read-only lookup proxy
38
+ class LookupROProxy
39
+ def initialize(lookup)
40
+ @lookup = lookup
41
+ end
42
+
43
+ # Get a value by key
44
+ # @param key [#hash] key for value
45
+ # @return [Object,nil]
46
+ def [](key)
47
+ @lookup.[](key)
48
+ end
49
+ end
36
50
  end
37
51
  end
@@ -1267,6 +1267,7 @@ module Landline
1267
1267
  }.freeze
1268
1268
 
1269
1269
  # Get MIME type by file extension
1270
+ # @note This function does no checks on the file - simply renaming the file to a different extension will yield an invalid result. Do not use this to check uploaded files - preferably, use libmagic or proper mime type tools for Ruby.
1270
1271
  # @param file [String] filename
1271
1272
  # @return [String] MIME type, defaults to "application/octet-stream"
1272
1273
  def self.get_mime_type(file)
@@ -8,28 +8,28 @@ module Landline
8
8
  # (not exactly precise) Regular expressions for some RFC definitions
9
9
  module HeaderRegexp
10
10
  # Matches the RFC2616 definiton of token
11
- TOKEN = /[!-~&&[^()<>@,;:\\"\/\[\]?={}\t]]+/.freeze
11
+ TOKEN = /[!-~&&[^()<>@,;:\\"\/\[\]?={}\t]]+/
12
12
  # Matches the RFC2616 definition of quoted-string
13
- QUOTED = /"[\x0-\x7E&&[^\x1-\x8\xb-\x1f]]*(?<!\\)"/.freeze
13
+ QUOTED = /"[\x0-\x7E&&[^\x1-\x8\xb-\x1f]]*(?<!\\)"/
14
14
  # Matches any CHAR except CTLs
15
- PRINTCHAR = /[\x2-\x7E]/.freeze
15
+ PRINTCHAR = /[\x2-\x7E]/
16
16
  # Matches 1 or more CHARs excluding CTLs
17
- PRINTABLE = /#{PRINTCHAR}+/o.freeze
17
+ PRINTABLE = /#{PRINTCHAR}+/o
18
18
  # Matches the RFC6265 definition of a cookie-octet
19
- COOKIE_OCTET = /[\x21-\x7E&&[^",;\\]]*/.freeze
20
- COOKIE_VALUE = /(?:#{QUOTED}|#{COOKIE_OCTET})/o.freeze
19
+ COOKIE_OCTET = /[\x21-\x7E&&[^",;\\]]*/
20
+ COOKIE_VALUE = /(?:#{QUOTED}|#{COOKIE_OCTET})/o
21
21
  COOKIE_NAME = TOKEN
22
22
  # Matches the RFC6265 definition of cookie-pair.
23
23
  # Captures name (1) and value (2).
24
- COOKIE_PAIR = /\A(#{COOKIE_NAME})=(#{COOKIE_VALUE})\Z/o.freeze
24
+ COOKIE_PAIR = /\A(#{COOKIE_NAME})=(#{COOKIE_VALUE})\z/o
25
25
  # Matches a very abstract definition of a quoted header paramter.
26
26
  # Captures name (1) and value (2).
27
- PARAM_QUOTED = /\A(#{TOKEN})=?(#{QUOTED}|#{PRINTCHAR}*)\Z/o.freeze
27
+ PARAM_QUOTED = /\A(#{TOKEN})=?(#{QUOTED}|#{PRINTCHAR}*)\z/o
28
28
  # Matches a very abstract definition of a header parameter.
29
29
  # Captures name (1) and value (2).
30
- PARAM = /\A(#{TOKEN})=?(#{PRINTCHAR}*)\Z/o.freeze
30
+ PARAM = /\A(#{TOKEN})=?(#{PRINTCHAR}*)\z/o
31
31
  # Specifically matches cookie parameters
32
- COOKIE_PARAM = /\A(#{TOKEN})=?(#{QUOTED}|#{COOKIE_OCTET})\Z/o.freeze
32
+ COOKIE_PARAM = /\A(#{TOKEN})=?(#{QUOTED}|#{COOKIE_OCTET})\z/o
33
33
  end
34
34
 
35
35
  # Module for all things related to parsing HTTP and related syntax.
@@ -48,7 +48,9 @@ module Landline
48
48
  # @param regexp [Regexp,nil] override param matching regexp
49
49
  # @return [Array(String, Hash)]
50
50
  def self.parse_value(input, sep: ";", unquote: false, regexp: nil)
51
- parts = input.split(sep).map { |x| URI.decode_uri_component(x).strip }
51
+ parts = input.split(sep).map do |x|
52
+ URI.decode_www_form_component(x).strip
53
+ end
52
54
  base = parts.shift
53
55
  opts = parts.map do |raw|
54
56
  key, value = raw.match(if regexp
@@ -8,6 +8,8 @@ module Landline
8
8
  # Query string parser
9
9
  class Query
10
10
  include Landline::Util::ParserSorting
11
+ attr_reader :query
12
+
11
13
  # @param query [String]
12
14
  def initialize(query)
13
15
  @query = query
data/lib/landline.rb CHANGED
@@ -1,16 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'landline/util/jwt'
3
4
  require_relative 'landline/server'
4
5
  require_relative 'landline/path'
5
6
  require_relative 'landline/probe'
6
7
  require_relative 'landline/request'
7
8
  require_relative 'landline/response'
8
9
  require_relative 'landline/template'
10
+ require_relative 'landline/app'
9
11
 
10
- # Landline is a hideously simple ruby web framework
12
+ # Landline is a backend framework born as a by-product of experimentation
11
13
  module Landline
12
14
  # Landline version
13
- VERSION = '0.10 "Node graph out of date. Rebuilding..." (beta)'
15
+ VERSION = '0.12 "Concrete and Gold" (pre-alpha)'
14
16
 
15
17
  # Landline branding and version
16
18
  VLINE = "Landline/#{Landline::VERSION} (Ruby/#{RUBY_VERSION}/#{RUBY_RELEASE_DATE})\n".freeze
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: landline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yessiest
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-21 00:00:00.000000000 Z
11
+ date: 2024-04-28 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  Landline is a no-hard-dependencies HTTP routing DSL that was made entirely for fun.
@@ -19,21 +19,22 @@ executables: []
19
19
  extensions: []
20
20
  extra_rdoc_files:
21
21
  - HACKING.md
22
- - LAYOUT.md
23
22
  - LICENSE.md
24
23
  - README.md
25
24
  files:
26
25
  - HACKING.md
27
- - LAYOUT.md
28
26
  - LICENSE.md
29
27
  - README.md
30
28
  - lib/landline.rb
29
+ - lib/landline/app.rb
31
30
  - lib/landline/dsl/constructors_path.rb
32
31
  - lib/landline/dsl/constructors_probe.rb
33
32
  - lib/landline/dsl/methods_common.rb
34
33
  - lib/landline/dsl/methods_path.rb
35
34
  - lib/landline/dsl/methods_probe.rb
36
35
  - lib/landline/dsl/methods_template.rb
36
+ - lib/landline/extensions/session.rb
37
+ - lib/landline/extensions/websocket.rb
37
38
  - lib/landline/node.rb
38
39
  - lib/landline/path.rb
39
40
  - lib/landline/pattern_matching.rb
@@ -41,6 +42,7 @@ files:
41
42
  - lib/landline/pattern_matching/rematch.rb
42
43
  - lib/landline/pattern_matching/util.rb
43
44
  - lib/landline/probe.rb
45
+ - lib/landline/probe/crosscall_handler.rb
44
46
  - lib/landline/probe/handler.rb
45
47
  - lib/landline/probe/http_method.rb
46
48
  - lib/landline/probe/serve_handler.rb
@@ -53,6 +55,7 @@ files:
53
55
  - lib/landline/util/cookie.rb
54
56
  - lib/landline/util/errors.rb
55
57
  - lib/landline/util/html.rb
58
+ - lib/landline/util/jwt.rb
56
59
  - lib/landline/util/lookup.rb
57
60
  - lib/landline/util/mime.rb
58
61
  - lib/landline/util/multipart.rb
@@ -61,7 +64,7 @@ files:
61
64
  - lib/landline/util/query.rb
62
65
  homepage: https://adastra7.net/git/Yessiest/landline
63
66
  licenses:
64
- - AGPL-3.0
67
+ - AGPL-3.0-or-later
65
68
  metadata: {}
66
69
  post_install_message:
67
70
  rdoc_options: []
@@ -78,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
78
81
  - !ruby/object:Gem::Version
79
82
  version: '0'
80
83
  requirements: []
81
- rubygems_version: 3.3.25
84
+ rubygems_version: 3.5.3
82
85
  signing_key:
83
86
  specification_version: 4
84
87
  summary: Elegant HTTP DSL
data/LAYOUT.md DELETED
@@ -1,86 +0,0 @@
1
- # Internal structure of Landline lib
2
-
3
- Note: If you want to start hacking on Landline and extending it, follow this
4
- layout as closely as possible.
5
-
6
- ## Core classes
7
-
8
- These are core classes of Landline and they are loaded as soon as the library is loaded.
9
-
10
- - Landline::Path [path.rb]
11
- - Landline::PathContext [path.rb]
12
- - Landline::Probe [probe.rb]
13
- - Landline::ProbeContext [probe.rb]
14
- - Landline::Node (parent of Path and Probe) [node.rb]
15
- - Landline::Server (Rack application interface) [server.rb]
16
- - Landline::ServerContext [server.rb]
17
- - Landline::Request (Rack request wrapper) [request.rb]
18
- - Landline::Response (Rack response wrapper) [response.rb]
19
- - Landline::Pattern [pattern\_matching.rb]
20
- - Landline::TemplateContext [tempalte.rb]
21
- - Landline::Template (template engine interface) [template.rb]
22
-
23
- ## Patterns
24
-
25
- These are classes that Landline::Pattern can interface with to create Patterns.
26
-
27
- - Landline::PatternMatching::ReMatch [pattern\_matching/rematch.rb]
28
- - Landline::PatternMatching::Glob [pattern\_matching/glob.rb]
29
-
30
- ## DSL Method mixins
31
-
32
- These are module mixins that add common methods to DSL bindings.
33
-
34
- - Landline::DSL::PathConstructors [dsl/constructors\_path.rb]
35
- - Landline::DSL::ProbeConstructures [dsl/constructors\_probe.rb]
36
- - Landline::DSL::CommonMethods [dsl/methods\_common.rb]
37
- - Landline::DSL::PathMethods [dsl/methods\_path.rb]
38
- - Landline::DSL::ProbeMethods [dsl/methods\_probe.rb]
39
- - Landline::DSL::TemplateMethods [dsl/methods\_template.rb]
40
-
41
- ## Utilities
42
-
43
- These are self-contained classes and methods that add extra functionality to Landline.
44
-
45
- - Landline::Util::Lookup [util/lookup.rb]
46
- - Landline::PatternMatching [pattern\_matching/util.rb]
47
- - Landline::Cookie (class) [util/cookie.rb]
48
- - Landline::Error (class) [util/errors.rb]
49
- - Landline::ParsingError (class) [util/errors.rb]
50
- - Landline::Util (html/http utilities) [util/html.rb]
51
- - Landline::MIME (MIME extension to type association) [util/mime.rb]
52
- - Landline::Util::ParserSorting (functions for sorting form/query hashes) [util/parsesorting.rb]
53
- - Landline::Util::Query (query class) [util/query.rb]
54
- - Landline::Util::FormPart (formparser struct) [util/multipart.rb]
55
- - Landline::Util::MultipartParser (multipart form parser) [util/multipart.rb]
56
- - Landline::Util::HeaderRegexp (helper regexps for headers) [util/parseutils.rb]
57
- - Landline::Util (parser methods) [util/parseutils.rb]
58
-
59
- ## Probe subclasses
60
-
61
- These are reactive request handlers with their own semantics, if needed.
62
-
63
- - Landline::Handlers::Handler [probe/handler.rb]
64
- - Landline::Handlers::GETHandler [probe/http\_method.rb]
65
- - Landline::Handlers::POSTHandler [probe/http\_method.rb]
66
- - Landline::Handlers::HEADHandler [probe/http\_method.rb]
67
- - Landline::Handlers::PUTHandler [probe/http\_method.rb]
68
- - Landline::Handlers::DELETEHandler [probe/http\_method.rb]
69
- - Landline::Handlers::CONNECTHandler [probe/http\_method.rb]
70
- - Landline::Handlers::OPTIONSHandler [probe/http\_method.rb]
71
- - Landline::Handlers::TRACEHandler [probe/http\_method.rb]
72
- - Landline::Handlers::PATCHHandler [probe/http\_method.rb]
73
- - Landline::Handlers::Serve
74
-
75
- ## Path subclasses
76
-
77
- These are navigation handlers with their own semantics.
78
-
79
- (currently none)
80
-
81
- ## Template engine interfaces
82
-
83
- These are uniform interfaces for various templating engines.
84
-
85
- - Landline::Templates::ERB [template/erb.rb]
86
- - Landline::Templates::Erubi [template/erubi.rb]