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 = <<END_OF_MESSAGE
From: Your Name <[email protected]>
Newsgroups: news.group.one, news.group.two ...
Subject: test message

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>]



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

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>]



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

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

#dateObject

DATE

Raises:



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

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)


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

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

#group(ng) ⇒ Object

GROUP <Newsgroup>



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

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>]



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

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

#helpObject

HELP



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

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>]



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

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



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

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]



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

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

#listgroup(ng) ⇒ Object

LISTGROUP <Newsgroup>



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

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]



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

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

#nextObject

NEXT



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

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



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

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

#over(range) ⇒ Object

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



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

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



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

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



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

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>]



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

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[-]



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

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[-]



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

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