ruby-net-nntp 0.2.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,840 @@
1
+ module Net
2
+
3
+ class NNTP
4
+ # NNTP Request base clase. Do not instantiate, use subclasses.
5
+ #
6
+ # == Class Hierarchy
7
+ #
8
+ # * Net::NNTP::Request
9
+ # * Net::NNTP::Authinfo
10
+ # * Net::NNTP::Capabilities
11
+ # * Net::NNTP::Date
12
+ # * Net::NNTP::Group
13
+ # * Net::NNTP::Hdr
14
+ # * Net::NNTP::Help
15
+ # * Net::NNTP::List
16
+ # * Net::NNTP::Modereader
17
+ # * Net::NNTP::Newgroups
18
+ # * Net::NNTP::Newnews
19
+ # * Net::NNTP::Over
20
+ # * Net::NNTP::PostingRequest
21
+ # * Net::NNTP::Post
22
+ # * Net::NNTP::Ihave
23
+ # * Net::NNTP::Quit
24
+ # * Net::NNTP::RetrievalRequest
25
+ # * Net::NNTP::Article
26
+ # * Net::NNTP::Body
27
+ # * Net::NNTP::Head
28
+ # * Net::NNTP::Stat
29
+ # * Net::NNTP::SelectionRequest
30
+ # * Net::NNTP::Group
31
+ # * Net::NNTP::Listgroup
32
+ # * Net::NNTP::Last
33
+ # * Net::NNTP::Next
34
+ # * Net::NNTP::Xhdr
35
+ # * Net::NNTP::Xover
36
+ # * Net::NNTP::Xpat
37
+ class Request
38
+
39
+ def initialize(command, rest=nil)
40
+ @command = ("%s %s" % [command.upcase, rest]).strip
41
+ end
42
+
43
+ def command
44
+ return [@command, "\r\n"].join
45
+ end
46
+
47
+ def dotstuff # :nodoc:
48
+ # dot-stuffing. Mail SHOULD be raw
49
+ stuff_regex = /^\./m
50
+ @body.to_s.gsub(stuff_regex, "..")
51
+ end
52
+
53
+ def msgid_or_range(rest) # :nodoc:
54
+ rval = ''
55
+ if rest
56
+ case rest
57
+ when String
58
+ rval = rest
59
+ when Hash
60
+ if rest.has_key? :msgid
61
+ rval = rest[:msgid].to_s
62
+ else
63
+ rval = range(rest)
64
+ end
65
+ end
66
+ end
67
+ rval
68
+ end
69
+
70
+ def range(rest) # :nodoc:
71
+ return rest unless rest
72
+ rval = ''
73
+ if rest.has_key? :start
74
+ rval = rest[:start].to_s << "-"
75
+ if rest.has_key? :end
76
+ rval << rest[:end].to_s
77
+ end
78
+ end
79
+ rval
80
+ end
81
+
82
+ def valid_responses
83
+ []
84
+ end
85
+ private :valid_responses
86
+
87
+ # Can be used to make sure that the response is valid (or a generic response) according to RFCs 3977 and 4643
88
+ def valid_response?(response)
89
+ response.generic? || valid_responses().include?(response.class)
90
+ end
91
+
92
+ # Returns the capability that the server should mention on a Capability Request to notify the user that the server
93
+ # is capable to process the request. Older and broken NNTP servers do not respond to a Capability request, thus
94
+ # leaving it to trial-and-error.
95
+ def capability
96
+ 'mandatory'
97
+ end
98
+ end
99
+
100
+ class ParameterError < StandardError; end
101
+
102
+ # {RFC 4643}[http://tools.ietf.org/html/rfc4643]
103
+ #
104
+ # Plain text AUTHINFO Request. Set user and pass with new.
105
+ #
106
+ # Parameter:
107
+ #
108
+ # * keyword:: either 'user' or 'pass'
109
+ # * data:: username if keyword is USER, password if keyword is PASS
110
+ #
111
+ # == Usage Example
112
+ #
113
+ # nntp = Net::NNTP.new
114
+ # nntp.server = 'localhost'
115
+ # nntp.connect
116
+ # request = Authinfo.new('user', 'test')
117
+ # response = nntp.process(request)
118
+ # if PasswordRequired === response
119
+ # request = Authinfo.new('pass', 'xxx')
120
+ # response = nntp.process(request)
121
+ # if OKResponse === response
122
+ # # authenticated, go ahead
123
+ # else
124
+ # raise StandardError, response.message
125
+ # end
126
+ # end
127
+ #
128
+ # Valid Responses: PasswordRequired, AuthenticationAccepted, AuthenticationFailed, AuthenticationOutOfSequence
129
+ class Authinfo < Request
130
+
131
+ # +user+ is the string used in AUTHINFO USER
132
+ # +pass+ is the string used in AUTHINFO PASS
133
+ #
134
+ # See: user_command, pass_command
135
+ def initialize(keyword, data)
136
+ super 'AUTHINFO', [keyword.upcase, data].join(" ")
137
+ end
138
+
139
+ def valid_responses
140
+ [PasswordRequired, AuthenticationAccepted, AuthenticationFailed, AuthenticationOutOfSequence]
141
+ end
142
+ private :valid_responses
143
+
144
+ def capability
145
+ 'authinfo'
146
+ end
147
+ end
148
+
149
+ # Classification of Requests expecting response codes in the 1xx range.
150
+ #
151
+ # == Subclasses:
152
+ # * Help
153
+ # * Capabilities
154
+ class InformationRequest < Request
155
+ class << self
156
+ private :new
157
+ end
158
+ end
159
+ # {RFC 3977}[http://tools.ietf.org/html/rfc3977]
160
+ #
161
+ # Valid Response: HelpResponse
162
+ class Help < InformationRequest
163
+ class << self
164
+ public :new
165
+ end
166
+ def initialize
167
+ super 'HELP'
168
+ end
169
+ def valid_responses
170
+ [HelpResponse]
171
+ end
172
+ private :valid_responses
173
+ end
174
+
175
+ # {RFC 3977}[http://tools.ietf.org/html/rfc3977]
176
+ #
177
+ # CAPABILITIES Request. Should be used to discover the commands that the server can process. Can change if MODE or
178
+ # authentication changes the state of the connection. Sadly, most servers do not implement this, leaving either the
179
+ # option to parse the informal response to a HELP request, or use trial-and-error.
180
+ #
181
+ # == Usage Example
182
+ #
183
+ # nntp = Net::NNTP.new
184
+ # nntp.server = 'localhost'
185
+ # nntp.connect
186
+ # request = Capabilities.new
187
+ # response = nntp.process(request)
188
+ # response.capabilities['version'] # returns the version, either as number, or array of numbers, i.e. [2, 3]
189
+ #
190
+ # Valid Response: CapabilityList
191
+ class Capabilities < InformationRequest
192
+ class << self
193
+ public :new
194
+ end
195
+ def initialize
196
+ super 'CAPABILITIES'
197
+ end
198
+
199
+ def valid_responses
200
+ [CapabilityList]
201
+ end
202
+ private :valid_responses
203
+ end
204
+
205
+ # {RFC 3977}[http://tools.ietf.org/html/rfc3977]
206
+ #
207
+ # Generates a 'MODE READER' command.
208
+ #
209
+ # Valid Responses: PostingAllowed, PostingProhibited
210
+ class Modereader < Request
211
+ def initialize()
212
+ super 'MODE', 'READER'
213
+ end
214
+
215
+ def valid_responses
216
+ [PostingAllowed, PostingProhibited]
217
+ end
218
+ private :valid_responses
219
+
220
+ def capability
221
+ 'mode-reader'
222
+ end
223
+ end
224
+
225
+ # {RFC 3977}[http://tools.ietf.org/html/rfc3977]
226
+ #
227
+ # Generates a QUIT command. *Important*: after issuing QUIT to the server, the connection MUST be closed by the
228
+ # client after a ConnectionClosing Response has been received.
229
+ #
230
+ # == Usage Example
231
+ #
232
+ # nntp = Net::NNTP.new
233
+ # nntp.server = 'localhost'
234
+ # nntp.connect
235
+ # request = Quit.new
236
+ # response = nntp.process(request)
237
+ # if (ConnectionClosing === response)
238
+ # nntp.disconnect
239
+ # end
240
+ #
241
+ # Valid Response: ConnectionClosing
242
+ class Quit < Request
243
+ def initialize
244
+ super 'QUIT'
245
+ end
246
+ def valid_responses
247
+ [ConnectionClosing]
248
+ end
249
+ private :valid_responses
250
+ end
251
+
252
+ # Categorizes requests that manipulate the internal server state (selecting groups and/or articles)
253
+ #
254
+ # Parent: Net::NNTP::Request
255
+ #
256
+ # Subclasses:
257
+ #
258
+ # * Net::NNTP::Group
259
+ # * Net::NNTP::Listgroup
260
+ # * Net::NNTP::Last
261
+ # * Net::NNTP::Next
262
+ class SelectionRequest < Request
263
+ class << self
264
+ private :new
265
+ end
266
+
267
+ def capability
268
+ 'reader'
269
+ end
270
+ end
271
+
272
+ # {RFC 3977}[http://tools.ietf.org/html/rfc3977]
273
+ #
274
+ # Request for retrieving group information.
275
+ #
276
+ # Parent: Net::NNTP::SelectionRequest
277
+ #
278
+ # Valid Response: GroupSelected
279
+ class Group < SelectionRequest
280
+ class << self
281
+ public :new
282
+ end
283
+
284
+ #
285
+ # +group+ is the group name for which the information should be
286
+ # retrieved.
287
+ def initialize(groupname)
288
+ super 'GROUP', groupname
289
+ end
290
+
291
+ def valid_responses
292
+ [GroupSelected]
293
+ end
294
+ private :valid_responses
295
+ end
296
+
297
+ # {RFC 3977}[http://tools.ietf.org/html/rfc3977]
298
+ #
299
+ # LISTGROUP request.
300
+ #
301
+ # Parent: Net::NNTP::SelectionRequest
302
+ #
303
+ # Valid Responses: GroupSelected, GroupUnknown, NoGroupSelected
304
+ class Listgroup < SelectionRequest
305
+ class << self
306
+ public :new
307
+ end
308
+
309
+ # * +groupname+ is the group that should be selected. If a name is given, the group will be selected; on success,
310
+ # the first article returned will be selected. If no name is given, the currently selected group will be used,
311
+ # and no range can be specified.
312
+ # * +range+ a range of article numbers in the specified group, given as Hash with the keys :start and :end; if
313
+ # :end is not specified, the range is seen as 'open range', thus selecting all articles beginning with the
314
+ # number given as :start value to the last number available in the group.
315
+ #
316
+ # == Usage Example
317
+ #
318
+ # # no groupname
319
+ # request = Net::NNTP::Listgroup.new
320
+ # # request.command => 'LISTGROUP'
321
+ # # only groupname
322
+ # request = Net::NNTP::Listgroup.new('alt.test')
323
+ # # request.command => 'LISTGROUP alt.test'
324
+ # # given only a start number
325
+ # request = Net::NNTP::Listgroup.new('alt.test', :start => 1000)
326
+ # # request.command => 'LISTGROUP alt.test 1000-'
327
+ #
328
+ # # given a range
329
+ # request = Net::NNTP::Listgroup.new('alt.test', :start => 1000, :end => 5000)
330
+ # # request.command => 'LISTGROUP alt.test 1000- 5000'
331
+ def initialize(groupname=nil, range={})
332
+ param = nil
333
+ if groupname then
334
+ param = groupname
335
+ unless range.empty?
336
+ if range.has_key?(:start)
337
+ if range.has_key?(:end)
338
+ param = "#{groupname} #{range[:start]}-#{range[:end]}"
339
+ else
340
+ param = "#{groupname} #{range[:start]}-"
341
+ end
342
+ end
343
+ end
344
+ end
345
+ super 'LISTGROUP', param
346
+ end
347
+
348
+ def valid_responses
349
+ [GroupSelected, GroupUnknown, NoGroupSelected]
350
+ end
351
+ private :valid_responses
352
+ end
353
+
354
+ # {RFC 3977}[http://tools.ietf.org/html/rfc3977]
355
+ #
356
+ # Request to select the article preceding the selected article in the current group.
357
+ #
358
+ # Parent: Net::NNTP::SelectionRequest
359
+ #
360
+ # Valid Responses: ArticleSelected, NoGroupSelected, InvalidArticle, NoPreviousArticle
361
+ class Last < SelectionRequest
362
+ class << self
363
+ public :new
364
+ end
365
+
366
+ def initialize
367
+ super 'LAST'
368
+ end
369
+
370
+ def valid_responses
371
+ [ArticleSelected, NoGroupSelected, InvalidArticle, NoPreviousArticle]
372
+ end
373
+ private :valid_responses
374
+ end
375
+
376
+ # {RFC 3977}[http://tools.ietf.org/html/rfc3977]
377
+ #
378
+ # LAST request.
379
+ #
380
+ # Parent: Net::NNTP::SelectionRequest
381
+ #
382
+ # Valid Responses: ArticleSelected, NoGroupSelected, InvalidArticle, NoNextArticle
383
+ class Next < SelectionRequest
384
+ class << self
385
+ public :new
386
+ end
387
+
388
+ def initialize
389
+ super 'NEXT'
390
+ end
391
+
392
+ def valid_responses
393
+ [ArticleSelected, NoGroupSelected, InvalidArticle, NoNextArticle]
394
+ end
395
+ private :valid_responses
396
+ end
397
+
398
+ # Categorizes the requests which are used to retrieve article information.
399
+ #
400
+ # Parent: Net::Request
401
+ #
402
+ # Subclasses:
403
+ #
404
+ # * Net::NNTP::Article
405
+ # * Net::NNTP::Head
406
+ # * Net::NNTP::Body
407
+ # * Net::NNTP::Stat
408
+ class RetrievalRequest < Request
409
+ class << self
410
+ private :new
411
+ end
412
+ def initialize(keyword, param)
413
+ super keyword, param
414
+ end
415
+ end
416
+
417
+ # {RFC 3977}[http://tools.ietf.org/html/rfc3977]
418
+ #
419
+ # ARTICLE Request (headers and body).
420
+ #
421
+ # Valid Responses: ArticleResponse, NoGroupSelected, InvalidArticle, InvalidNumberOrRange, NoSuchMessageid
422
+ class Article < RetrievalRequest
423
+ class << self
424
+ public :new
425
+ end
426
+
427
+ #
428
+ # * +param+ is either a message-id or an article number in the selected group.
429
+ #
430
+ # == Usage Example:
431
+ # request = Net::NNTP::Article.new(100) # => request.command will be 'ARTICLE 100'
432
+ def initialize(param=nil)
433
+ super 'ARTICLE', param
434
+ end
435
+ def valid_responses
436
+ [ArticleResponse, NoGroupSelected, InvalidArticle, InvalidNumberOrRange, NoSuchMessageid]
437
+ end
438
+ private :valid_responses
439
+
440
+ def capability
441
+ 'reader'
442
+ end
443
+ end
444
+
445
+ # {RFC 3977}[http://tools.ietf.org/html/rfc3977]
446
+ #
447
+ # HEAD request
448
+ #
449
+ # Valid Responses: HeaderResponse, NoGroupSelected, InvalidArticle, InvalidNumberOrRange, NoSuchMessageid
450
+ class Head < RetrievalRequest
451
+ class << self
452
+ public :new
453
+ end
454
+ #
455
+ # * +param+ is either a message-id or an article number in the selected group.
456
+ #
457
+ # see Net::NNTP::Article.new
458
+ def initialize(param=nil)
459
+ super 'HEAD', param
460
+ end
461
+ def valid_responses
462
+ [HeaderResponse, NoGroupSelected, InvalidArticle, InvalidNumberOrRange, NoSuchMessageid]
463
+ end
464
+ private :valid_responses
465
+ end
466
+
467
+ # {RFC 3977}[http://tools.ietf.org/html/rfc3977]
468
+ #
469
+ # BODY request
470
+ #
471
+ # Valid Responses: BodyResponse, NoGroupSelected, InvalidArticle, InvalidNumberOrRange, NoSuchMessageid
472
+ class Body < RetrievalRequest
473
+ class << self
474
+ public :new
475
+ end
476
+ #
477
+ # * +param+ is either a message-id or an article number in the selected group.
478
+ #
479
+ # see Net::NNTP::Article.new
480
+ def initialize(param=nil)
481
+ super 'BODY', param
482
+ end
483
+ def valid_responses
484
+ [BodyResponse, NoGroupSelected, InvalidArticle, InvalidNumberOrRange, NoSuchMessageid]
485
+ end
486
+ private :valid_responses
487
+
488
+ def capability
489
+ 'reader'
490
+ end
491
+ end
492
+
493
+ # {RFC 3977}[http://tools.ietf.org/html/rfc3977]
494
+ #
495
+ # STAT request
496
+ #
497
+ # Valid Responses: ArticleSelected, NoGroupSelected, InvalidArticle, InvalidNumberOrRange, NoSuchMessageid
498
+ class Stat < Request
499
+ class << self
500
+ public :new
501
+ end
502
+ #
503
+ # * +param+ is either a message-id or an article number in the selected group.
504
+ #
505
+ # see Net::NNTP::Article.new
506
+ def initialize(param=nil)
507
+ super 'STAT', param
508
+ end
509
+
510
+ def valid_responses
511
+ [ArticleSelected, NoGroupSelected, InvalidArticle, InvalidNumberOrRange, NoSuchMessageid]
512
+ end
513
+ private :valid_responses
514
+ end
515
+
516
+ # Posting Request Base class.
517
+ #
518
+ # Do NOT instantiate, use subclasses Post or Ihave.
519
+ class PostingRequest < Request
520
+ class << self
521
+ private :new
522
+ end
523
+
524
+ def initialize(keyword, param=nil)
525
+ super keyword, param
526
+ end
527
+
528
+ # Sets the body of the posting to be transmitted.
529
+ #
530
+ # Do NOT use any dot stuffing or <CRLF>.<CRLF> sequences - dot-stuffing will happen automatically when sending.
531
+ #
532
+ # +body+ can be a TMail::Mail object, a string or any line-based IO object supporting +readline+, where the
533
+ # content has to be a valid mail, able to be parsed. The *body* attribute will be set to a TMail::Mail object,
534
+ # ready to be sent.
535
+ #
536
+ # == Example
537
+ # # ...
538
+ # request = Post.new # use subclass Post
539
+ # request.body =<<-EOF
540
+ # Newsgroups: alt.test
541
+ # From: Demo User <user@example.com>
542
+ # Subject: I am a test posting
543
+ #
544
+ # This is just a test posting
545
+ # EOF
546
+ # puts request.body.to_s
547
+ #
548
+ # # => From: Demo User <user@example.com>
549
+ # # => Subject: I am a test posting
550
+ # # => Newsgroups: alt.test
551
+ # # =>
552
+ # # => This is just a test posting.
553
+ #
554
+ def body=(body)
555
+ if body.is_a?(TMail::Mail)
556
+ @body = body
557
+ else
558
+ if body.respond_to? :readline
559
+ begin
560
+ raw = ''
561
+ loop do
562
+ line = body.readline
563
+ raw << line
564
+ end
565
+ rescue EOFError => e
566
+ end
567
+ else
568
+ raw = body
569
+ end
570
+ @body = TMail::Mail.parse(raw)
571
+ end
572
+ end
573
+
574
+ # Returns the *body* attribute; given a block, sets the body from the result of that block first.
575
+ def body
576
+ if block_given?
577
+ self.body = yield
578
+ end
579
+ @body
580
+ end
581
+
582
+ end
583
+
584
+ # {RFC 3977}[http://tools.ietf.org/html/rfc3977]
585
+ #
586
+ # POST request.
587
+ #
588
+ # Valid Responses: ArticleReceived, PostArticle, PostingNotPermitted, PostingFailed
589
+ class Post < PostingRequest
590
+ class << self
591
+ public :new
592
+ end
593
+
594
+ # The parameter +body+ should be the body in Mail format. DO NOT use dot-stuffing or <CR-LF>.<CR-LF> delimiter!
595
+ #
596
+ # See PostingRequest for an example.
597
+ def initialize(body=nil)
598
+ self.body = body unless body.nil?
599
+ super "POST"
600
+ end
601
+
602
+ def valid_responses
603
+ [ArticleReceived, PostArticle, PostingNotPermitted, PostingFailed]
604
+ end
605
+ private :valid_responses
606
+ end
607
+
608
+ # {RFC 3977}[http://tools.ietf.org/html/rfc3977]
609
+ #
610
+ # IHAVE request.
611
+ #
612
+ # Valid Responses: TransferOK, TransferArticle, ArticleNotWanted, TransferNotPossible, TransferRejected
613
+ class Ihave < PostingRequest
614
+ class << self
615
+ public :new
616
+ end
617
+
618
+ # * +id+ is the messageid that will be presented to the server. Will be superficially formally checked
619
+ # * +body+ is the posting that should be subsequently posted on a good response (335). It MUST NOT
620
+ # be dot-stuffed.
621
+ # See also: Post, PostingRequest, TransferOK, TransferArticle, ArticleNotWanted, TransferNotPossible, TransferRejected
622
+ def initialize(id, body=nil)
623
+ raise ParameterError,"Messageid must not be nil!" unless id
624
+ raise ParameterError,"Malformed messageid" unless id.match(/<.+@.+>/)
625
+ self.body = body unless body.nil?
626
+ super "IHAVE", id
627
+ end
628
+
629
+ def valid_responses
630
+ [TransferOK, TransferArticle, ArticleNotWanted, TransferNotPossible, TransferRejected]
631
+ end
632
+ private :valid_responses
633
+
634
+ def capability
635
+ 'ihave'
636
+ end
637
+ end
638
+
639
+ # {RFC 3977}[http://tools.ietf.org/html/rfc3977]
640
+ #
641
+ # DATE request.
642
+ #
643
+ # Valid Response: DateResponse
644
+ class Date < Request
645
+ def initialize
646
+ super 'DATE'
647
+ end
648
+
649
+ def valid_responses
650
+ [DateResponse]
651
+ end
652
+ private :valid_responses
653
+
654
+ def capability
655
+ 'reader'
656
+ end
657
+ end
658
+
659
+ # {RFC 3977}[http://tools.ietf.org/html/rfc3977]
660
+ #
661
+ # NEWGROUPS request.
662
+ #
663
+ # Valid Response: NewgroupsResponse
664
+ class Newgroups < Request
665
+ # * +date_str+ should be a string that will be parsed by DateTime.parse. Do not use system dates, rather use the
666
+ # date given by a Date request or a formerly stored date from a Date request.
667
+ # * +is_gmt+ denotes that the given date should be used as GMT (UTC) date (adding the string GMT to the request.)
668
+ # Use if possible.
669
+ def initialize(date_str, is_gmt=true)
670
+ super 'NEWGROUPS', DateTime.parse(date_str, true).strftime('%Y%m%d %H%M%S') << (is_gmt ? ' GMT' : '')
671
+ end
672
+
673
+ def valid_responses
674
+ [NewgroupsResponse]
675
+ end
676
+ private :valid_responses
677
+
678
+ def capability
679
+ 'reader'
680
+ end
681
+ end
682
+
683
+ # {RFC 3977}[http://tools.ietf.org/html/rfc3977]
684
+ #
685
+ # NEWNEWS request.
686
+ #
687
+ # Valid Response: NewnewsResponse
688
+ class Newnews < Request
689
+ #
690
+ # * +group+ must be an array of group wildmatches which should be checked for news since date_str.
691
+ # * +date_str+ should be a string that will be parsed by DateTime.parse. Do not use system dates, rather use the
692
+ # date given by a Date request or a formerly stored date from a Date request.
693
+ # * +is_gmt+ denotes that the given date should be used as GMT (UTC) date (adding the string GMT to the request.)
694
+ # Use if possible.
695
+ def initialize(groups, date_str, is_gmt=true)
696
+ super 'NEWNEWS', groups.join(',') << ' ' << DateTime.parse(date_str, true).strftime('%Y%m%d %H%M%S') << (is_gmt ? ' GMT' : '')
697
+ end
698
+
699
+ def valid_responses
700
+ [NewnewsResponse]
701
+ end
702
+ private :valid_responses
703
+ end
704
+
705
+ # {RFC 3977}[http://tools.ietf.org/html/rfc3977]
706
+ #
707
+ # LIST request.
708
+ #
709
+ # Valid Response: ListInformationFollows
710
+ class List < Request
711
+ attr_reader :keyword
712
+ # * +keyword+ must be nil or one of ACTIVE, ACTIVE.TIMES, DISTRIB.PATS, HEADERS, NEWSGROUPS or OVERVIEW.FMT.
713
+ # * +rest+ can be a string or a hash with either the key :msgid or the keys :start and optionally :end to denote a
714
+ # range.
715
+ #
716
+ # == Example
717
+ # request = Net::NNTP::List.new # => LIST
718
+ # request = Net::NNTP::List.new('ACTIVE') # => LIST ACTIVE
719
+ # request = Net::NNTP::List.new('ACTIVE.TIMES', 'at.*') # => LIST ACTIVE at.*
720
+ # request = Net::NNTP::List.new('HEADERS', :msgid => '<this.is@invalid>') # => LIST HEADERS <this.is@invalid>
721
+ # request = Net::NNTP::List.new('HEADERS', :start => 123, :end => 456) # => LIST HEADERS 123-456
722
+ #
723
+ def initialize(keyword=nil, rest={})
724
+ Net::NNTP.logger.debug("Keyword: <#{keyword}>")
725
+ @keyword = keyword.strip.upcase if keyword
726
+ parameters = (@keyword ? @keyword.dup : '')
727
+ parameters << ' ' << msgid_or_range(rest)
728
+ super 'LIST', parameters
729
+ end
730
+
731
+ def valid_responses
732
+ [ListInformationFollows]
733
+ end
734
+
735
+ def capability
736
+ 'list'
737
+ end
738
+ end
739
+
740
+ # {RFC 3977}[http://tools.ietf.org/html/rfc3977]
741
+ #
742
+ # OVER request.
743
+ #
744
+ # Valid Responses: OverviewInformation, NoGroupSelected, InvalidArticle, InvalidNumberOrRange, NoSuchMessageid
745
+ class Over < Request
746
+ # * +range+ can be a string or a hash with either the key :msgid or the keys :start and optionally :end to denote a
747
+ # range.
748
+ def initialize(range=nil)
749
+ super 'OVER', msgid_or_range(range)
750
+ end
751
+
752
+ def valid_responses
753
+ [OverviewInformation, NoGroupSelected, InvalidArticle, InvalidNumberOrRange, NoSuchMessageid]
754
+ end
755
+ private :valid_responses
756
+
757
+ def capability
758
+ 'over'
759
+ end
760
+ end
761
+
762
+ # {RFC 3977}[http://tools.ietf.org/html/rfc3977]
763
+ #
764
+ # HDR request.
765
+ #
766
+ # Valid Responses: HdrResponse, NoGroupSelected, InvalidArticle, InvalidNumberOrRange, NoSuchMessageid
767
+ class Hdr < Request
768
+ # * +field+ is the header field that should be retrieved
769
+ # * +range+ can be a string or a hash with either the key :msgid or the keys :start and optionally :end to denote a
770
+ # range.
771
+ def initialize(field, range=nil)
772
+ params = field
773
+ params << ' ' << msgid_or_range(range) if range
774
+ super 'HDR', params
775
+ end
776
+
777
+ def valid_responses
778
+ [HdrResponse, NoGroupSelected, InvalidArticle, InvalidNumberOrRange, NoSuchMessageid]
779
+ end
780
+ private :valid_responses
781
+
782
+ def capability
783
+ 'hdr'
784
+ end
785
+ end
786
+
787
+ # Extension {RFC2980}[http://tools.ietf.org/html/rfc2980]
788
+ #
789
+ # XHDR request.
790
+ #
791
+ # *Warning*:: Deprecated in {RFC 3977}[http://tools.ietf.org/html/rfc3977]. See Hdr. Still there, because a lot of
792
+ # servers do run the old version.
793
+ #
794
+ # See also: HeaderResponse
795
+ class Xhdr < Request
796
+ #
797
+ # * +field+ is the header field that should be retrieved
798
+ # * +range+ can be a string or a hash with either the key :msgid or the keys :start and optionally :end to denote a
799
+ # range.
800
+ def initialize(field, range=nil)
801
+ params = field
802
+ params << ' ' << msgid_or_range(range) if range
803
+ super 'XHDR', params
804
+ end
805
+ end
806
+
807
+ # Extension {RFC2980}[http://tools.ietf.org/html/rfc2980]
808
+ #
809
+ # XOVER request.
810
+ #
811
+ # *Warning*:: Deprecated in {RFC 3977}[http://tools.ietf.org/html/rfc3977]. See Over. Still there, because a lot of
812
+ # servers do run the old version.
813
+ #
814
+ # Valid Response: OverviewInformation
815
+ class Xover < Request
816
+ #
817
+ # * +range+ can be a string or a hash with either the key :msgid or the keys :start and optionally :end to denote a
818
+ # range.
819
+ def initialize(range=nil)
820
+ super 'XOVER', range(range)
821
+ end
822
+ end
823
+
824
+ # Extension {RFC2980}[http://tools.ietf.org/html/rfc2980]
825
+ #
826
+ # XPAT request.
827
+ class Xpat < Request
828
+ # * +field+ is the header field that should be searched
829
+ # * +patterns+ is a string or an array of patterns
830
+ # * +range+ can be a string or a hash with either the key :msgid or the keys :start and optionally :end to denote a
831
+ # range.
832
+ def initialize(field, patterns, range)
833
+ patterns = [patterns] unless patterns.is_a?(Array)
834
+ parameters = field << ' ' << msgid_or_range(range) << ' ' << patterns.join(' ')
835
+ super 'XPAT', parameters
836
+ end
837
+ end
838
+ end
839
+
840
+ end