diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml new file mode 100644 index 0000000..1174e6b --- /dev/null +++ b/.github/workflows/push_gem.yml @@ -0,0 +1,46 @@ +name: Publish gem to rubygems.org + +on: + push: + tags: + - 'v*' + +permissions: + contents: read + +jobs: + push: + if: github.repository == 'ruby/webrick' + runs-on: ubuntu-latest + + environment: + name: rubygems.org + url: https://rubygems.org/gems/webrick + + permissions: + contents: write + id-token: write + + steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + + - name: Set up Ruby + uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # v1.190.0 + with: + bundler-cache: true + ruby-version: ruby + + - name: Publish to RubyGems + uses: rubygems/release-gem@612653d273a73bdae1df8453e090060bb4db5f31 # v1 + + - name: Create GitHub release + run: | + tag_name="$(git describe --tags --abbrev=0)" + gh release create "${tag_name}" --verify-tag --generate-notes + env: + GITHUB_TOKEN: ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index edede5b..6568a31 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,3 +34,6 @@ jobs: bundler-cache: true # 'bundle install' and cache - name: Run test run: bundle exec rake test + - name: RBS validate + run: bundle exec rbs -r openssl -r digest -r uri -r erb -r singleton -r tempfile -r socket -I sig validate + if: ${{ ! startsWith(matrix.ruby, '2.') }} # rbs requires ruby 3.0+ diff --git a/Gemfile b/Gemfile index f5b6c4b..d069402 100644 --- a/Gemfile +++ b/Gemfile @@ -5,3 +5,6 @@ gemspec gem "rake" gem "test-unit" gem "test-unit-ruby-core" + +# rbs requires ruby 3.0+ +gem "rbs", require: false if !RUBY_VERSION.start_with?('2.') diff --git a/README.md b/README.md index 0027072..685dc84 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ A WEBrick server can be composed of multiple WEBrick servers or servlets to prov WEBrick also includes tools for daemonizing a process and starting a process at a higher privilege level and dropping permissions. +WEBrick is suitable for use in testing and for development. However, while the developers of WEBrick will attempt to fix security issues, they do not encourage the use of WEBrick to serve production web applications that may be subject to hostile input. + ## Installation Add this line to your application's Gemfile: diff --git a/lib/webrick/version.rb b/lib/webrick/version.rb index fbea0dd..fed769a 100644 --- a/lib/webrick/version.rb +++ b/lib/webrick/version.rb @@ -14,5 +14,5 @@ module WEBrick ## # The WEBrick version - VERSION = "1.8.2" + VERSION = "1.9.0" end diff --git a/sig/accesslog.rbs b/sig/accesslog.rbs new file mode 100644 index 0000000..ffdb91d --- /dev/null +++ b/sig/accesslog.rbs @@ -0,0 +1,24 @@ +module WEBrick + module AccessLog + class AccessLogError < StandardError + end + + CLF_TIME_FORMAT: String + + COMMON_LOG_FORMAT: String + + CLF: String + + REFERER_LOG_FORMAT: String + + AGENT_LOG_FORMAT: String + + COMBINED_LOG_FORMAT: String + + def self?.setup_params: (Hash[Symbol, untyped] config, HTTPRequest req, HTTPResponse res) -> Hash[String, untyped] + + def self?.format: (String format_string, Hash[String, untyped] params) -> String + + def self?.escape: (String data) -> String + end +end diff --git a/sig/cgi.rbs b/sig/cgi.rbs new file mode 100644 index 0000000..582adb7 --- /dev/null +++ b/sig/cgi.rbs @@ -0,0 +1,92 @@ +module WEBrick + class CGI + @options: Array[untyped] + + class CGIError < StandardError + end + + attr_reader config: Hash[Symbol, untyped] + + attr_reader logger: BasicLog + + def initialize: (*untyped args) -> void + + def []: (Symbol key) -> untyped + + interface _Env + def []: (String) -> String? + end + + def start: (?_Env env, ?IO stdin, ?IO stdout) -> void + + def self.setup_header: () -> untyped + + def self.status_line: () -> "" + + def service: (HTTPRequest req, HTTPResponse res) -> void + + class Socket + @config: Hash[Symbol, untyped] + + @env: _Env + + @header_part: StringIO + + @body_part: IO + + @out_port: IO + + @server_addr: String + + @server_name: String? + + @server_port: String? + + @remote_addr: String? + + @remote_host: String? + + @remote_port: (String | 0) + + include Enumerable[String] + + private + + def initialize: (Hash[Symbol, untyped] config, _Env env, IO stdin, IO stdout) -> void + + def request_line: () -> String + + def setup_header: () -> void + + def add_header: (String hdrname, String value) -> void + + def input: () -> (IO | StringIO) + + public + + def peeraddr: () -> [nil, (String | 0), String?, String?] + + def addr: () -> [nil, String?, String?, String] + + def gets: (?String eol, ?Integer? size) -> String? + + def read: (?Integer? size) -> String? + + def each: () { (String) -> void } -> void + + def eof?: () -> bool + + def <<: (_ToS data) -> IO + + def write: (_ToS data) -> Integer + + def cert: () -> OpenSSL::X509::Certificate? + + def peer_cert: () -> OpenSSL::X509::Certificate? + + def peer_cert_chain: () -> Array[OpenSSL::X509::Certificate]? + + def cipher: () -> [String?, String?, String?, String?]? + end + end +end diff --git a/sig/compat.rbs b/sig/compat.rbs new file mode 100644 index 0000000..8d5b745 --- /dev/null +++ b/sig/compat.rbs @@ -0,0 +1,18 @@ +# +# System call error module used by webrick for cross platform compatibility. +# +# EPROTO:: protocol error +# ECONNRESET:: remote host reset the connection request +# ECONNABORTED:: Client sent TCP reset (RST) before server has accepted the +# connection requested by client. +# +module Errno + class EPROTO < SystemCallError + end + + class ECONNRESET < SystemCallError + end + + class ECONNABORTED < SystemCallError + end +end diff --git a/sig/config.rbs b/sig/config.rbs new file mode 100644 index 0000000..be0a6a6 --- /dev/null +++ b/sig/config.rbs @@ -0,0 +1,17 @@ +module WEBrick + module Config + LIBDIR: String + + # for GenericServer + General: Hash[Symbol, untyped] + + # for HTTPServer, HTTPRequest, HTTPResponse ... + HTTP: Hash[Symbol, untyped] + + FileHandler: Hash[Symbol, untyped] + + BasicAuth: Hash[Symbol, untyped] + + DigestAuth: Hash[Symbol, untyped] + end +end diff --git a/sig/cookie.rbs b/sig/cookie.rbs new file mode 100644 index 0000000..6a7dc10 --- /dev/null +++ b/sig/cookie.rbs @@ -0,0 +1,37 @@ +module WEBrick + class Cookie + @expires: String? + + attr_reader name: String? + + attr_accessor value: String? + + attr_accessor version: Integer + + # + # The cookie domain + attr_accessor domain: String? + + attr_accessor path: String? + + attr_accessor secure: true? + + attr_accessor comment: String? + + attr_accessor max_age: Integer? + + def initialize: (untyped name, untyped value) -> void + + def expires=: ((Time | _ToS)? t) -> untyped + + def expires: () -> Time? + + def to_s: () -> String + + def self.parse: (String? str) -> Array[instance]? + + def self.parse_set_cookie: (String str) -> instance + + def self.parse_set_cookies: (String str) -> Array[instance] + end +end diff --git a/sig/htmlutils.rbs b/sig/htmlutils.rbs new file mode 100644 index 0000000..00331b6 --- /dev/null +++ b/sig/htmlutils.rbs @@ -0,0 +1,5 @@ +module WEBrick + module HTMLUtils + def self?.escape: (String? string) -> String + end +end diff --git a/sig/httpauth.rbs b/sig/httpauth.rbs new file mode 100644 index 0000000..90f2977 --- /dev/null +++ b/sig/httpauth.rbs @@ -0,0 +1,13 @@ +module WEBrick + module HTTPAuth + interface _Callable + def call: (String user, String pass) -> bool + end + + def self?._basic_auth: (HTTPRequest req, HTTPResponse res, String realm, String req_field, String res_field, HTTPStatus::Error err_type, _Callable block) -> void + + def self?.basic_auth: (HTTPRequest req, HTTPResponse res, String realm) { (String user, String pass) -> bool } -> void + + def self?.proxy_basic_auth: (HTTPRequest req, HTTPResponse res, String realm) { (String user, String pass) -> bool } -> void + end +end diff --git a/sig/httpauth/authenticator.rbs b/sig/httpauth/authenticator.rbs new file mode 100644 index 0000000..394899d --- /dev/null +++ b/sig/httpauth/authenticator.rbs @@ -0,0 +1,55 @@ +module WEBrick + module HTTPAuth + module Authenticator + @reload_db: bool? + + @request_field: String + + @response_field: String + + @resp_info_field: String + + @auth_exception: singleton(HTTPStatus::ClientError) + + @auth_scheme: String + + RequestField: String + + ResponseField: String + + ResponseInfoField: String + + AuthException: singleton(HTTPStatus::ClientError) + + AuthScheme: String? + + attr_reader realm: String? + + attr_reader userdb: UserDB + + attr_reader logger: Log + + private + + def check_init: (Hash[Symbol, untyped] config) -> void + + def check_scheme: (HTTPRequest req) -> String? + + def log: (interned meth, String fmt, *untyped args) -> void + + def error: (String fmt, *untyped args) -> void + + def info: (String fmt, *untyped args) -> void + end + + module ProxyAuthenticator + RequestField: String + + ResponseField: String + + InfoField: String + + AuthException: singleton(HTTPStatus::ClientError) + end + end +end diff --git a/sig/httpauth/basicauth.rbs b/sig/httpauth/basicauth.rbs new file mode 100644 index 0000000..4eb41df --- /dev/null +++ b/sig/httpauth/basicauth.rbs @@ -0,0 +1,29 @@ +module WEBrick + module HTTPAuth + class BasicAuth + @config: Hash[Symbol, untyped] + + include Authenticator + + AuthScheme: String + + def self.make_passwd: (String? realm, String? user, String? pass) -> String + + attr_reader realm: String? + + attr_reader userdb: UserDB + + attr_reader logger: Log + + def initialize: (Hash[Symbol, untyped] config, ?Hash[Symbol, untyped] default) -> void + + def authenticate: (HTTPRequest req, HTTPResponse res) -> void + + def challenge: (HTTPRequest req, HTTPResponse res) -> bot + end + + class ProxyBasicAuth < BasicAuth + include ProxyAuthenticator + end + end +end diff --git a/sig/httpauth/digestauth.rbs b/sig/httpauth/digestauth.rbs new file mode 100644 index 0000000..8d12f91 --- /dev/null +++ b/sig/httpauth/digestauth.rbs @@ -0,0 +1,85 @@ +module WEBrick + module HTTPAuth + class DigestAuth + @config: Hash[Symbol, untyped] + + @domain: Array[String]? + + @use_opaque: bool + + @use_next_nonce: bool + + @check_nc: bool + + @use_auth_info_header: bool + + @nonce_expire_period: Integer + + @nonce_expire_delta: Integer + + @internet_explorer_hack: bool + + @h: singleton(Digest::Base) + + @instance_key: String + + @opaques: Hash[String, OpaqueInfo] + + @last_nonce_expire: Time + + @mutex: Thread::Mutex + + include Authenticator + + AuthScheme: String + + class OpaqueInfo < Struct[untyped] + attr_accessor time(): Time + attr_accessor nonce(): String? + attr_accessor nc(): String + end + + attr_reader algorithm: String? + + attr_reader qop: Array[String] + + def self.make_passwd: (String realm, String user, String pass) -> untyped + + def initialize: (Hash[Symbol, untyped] config, ?Hash[Symbol, untyped] default) -> void + + def authenticate: (HTTPRequest req, HTTPResponse res) -> void + + def challenge: (HTTPRequest req, HTTPResponse res, ?bool stale) -> bot + + private + + MustParams: Array[String] + + MustParamsAuth: Array[String] + + def _authenticate: (HTTPRequest req, HTTPResponse res) -> (:nonce_is_stale | bool) + + def split_param_value: (String string) -> Hash[String, String] + + def generate_next_nonce: (HTTPRequest req) -> String + + def check_nonce: (HTTPRequest req, Hash[String, String] auth_req) -> bool + + def generate_opaque: (HTTPRequest req) -> String + + def check_opaque: (OpaqueInfo opaque_struct, untyped req, Hash[String, String] auth_req) -> bool + + def check_uri: (HTTPRequest req, Hash[String, String] auth_req) -> bool + + def hexdigest: (*_ToS? args) -> String + end + + class ProxyDigestAuth < DigestAuth + include ProxyAuthenticator + + private + + def check_uri: (HTTPRequest req, Hash[String, String] auth_req) -> true + end + end +end diff --git a/sig/httpauth/htdigest.rbs b/sig/httpauth/htdigest.rbs new file mode 100644 index 0000000..19037e7 --- /dev/null +++ b/sig/httpauth/htdigest.rbs @@ -0,0 +1,31 @@ +module WEBrick + module HTTPAuth + class Htdigest + @path: String + + @mtime: Time + + @digest: Hash[String, Hash[String, String]] + + @mutex: Thread::Mutex + + @auth_type: String + + include UserDB + + def initialize: (String path) -> void + + def reload: () -> void + + def flush: (?String? output) -> void + + def get_passwd: (String realm, String user, bool reload_db) -> String? + + def set_passwd: (String realm, String user, String pass) -> String + + def delete_passwd: (String realm, String user) -> String? + + def each: () { (String user, String realm, String password_hash) -> void } -> void + end + end +end diff --git a/sig/httpauth/htgroup.rbs b/sig/httpauth/htgroup.rbs new file mode 100644 index 0000000..00ca7a6 --- /dev/null +++ b/sig/httpauth/htgroup.rbs @@ -0,0 +1,21 @@ +module WEBrick + module HTTPAuth + class Htgroup + @path: String + + @mtime: Time + + @group: Hash[String, Array[String]] + + def initialize: (String path) -> void + + def reload: () -> void + + def flush: (?String? output) -> void + + def members: (String group) -> Array[String] + + def add: (String group, Array[String] members) -> void + end + end +end diff --git a/sig/httpauth/htpasswd.rbs b/sig/httpauth/htpasswd.rbs new file mode 100644 index 0000000..6b4cc3e --- /dev/null +++ b/sig/httpauth/htpasswd.rbs @@ -0,0 +1,31 @@ +module WEBrick + module HTTPAuth + class Htpasswd + @path: String + + @mtime: Time + + @passwd: Hash[String, String] + + @auth_type: String + + @password_hash: (:crypt | :bcrypt) + + include UserDB + + def initialize: (String path, ?password_hash: (:crypt | :bcrypt)?) -> void + + def reload: () -> void + + def flush: (?String? output) -> void + + def get_passwd: (String realm, String user, bool reload_db) -> String? + + def set_passwd: (String realm, String user, String pass) -> void + + def delete_passwd: (String realm, String user) -> String + + def each: () { ([String, String]) -> void } -> void + end + end +end diff --git a/sig/httpauth/userdb.rbs b/sig/httpauth/userdb.rbs new file mode 100644 index 0000000..a6aefd9 --- /dev/null +++ b/sig/httpauth/userdb.rbs @@ -0,0 +1,13 @@ +module WEBrick + module HTTPAuth + module UserDB + attr_accessor auth_type: String + + def make_passwd: (String realm, String user, String pass) -> String + + def set_passwd: (String realm, String user, String pass) -> void + + def get_passwd: (String realm, String user, ?bool reload_db) -> String + end + end +end diff --git a/sig/httpproxy.rbs b/sig/httpproxy.rbs new file mode 100644 index 0000000..c2bc26b --- /dev/null +++ b/sig/httpproxy.rbs @@ -0,0 +1,61 @@ +module WEBrick + NullReader: untyped + + def self.read: (*untyped args) -> nil + + alias self.gets self.read + + FakeProxyURI: untyped + + def self.method_missing: (untyped meth, *untyped args) -> (nil | untyped) + + class HTTPProxyServer < HTTPServer + @via: untyped + + def initialize: (?::Hash[untyped, untyped] config, ?untyped default) -> void + + # :stopdoc: + def service: (HTTPRequest req, HTTPResponse res) -> untyped + + def proxy_auth: (HTTPRequest req, HTTPResponse res) -> untyped + + def proxy_uri: (HTTPRequest req, HTTPResponse res) -> untyped + + def proxy_service: (HTTPRequest req, HTTPResponse res) -> untyped + + def do_CONNECT: (HTTPRequest req, HTTPResponse res) -> untyped + + def do_GET: (HTTPRequest req, HTTPResponse res) -> untyped + + def do_HEAD: (HTTPRequest req, HTTPResponse res) -> untyped + + def do_POST: (HTTPRequest req, HTTPResponse res) -> untyped + + def do_OPTIONS: (HTTPRequest req, HTTPResponse res) -> untyped + + private + + # Some header fields should not be transferred. + HopByHop: ::Array["connection" | "keep-alive" | "proxy-authenticate" | "upgrade" | "proxy-authorization" | "te" | "trailers" | "transfer-encoding"] + + ShouldNotTransfer: ::Array["set-cookie" | "proxy-connection"] + + def split_field: (untyped f) -> (untyped | ::Array[untyped]) + + def choose_header: (untyped src, untyped dst) -> untyped + + # Net::HTTP is stupid about the multiple header fields. + # Here is workaround: + def set_cookie: (untyped src, untyped dst) -> (untyped | nil) + + def set_via: (untyped h) -> (untyped | nil) + + def setup_proxy_header: (HTTPRequest req, HTTPResponse res) -> untyped + + def setup_upstream_proxy_authentication: (HTTPRequest req, HTTPResponse res, untyped header) -> untyped + + def create_net_http: (untyped uri, untyped upstream) -> untyped + + def perform_proxy_request: (HTTPRequest req, HTTPResponse res, untyped req_class, ?untyped? body_stream) -> untyped + end +end diff --git a/sig/httprequest.rbs b/sig/httprequest.rbs new file mode 100644 index 0000000..15a1f4c --- /dev/null +++ b/sig/httprequest.rbs @@ -0,0 +1,169 @@ +module WEBrick + class HTTPRequest + @config: Hash[Symbol, untyped] + + @buffer_size: Integer + + @logger: Log + + @query: Hash[String, HTTPUtils::FormData]? + + @form_data: nil + + @body: String + + @remaining_size: Integer? + + @socket: TCPSocket? + + @forwarded_proto: String? + + @host: String? + + @port: Integer? + + @body_tmp: Array[String] + + @body_rd: Fiber + + @request_bytes: Integer + + @forwarded_server: String? + + @forwarded_host: String? + + @forwarded_port: Integer? + + @forwarded_for: String? + + BODY_CONTAINABLE_METHODS: Array[String] + + attr_reader request_line: String? + + attr_reader request_method: String? + + attr_reader unparsed_uri: String? + + attr_reader http_version: HTTPVersion? + + attr_reader request_uri: URI::Generic? + + attr_reader path: String? + + attr_accessor script_name: String? + + attr_accessor path_info: String? + + attr_accessor query_string: String? + + attr_reader raw_header: Array[String] + + attr_reader header: Hash[String, Array[String]]? + + attr_reader cookies: Array[Cookie] + + attr_reader accept: Array[String] + + attr_reader accept_charset: Array[String] + + attr_reader accept_encoding: Array[String] + + attr_reader accept_language: Array[String] + + attr_accessor user: String? + + attr_reader addr: ([String, Integer, String, String] | [])? + + attr_reader peeraddr: ([String, Integer, String, String] | [])? + + attr_reader attributes: Hash[untyped, untyped] + + attr_reader keep_alive: bool + + attr_reader request_time: Time? + + def initialize: (Hash[Symbol, untyped] config) -> void + + def parse: (?TCPSocket? socket) -> void + + def continue: () -> void + + type body_chunk_block = ^(String body_chunk) -> void + + def body: () ?{ (String body_chunk) -> void } -> String + + def body_reader: () -> self + + # for IO.copy_stream. + def readpartial: (Integer size, ?String buf) -> String + + def query: () -> Hash[String, HTTPUtils::FormData] + + def content_length: () -> Integer + + def content_type: () -> String? + + def []: (String header_name) -> String? + + def each: [T] () { (String, String) -> T } -> T? + + def host: () -> String? + + def port: () -> Integer? + + def server_name: () -> String? + + def remote_ip: () -> String? + + def ssl?: () -> bool + + def keep_alive?: () -> bool + + def to_s: () -> String + + def fixup: () -> void + + def meta_vars: () -> Hash[String, String] + + private + + MAX_URI_LENGTH: Integer + + # same as Mongrel, Thin and Puma + MAX_HEADER_LENGTH: Integer + + def read_request_line: (IO socket) -> void + + def read_header: (IO socket) -> void + + def parse_uri: (String str, ?String scheme) -> URI::Generic + + HOST_PATTERN: Regexp + + def parse_host_request_line: (String host) -> [String, String] + + def read_body: (IO socket, body_chunk_block block) -> String + | (nil socket, top block) -> nil + + def read_chunk_size: (IO socket) -> [Integer, String?] + + def read_chunked: (IO socket, body_chunk_block block) -> void + + def _read_data: (IO io, Symbol method, *untyped arg) -> String? + + def read_line: (IO io, ?Integer size) -> String? + + def read_data: (IO io, Integer size) -> String? + + def parse_query: () -> void + + PrivateNetworkRegexp: Regexp + + # It's said that all X-Forwarded-* headers will contain more than one + # (comma-separated) value if the original request already contained one of + # these headers. Since we could use these values as Host header, we choose + # the initial(first) value. (apr_table_mergen() adds new value after the + # existing value with ", " prefix) + def setup_forwarded_info: () -> void + end +end diff --git a/sig/httpresponse.rbs b/sig/httpresponse.rbs new file mode 100644 index 0000000..2baf093 --- /dev/null +++ b/sig/httpresponse.rbs @@ -0,0 +1,117 @@ +module WEBrick + class HTTPResponse + @buffer_size: Integer + + @logger: Log + + @chunked: bool + + @bodytempfile: File | Tempfile | nil + + class InvalidHeader < StandardError + end + + attr_reader http_version: HTTPVersion + + attr_reader status: Integer + + attr_reader header: Hash[String, String] + + attr_reader cookies: Array[Cookie] + + attr_accessor reason_phrase: String + + interface _CallableBody + def call: (_Writer) -> void + end + + attr_accessor body: String | _ReaderPartial | _CallableBody + + attr_accessor request_method: String? + + attr_accessor request_uri: URI::Generic? + + attr_accessor request_http_version: HTTPVersion? + + attr_accessor filename: String? + + attr_accessor keep_alive: bool + + attr_reader config: Hash[Symbol, untyped] + + attr_reader sent_size: Integer + + attr_accessor upgrade: String? + + def initialize: (Hash[Symbol, untyped] config) -> void + + def status_line: () -> String + + def status=: (Integer status) -> Integer + + def []: (String field) -> String? + + def []=: (String field, _ToS value) -> _ToS + + def content_length: () -> Integer? + + def content_length=: (Integer len) -> Integer + + def content_type: () -> String? + + def content_type=: (String type) -> String + + def each: () { (String, String) -> void } -> void + + def chunked?: () -> bool + + def chunked=: (boolish val) -> boolish + + def keep_alive?: () -> bool + + def upgrade!: (String protocol) -> void + + def send_response: (_Writer socket) -> void + + def setup_header: () -> void + + def make_body_tempfile: () -> void + + def remove_body_tempfile: () -> void + + def send_header: (_Writer socket) -> void + + def send_body: (_Writer socket) -> void + + def set_redirect: (singleton(WEBrick::HTTPStatus::Redirect) status, URI::Generic | String url) -> bot + + def set_error: (singleton(Exception) ex, ?bool backtrace) -> void + + private + + def check_header: (_ToS header_value) -> String + + def error_body: (bool backtrace, singleton(Exception) ex, String? host, Integer? port) -> void + + def send_body_io: (_Writer socket) -> void + + def send_body_string: (_Writer socket) -> void + + def send_body_proc: (_Writer socket) -> void + + class ChunkedWrapper + @socket: _Writer + + @resp: HTTPResponse + + def initialize: (_Writer socket, HTTPResponse resp) -> void + + def write: (_ToS buf) -> Integer + + def <<: (*_ToS buf) -> self + end + + # preserved for compatibility with some 3rd-party handlers + def _write_data: [T] (T socket, _ToS data) -> T + end +end diff --git a/sig/https.rbs b/sig/https.rbs new file mode 100644 index 0000000..43dcd1e --- /dev/null +++ b/sig/https.rbs @@ -0,0 +1,49 @@ +module WEBrick + class HTTPRequest + @client_cert_chain: Array[OpenSSL::X509::Certificate] + + attr_reader cipher: [String, String, Integer, Integer]? + + attr_reader server_cert: OpenSSL::X509::Certificate + + attr_reader client_cert: OpenSSL::X509::Certificate? + + alias orig_parse parse + + def parse: (?(TCPSocket | OpenSSL::SSL::SSLSocket)? socket) -> void + | ... + + alias orig_parse_uri parse_uri + + private + + def parse_uri: (String str, ?::String scheme) -> URI::Generic + | ... + + public + + alias orig_meta_vars meta_vars + + def meta_vars: () -> Hash[String, String] + | ... + end + + class SNIRequest + attr_reader host: String? + + attr_reader addr: [String, Integer, String, String] + + attr_reader port: Integer + + def initialize: (OpenSSL::SSL::SSLSocket sslsocket, ?String? hostname) -> void + end + + class HTTPServer < ::WEBrick::GenericServer + def ssl_servername_callback: (OpenSSL::SSL::SSLSocket sslsocket, ?String? hostname) -> OpenSSL::SSL::SSLContext? + + alias orig_virtual_host virtual_host + + def virtual_host: (instance server) -> void + | ... + end +end diff --git a/sig/httpserver.rbs b/sig/httpserver.rbs new file mode 100644 index 0000000..6c966e9 --- /dev/null +++ b/sig/httpserver.rbs @@ -0,0 +1,71 @@ +module WEBrick + class HTTPServerError < ServerError + end + + class HTTPServer < ::WEBrick::GenericServer + @http_version: HTTPVersion + + @mount_tab: MountTable + + @virtual_hosts: Array[untyped] + + def initialize: (?Hash[Symbol, untyped] config, ?Hash[Symbol, untyped] default) -> void + + def run: (TCPSocket sock) -> void + + def service: (HTTPRequest req, HTTPResponse res) -> void + + def do_OPTIONS: (HTTPRequest req, HTTPResponse res) -> void + + def mount: (String dir, singleton(HTTPServlet::AbstractServlet) servlet, *untyped options) -> void + + def mount_proc: (String dir, ?HTTPServlet::ProcHandler::_Callable proc) -> void + | (String dir, ?nil proc) { (HTTPRequest, HTTPResponse) -> void } -> void + + def unmount: (String dir) -> MountTable::value_type + + alias umount unmount + + def search_servlet: (String path) -> [singleton(HTTPServlet::AbstractServlet), Array[untyped], String, String]? + + def virtual_host: (instance server) -> void + + def lookup_server: (HTTPRequest req) -> instance? + + def access_log: (Hash[Symbol, untyped] config, HTTPRequest req, HTTPResponse res) -> void + + # + # Creates the HTTPRequest used when handling the HTTP + # request. Can be overridden by subclasses. + def create_request: (Hash[Symbol, untyped] with_webrick_config) -> HTTPRequest + + # + # Creates the HTTPResponse used when handling the HTTP + # request. Can be overridden by subclasses. + def create_response: (Hash[Symbol, untyped] with_webrick_config) -> HTTPResponse + + class MountTable + type value_type = [singleton(HTTPServlet::AbstractServlet), Array[untyped]] + + @tab: Hash[String, value_type] + + @scanner: Regexp + + def initialize: () -> void + + def []: (String dir) -> value_type + + def []=: (String dir, value_type val) -> value_type + + def delete: (String dir) -> value_type + + def scan: (String path) -> [String, String] + + private + + def compile: () -> void + + def normalize: (String dir) -> String + end + end +end diff --git a/sig/httpservlet.rbs b/sig/httpservlet.rbs new file mode 100644 index 0000000..36122e9 --- /dev/null +++ b/sig/httpservlet.rbs @@ -0,0 +1,4 @@ +module WEBrick + module HTTPServlet + end +end diff --git a/sig/httpservlet/abstract.rbs b/sig/httpservlet/abstract.rbs new file mode 100644 index 0000000..b067deb --- /dev/null +++ b/sig/httpservlet/abstract.rbs @@ -0,0 +1,36 @@ +module WEBrick + module HTTPServlet + class HTTPServletError < StandardError + end + + class AbstractServlet + @server: HTTPServer + + interface _Config + def []: (Symbol) -> untyped + end + + @config: _Config + + @logger: Log + + @options: untyped # Array[untyped] causes RBS::InstanceVariableTypeError + + def self.get_instance: (HTTPServer server, *untyped options) -> instance + + def initialize: (HTTPServer server, *untyped options) -> void + + def service: (HTTPRequest req, HTTPResponse res) -> void + + def do_GET: (HTTPRequest req, HTTPResponse res) -> bot + + def do_HEAD: (HTTPRequest req, HTTPResponse res) -> bot + + def do_OPTIONS: (HTTPRequest req, HTTPResponse res) -> void + + private + + def redirect_to_directory_uri: (HTTPRequest req, HTTPResponse res) -> void + end + end +end diff --git a/sig/httpservlet/cgi_runner.rbs b/sig/httpservlet/cgi_runner.rbs new file mode 100644 index 0000000..3112976 --- /dev/null +++ b/sig/httpservlet/cgi_runner.rbs @@ -0,0 +1,3 @@ +class Object + def sysread: (IO io, Integer size) -> String +end diff --git a/sig/httpservlet/cgihandler.rbs b/sig/httpservlet/cgihandler.rbs new file mode 100644 index 0000000..f56427c --- /dev/null +++ b/sig/httpservlet/cgihandler.rbs @@ -0,0 +1,23 @@ +module WEBrick + module HTTPServlet + class CGIHandler < AbstractServlet + @script_filename: String + + @tempdir: String? + + @cgicmd: Array[String] | String + + Ruby: String + + CGIRunner: String + + CGIRunnerArray: [String, String] + + def initialize: (HTTPServer server, String name) -> void + + def do_GET: (HTTPRequest req, HTTPResponse res) -> void + + alias do_POST do_GET + end + end +end diff --git a/sig/httpservlet/erbhandler.rbs b/sig/httpservlet/erbhandler.rbs new file mode 100644 index 0000000..9ffa569 --- /dev/null +++ b/sig/httpservlet/erbhandler.rbs @@ -0,0 +1,17 @@ +module WEBrick + module HTTPServlet + class ERBHandler < AbstractServlet + @script_filename: String + + def initialize: (HTTPServer server, String name) -> void + + def do_GET: (HTTPRequest req, HTTPResponse res) -> void + + alias do_POST do_GET + + private + + def evaluate: (ERB erb, HTTPRequest servlet_request, HTTPResponse servlet_response) -> String + end + end +end diff --git a/sig/httpservlet/filehandler.rbs b/sig/httpservlet/filehandler.rbs new file mode 100644 index 0000000..792fd2a --- /dev/null +++ b/sig/httpservlet/filehandler.rbs @@ -0,0 +1,76 @@ +module WEBrick + module HTTPServlet + class DefaultFileHandler < AbstractServlet + @local_path: String + + def initialize: (HTTPServer server, String local_path) -> void + + def do_GET: (HTTPRequest req, HTTPResponse res) -> void + + def not_modified?: (HTTPRequest req, HTTPResponse res, Time mtime, String etag) -> bool + + # returns a lambda for webrick/httpresponse.rb send_body_proc + def multipart_body: (File body, Array[[Numeric, Numeric]] parts, String boundary, String mtype, Integer filesize) -> HTTPResponse::_CallableBody + + def make_partial_content: (HTTPRequest req, HTTPResponse res, String filename, Integer filesize) -> void + + def prepare_range: (Range[Integer] range, Integer filesize) -> [Numeric, Numeric] + end + + class FileHandler < AbstractServlet + @config: AbstractServlet::_Config + + @logger: Log + + @root: String + + @options: Hash[Symbol, untyped] + + HandlerTable: Hash[String, singleton(AbstractServlet)] + + def self.add_handler: (String suffix, singleton(AbstractServlet) handler) -> singleton(AbstractServlet) + + def self.remove_handler: (String suffix) -> singleton(AbstractServlet) + + def initialize: (HTTPServer server, String root, ?Hash[Symbol, untyped] options, ?Hash[Symbol, untyped] default) -> void + + def set_filesystem_encoding: (String str) -> String + + def service: (HTTPRequest req, HTTPResponse res) -> void + + def do_GET: (HTTPRequest req, HTTPResponse res) -> void + + def do_POST: (HTTPRequest req, HTTPResponse res) -> void + + def do_OPTIONS: (HTTPRequest req, HTTPResponse res) -> void + + private + + def trailing_pathsep?: (String path) -> bool + + def prevent_directory_traversal: (HTTPRequest req, HTTPResponse res) -> void + + def exec_handler: (HTTPRequest req, HTTPResponse res) -> bool + + def get_handler: (HTTPRequest req, HTTPResponse res) -> singleton(AbstractServlet) + + def set_filename: (HTTPRequest req, HTTPResponse res) -> bool + + def check_filename: (HTTPRequest req, HTTPResponse res, String name) -> void + + def shift_path_info: (HTTPRequest req, HTTPResponse res, String path_info, ?String? base) -> void + + def search_index_file: (HTTPRequest req, HTTPResponse res) -> String? + + def search_file: (HTTPRequest req, HTTPResponse res, String basename) -> String? + + def call_callback: (Symbol callback_name, HTTPRequest req, HTTPResponse res) -> void + + def windows_ambiguous_name?: (String name) -> bool + + def nondisclosure_name?: (String name) -> bool + + def set_dir_list: (HTTPRequest req, HTTPResponse res) -> void + end + end +end diff --git a/sig/httpservlet/prochandler.rbs b/sig/httpservlet/prochandler.rbs new file mode 100644 index 0000000..66f6929 --- /dev/null +++ b/sig/httpservlet/prochandler.rbs @@ -0,0 +1,21 @@ +module WEBrick + module HTTPServlet + class ProcHandler < AbstractServlet + interface _Callable + def call: (WEBrick::HTTPRequest, WEBrick::HTTPResponse) -> void + end + + @proc: _Callable + + def get_instance: (HTTPServer server, *untyped options) -> self + + def initialize: (_Callable proc) -> void + + def do_GET: (HTTPRequest request, HTTPResponse response) -> void + + alias do_POST do_GET + + alias do_PUT do_GET + end + end +end diff --git a/sig/httpstatus.rbs b/sig/httpstatus.rbs new file mode 100644 index 0000000..0b83ada --- /dev/null +++ b/sig/httpstatus.rbs @@ -0,0 +1,255 @@ +module WEBrick + # + # This module is used to manager HTTP status codes. + # + # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html for more + # information. + module HTTPStatus + # + # Root of the HTTP status class hierarchy + class Status < StandardError + attr_reader self.code: Integer + + attr_reader self.reason_phrase: String + + # Returns the HTTP status code + def code: () -> Integer + + # Returns the HTTP status description + def reason_phrase: () -> String + + alias to_i code + end + + # Root of the HTTP info statuses + class Info < Status + end + + # Root of the HTTP success statuses + class Success < Status + end + + # Root of the HTTP redirect statuses + class Redirect < Status + end + + # Root of the HTTP error statuses + class Error < Status + end + + # Root of the HTTP client error statuses + class ClientError < Error + end + + # Root of the HTTP server error statuses + class ServerError < Error + end + + class EOFError < StandardError + end + + # HTTP status codes and descriptions + StatusMessage: Hash[Integer, String] + + # Maps a status code to the corresponding Status class + CodeToError: Hash[Integer, singleton(Status)] + + # WEBrick::HTTPStatus::constants.grep(/\ARC_/).map{"#{_1}: #{WEBrick::HTTPStatus.const_get(_1)}"} + + RC_CONTINUE: 100 + RC_SWITCHING_PROTOCOLS: 101 + RC_OK: 200 + RC_CREATED: 201 + RC_ACCEPTED: 202 + RC_NON_AUTHORITATIVE_INFORMATION: 203 + RC_NO_CONTENT: 204 + RC_RESET_CONTENT: 205 + RC_PARTIAL_CONTENT: 206 + RC_MULTI_STATUS: 207 + RC_MULTIPLE_CHOICES: 300 + RC_MOVED_PERMANENTLY: 301 + RC_FOUND: 302 + RC_SEE_OTHER: 303 + RC_NOT_MODIFIED: 304 + RC_USE_PROXY: 305 + RC_TEMPORARY_REDIRECT: 307 + RC_BAD_REQUEST: 400 + RC_UNAUTHORIZED: 401 + RC_PAYMENT_REQUIRED: 402 + RC_FORBIDDEN: 403 + RC_NOT_FOUND: 404 + RC_METHOD_NOT_ALLOWED: 405 + RC_NOT_ACCEPTABLE: 406 + RC_PROXY_AUTHENTICATION_REQUIRED: 407 + RC_REQUEST_TIMEOUT: 408 + RC_CONFLICT: 409 + RC_GONE: 410 + RC_PRECONDITION_FAILED: 412 + RC_LENGTH_REQUIRED: 411 + RC_REQUEST_ENTITY_TOO_LARGE: 413 + RC_REQUEST_URI_TOO_LARGE: 414 + RC_UNSUPPORTED_MEDIA_TYPE: 415 + RC_EXPECTATION_FAILED: 417 + RC_UNPROCESSABLE_ENTITY: 422 + RC_LOCKED: 423 + RC_FAILED_DEPENDENCY: 424 + RC_REQUEST_RANGE_NOT_SATISFIABLE: 416 + RC_UPGRADE_REQUIRED: 426 + RC_PRECONDITION_REQUIRED: 428 + RC_TOO_MANY_REQUESTS: 429 + RC_REQUEST_HEADER_FIELDS_TOO_LARGE: 431 + RC_UNAVAILABLE_FOR_LEGAL_REASONS: 451 + RC_INTERNAL_SERVER_ERROR: 500 + RC_NOT_IMPLEMENTED: 501 + RC_BAD_GATEWAY: 502 + RC_SERVICE_UNAVAILABLE: 503 + RC_GATEWAY_TIMEOUT: 504 + RC_HTTP_VERSION_NOT_SUPPORTED: 505 + RC_INSUFFICIENT_STORAGE: 507 + RC_NETWORK_AUTHENTICATION_REQUIRED: 511 + + # WEBrick::HTTPStatus::CodeToError.each_value.map{"class #{_1.name.split(/::/).last} < #{_1.superclass.name.split(/::/).last}\nend"} + + class Continue < Info + end + class SwitchingProtocols < Info + end + class OK < Success + end + class Created < Success + end + class Accepted < Success + end + class NonAuthoritativeInformation < Success + end + class NoContent < Success + end + class ResetContent < Success + end + class PartialContent < Success + end + class MultiStatus < Success + end + class MultipleChoices < Redirect + end + class MovedPermanently < Redirect + end + class Found < Redirect + end + class SeeOther < Redirect + end + class NotModified < Redirect + end + class UseProxy < Redirect + end + class TemporaryRedirect < Redirect + end + class BadRequest < ClientError + end + class Unauthorized < ClientError + end + class PaymentRequired < ClientError + end + class Forbidden < ClientError + end + class NotFound < ClientError + end + class MethodNotAllowed < ClientError + end + class NotAcceptable < ClientError + end + class ProxyAuthenticationRequired < ClientError + end + class RequestTimeout < ClientError + end + class Conflict < ClientError + end + class Gone < ClientError + end + class LengthRequired < ClientError + end + class PreconditionFailed < ClientError + end + class RequestEntityTooLarge < ClientError + end + class RequestURITooLarge < ClientError + end + class UnsupportedMediaType < ClientError + end + class RequestRangeNotSatisfiable < ClientError + end + class ExpectationFailed < ClientError + end + class UnprocessableEntity < ClientError + end + class Locked < ClientError + end + class FailedDependency < ClientError + end + class UpgradeRequired < ClientError + end + class PreconditionRequired < ClientError + end + class TooManyRequests < ClientError + end + class RequestHeaderFieldsTooLarge < ClientError + end + class UnavailableForLegalReasons < ClientError + end + class InternalServerError < ServerError + end + class NotImplemented < ServerError + end + class BadGateway < ServerError + end + class ServiceUnavailable < ServerError + end + class GatewayTimeout < ServerError + end + class HTTPVersionNotSupported < ServerError + end + class InsufficientStorage < ServerError + end + class NetworkAuthenticationRequired < ServerError + end + + # + # Returns the description corresponding to the HTTP status +code+ + # + # WEBrick::HTTPStatus.reason_phrase 404 + # => "Not Found" + def self?.reason_phrase: (Integer code) -> String + + # + # Is +code+ an informational status? + def self?.info?: (Integer code) -> bool + + # + # Is +code+ a successful status? + def self?.success?: (Integer code) -> bool + + # + # Is +code+ a redirection status? + def self?.redirect?: (Integer code) -> bool + + # + # Is +code+ an error status? + def self?.error?: (Integer code) -> bool + + # + # Is +code+ a client error status? + def self?.client_error?: (Integer code) -> bool + + # + # Is +code+ a server error status? + def self?.server_error?: (Integer code) -> bool + + # + # Returns the status class corresponding to +code+ + # + # WEBrick::HTTPStatus[302] + # => WEBrick::HTTPStatus::NotFound + # + def self.[]: (Integer code) -> singleton(Status) + end +end diff --git a/sig/httputils.rbs b/sig/httputils.rbs new file mode 100644 index 0000000..a554cdf --- /dev/null +++ b/sig/httputils.rbs @@ -0,0 +1,116 @@ +module WEBrick + CR: String + + LF: String + + CRLF: String + + module HTTPUtils + def self?.normalize_path: (String path) -> String + + type mime_types = Hash[String, String] + + DefaultMimeTypes: mime_types + + def self?.load_mime_types: (string | _ToPath file) -> mime_types + + def self?.mime_type: (String filename, mime_types mime_tab) -> String + + class SplitHeader < Array[String] + def join: (?String separator) -> String + end + + class CookieHeader < Array[String] + def join: (?String separator) -> String + end + + HEADER_CLASSES: Hash[String, untyped] + + def self?.parse_header: (String raw) -> Hash[String, Array[String]] + + def self?.split_header_value: (String str) -> Array[String] + + def self?.parse_range_header: (String? ranges_specifier) -> Array[Range[Integer]]? + + def self?.parse_qvalues: (String? value) -> Array[String] + + def self?.dequote: (String str) -> String + + def self?.quote: (String str) -> String + + class FormData < String + @raw_header: Array[String] + + @header: Hash[String, Array[String]] + + EmptyRawHeader: Array[String] + + EmptyHeader: Hash[String, Array[String]] + + attr_accessor name: String? + + attr_accessor filename: String? + + attr_accessor next_data: instance? + + def initialize: (*String args) -> void + + def []: (*String key) -> String + # following is as same as String#[] + | (int start, ?int length) -> String? + | (range[int?] range) -> String? + | (Regexp regexp, ?MatchData::capture backref) -> String? + | (String substring) -> String? + + def <<: (String str) -> self + + def append_data: (instance data) -> self + + def each_data: () { (instance) -> void } -> void + + def list: () -> Array[String] + + alias to_ary list + + def to_s: () -> String + end + + def self?.parse_query: (String? str) -> Hash[String, FormData] + + interface _EachLine + def each_line: () { (String) -> void } -> void + end + + def self?.parse_form_data: (_EachLine? io, interned boundary) -> Hash[String, FormData] + + def self?._make_regex: (String str) -> Regexp + + def self?._make_regex!: (String str) -> Regexp + + def self?._escape: (String str, Regexp regex) -> String + + def self?._unescape: (String str, Regexp regex) -> String + + UNESCAPED: Regexp + + UNESCAPED_FORM: Regexp + + NONASCII: Regexp + + ESCAPED: Regexp + + UNESCAPED_PCHAR: Regexp + + def self?.escape: (String str) -> String + + def self?.unescape: (String str) -> String + + def self?.escape_form: (String str) -> String + + def self?.unescape_form: (String str) -> String + + def self?.escape_path: (String str) -> String + + def self?.escape8bit: (String str) -> String + end +end diff --git a/sig/httpversion.rbs b/sig/httpversion.rbs new file mode 100644 index 0000000..92c8940 --- /dev/null +++ b/sig/httpversion.rbs @@ -0,0 +1,17 @@ +module WEBrick + class HTTPVersion + include Comparable + + attr_accessor major: Integer + + attr_accessor minor: Integer + + def self.convert: (HTTPVersion | String version) -> instance + + def initialize: (HTTPVersion | String version) -> void + + def <=>: (HTTPVersion | String other) -> Integer? + + def to_s: () -> String + end +end diff --git a/sig/log.rbs b/sig/log.rbs new file mode 100644 index 0000000..e985f4a --- /dev/null +++ b/sig/log.rbs @@ -0,0 +1,93 @@ +module WEBrick + class BasicLog + @log: IO? + + @opened: TrueClass? + + FATAL: 1 + + ERROR: 2 + + WARN: 3 + + INFO: 4 + + DEBUG: 5 + + # log-level, messages above this level will be logged + attr_accessor level: Integer + + type log_file = (IO | String)? + + def initialize: (?log_file log_file, ?Integer? level) -> void + + # + # Closes the logger (also closes the log device associated to the logger) + def close: () -> void + + def log: (Integer level, String data) -> IO? + + # + # Synonym for log(INFO, obj.to_s) + def <<: (_ToS obj) -> IO? + + type message = Exception | _ToStr | Object + + # Shortcut for logging a FATAL message + def fatal: (message msg) -> IO? + + # Shortcut for logging an ERROR message + def error: (message msg) -> IO? + + # Shortcut for logging a WARN message + def warn: (message msg) -> IO? + + # Shortcut for logging an INFO message + def info: (message msg) -> IO? + + # Shortcut for logging a DEBUG message + def debug: (message msg) -> IO? + + # Will the logger output FATAL messages? + def fatal?: () -> bool + + # Will the logger output ERROR messages? + def error?: () -> bool + + # Will the logger output WARN messages? + def warn?: () -> bool + + # Will the logger output INFO messages? + def info?: () -> bool + + # Will the logger output DEBUG messages? + def debug?: () -> bool + + private + + # + # Formats +arg+ for the logger + # + # * If +arg+ is an Exception, it will format the error message and + # the back trace. + # * If +arg+ responds to #to_str, it will return it. + # * Otherwise it will return +arg+.inspect. + def format: (message arg) -> String + end + + class Log < BasicLog + # Format of the timestamp which is applied to each logged line. The + # default is "[%Y-%m-%d %H:%M:%S]" + attr_accessor time_format: String + + # + # Same as BasicLog#initialize + # + # You can set the timestamp format through #time_format + def initialize: (?BasicLog::log_file log_file, ?Integer? level) -> void + + # + # Same as BasicLog#log + def log: (Integer level, String data) -> IO? + end +end diff --git a/sig/manifest.yaml b/sig/manifest.yaml new file mode 100644 index 0000000..7ce3ffe --- /dev/null +++ b/sig/manifest.yaml @@ -0,0 +1,8 @@ +dependencies: + - name: digest + - name: erb + - name: openssl + - name: singleton + - name: socket + - name: tempfile + - name: uri diff --git a/sig/server.rbs b/sig/server.rbs new file mode 100644 index 0000000..2565680 --- /dev/null +++ b/sig/server.rbs @@ -0,0 +1,57 @@ +module WEBrick + class ServerError < StandardError + end + + class SimpleServer + def self.start: [T] () { () -> T } -> T + end + + class Daemon + def self.start: () -> void + | [T] () { () -> T } -> T + end + + class GenericServer + @shutdown_pipe: [IO, IO]? + + attr_reader status: :Stop | :Running | :Shutdown + + attr_reader config: Hash[Symbol, untyped] + + attr_reader logger: BasicLog + + attr_reader tokens: Thread::SizedQueue + + attr_reader listeners: Array[TCPServer| OpenSSL::SSL::SSLServer] + + def initialize: (?Hash[Symbol, untyped] config, ?Hash[Symbol, untyped] default) -> void + + def []: (Symbol key) -> untyped + + def listen: (String address, Integer port) -> void + + def start: () { (TCPSocket) -> void } -> void + + def stop: () -> void + + def shutdown: () -> void + + def run: (TCPSocket sock) -> void + + private + + def accept_client: (TCPServer svr) -> TCPSocket? + + def start_thread: (TCPSocket sock) { (TCPSocket) -> void } -> Thread + + def call_callback: (Symbol callback_name, *untyped args) -> untyped + + def setup_shutdown_pipe: () -> [IO, IO] + + def cleanup_shutdown_pipe: ([IO, IO]? shutdown_pipe) -> void + + def alarm_shutdown_pipe: [T] () { (IO) -> T } -> T? + + def cleanup_listener: () -> void + end +end diff --git a/sig/ssl.rbs b/sig/ssl.rbs new file mode 100644 index 0000000..83f08f7 --- /dev/null +++ b/sig/ssl.rbs @@ -0,0 +1,19 @@ +module WEBrick + module Config + SSL: Hash[Symbol, untyped] + end + + module Utils + def self?.create_self_signed_cert: (untyped bits, untyped cn, untyped comment) -> ::Array[untyped] + end + + class GenericServer + @ssl_context: OpenSSL::SSL::SSLContext? + + def ssl_context: () -> OpenSSL::SSL::SSLContext? + + def setup_ssl_context: (Hash[Symbol, untyped] config) -> OpenSSL::SSL::SSLContext + + def ssl_servername_callback: (untyped sslsocket, ?untyped? hostname) -> untyped + end +end diff --git a/sig/utils.rbs b/sig/utils.rbs new file mode 100644 index 0000000..c7d7a68 --- /dev/null +++ b/sig/utils.rbs @@ -0,0 +1,122 @@ +module WEBrick + module Utils + # + # Sets IO operations on +io+ to be non-blocking + def self?.set_non_blocking: (IO io) -> void + + # + # Sets the close on exec flag for +io+ + def self?.set_close_on_exec: (IO io) -> void + + # + # Changes the process's uid and gid to the ones of +user+ + def self?.su: (String user) -> void + + # + # The server hostname + def self?.getservername: () -> String + + # + # Creates TCP server sockets bound to +address+:+port+ and returns them. + # + # It will create IPV4 and IPV6 sockets on all interfaces. + def self?.create_listeners: (String host, Integer port) -> Array[TCPServer] + + # + # Characters used to generate random strings + RAND_CHARS: String + + # + # Generates a random string of length +len+ + def self?.random_string: (Integer len) -> String + + # + # Class used to manage timeout handlers across multiple threads. + # + # Timeout handlers should be managed by using the class methods which are + # synchronized. + # + # id = TimeoutHandler.register(10, Timeout::Error) + # begin + # sleep 20 + # puts 'foo' + # ensure + # TimeoutHandler.cancel(id) + # end + # + # will raise Timeout::Error + # + # id = TimeoutHandler.register(10, Timeout::Error) + # begin + # sleep 5 + # puts 'foo' + # ensure + # TimeoutHandler.cancel(id) + # end + # + # will print 'foo' + # + class TimeoutHandler + @queue: Thread::Queue + + @watcher: Thread? + + include Singleton + + # + # Mutex used to synchronize access across threads + TimeoutMutex: Thread::Mutex + + # + # Registers a new timeout handler + # + # +time+:: Timeout in seconds + # +exception+:: Exception to raise when timeout elapsed + def self.register: (Numeric seconds, singleton(Exception) exception) -> Integer + + # + # Cancels the timeout handler +id+ + def self.cancel: (Integer id) -> bool + + def self.terminate: () -> Thread? + + # + # Creates a new TimeoutHandler. You should use ::register and ::cancel + # instead of creating the timeout handler directly. + def initialize: () -> void + + private + + def watch: () -> bot + + def watcher: () -> Thread + + public + + # + # Interrupts the timeout handler +id+ and raises +exception+ + def interrupt: (Thread thread, Integer id, singleton(Exception) exception) -> nil + + # + # Registers a new timeout handler + # + # +time+:: Timeout in seconds + # +exception+:: Exception to raise when timeout elapsed + def register: (Thread thread, Numeric time, singleton(Exception) exception) -> Integer + + # + # Cancels the timeout handler +id+ + def cancel: (Thread thread, Integer id) -> bool + + # + def terminate: () -> Thread? + end + + # + # Executes the passed block and raises +exception+ if execution takes more + # than +seconds+. + # + # If +seconds+ is zero or nil, simply executes the block + def self?.timeout: [T] (Numeric? seconds, ?singleton(Exception) exception) { (?Numeric) -> T } -> T + end +end diff --git a/sig/version.rbs b/sig/version.rbs new file mode 100644 index 0000000..64d0ef0 --- /dev/null +++ b/sig/version.rbs @@ -0,0 +1,3 @@ +module WEBrick + VERSION: String +end diff --git a/webrick.gemspec b/webrick.gemspec index 31423e9..1ebc674 100644 --- a/webrick.gemspec +++ b/webrick.gemspec @@ -53,6 +53,41 @@ Gem::Specification.new do |s| "lib/webrick/ssl.rb", "lib/webrick/utils.rb", "lib/webrick/version.rb", + "sig/accesslog.rbs", + "sig/cgi.rbs", + "sig/compat.rbs", + "sig/config.rbs", + "sig/cookie.rbs", + "sig/htmlutils.rbs", + "sig/httpauth.rbs", + "sig/httpauth/authenticator.rbs", + "sig/httpauth/basicauth.rbs", + "sig/httpauth/digestauth.rbs", + "sig/httpauth/htdigest.rbs", + "sig/httpauth/htgroup.rbs", + "sig/httpauth/htpasswd.rbs", + "sig/httpauth/userdb.rbs", + "sig/httpproxy.rbs", + "sig/httprequest.rbs", + "sig/httpresponse.rbs", + "sig/https.rbs", + "sig/httpserver.rbs", + "sig/httpservlet.rbs", + "sig/httpservlet/abstract.rbs", + "sig/httpservlet/cgi_runner.rbs", + "sig/httpservlet/cgihandler.rbs", + "sig/httpservlet/erbhandler.rbs", + "sig/httpservlet/filehandler.rbs", + "sig/httpservlet/prochandler.rbs", + "sig/httpstatus.rbs", + "sig/httputils.rbs", + "sig/httpversion.rbs", + "sig/log.rbs", + "sig/manifest.yaml", + "sig/server.rbs", + "sig/ssl.rbs", + "sig/utils.rbs", + "sig/version.rbs", "webrick.gemspec", ] s.required_ruby_version = ">= 2.4.0"