Class: Net::NNTP

Inherits:
Object
  • Object
show all
Defined in:
lib/sanguinews/nntp.rb

Overview

Net::NNTP

What is This Library?

This library provides functionality to retrieve and, or post Usenet news articles via NNTP, the Network News Transfer Protocol. The Usenet is a world-wide distributed discussion system. It consists of a set of “newsgroups” with names that are classified hierarchically by topic. “articles” or “messages” are “posted” to these newsgroups by people on computers with the appropriate software – these articles are then broadcast to other interconnected NNTP servers via a wide variety of networks. For details of NNTP itself, see [RFC977] (www.ietf.org/rfc/rfc977.txt).

What is This Library NOT?

This library does NOT provide functions to compose Usenet news. You must create and, or format them yourself as per guidelines per Standard for Interchange of Usenet messages, see [RFC850], [RFC2047] and a fews other RFC’s (www.ietf.org/rfc/rfc850.txt), (www.ietf.org/rfc/rfc2047.txt).

FYI: the official documentation on Usenet news extentions is: [RFC2980] (www.ietf.org/rfc/rfc2980.txt).

Examples

Posting Messages

You must open a connection to an NNTP server before posting messages. The first argument is the address of your NNTP server, and the second argument is the port number. Using NNTP.start with a block is the simplest way to do this. This way, the NNTP connection is closed automatically after the block is executed.

require 'rubygems'
require 'nntp'
Net::NNTP.start('your.nntp.server', 119) do |nntp|
  # Use the NNTP object nntp only in this block.
end

Replace ‘your.nntp.server’ with your NNTP server. Normally your system manager or internet provider supplies a server for you.

Then you can post messages.

require 'date'
date = DateTime.now().strftime(fmt='%a, %d %b %Y %T %z')

msgstr = "From: Your Name <[email protected]>\nNewsgroups: news.group.one, news.group.two ...\nSubject: test message\n"

Date: ##date

This is a test message.
END_OF_MESSAGE

require 'rubygems'
require 'nntp'
Net::NNTP.start('your.nntp.server', 119) do |nntp|
  nntp.post msgstr
end

NOTE: The NNTP message headers such as Date:, Message-ID: and, or Path: if ommited, may also be generated and added by your Usenet news server; better you verify the behavior of your news server.

Closing the Session

You MUST close the NNTP session after posting messages, by calling the Net::NNTP#finish method:

# using NNTP#finish
nntp = Net::NNTP.start('your.nntp.server', 119)
nntp.post msgstr
nntp.finish

You can also use the block form of NNTP.start/NNTP#start. This closes the NNTP session automatically:

# using block form of NNTP.start
Net::NNTP.start('your.nntp.server', 119) do |nntp|
  nntp.post msgstr
end

I strongly recommend this scheme. This form is simpler and more robust.

NNTP Authentication

The Net::NNTP class may support various authentication schemes depending on your news server’s reponse to CAPABILITIES command. To use NNTP authentication, pass extra arguments to NNTP.start/NNTP#start.

See NNTP Extension for Authentication: (www.ietf.org/internet-drafts/draft-ietf-nntpext-authinfo-07.txt)

Net::NNTP.start('your.nntp.server', 119,
                'YourAccountName', 'YourPassword', :method)

Where :method can be one of the ‘gassapi’, ‘digest_md5’, ‘cram_md5’, ‘starttls’, ‘external’, ‘plain’, ‘generic’, ‘simple’ or ‘original’; the later and, or unencrypted ones are less secure!

In the case of method :generic argumnents should be passed to a format string as follows:

Net::NNTP.start('your.nntp.server', 119,
                "format", *arguments, :generic)

NOTE: The Authentication mechanism will fallback to a lesser secure scheme, if your Usenet server does not supports method opted by you, except for the :generic option.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(address, port = nil) ⇒ NNTP

Creates a new Net::NNTP object.

address is the hostname or ip address of your NNTP server. port is the port to connect to; it defaults to port 119.

This method does not opens any TCP connection. You can use NNTP.start instead of NNTP.new if you want to do everything at once. Otherwise, follow NNTP.new with optional changes to :open_timeout, :read_timeout and, or NNTP#set_debug_output and then NNTP#start.



216
217
218
219
220
221
222
223
224
225
# File 'lib/sanguinews/nntp.rb', line 216

