require 'socket' class NntpClient attr_reader :host, :sock, :curgroup, :ydec def initialize(host = 'news.free.fr', curgroup = nil) @host = host @curgroup = curgroup @sock = nil @ydec = Ydec.new end def dl_bin(id, m=nil) retried = 0 begin body(id) { |l| @ydec.parse_line l, m } rescue retried += 1 raise if retried > 4 @ydec.restart retry end end def reconnect @sock = TCPSocket.new @host, 'nntp' puts ' [+] connected' s = @sock.gets puts s if $VERBOSE mode 'reader' group @curgroup if @curgroup end def method_missing(*a) if a.first == :group @curgroup = a[1] end cmd = a * ' ' begin begin @sock.puts cmd rescue return if a.first == :quit reconnect @sock.puts cmd end puts "> #{cmd}" if $VERBOSE str = @sock.gets.chomp! or raise 'partial read' puts str if $VERBOSE rescue retry end # no yield if response != 2xx if block_given? if str[0] == ?2 loop do str = @sock.gets.chomp! or raise 'partial read' return if str == '.' str = str[1..-1] if str[0] == ?. yield str end else puts str end end str end end class Ydec def initialize restart end def restart @ybeginline = true @outfile = nil @outlen = nil @linelen = nil @ypartline = false @partoffset = nil @partsize = nil @partcrc = nil @curbuf = nil @specialchar = false end # returns true if you should pass the next line def parse_line(l, m=nil) l = l.chomp if @ybeginline parse_ybegin $1 if l =~ /^=ybegin\s+(.*?)\s*$/ !@ybeginline elsif @ypartline parse_ypart $1 if l =~ /^=ypart\s+(.*?)\s*$/ !@ypartline elsif @curbuf if l =~ /^=yend\s+(.*?)\s*$/ parse_yend $1, m else ydecode l end @curbuf end end def parse_ybegin(l) raise "invalid ybegin #{l.inspect}" unless l =~ /(.*) name=(.*)/ l = $1 @outfile = $2 @ypartline = (l =~ /\bpart\b/) puts "ydec: decoding #{@outfile.inspect}#{' (part)' if @ypartline}" if $VERBOSE @outfile.gsub!(/[^a-zA-Z0-9_.+\-\[\]\(\)#'" ]/, '_') l.scan(/(\w+)=(\w+)/).each { |k, v| case k when 'line': @linelen = v.to_i when 'size': @outlen = v.to_i when 'part', 'total' else puts "unknown ybegin field #{k.inspect} = #{v.inspect}" end } @curbuf = '' @ybeginline = false true end def parse_ypart(l) l.scan(/(\w+)=(\w+)/).each { |k, v| case k when 'begin': @partoffset = v.to_i - 1 when 'end': @partsize = v.to_i - @partoffset else puts "unknown ypart field #{k.inspect} = #{v.inspect}" end } puts "ydec: decoding part: #@partsize o at #@partoffset" if $VERBOSE @curbuf = '' @ypartline = false true end def parse_yend(l, m=nil) sz = nil crc = nil pcrc = nil l.scan(/(\w+)=(\w+)/).each { |k, v| case k when 'size': sz = v.to_i when 'crc32': crc = v[0..7].hex when 'pcrc32': pcrc = v[0..7].hex when 'part' else puts "unknown yend field #{k.inspect} = #{v.inspect}" end } puts "ydec: finished" if $VERBOSE if @partsize m.lock if m File.open(@outfile, 'w') {} unless File.exist? @outfile File.open(@outfile, 'r+') { |fd| fd.seek @partoffset ; fd.write @curbuf } m.unlock if m else File.open(@outfile, 'w') { |fd| fd.write @curbuf } end @curbuf = nil @ybeginline = true end def ydecode(l) l.each_byte { |b| if @specialchar @specialchar = false @curbuf << ((b - 64 - 42) & 255) else case b when ?= @specialchar = true when ?\n, ?\r, ?\0 puts "special char #{b.chr.inspect} in stream..." else @curbuf << ((b - 42) & 255) end end } if @specialchar puts "line ends with specialchar.." @specialchar = false end end end