diff --git a/trunk/ripnews/net/nntp.rb.txt b/trunk/ripnews/net/nntp.rb.txt new file mode 100644 index 0000000..6929f28 --- /dev/null +++ b/trunk/ripnews/net/nntp.rb.txt @@ -0,0 +1,319 @@ +################################# +# +# nntp.rb - an NNTP client implementing RFC 977 +# ported from the Python code by Jefferson Heard +# this software is released under the terms of the GNU Library General Public License +# (C) 2001, Jefferson Heard +# +# Contributors: Jefferson Heard, Ward Wouts +# +# Release History +# 0.1: 11.7.2001 - Initial revision. +# 0.2: 11-9-2001 - fixed regexp bugs, +# fixed XHDR bugs, +# made internal methods private, +# changed constructor default arg +# 0.3: 11-14-2001 - Fixed numerous bugs and made things a little cleaner +# as per the suggestions of Ward Wouts +# 0.4: 11-15-2001 - Fixed statcmd bug - Ward Wouts +# 0.5: 12-06-2001 - Fixed post buf - Ozawa, Sakuro +################################# + +require 'socket' +require 'net/protocol' + +module Net + +# Exceptions raised by NNTP + +class NNTPError < RuntimeError; end +class NNTPReplyError < NNTPError; end +class NNTPTemporaryError < NNTPError; end +class NNTPPermanentError < NNTPError; end +class NNTPDataError < NNTPError; end + +class NNTP + NNTP_PORT = 119 + LONGRESP = ['100', '215', '220', '221', '222', '224', '230', '231', '282'] + CRLF = "\r\n" + + def initialize(host, port=NNTP_PORT, user=nil, password=nil, readermode=nil) + @debuglevel = 2 + @host = host + if port then @port = port else @port = NNTP_PORT end + @socket = TCPSocket.new @host, @port + @welcome = getresp + readermode_afterauth = false + + if readermode + begin + @welcome = shortcmd('mode reader') + rescue NNTPPermanentError + rescue NNTPTemporaryError + if user and $!.response[0...3] == '480' + readermode_afterauth = true + else + raise + end + end + end + + if user + resp = shortcmd "authinfo user #{user}" + if resp[0...3] == '381' # then we need a password + raise NNTPReplyError, resp, caller unless password + resp = shortcmd "authinfo pass #{password}" + raise NNTPPermanentError, resp, caller unless resp[0...3] == '281' + end + end + + if readermode_afterauth + begin + @welcome = shortcmd('mode reader') + rescue NNTPPermanentError + end + end + end + + def welcome + puts "*welcome*, #{@welcome}" if @debuglevel > 0 + return @welcome + end + + attr_writer :debuglevel + + def putline(line) + puts '*put* '+line+'\r\n' if @debuglevel > 1 + @socket.send "#{line}\r\n", 0 + end + + def putcmd(cmd) + puts "*cmd* #{cmd}" if @debuglevel > 0 + putline cmd + end + + def getline + line = '' + line.concat @socket.recv 1 until line.length > 2 and line[-1] == "\n" or line[-2..-1] == "\r\n" + puts '*getline* '+line if @debuglevel > 0 + line = line[0...-2] if line[-2..-1] == "\r\n" + line = line[0...-1] if "\r\n".include? line[-1].to_s + return line + end + + def getresp + resp = getline + puts "*getresp* #{resp}" if @debuglevel > 0 + c = resp[0] + case c + when c == '4' then raise NNTPTemporaryError, resp, caller + when c == '5' then raise NNTPPermanentError, resp, caller + when '123'.include?(c) then raise NNTPProtocolError, resp, caller + end + return resp + end + + def getlongresp + resp = getresp + raise NNTPReplyError, resp, caller unless LONGRESP.include? resp[0...3] + list = [] + while true + line = getline + break if line == '.' + line = line[1..-1] if line.to_s[0...2] == '..' + list << line + end + return resp, list + end + + def shortcmd(line) + putcmd line + return getresp + end + + def longcmd(line) + putcmd line + return getlongresp + end + + def newgroups(date, time) + return longcmd "NEWGROUPS #{date.to_s} #{time.to_s}" + end + + def newnews(group, date, time) + return longcmd "NEWNEWS #{group} #{date.to_s} #{time.to_s}" + end + + def list + resp, list = longcmd "LIST" + list.each_index {|ix| + list[ix] = list[ix].split " " + } + return resp, list + end + + def group(name) + resp = shortcmd "GROUP #{name}" + raise NNTPReplyError, resp, caller unless resp[0...3] == '211' + words = resp.split " " + count, first, last = 0 + n = words.length + if n>1 + count = words[1] + if n>2 + first = words[2] + if n>3 + last = words[3] + if n>4 + name = words[4].downcase + end + end + end + end + return resp, count, first, last, name + end + + def help + return longcmd "HELP" + end + + def statparse(resp) + raise NNTPReplyError, resp, caller unless resp[0...2] == '22' + words = resp.split " " + nr = 0 + id = '' + n = words.length + if n>1 + nr = words[1] + if n>2 + id = words[2] + end + end + return resp, nr, id + end + + def statcmd(line) + resp = shortcmd line + return statparse(resp) + end + + def stat(id) + return statcmd "STAT #{id}" + end + + def next + return statcmd "NEXT" + end + + def last + return statcmd "LAST" + end + + def articlecmd(line) + resp, list = longcmd line + resp, nr, id = statparse(resp) + return resp, nr, id, list + end + + def head(id) + return articlecmd "HEAD #{id}" + end + + def body(id) + return articlecmd "BODY #{id}" + end + + def article(id) + return articlecmd "ARTICLE #{id}" + end + + def slave(id) + return shortcmd "SLAVE" + end + + def xhdr(hdr, str) + pat = Regexp.new '^([0-9]+) ?(.*)\n?' + resp, lines = longcmd "XHDR #{hdr} #{str}" + lines.each_index {|ix| + line = lines[ix] + m = pat.match line + lines[ix] = m[1..2] if m + } + return resp, lines + end + + def xover(start, ed) + begin + resp, lines = longcmd "XOVER #{start}-#{ed}" + xover_lines = [] + lines.each {|line| + elements = line.split "\t" + elements[5].split! " " + 0.upto(7) {|ix| xover_lines << element[ix]} + } + return resp, xover_lines + rescue RuntimeError + raise NNTPDataError line, caller + end + end + + def xgtitle(group) + line_pat = Regexp.new "^([^\t]+)[\t]+(.*)$" + resp, raw_lines = longcmd "XGTITLE #{group}" + lines = [] + raw_lines.each {|line| + match = line_pat.match line.strip + lines << match[1..2] if match + } + return resp, lines + end + + def date + resp = shortcmd "DATE" + raise NNTPReplyError unless resp[0...3] == '111' + resp.split! " " + raise NNTPDataError unless resp.length == 2 + date = resp[1][2...8] + time = resp[1][-6..-1] + raise NNTPDataError resp, caller unless date.length == 6 and time.length == 6 + return resp, date, time + end + + def post(f) + resp = shortcmd "POST" + raise NNTPReplyError unless resp =~ /^3/ #[0] == 3 + lines = f.readlines + lines.each {|line| + line.chop! + line = '.' + line if line[0] == '.' + putline line + } + putline '.' + return getresp + end + + def quit + resp = shortcmd "QUIT" + @socket.close_read + @socket.close_write + return resp + end + + private :statparse, :getline, :putline, :articlecmd, :statcmd + protected :getresp, :getlongresp +end + +end + +if __FILE__ == $0 + s = Net::NNTP.new('news') + resp, count, first, last, name = s.group('comp.lang.ruby') + puts resp + puts "group #{name} has #{count} articles, range #{first} to #{last}" + resp, subs = s.xhdr('subject', "#{first}-#{last}") + puts resp + subs.each do |sub| puts sub end + resp = s.quit + puts resp +end +