def initialize(address, port = nil)
  @address = address
  @port = (port || NNTP.default_port)
  @socket = nil
  @started = false
  @open_timeout = 30
  @read_timeout = 60
  @error_occured = false
  @debug_output = nil
end

Instance Attribute Details

#addressObject (readonly)

The address of the NNTP server to connect to.



233
234
235
# File 'lib/sanguinews/nntp.rb', line 233

def address
  @address
end

#open_timeoutObject

Seconds to wait while attempting to open a connection. If the connection cannot be opened within this time, a TimeoutError is raised.



240
241
242
# File 'lib/sanguinews/nntp.rb', line 240

def open_timeout
  @open_timeout
end

#portObject (readonly)

The port number of the NNTP server to connect to.



236
237
238
# File 'lib/sanguinews/nntp.rb', line 236

def port
  @port
end

#read_timeoutObject

Seconds to wait while reading one block (by one read(2) call). If the read(2) call does not complete within this time, a TimeoutError is raised.



245
246
247
# File 'lib/sanguinews/nntp.rb', line 245

def read_timeout
  @read_timeout
end

Class Method Details

.default_portObject

The default NNTP port, port 119.



202
203
204
# File 'lib/sanguinews/nntp.rb', line 202

def NNTP.default_port
  119
end

.start(address, port = nil, user = nil, secret = nil, method = nil, &block) ⇒ Object

Creates a new Net::NNTP object and connects to the server.

This method is equivalent to:

Net::NNTP.new(address, port).start(, password, :method)

Example

Net::NNTP.start('your.nntp.server') do |nntp|
  nntp.post msgstr
end

Block Usage

If called with a block, the newly-opened Net::NNTP object is yielded to the block, and automatically closed when the block finishes. If called without a block, the newly-opened Net::NNTP object is returned to the caller, and it is the caller’s responsibility to close it when finished.

Parameters

address is the hostname or ip address of your nntp server.

port is the port to connect to; it defaults to port 119.

The remaining arguments are used for NNTP authentication, if required or desired. user is the account name, secret is your password or other authentication token, and method is the authentication type; defaults to ‘original’. Please read the discussion of NNTP Authentication in the overview notes above.

Errors

This method may raise:

  • Net::NNTPAuthenticationError

  • Net::NNTPFatalError

  • Net::NNTPServerBusy

  • Net::NNTPSyntaxError

  • Net::NNTPUnknownError

  • IOError

  • TimeoutError



319
320
321
322
323
# File 'lib/sanguinews/nntp.rb', line 319

def NNTP.start(address, port = nil,
               user = nil, secret = nil, method = nil,
               &block) # :yield: nntp
  new(address, port).start(user, secret, method, &block)
end

Instance Method Details

#article(id_num = nil) ⇒ Object

ARTICLE [<Message-ID>|<Number>]



541
542
543
544
# File 'lib/sanguinews/nntp.rb', line 541

def article(id_num = nil)
  stat, text = longcmd("ARTICLE #{id_num}".strip)
  return stat[0..2], text
end

#body(id_num = nil) ⇒ Object

BODY [<Message-ID>|<Number>]



547
548
549
550
# File 'lib/sanguinews/nntp.rb', line 547

def body(id_num = nil)
  stat, text = longcmd("BODY #{id_num}".strip)
  return stat[0..2], text
end

#dateObject

DATE

Raises:



559
560
561
562
563
564
565
566
# File 'lib/sanguinews/nntp.rb', line 559

def date
  text = []
  stat = shortcmd("DATE")
  text << stat[4...12]
  text << stat[12...18]
  raise NNTPDataError, stat, caller unless text[0].length == 8 and text[1].length == 6
  return stat[0..2], text
end

#finishObject

Finishes the NNTP session and closes TCP connection. Raises IOError if not started.

Raises:

  • (IOError)


445
446
447
448
# File 'lib/sanguinews/nntp.rb', line 445

def finish
  raise IOError, 'not yet started' unless started?
  do_finish
end

#group(ng) ⇒ Object

GROUP <Newsgroup>



569
570
571
572
# File 'lib/sanguinews/nntp.rb', line 569

def group(ng)
  stat = shortcmd("GROUP %s", ng)
  return stat[0..2], stat[4..-1].chop
end

#head(id_num = nil) ⇒ Object

HEAD [<Message-ID>|<Number>]



