blob: d97813fed4b1d877872d2c562dcaa9f8d8a6d818 [file] [log] [blame]
Guido van Rossum54f22ed2000-02-04 15:10:34 +00001"""An NNTP client class based on RFC 977: Network News Transfer Protocol.
Guido van Rossumc629d341992-11-05 10:43:02 +00002
Guido van Rossum54f22ed2000-02-04 15:10:34 +00003Example:
Guido van Rossumc629d341992-11-05 10:43:02 +00004
Guido van Rossum54f22ed2000-02-04 15:10:34 +00005>>> from nntplib import NNTP
6>>> s = NNTP('news')
7>>> resp, count, first, last, name = s.group('comp.lang.python')
8>>> print 'Group', name, 'has', count, 'articles, range', first, 'to', last
9Group comp.lang.python has 51 articles, range 5770 to 5821
10>>> resp, subs = s.xhdr('subject', first + '-' + last)
11>>> resp = s.quit()
12>>>
Guido van Rossumc629d341992-11-05 10:43:02 +000013
Guido van Rossum54f22ed2000-02-04 15:10:34 +000014Here 'resp' is the server response line.
15Error responses are turned into exceptions.
16
17To post an article from a file:
18>>> f = open(filename, 'r') # file containing article, including header
19>>> resp = s.post(f)
20>>>
21
22For descriptions of all methods, read the comments in the code below.
23Note that all arguments and return values representing article numbers
24are strings, not numbers, since they are rarely used for calculations.
25"""
26
27# RFC 977 by Brian Kantor and Phil Lapsley.
28# xover, xgtitle, xpath, date methods by Kevan Heydon
Guido van Rossum8421c4e1995-09-22 00:52:38 +000029
Guido van Rossumc629d341992-11-05 10:43:02 +000030
31# Imports
Guido van Rossum9694fca1997-10-22 21:00:49 +000032import re
Guido van Rossumc629d341992-11-05 10:43:02 +000033import socket
Guido van Rossumc629d341992-11-05 10:43:02 +000034
Skip Montanaro269b83b2001-02-06 01:07:02 +000035__all__ = ["NNTP","NNTPReplyError","NNTPTemporaryError",
36 "NNTPPermanentError","NNTPProtocolError","NNTPDataError",
37 "error_reply","error_temp","error_perm","error_proto",
38 "error_data",]
Tim Peters2344fae2001-01-15 00:50:52 +000039
Barry Warsaw9dd78722000-02-10 20:25:53 +000040# Exceptions raised when an error or invalid response is received
41class NNTPError(Exception):
Tim Peters2344fae2001-01-15 00:50:52 +000042 """Base class for all nntplib exceptions"""
43 def __init__(self, *args):
44 apply(Exception.__init__, (self,)+args)
45 try:
46 self.response = args[0]
47 except IndexError:
48 self.response = 'No response given'
Barry Warsaw9dd78722000-02-10 20:25:53 +000049
50class NNTPReplyError(NNTPError):
Tim Peters2344fae2001-01-15 00:50:52 +000051 """Unexpected [123]xx reply"""
52 pass
Barry Warsaw9dd78722000-02-10 20:25:53 +000053
54class NNTPTemporaryError(NNTPError):
Tim Peters2344fae2001-01-15 00:50:52 +000055 """4xx errors"""
56 pass
Barry Warsaw9dd78722000-02-10 20:25:53 +000057
58class NNTPPermanentError(NNTPError):
Tim Peters2344fae2001-01-15 00:50:52 +000059 """5xx errors"""
60 pass
Barry Warsaw9dd78722000-02-10 20:25:53 +000061
62class NNTPProtocolError(NNTPError):
Tim Peters2344fae2001-01-15 00:50:52 +000063 """Response does not begin with [1-5]"""
64 pass
Barry Warsaw9dd78722000-02-10 20:25:53 +000065
66class NNTPDataError(NNTPError):
Tim Peters2344fae2001-01-15 00:50:52 +000067 """Error in response data"""
68 pass
Barry Warsaw9dd78722000-02-10 20:25:53 +000069
70# for backwards compatibility
71error_reply = NNTPReplyError
72error_temp = NNTPTemporaryError
73error_perm = NNTPPermanentError
74error_proto = NNTPProtocolError
75error_data = NNTPDataError
Guido van Rossumc629d341992-11-05 10:43:02 +000076
77
Tim Peters2344fae2001-01-15 00:50:52 +000078
Guido van Rossumc629d341992-11-05 10:43:02 +000079# Standard port used by NNTP servers
80NNTP_PORT = 119
81
82
83# Response numbers that are followed by additional text (e.g. article)
Guido van Rossum8421c4e1995-09-22 00:52:38 +000084LONGRESP = ['100', '215', '220', '221', '222', '224', '230', '231', '282']
Guido van Rossumc629d341992-11-05 10:43:02 +000085
86
87# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
88CRLF = '\r\n'
89
90
Tim Peters2344fae2001-01-15 00:50:52 +000091
Guido van Rossumc629d341992-11-05 10:43:02 +000092# The class itself
Guido van Rossumc629d341992-11-05 10:43:02 +000093class NNTP:
Tim Peters2344fae2001-01-15 00:50:52 +000094 def __init__(self, host, port=NNTP_PORT, user=None, password=None,
95 readermode=None):
96 """Initialize an instance. Arguments:
97 - host: hostname to connect to
98 - port: port to connect to (default the standard NNTP port)
99 - user: username to authenticate with
100 - password: password to use with username
101 - readermode: if true, send 'mode reader' command after
102 connecting.
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000103
Tim Peters2344fae2001-01-15 00:50:52 +0000104 readermode is sometimes necessary if you are connecting to an
105 NNTP server on the local machine and intend to call
106 reader-specific comamnds, such as `group'. If you get
107 unexpected NNTPPermanentErrors, you might need to set
108 readermode.
109 """
110 self.host = host
111 self.port = port
112 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
113 self.sock.connect((self.host, self.port))
114 self.file = self.sock.makefile('rb')
115 self.debugging = 0
116 self.welcome = self.getresp()
Tim Petersdfb673b2001-01-16 07:12:46 +0000117
Thomas Wouters47adcba2001-01-16 06:35:14 +0000118 # 'mode reader' is sometimes necessary to enable 'reader' mode.
Tim Petersdfb673b2001-01-16 07:12:46 +0000119 # However, the order in which 'mode reader' and 'authinfo' need to
Thomas Wouters47adcba2001-01-16 06:35:14 +0000120 # arrive differs between some NNTP servers. Try to send
121 # 'mode reader', and if it fails with an authorization failed
122 # error, try again after sending authinfo.
123 readermode_afterauth = 0
Tim Peters2344fae2001-01-15 00:50:52 +0000124 if readermode:
125 try:
126 self.welcome = self.shortcmd('mode reader')
127 except NNTPPermanentError:
128 # error 500, probably 'not implemented'
129 pass
Thomas Wouters47adcba2001-01-16 06:35:14 +0000130 except NNTPTemporaryError, e:
131 if user and e.response[:3] == '480':
132 # Need authorization before 'mode reader'
133 readermode_afterauth = 1
134 else:
135 raise
Tim Peters2344fae2001-01-15 00:50:52 +0000136 if user:
137 resp = self.shortcmd('authinfo user '+user)
138 if resp[:3] == '381':
139 if not password:
140 raise NNTPReplyError(resp)
141 else:
142 resp = self.shortcmd(
143 'authinfo pass '+password)
144 if resp[:3] != '281':
145 raise NNTPPermanentError(resp)
Thomas Wouters47adcba2001-01-16 06:35:14 +0000146 if readermode_afterauth:
147 try:
148 self.welcome = self.shortcmd('mode reader')
149 except NNTPPermanentError:
150 # error 500, probably 'not implemented'
151 pass
Tim Petersdfb673b2001-01-16 07:12:46 +0000152
Barry Warsaw9dd78722000-02-10 20:25:53 +0000153
Tim Peters2344fae2001-01-15 00:50:52 +0000154 # Get the welcome message from the server
155 # (this is read and squirreled away by __init__()).
156 # If the response code is 200, posting is allowed;
157 # if it 201, posting is not allowed
Guido van Rossumc629d341992-11-05 10:43:02 +0000158
Tim Peters2344fae2001-01-15 00:50:52 +0000159 def getwelcome(self):
160 """Get the welcome message from the server
161 (this is read and squirreled away by __init__()).
162 If the response code is 200, posting is allowed;
163 if it 201, posting is not allowed."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000164
Tim Peters2344fae2001-01-15 00:50:52 +0000165 if self.debugging: print '*welcome*', `self.welcome`
166 return self.welcome
Guido van Rossumc629d341992-11-05 10:43:02 +0000167
Tim Peters2344fae2001-01-15 00:50:52 +0000168 def set_debuglevel(self, level):
169 """Set the debugging level. Argument 'level' means:
170 0: no debugging output (default)
171 1: print commands and responses but not body text etc.
172 2: also print raw lines read and sent before stripping CR/LF"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000173
Tim Peters2344fae2001-01-15 00:50:52 +0000174 self.debugging = level
175 debug = set_debuglevel
Guido van Rossumc629d341992-11-05 10:43:02 +0000176
Tim Peters2344fae2001-01-15 00:50:52 +0000177 def putline(self, line):
178 """Internal: send one line to the server, appending CRLF."""
179 line = line + CRLF
180 if self.debugging > 1: print '*put*', `line`
181 self.sock.send(line)
Guido van Rossumc629d341992-11-05 10:43:02 +0000182
Tim Peters2344fae2001-01-15 00:50:52 +0000183 def putcmd(self, line):
184 """Internal: send one command to the server (through putline())."""
185 if self.debugging: print '*cmd*', `line`
186 self.putline(line)
Guido van Rossumc629d341992-11-05 10:43:02 +0000187
Tim Peters2344fae2001-01-15 00:50:52 +0000188 def getline(self):
189 """Internal: return one line from the server, stripping CRLF.
190 Raise EOFError if the connection is closed."""
191 line = self.file.readline()
192 if self.debugging > 1:
193 print '*get*', `line`
194 if not line: raise EOFError
195 if line[-2:] == CRLF: line = line[:-2]
196 elif line[-1:] in CRLF: line = line[:-1]
197 return line
Guido van Rossumc629d341992-11-05 10:43:02 +0000198
Tim Peters2344fae2001-01-15 00:50:52 +0000199 def getresp(self):
200 """Internal: get a response from the server.
201 Raise various errors if the response indicates an error."""
202 resp = self.getline()
203 if self.debugging: print '*resp*', `resp`
204 c = resp[:1]
205 if c == '4':
206 raise NNTPTemporaryError(resp)
207 if c == '5':
208 raise NNTPPermanentError(resp)
209 if c not in '123':
210 raise NNTPProtocolError(resp)
211 return resp
Guido van Rossumc629d341992-11-05 10:43:02 +0000212
Tim Peters2344fae2001-01-15 00:50:52 +0000213 def getlongresp(self):
214 """Internal: get a response plus following text from the server.
215 Raise various errors if the response indicates an error."""
216 resp = self.getresp()
217 if resp[:3] not in LONGRESP:
218 raise NNTPReplyError(resp)
219 list = []
220 while 1:
221 line = self.getline()
222 if line == '.':
223 break
224 if line[:2] == '..':
225 line = line[1:]
226 list.append(line)
227 return resp, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000228
Tim Peters2344fae2001-01-15 00:50:52 +0000229 def shortcmd(self, line):
230 """Internal: send a command and get the response."""
231 self.putcmd(line)
232 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000233
Tim Peters2344fae2001-01-15 00:50:52 +0000234 def longcmd(self, line):
235 """Internal: send a command and get the response plus following text."""
236 self.putcmd(line)
237 return self.getlongresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000238
Tim Peters2344fae2001-01-15 00:50:52 +0000239 def newgroups(self, date, time):
240 """Process a NEWGROUPS command. Arguments:
241 - date: string 'yymmdd' indicating the date
242 - time: string 'hhmmss' indicating the time
243 Return:
244 - resp: server response if successful
245 - list: list of newsgroup names"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000246
Tim Peters2344fae2001-01-15 00:50:52 +0000247 return self.longcmd('NEWGROUPS ' + date + ' ' + time)
Guido van Rossumc629d341992-11-05 10:43:02 +0000248
Tim Peters2344fae2001-01-15 00:50:52 +0000249 def newnews(self, group, date, time):
250 """Process a NEWNEWS command. Arguments:
251 - group: group name or '*'
252 - date: string 'yymmdd' indicating the date
253 - time: string 'hhmmss' indicating the time
254 Return:
255 - resp: server response if successful
256 - list: list of article ids"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000257
Tim Peters2344fae2001-01-15 00:50:52 +0000258 cmd = 'NEWNEWS ' + group + ' ' + date + ' ' + time
259 return self.longcmd(cmd)
Guido van Rossumc629d341992-11-05 10:43:02 +0000260
Tim Peters2344fae2001-01-15 00:50:52 +0000261 def list(self):
262 """Process a LIST command. Return:
263 - resp: server response if successful
264 - list: list of (group, last, first, flag) (strings)"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000265
Tim Peters2344fae2001-01-15 00:50:52 +0000266 resp, list = self.longcmd('LIST')
267 for i in range(len(list)):
268 # Parse lines into "group last first flag"
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000269 list[i] = tuple(list[i].split())
Tim Peters2344fae2001-01-15 00:50:52 +0000270 return resp, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000271
Tim Peters2344fae2001-01-15 00:50:52 +0000272 def group(self, name):
273 """Process a GROUP command. Argument:
274 - group: the group name
275 Returns:
276 - resp: server response if successful
277 - count: number of articles (string)
278 - first: first article number (string)
279 - last: last article number (string)
280 - name: the group name"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000281
Tim Peters2344fae2001-01-15 00:50:52 +0000282 resp = self.shortcmd('GROUP ' + name)
283 if resp[:3] != '211':
284 raise NNTPReplyError(resp)
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000285 words = resp.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000286 count = first = last = 0
287 n = len(words)
288 if n > 1:
289 count = words[1]
290 if n > 2:
291 first = words[2]
292 if n > 3:
293 last = words[3]
294 if n > 4:
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000295 name = words[4].lower()
Tim Peters2344fae2001-01-15 00:50:52 +0000296 return resp, count, first, last, name
Guido van Rossumc629d341992-11-05 10:43:02 +0000297
Tim Peters2344fae2001-01-15 00:50:52 +0000298 def help(self):
299 """Process a HELP command. Returns:
300 - resp: server response if successful
301 - list: list of strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000302
Tim Peters2344fae2001-01-15 00:50:52 +0000303 return self.longcmd('HELP')
Guido van Rossumc629d341992-11-05 10:43:02 +0000304
Tim Peters2344fae2001-01-15 00:50:52 +0000305 def statparse(self, resp):
306 """Internal: parse the response of a STAT, NEXT or LAST command."""
307 if resp[:2] != '22':
308 raise NNTPReplyError(resp)
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000309 words = resp.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000310 nr = 0
311 id = ''
312 n = len(words)
313 if n > 1:
314 nr = words[1]
315 if n > 2:
316 id = words[2]
317 return resp, nr, id
Guido van Rossumc629d341992-11-05 10:43:02 +0000318
Tim Peters2344fae2001-01-15 00:50:52 +0000319 def statcmd(self, line):
320 """Internal: process a STAT, NEXT or LAST command."""
321 resp = self.shortcmd(line)
322 return self.statparse(resp)
Guido van Rossumc629d341992-11-05 10:43:02 +0000323
Tim Peters2344fae2001-01-15 00:50:52 +0000324 def stat(self, id):
325 """Process a STAT command. Argument:
326 - id: article number or message id
327 Returns:
328 - resp: server response if successful
329 - nr: the article number
330 - id: the article id"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000331
Tim Peters2344fae2001-01-15 00:50:52 +0000332 return self.statcmd('STAT ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000333
Tim Peters2344fae2001-01-15 00:50:52 +0000334 def next(self):
335 """Process a NEXT command. No arguments. Return as for STAT."""
336 return self.statcmd('NEXT')
Guido van Rossumc629d341992-11-05 10:43:02 +0000337
Tim Peters2344fae2001-01-15 00:50:52 +0000338 def last(self):
339 """Process a LAST command. No arguments. Return as for STAT."""
340 return self.statcmd('LAST')
Guido van Rossumc629d341992-11-05 10:43:02 +0000341
Tim Peters2344fae2001-01-15 00:50:52 +0000342 def artcmd(self, line):
343 """Internal: process a HEAD, BODY or ARTICLE command."""
344 resp, list = self.longcmd(line)
345 resp, nr, id = self.statparse(resp)
346 return resp, nr, id, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000347
Tim Peters2344fae2001-01-15 00:50:52 +0000348 def head(self, id):
349 """Process a HEAD command. Argument:
350 - id: article number or message id
351 Returns:
352 - resp: server response if successful
353 - nr: article number
354 - id: message id
355 - list: the lines of the article's header"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000356
Tim Peters2344fae2001-01-15 00:50:52 +0000357 return self.artcmd('HEAD ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000358
Tim Peters2344fae2001-01-15 00:50:52 +0000359 def body(self, id):
360 """Process a BODY command. Argument:
361 - id: article number or message id
362 Returns:
363 - resp: server response if successful
364 - nr: article number
365 - id: message id
366 - list: the lines of the article's body"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000367
Tim Peters2344fae2001-01-15 00:50:52 +0000368 return self.artcmd('BODY ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000369
Tim Peters2344fae2001-01-15 00:50:52 +0000370 def article(self, id):
371 """Process an ARTICLE command. Argument:
372 - id: article number or message id
373 Returns:
374 - resp: server response if successful
375 - nr: article number
376 - id: message id
377 - list: the lines of the article"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000378
Tim Peters2344fae2001-01-15 00:50:52 +0000379 return self.artcmd('ARTICLE ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000380
Tim Peters2344fae2001-01-15 00:50:52 +0000381 def slave(self):
382 """Process a SLAVE command. Returns:
383 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000384
Tim Peters2344fae2001-01-15 00:50:52 +0000385 return self.shortcmd('SLAVE')
Guido van Rossumc629d341992-11-05 10:43:02 +0000386
Tim Peters2344fae2001-01-15 00:50:52 +0000387 def xhdr(self, hdr, str):
388 """Process an XHDR command (optional server extension). Arguments:
389 - hdr: the header type (e.g. 'subject')
390 - str: an article nr, a message id, or a range nr1-nr2
391 Returns:
392 - resp: server response if successful
393 - list: list of (nr, value) strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000394
Tim Peters2344fae2001-01-15 00:50:52 +0000395 pat = re.compile('^([0-9]+) ?(.*)\n?')
396 resp, lines = self.longcmd('XHDR ' + hdr + ' ' + str)
397 for i in range(len(lines)):
398 line = lines[i]
399 m = pat.match(line)
400 if m:
401 lines[i] = m.group(1, 2)
402 return resp, lines
Guido van Rossumc629d341992-11-05 10:43:02 +0000403
Tim Peters2344fae2001-01-15 00:50:52 +0000404 def xover(self,start,end):
405 """Process an XOVER command (optional server extension) Arguments:
406 - start: start of range
407 - end: end of range
408 Returns:
409 - resp: server response if successful
410 - list: list of (art-nr, subject, poster, date,
411 id, references, size, lines)"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000412
Tim Peters2344fae2001-01-15 00:50:52 +0000413 resp, lines = self.longcmd('XOVER ' + start + '-' + end)
414 xover_lines = []
415 for line in lines:
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000416 elem = line.split("\t")
Tim Peters2344fae2001-01-15 00:50:52 +0000417 try:
418 xover_lines.append((elem[0],
419 elem[1],
420 elem[2],
421 elem[3],
422 elem[4],
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000423 elem[5].split(),
Tim Peters2344fae2001-01-15 00:50:52 +0000424 elem[6],
425 elem[7]))
426 except IndexError:
427 raise NNTPDataError(line)
428 return resp,xover_lines
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000429
Tim Peters2344fae2001-01-15 00:50:52 +0000430 def xgtitle(self, group):
431 """Process an XGTITLE command (optional server extension) Arguments:
432 - group: group name wildcard (i.e. news.*)
433 Returns:
434 - resp: server response if successful
435 - list: list of (name,title) strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000436
Tim Peters2344fae2001-01-15 00:50:52 +0000437 line_pat = re.compile("^([^ \t]+)[ \t]+(.*)$")
438 resp, raw_lines = self.longcmd('XGTITLE ' + group)
439 lines = []
440 for raw_line in raw_lines:
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000441 match = line_pat.search(raw_line.strip())
Tim Peters2344fae2001-01-15 00:50:52 +0000442 if match:
443 lines.append(match.group(1, 2))
444 return resp, lines
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000445
Tim Peters2344fae2001-01-15 00:50:52 +0000446 def xpath(self,id):
447 """Process an XPATH command (optional server extension) Arguments:
448 - id: Message id of article
449 Returns:
450 resp: server response if successful
451 path: directory path to article"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000452
Tim Peters2344fae2001-01-15 00:50:52 +0000453 resp = self.shortcmd("XPATH " + id)
454 if resp[:3] != '223':
455 raise NNTPReplyError(resp)
456 try:
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000457 [resp_num, path] = resp.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000458 except ValueError:
459 raise NNTPReplyError(resp)
460 else:
461 return resp, path
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000462
Tim Peters2344fae2001-01-15 00:50:52 +0000463 def date (self):
464 """Process the DATE command. Arguments:
465 None
466 Returns:
467 resp: server response if successful
468 date: Date suitable for newnews/newgroups commands etc.
469 time: Time suitable for newnews/newgroups commands etc."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000470
Tim Peters2344fae2001-01-15 00:50:52 +0000471 resp = self.shortcmd("DATE")
472 if resp[:3] != '111':
473 raise NNTPReplyError(resp)
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000474 elem = resp.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000475 if len(elem) != 2:
476 raise NNTPDataError(resp)
477 date = elem[1][2:8]
478 time = elem[1][-6:]
479 if len(date) != 6 or len(time) != 6:
480 raise NNTPDataError(resp)
481 return resp, date, time
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000482
483
Tim Peters2344fae2001-01-15 00:50:52 +0000484 def post(self, f):
485 """Process a POST command. Arguments:
486 - f: file containing the article
487 Returns:
488 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000489
Tim Peters2344fae2001-01-15 00:50:52 +0000490 resp = self.shortcmd('POST')
491 # Raises error_??? if posting is not allowed
492 if resp[0] != '3':
493 raise NNTPReplyError(resp)
494 while 1:
495 line = f.readline()
496 if not line:
497 break
498 if line[-1] == '\n':
499 line = line[:-1]
500 if line[:1] == '.':
501 line = '.' + line
502 self.putline(line)
503 self.putline('.')
504 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000505
Tim Peters2344fae2001-01-15 00:50:52 +0000506 def ihave(self, id, f):
507 """Process an IHAVE command. Arguments:
508 - id: message-id of the article
509 - f: file containing the article
510 Returns:
511 - resp: server response if successful
512 Note that if the server refuses the article an exception is raised."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000513
Tim Peters2344fae2001-01-15 00:50:52 +0000514 resp = self.shortcmd('IHAVE ' + id)
515 # Raises error_??? if the server already has it
516 if resp[0] != '3':
517 raise NNTPReplyError(resp)
518 while 1:
519 line = f.readline()
520 if not line:
521 break
522 if line[-1] == '\n':
523 line = line[:-1]
524 if line[:1] == '.':
525 line = '.' + line
526 self.putline(line)
527 self.putline('.')
528 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000529
Tim Peters2344fae2001-01-15 00:50:52 +0000530 def quit(self):
531 """Process a QUIT command and close the socket. Returns:
532 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000533
Tim Peters2344fae2001-01-15 00:50:52 +0000534 resp = self.shortcmd('QUIT')
535 self.file.close()
536 self.sock.close()
537 del self.file, self.sock
538 return resp
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000539
540
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000541def _test():
Tim Peters2344fae2001-01-15 00:50:52 +0000542 """Minimal test function."""
543 s = NNTP('news', readermode='reader')
544 resp, count, first, last, name = s.group('comp.lang.python')
545 print resp
546 print 'Group', name, 'has', count, 'articles, range', first, 'to', last
547 resp, subs = s.xhdr('subject', first + '-' + last)
548 print resp
549 for item in subs:
550 print "%7s %s" % item
551 resp = s.quit()
552 print resp
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000553
554
555# Run the test when run as a script
556if __name__ == '__main__':
Tim Peters2344fae2001-01-15 00:50:52 +0000557 _test()