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/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"