575
576
577
578
# File 'lib/sanguinews/nntp.rb', line 575

def head(id_num = nil)
  stat, text = longcmd("HEAD #{id_num}".strip)
  return stat[0..2], text
end

#helpObject

HELP



581
582
583
584
585
586
587
# File 'lib/sanguinews/nntp.rb', line 581

def help
  stat, text = longcmd('HELP')
  text.each_with_index do |line, index|
    text[index] = line.gsub(/\A\s+/, '')
  end
  return stat[0..2], text
end

#inspectObject

Provide human-readable stringification of class state.



228
229
230
# File 'lib/sanguinews/nntp.rb', line 228

def inspect  #:nodoc:
  "#<#{self.class} #{@address}:#{@port} started=#{@started}>"
end

#io_body(io_output, id_num = nil) ⇒ Object

IO_BODY <output IO object> [<Message-ID>|<Number>]



553
554
555
556
# File 'lib/sanguinews/nntp.rb', line 553

def io_body (io_output, id_num = nil)
  stat = io_longcmd(io_output, "BODY #{id_num}".strip)
  return stat[0..2], io_output
end

#lastObject

LAST



590
591
592
593
# File 'lib/sanguinews/nntp.rb', line 590

def last
  stat = shortcmd('LAST')
  return stat[0..2], stat[4..-1].chop
end

#list(opts = nil) ⇒ Object

LIST [ACTIVE|NEWSGROUPS] [<Wildmat>]]:br: LIST [ACTIVE.TIMES|EXTENSIONS|SUBSCRIPTIONS|OVERVIEW.FMT]



597
598
599
600
# File 'lib/sanguinews/nntp.rb', line 597

def list(opts = nil)
  stat, text = longcmd("LIST #{opts}".strip)
  return stat[0..2], text
end

#listgroup(ng) ⇒ Object

LISTGROUP <Newsgroup>



603
604
605
606
# File 'lib/sanguinews/nntp.rb', line 603

def listgroup(ng)
  stat, text = longcmd("LISTGROUP #{ng}".strip)
  return stat[0..2], text
end

#newgroups(date, time, tzone = nil) ⇒ Object

NEWGROUPS <yymmdd> <hhmmss> [GMT]



616
617
618
619
# File 'lib/sanguinews/nntp.rb', line 616

def newgroups(date, time, tzone = nil)
  stat, text = longcmd("NEWGROUPS #{date} #{time} #{tzone}".strip)
  return stat[0..2], text
end

#nextObject

NEXT



622
623
624
625
# File 'lib/sanguinews/nntp.rb', line 622

def next
  stat = shortcmd('NEXT')
  return stat[0..2], stat[4..-1].chop
end

#open_message_stream(&block) ⇒ Object

Opens a message writer stream and gives it to the block. The stream is valid only in the block, and has these methods:

puts(str = ”)

outputs STR and CR LF.

print(str)

outputs STR.

printf(fmt, *args)

outputs sprintf(fmt,*args).

write(str)

outputs STR and returns the length of written bytes.

<<(str)

outputs STR and returns self.

If a single CR (“r”) or LF (“n”) is found in the message, it is converted to the CR LF pair. You cannot post a binary message with this method.

Parameters

Block

Example

Net::NNTP.start('nntp.example.com', 119) do |nntp|
  nntp.open_message_stream do |f|
    f.puts 'From: [email protected]'
    f.puts 'Newsgroups: news.group.one, news.group.two ...'
    f.puts 'Subject: test message'
    f.puts
    f.puts 'This is a test message.'
  end
end

Errors

This method may raise:

  • Net::NNTPFatalError

  • Net::NNTPPostingNotAllowed

  • Net::NNTPServerBusy

  • Net::NNTPSyntaxError

  • Net::NNTPUnknownError

  • IOError

  • TimeoutError



536
537
538
# File 'lib/sanguinews/nntp.rb', line 536

def open_message_stream(&block) # :yield: stream
  post0 { @socket.write_message_by_block(&block) }
end

#over(range) ⇒ Object

OVER <Range> # e.g first[-]



628
629
630
631
# File 'lib/sanguinews/nntp.rb', line 628

def over(range)
  stat, text = longcmd("OVER #{range}".strip)
  return stat[0..2], text
end

#post(msgstr) ⇒ Object

POST

