From ee80e9eb348e5e58d767e3ca5d0079541fdcbf4f Mon Sep 17 00:00:00 2001 From: Adam Cammack Date: Wed, 19 Feb 2020 12:56:36 -0600 Subject: [PATCH] Vendor the stdlib `expect` for Windows sockets `expect.rb` is part of the `pty` extension to the Ruby stdlib since it uses `select` and is meant for use with things like pipes or terminals and Windows does not allow for selecting on those sorts of handles. Since we only use it for sockets, we can pull it in straight to allow for use on Windows without building the whole `pty` extension. --- LICENSE | 4 +++ lib/expect.rb | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 lib/expect.rb diff --git a/LICENSE b/LICENSE index 4bd4d5418b..da91e0039b 100644 --- a/LICENSE +++ b/LICENSE @@ -71,6 +71,10 @@ Files: lib/anemone.rb lib/anemone/* Copyright: 2009 Vertive, Inc. License: MIT +Files: lib/expect.rb +Copyright: 2017 Yukihiro Matsumoto +License: Ruby + Files: lib/msf/core/modules/external/python/async_timeout/* Copyright: 2016-2017 Andrew Svetlov License: Apache 2.0 diff --git a/lib/expect.rb b/lib/expect.rb new file mode 100644 index 0000000000..d4b9396314 --- /dev/null +++ b/lib/expect.rb @@ -0,0 +1,74 @@ +# Sourced from Ruby's ext/pty/lib/expect.rb to allow for access from Windows, +# which does not seem to have an issue using this particular method with +# sockets (pipes and other handles won't work, so don't use it for that). +# frozen_string_literal: true +$expect_verbose = false + +# Expect library adds the IO instance method #expect, which does similar act to +# tcl's expect extension. +# +# In order to use this method, you must require expect: +# +# require 'expect' +# +# Please see #expect for usage. +class IO + # call-seq: + # IO#expect(pattern,timeout=9999999) -> Array + # IO#expect(pattern,timeout=9999999) { |result| ... } -> nil + # + # Reads from the IO until the given +pattern+ matches or the +timeout+ is over. + # + # It returns an array with the read buffer, followed by the matches. + # If a block is given, the result is yielded to the block and returns nil. + # + # When called without a block, it waits until the input that matches the + # given +pattern+ is obtained from the IO or the time specified as the + # timeout passes. An array is returned when the pattern is obtained from the + # IO. The first element of the array is the entire string obtained from the + # IO until the pattern matches, followed by elements indicating which the + # pattern which matched to the anchor in the regular expression. + # + # The optional timeout parameter defines, in seconds, the total time to wait + # for the pattern. If the timeout expires or eof is found, nil is returned + # or yielded. However, the buffer in a timeout session is kept for the next + # expect call. The default timeout is 9999999 seconds. + def expect(pat,timeout=9999999) + buf = ''.dup + case pat + when String + e_pat = Regexp.new(Regexp.quote(pat)) + when Regexp + e_pat = pat + else + raise TypeError, "unsupported pattern class: #{pat.class}" + end + @unusedBuf ||= '' + while true + if not @unusedBuf.empty? + c = @unusedBuf.slice!(0) + elsif !IO.select([self],nil,nil,timeout) or eof? then + result = nil + @unusedBuf = buf + break + else + c = getc + end + buf << c + if $expect_verbose + STDOUT.print c + STDOUT.flush + end + if mat=e_pat.match(buf) then + result = [buf,*mat.captures] + break + end + end + if block_given? then + yield result + else + return result + end + nil + end +end