blob: f09c69a42192bb8d314fb4c6c33e7f94690e560e [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`
Martin v. Löwise12454f2002-02-16 23:06:19 +0000181 self.sock.sendall(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
Fredrik Lundha5e61652001-10-18 20:58:25 +0000213 def getlongresp(self, file=None):
Tim Peters2344fae2001-01-15 00:50:52 +0000214 """Internal: get a response plus following text from the server.
215 Raise various errors if the response indicates an error."""
Guido van Rossumd1d584f2001-10-01 13:46:55 +0000216
217 openedFile = None
218 try:
219 # If a string was passed then open a file with that name
Walter Dörwald65230a22002-06-03 15:58:32 +0000220 if isinstance(file, str):
Fredrik Lundha5e61652001-10-18 20:58:25 +0000221 openedFile = file = open(file, "w")
Guido van Rossumd1d584f2001-10-01 13:46:55 +0000222
223 resp = self.getresp()
224 if resp[:3] not in LONGRESP:
225 raise NNTPReplyError(resp)
226 list = []
227 while 1:
228 line = self.getline()
229 if line == '.':
230 break
231 if line[:2] == '..':
232 line = line[1:]
Fredrik Lundha5e61652001-10-18 20:58:25 +0000233 if file:
234 file.write(line + "\n")
Guido van Rossumd1d584f2001-10-01 13:46:55 +0000235 else:
236 list.append(line)
237 finally:
238 # If this method created the file, then it must close it
239 if openedFile:
240 openedFile.close()
241
Tim Peters2344fae2001-01-15 00:50:52 +0000242 return resp, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000243
Tim Peters2344fae2001-01-15 00:50:52 +0000244 def shortcmd(self, line):
245 """Internal: send a command and get the response."""
246 self.putcmd(line)
247 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000248
Fredrik Lundha5e61652001-10-18 20:58:25 +0000249 def longcmd(self, line, file=None):
Tim Peters2344fae2001-01-15 00:50:52 +0000250 """Internal: send a command and get the response plus following text."""
251 self.putcmd(line)
Fredrik Lundha5e61652001-10-18 20:58:25 +0000252 return self.getlongresp(file)
Guido van Rossumc629d341992-11-05 10:43:02 +0000253
Tim Peters2344fae2001-01-15 00:50:52 +0000254 def newgroups(self, date, time):
255 """Process a NEWGROUPS command. Arguments:
256 - date: string 'yymmdd' indicating the date
257 - time: string 'hhmmss' indicating the time
258 Return:
259 - resp: server response if successful
260 - list: list of newsgroup names"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000261
Tim Peters2344fae2001-01-15 00:50:52 +0000262 return self.longcmd('NEWGROUPS ' + date + ' ' + time)
Guido van Rossumc629d341992-11-05 10:43:02 +0000263
Tim Peters2344fae2001-01-15 00:50:52 +0000264 def newnews(self, group, date, time):
265 """Process a NEWNEWS command. Arguments:
266 - group: group name or '*'
267 - date: string 'yymmdd' indicating the date
268 - time: string 'hhmmss' indicating the time
269 Return:
270 - resp: server response if successful
271 - list: list of article ids"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000272
Tim Peters2344fae2001-01-15 00:50:52 +0000273 cmd = 'NEWNEWS ' + group + ' ' + date + ' ' + time
274 return self.longcmd(cmd)
Guido van Rossumc629d341992-11-05 10:43:02 +0000275
Tim Peters2344fae2001-01-15 00:50:52 +0000276 def list(self):
277 """Process a LIST command. Return:
278 - resp: server response if successful
279 - list: list of (group, last, first, flag) (strings)"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000280
Tim Peters2344fae2001-01-15 00:50:52 +0000281 resp, list = self.longcmd('LIST')
282 for i in range(len(list)):
283 # Parse lines into "group last first flag"
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000284 list[i] = tuple(list[i].split())
Tim Peters2344fae2001-01-15 00:50:52 +0000285 return resp, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000286
Tim Peters2344fae2001-01-15 00:50:52 +0000287 def group(self, name):
288 """Process a GROUP command. Argument:
289 - group: the group name
290 Returns:
291 - resp: server response if successful
292 - count: number of articles (string)
293 - first: first article number (string)
294 - last: last article number (string)
295 - name: the group name"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000296
Tim Peters2344fae2001-01-15 00:50:52 +0000297 resp = self.shortcmd('GROUP ' + name)
298 if resp[:3] != '211':
299 raise NNTPReplyError(resp)
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000300 words = resp.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000301 count = first = last = 0
302 n = len(words)
303 if n > 1:
304 count = words[1]
305 if n > 2:
306 first = words[2]
307 if n > 3:
308 last = words[3]
309 if n > 4:
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000310 name = words[4].lower()
Tim Peters2344fae2001-01-15 00:50:52 +0000311 return resp, count, first, last, name
Guido van Rossumc629d341992-11-05 10:43:02 +0000312
Tim Peters2344fae2001-01-15 00:50:52 +0000313 def help(self):
314 """Process a HELP command. Returns:
315 - resp: server response if successful
316 - list: list of strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000317
Tim Peters2344fae2001-01-15 00:50:52 +0000318 return self.longcmd('HELP')
Guido van Rossumc629d341992-11-05 10:43:02 +0000319
Tim Peters2344fae2001-01-15 00:50:52 +0000320 def statparse(self, resp):
321 """Internal: parse the response of a STAT, NEXT or LAST command."""
322 if resp[:2] != '22':
323 raise NNTPReplyError(resp)
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000324 words = resp.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000325 nr = 0
326 id = ''
327 n = len(words)
328 if n > 1:
329 nr = words[1]
330 if n > 2:
331 id = words[2]
332 return resp, nr, id
Guido van Rossumc629d341992-11-05 10:43:02 +0000333
Tim Peters2344fae2001-01-15 00:50:52 +0000334 def statcmd(self, line):
335 """Internal: process a STAT, NEXT or LAST command."""
336 resp = self.shortcmd(line)
337 return self.statparse(resp)
Guido van Rossumc629d341992-11-05 10:43:02 +0000338
Tim Peters2344fae2001-01-15 00:50:52 +0000339 def stat(self, id):
340 """Process a STAT command. Argument:
341 - id: article number or message id
342 Returns:
343 - resp: server response if successful
344 - nr: the article number
345 - id: the article id"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000346
Tim Peters2344fae2001-01-15 00:50:52 +0000347 return self.statcmd('STAT ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000348
Tim Peters2344fae2001-01-15 00:50:52 +0000349 def next(self):
350 """Process a NEXT command. No arguments. Return as for STAT."""
351 return self.statcmd('NEXT')
Guido van Rossumc629d341992-11-05 10:43:02 +0000352
Tim Peters2344fae2001-01-15 00:50:52 +0000353 def last(self):
354 """Process a LAST command. No arguments. Return as for STAT."""
355 return self.statcmd('LAST')
Guido van Rossumc629d341992-11-05 10:43:02 +0000356
Fredrik Lundha5e61652001-10-18 20:58:25 +0000357 def artcmd(self, line, file=None):
Tim Peters2344fae2001-01-15 00:50:52 +0000358 """Internal: process a HEAD, BODY or ARTICLE command."""
Fredrik Lundha5e61652001-10-18 20:58:25 +0000359 resp, list = self.longcmd(line, file)
Tim Peters2344fae2001-01-15 00:50:52 +0000360 resp, nr, id = self.statparse(resp)
361 return resp, nr, id, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000362
Tim Peters2344fae2001-01-15 00:50:52 +0000363 def head(self, id):
364 """Process a HEAD command. Argument:
365 - id: article number or message id
366 Returns:
367 - resp: server response if successful
368 - nr: article number
369 - id: message id
370 - list: the lines of the article's header"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000371
Tim Peters2344fae2001-01-15 00:50:52 +0000372 return self.artcmd('HEAD ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000373
Fredrik Lundha5e61652001-10-18 20:58:25 +0000374 def body(self, id, file=None):
Tim Peters2344fae2001-01-15 00:50:52 +0000375 """Process a BODY command. Argument:
376 - id: article number or message id
Fredrik Lundha5e61652001-10-18 20:58:25 +0000377 - file: Filename string or file object to store the article in
Tim Peters2344fae2001-01-15 00:50:52 +0000378 Returns:
379 - resp: server response if successful
380 - nr: article number
381 - id: message id
Guido van Rossumd1d584f2001-10-01 13:46:55 +0000382 - list: the lines of the article's body or an empty list
Fredrik Lundha5e61652001-10-18 20:58:25 +0000383 if file was used"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000384
Fredrik Lundha5e61652001-10-18 20:58:25 +0000385 return self.artcmd('BODY ' + id, file)
Guido van Rossumc629d341992-11-05 10:43:02 +0000386
Tim Peters2344fae2001-01-15 00:50:52 +0000387 def article(self, id):
388 """Process an ARTICLE command. Argument:
389 - id: article number or message id
390 Returns:
391 - resp: server response if successful
392 - nr: article number
393 - id: message id
394 - list: the lines of the article"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000395
Tim Peters2344fae2001-01-15 00:50:52 +0000396 return self.artcmd('ARTICLE ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000397
Tim Peters2344fae2001-01-15 00:50:52 +0000398 def slave(self):
399 """Process a SLAVE command. Returns:
400 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000401
Tim Peters2344fae2001-01-15 00:50:52 +0000402 return self.shortcmd('SLAVE')
Guido van Rossumc629d341992-11-05 10:43:02 +0000403
Tim Peters2344fae2001-01-15 00:50:52 +0000404 def xhdr(self, hdr, str):
405 """Process an XHDR command (optional server extension). Arguments:
406 - hdr: the header type (e.g. 'subject')
407 - str: an article nr, a message id, or a range nr1-nr2
408 Returns:
409 - resp: server response if successful
410 - list: list of (nr, value) strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000411
Tim Peters2344fae2001-01-15 00:50:52 +0000412 pat = re.compile('^([0-9]+) ?(.*)\n?')
413 resp, lines = self.longcmd('XHDR ' + hdr + ' ' + str)
414 for i in range(len(lines)):
415 line = lines[i]
416 m = pat.match(line)
417 if m:
418 lines[i] = m.group(1, 2)
419 return resp, lines
Guido van Rossumc629d341992-11-05 10:43:02 +0000420
Tim Peters2344fae2001-01-15 00:50:52 +0000421 def xover(self,start,end):
422 """Process an XOVER command (optional server extension) Arguments:
423 - start: start of range
424 - end: end of range
425 Returns:
426 - resp: server response if successful
427 - list: list of (art-nr, subject, poster, date,
428 id, references, size, lines)"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000429
Tim Peters2344fae2001-01-15 00:50:52 +0000430 resp, lines = self.longcmd('XOVER ' + start + '-' + end)
431 xover_lines = []
432 for line in lines:
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000433 elem = line.split("\t")
Tim Peters2344fae2001-01-15 00:50:52 +0000434 try:
435 xover_lines.append((elem[0],
436 elem[1],
437 elem[2],
438 elem[3],
439 elem[4],
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000440 elem[5].split(),
Tim Peters2344fae2001-01-15 00:50:52 +0000441 elem[6],
442 elem[7]))
443 except IndexError:
444 raise NNTPDataError(line)
445 return resp,xover_lines
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000446
Tim Peters2344fae2001-01-15 00:50:52 +0000447 def xgtitle(self, group):
448 """Process an XGTITLE command (optional server extension) Arguments:
449 - group: group name wildcard (i.e. news.*)
450 Returns:
451 - resp: server response if successful
452 - list: list of (name,title) strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000453
Tim Peters2344fae2001-01-15 00:50:52 +0000454 line_pat = re.compile("^([^ \t]+)[ \t]+(.*)$")
455 resp, raw_lines = self.longcmd('XGTITLE ' + group)
456 lines = []
457 for raw_line in raw_lines:
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000458 match = line_pat.search(raw_line.strip())
Tim Peters2344fae2001-01-15 00:50:52 +0000459 if match:
460 lines.append(match.group(1, 2))
461 return resp, lines
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000462
Tim Peters2344fae2001-01-15 00:50:52 +0000463 def xpath(self,id):
464 """Process an XPATH command (optional server extension) Arguments:
465 - id: Message id of article
466 Returns:
467 resp: server response if successful
468 path: directory path to article"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000469
Tim Peters2344fae2001-01-15 00:50:52 +0000470 resp = self.shortcmd("XPATH " + id)
471 if resp[:3] != '223':
472 raise NNTPReplyError(resp)
473 try:
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000474 [resp_num, path] = resp.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000475 except ValueError:
476 raise NNTPReplyError(resp)
477 else:
478 return resp, path
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000479
Tim Peters2344fae2001-01-15 00:50:52 +0000480 def date (self):
481 """Process the DATE command. Arguments:
482 None
483 Returns:
484 resp: server response if successful
485 date: Date suitable for newnews/newgroups commands etc.
486 time: Time suitable for newnews/newgroups commands etc."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000487
Tim Peters2344fae2001-01-15 00:50:52 +0000488 resp = self.shortcmd("DATE")
489 if resp[:3] != '111':
490 raise NNTPReplyError(resp)
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000491 elem = resp.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000492 if len(elem) != 2:
493 raise NNTPDataError(resp)
494 date = elem[1][2:8]
495 time = elem[1][-6:]
496 if len(date) != 6 or len(time) != 6:
497 raise NNTPDataError(resp)
498 return resp, date, time
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000499
500
Tim Peters2344fae2001-01-15 00:50:52 +0000501 def post(self, f):
502 """Process a POST command. Arguments:
503 - f: file containing the article
504 Returns:
505 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000506
Tim Peters2344fae2001-01-15 00:50:52 +0000507 resp = self.shortcmd('POST')
508 # Raises error_??? if posting is not allowed
509 if resp[0] != '3':
510 raise NNTPReplyError(resp)
511 while 1:
512 line = f.readline()
513 if not line:
514 break
515 if line[-1] == '\n':
516 line = line[:-1]
517 if line[:1] == '.':
518 line = '.' + line
519 self.putline(line)
520 self.putline('.')
521 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000522
Tim Peters2344fae2001-01-15 00:50:52 +0000523 def ihave(self, id, f):
524 """Process an IHAVE command. Arguments:
525 - id: message-id of the article
526 - f: file containing the article
527 Returns:
528 - resp: server response if successful
529 Note that if the server refuses the article an exception is raised."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000530
Tim Peters2344fae2001-01-15 00:50:52 +0000531 resp = self.shortcmd('IHAVE ' + id)
532 # Raises error_??? if the server already has it
533 if resp[0] != '3':
534 raise NNTPReplyError(resp)
535 while 1:
536 line = f.readline()
537 if not line:
538 break
539 if line[-1] == '\n':
540 line = line[:-1]
541 if line[:1] == '.':
542 line = '.' + line
543 self.putline(line)
544 self.putline('.')
545 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000546
Tim Peters2344fae2001-01-15 00:50:52 +0000547 def quit(self):
548 """Process a QUIT command and close the socket. Returns:
549 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000550
Tim Peters2344fae2001-01-15 00:50:52 +0000551 resp = self.shortcmd('QUIT')
552 self.file.close()
553 self.sock.close()
554 del self.file, self.sock
555 return resp
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000556
557
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000558def _test():
Tim Peters2344fae2001-01-15 00:50:52 +0000559 """Minimal test function."""
560 s = NNTP('news', readermode='reader')
561 resp, count, first, last, name = s.group('comp.lang.python')
562 print resp
563 print 'Group', name, 'has', count, 'articles, range', first, 'to', last
564 resp, subs = s.xhdr('subject', first + '-' + last)
565 print resp
566 for item in subs:
567 print "%7s %s" % item
568 resp = s.quit()
569 print resp
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000570
571
572# Run the test when run as a script
573if __name__ == '__main__':
Tim Peters2344fae2001-01-15 00:50:52 +0000574 _test()