Posts msgstr as a message. Single CR (“r”) and LF (“n”) found in the msgstr, are converted into the CR LF pair. You cannot post a binary message with this method. msgstr _should include both the message headers and body_. All non US-ASCII, binary and, or multi-part messages should be submitted in an encoded form as per MIME standards.

Example

Net::NNTP.start('nntp.example.com') do |nntp|
  nntp.post msgstr
end

Errors

This method may raise:

  • Net::NNTPFatalError

  • Net::NNTPPostingNotAllowed

  • Net::NNTPServerBusy

  • Net::NNTPSyntaxError

  • Net::NNTPUnknownError

  • IOError

  • TimeoutError



488
489
490
491
492
493
# File 'lib/sanguinews/nntp.rb', line 488

def post(msgstr)
  stat = post0 {
    @socket.write_message msgstr
  }
  return stat[0..3], stat[4..-1].chop
end

#set_debug_output(arg) ⇒ Object

Set an output stream for debug logging. You must call this before #start.

Example

nntp = Net::NNTP.new(addr, port)
nntp.set_debug_output $stderr
nntp.start do |nntp|
  ....
end

WARNING: This method causes serious security holes. Use this method for only debugging.



267
268
269
# File 'lib/sanguinews/nntp.rb', line 267

def set_debug_output(arg)
  @debug_output = arg
end

#slaveObject

SLAVE



640
641
642
643
# File 'lib/sanguinews/nntp.rb', line 640

def slave
  stat = shortcmd('SLAVE')
  return stat[0..2], stat[4..-1].chop
end

#start(user = nil, secret = nil, method = nil) ⇒ Object

Opens a TCP connection and starts the NNTP session.

Parameters

If both of user and secret are given, NNTP authentication will be attempted using the AUTH command. The method specifies the type of authentication to attempt; it must be one of :original, :simple, :generic, :plain, :starttls, :external, :cram_md5, :digest_md5 and, or :gassapi may be used. See the discussion of NNTP Authentication in the overview notes.

Block Usage

When this methods is called with a block, the newly-started NNTP object is yielded to the block, and automatically closed after the block call finishes. Otherwise, it is the caller’s responsibility to close the session when finished.

Example

This is very similar to the class method NNTP.start.

require 'rubygems'
require 'nntp'
nntp = Net::NNTP.new('nntp.news.server', 119)
nntp.start(, password, method) do |nntp|
  nntp.post msgstr
end

The primary use of this method (as opposed to NNTP.start) is probably to set debugging (#set_debug_output), which must be done before the session is started.

Errors

If session has already been started, an IOError will be raised.

This method may raise:

  • Net::NNTPAuthenticationError

  • Net::NNTPFatalError

  • Net::NNTPServerBusy

  • Net::NNTPSyntaxError

  • Net::NNTPUnknownError

  • IOError

  • TimeoutError



378
379
380
381
382
383
384
385
386
387
388
389
390
# File 'lib/sanguinews/nntp.rb', line 378

def start(user = nil, secret = nil, method = nil) # :yield: nntp
  if block_given?
    begin
      do_start(user, secret, method)
      return yield(self)
    ensure
      do_finish
    end
  else
    do_start(user, secret, method)
    return self
  end
end

#started?Boolean

true if the NNTP session has been started.

Returns:

  • (Boolean)


326
327
328
# File 'lib/sanguinews/nntp.rb', line 326

def started?
  @started
end

#stat(id_num = nil) ⇒ Object

STAT [<Message-ID>|<Number>]



646
647
648
649
# File 'lib/sanguinews/nntp.rb', line 646

def stat(id_num = nil)
  stat = shortcmd("STAT #{id_num}".strip)
  return stat[0..2], stat[4..-1].chop
end

#xhdr(header, id_range) ⇒ Object

XHDR <Header> <Message-ID>|<Range> # e.g first[-]



652
653
654
655
# File 'lib/sanguinews/nntp.rb', line 652

def xhdr(header, id_range)
  stat, text = longcmd("XHDR #{header} #{id_range}".strip)
  return stat[0..2], text
end

#xover(range) ⇒ Object

XOVER <Range> # e.g first[-]



658
659
660
661
# File 'lib/sanguinews/nntp.rb', line 658

def xover(range)
  stat, text = longcmd("XOVER #{range}".strip)
  return stat[0..2], text
end