blob: fad6b08c6e9b64c72241449f571155bef06d1783 [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 Rossumd1d584f2001-10-01 13:46:55 +000034import types
Guido van Rossumc629d341992-11-05 10:43:02 +000035
Skip Montanaro269b83b2001-02-06 01:07:02 +000036__all__ = ["NNTP","NNTPReplyError","NNTPTemporaryError",
37 "NNTPPermanentError","NNTPProtocolError","NNTPDataError",
38 "error_reply","error_temp","error_perm","error_proto",
39 "error_data",]
Tim Peters2344fae2001-01-15 00:50:52 +000040
Barry Warsaw9dd78722000-02-10 20:25:53 +000041# Exceptions raised when an error or invalid response is received
42class NNTPError(Exception):
Tim Peters2344fae2001-01-15 00:50:52 +000043 """Base class for all nntplib exceptions"""
44 def __init__(self, *args):
45 apply(Exception.__init__, (self,)+args)
46 try:
47 self.response = args[0]
48 except IndexError:
49 self.response = 'No response given'
Barry Warsaw9dd78722000-02-10 20:25:53 +000050
51class NNTPReplyError(NNTPError):
Tim Peters2344fae2001-01-15 00:50:52 +000052 """Unexpected [123]xx reply"""
53 pass
Barry Warsaw9dd78722000-02-10 20:25:53 +000054
55class NNTPTemporaryError(NNTPError):
Tim Peters2344fae2001-01-15 00:50:52 +000056 """4xx errors"""
57 pass
Barry Warsaw9dd78722000-02-10 20:25:53 +000058
59class NNTPPermanentError(NNTPError):
Tim Peters2344fae2001-01-15 00:50:52 +000060 """5xx errors"""
61 pass
Barry Warsaw9dd78722000-02-10 20:25:53 +000062
63class NNTPProtocolError(NNTPError):
Tim Peters2344fae2001-01-15 00:50:52 +000064 """Response does not begin with [1-5]"""
65 pass
Barry Warsaw9dd78722000-02-10 20:25:53 +000066
67class NNTPDataError(NNTPError):
Tim Peters2344fae2001-01-15 00:50:52 +000068 """Error in response data"""
69 pass
Barry Warsaw9dd78722000-02-10 20:25:53 +000070
71# for backwards compatibility
72error_reply = NNTPReplyError
73error_temp = NNTPTemporaryError
74error_perm = NNTPPermanentError
75error_proto = NNTPProtocolError
76error_data = NNTPDataError
Guido van Rossumc629d341992-11-05 10:43:02 +000077
78
Tim Peters2344fae2001-01-15 00:50:52 +000079
Guido van Rossumc629d341992-11-05 10:43:02 +000080# Standard port used by NNTP servers
81NNTP_PORT = 119
82
83
84# Response numbers that are followed by additional text (e.g. article)
Guido van Rossum8421c4e1995-09-22 00:52:38 +000085LONGRESP = ['100', '215', '220', '221', '222', '224', '230', '231', '282']
Guido van Rossumc629d341992-11-05 10:43:02 +000086
87
88# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
89CRLF = '\r\n'
90
91
Tim Peters2344fae2001-01-15 00:50:52 +000092
Guido van Rossumc629d341992-11-05 10:43:02 +000093# The class itself
Guido van Rossumc629d341992-11-05 10:43:02 +000094class NNTP:
Tim Peters2344fae2001-01-15 00:50:52 +000095 def __init__(self, host, port=NNTP_PORT, user=None, password=None,
96 readermode=None):
97 """Initialize an instance. Arguments:
98 - host: hostname to connect to
99 - port: port to connect to (default the standard NNTP port)
100 - user: username to authenticate with
101 - password: password to use with username
102 - readermode: if true, send 'mode reader' command after
103 connecting.
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000104
Tim Peters2344fae2001-01-15 00:50:52 +0000105 readermode is sometimes necessary if you are connecting to an
106 NNTP server on the local machine and intend to call
107 reader-specific comamnds, such as `group'. If you get
108 unexpected NNTPPermanentErrors, you might need to set
109 readermode.
110 """
111 self.host = host
112 self.port = port
113 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
114 self.sock.connect((self.host, self.port))
115 self.file = self.sock.makefile('rb')
116 self.debugging = 0
117 self.welcome = self.getresp()
Tim Petersdfb673b2001-01-16 07:12:46 +0000118
Thomas Wouters47adcba2001-01-16 06:35:14 +0000119 # 'mode reader' is sometimes necessary to enable 'reader' mode.
Tim Petersdfb673b2001-01-16 07:12:46 +0000120 # However, the order in which 'mode reader' and 'authinfo' need to
Thomas Wouters47adcba2001-01-16 06:35:14 +0000121 # arrive differs between some NNTP servers. Try to send
122 # 'mode reader', and if it fails with an authorization failed
123 # error, try again after sending authinfo.
124 readermode_afterauth = 0
Tim Peters2344fae2001-01-15 00:50:52 +0000125 if readermode:
126 try:
127 self.welcome = self.shortcmd('mode reader')
128 except NNTPPermanentError:
129 # error 500, probably 'not implemented'
130 pass
Thomas Wouters47adcba2001-01-16 06:35:14 +0000131 except NNTPTemporaryError, e:
132 if user and e.response[:3] == '480':
133 # Need authorization before 'mode reader'
134 readermode_afterauth = 1
135 else:
136 raise
Tim Peters2344fae2001-01-15 00:50:52 +0000137 if user:
138 resp = self.shortcmd('authinfo user '+user)
139 if resp[:3] == '381':
140 if not password:
141 raise NNTPReplyError(resp)
142 else:
143 resp = self.shortcmd(
144 'authinfo pass '+password)
145 if resp[:3] != '281':
146 raise NNTPPermanentError(resp)
Thomas Wouters47adcba2001-01-16 06:35:14 +0000147 if readermode_afterauth:
148 try:
149 self.welcome = self.shortcmd('mode reader')
150 except NNTPPermanentError:
151 # error 500, probably 'not implemented'
152 pass
Tim Petersdfb673b2001-01-16 07:12:46 +0000153
Barry Warsaw9dd78722000-02-10 20:25:53 +0000154
Tim Peters2344fae2001-01-15 00:50:52 +0000155 # Get the welcome message from the server
156 # (this is read and squirreled away by __init__()).
157 # If the response code is 200, posting is allowed;
158 # if it 201, posting is not allowed
Guido van Rossumc629d341992-11-05 10:43:02 +0000159
Tim Peters2344fae2001-01-15 00:50:52 +0000160 def getwelcome(self):
161 """Get the welcome message from the server
162 (this is read and squirreled away by __init__()).
163 If the response code is 200, posting is allowed;
164 if it 201, posting is not allowed."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000165
Tim Peters2344fae2001-01-15 00:50:52 +0000166 if self.debugging: print '*welcome*', `self.welcome`
167 return self.welcome
Guido van Rossumc629d341992-11-05 10:43:02 +0000168
Tim Peters2344fae2001-01-15 00:50:52 +0000169 def set_debuglevel(self, level):
170 """Set the debugging level. Argument 'level' means:
171 0: no debugging output (default)
172 1: print commands and responses but not body text etc.
173 2: also print raw lines read and sent before stripping CR/LF"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000174
Tim Peters2344fae2001-01-15 00:50:52 +0000175 self.debugging = level
176 debug = set_debuglevel
Guido van Rossumc629d341992-11-05 10:43:02 +0000177
Tim Peters2344fae2001-01-15 00:50:52 +0000178 def putline(self, line):
179 """Internal: send one line to the server, appending CRLF."""
180 line = line + CRLF
181 if self.debugging > 1: print '*put*', `line`
182 self.sock.send(line)
Guido van Rossumc629d341992-11-05 10:43:02 +0000183
Tim Peters2344fae2001-01-15 00:50:52 +0000184 def putcmd(self, line):
185 """Internal: send one command to the server (through putline())."""
186 if self.debugging: print '*cmd*', `line`
187 self.putline(line)
Guido van Rossumc629d341992-11-05 10:43:02 +0000188
Tim Peters2344fae2001-01-15 00:50:52 +0000189 def getline(self):
190 """Internal: return one line from the server, stripping CRLF.
191 Raise EOFError if the connection is closed."""
192 line = self.file.readline()
193 if self.debugging > 1:
194 print '*get*', `line`
195 if not line: raise EOFError
196 if line[-2:] == CRLF: line = line[:-2]
197 elif line[-1:] in CRLF: line = line[:-1]
198 return line
Guido van Rossumc629d341992-11-05 10:43:02 +0000199
Tim Peters2344fae2001-01-15 00:50:52 +0000200 def getresp(self):
201 """Internal: get a response from the server.
202 Raise various errors if the response indicates an error."""
203 resp = self.getline()
204 if self.debugging: print '*resp*', `resp`
205 c = resp[:1]
206 if c == '4':
207 raise NNTPTemporaryError(resp)
208 if c == '5':
209 raise NNTPPermanentError(resp)
210 if c not in '123':
211 raise NNTPProtocolError(resp)
212 return resp
Guido van Rossumc629d341992-11-05 10:43:02 +0000213
Fredrik Lundha5e61652001-10-18 20:58:25 +0000214 def getlongresp(self, file=None):
Tim Peters2344fae2001-01-15 00:50:52 +0000215 """Internal: get a response plus following text from the server.
216 Raise various errors if the response indicates an error."""
Guido van Rossumd1d584f2001-10-01 13:46:55 +0000217
218 openedFile = None
219 try:
220 # If a string was passed then open a file with that name
Fredrik Lundha5e61652001-10-18 20:58:25 +0000221 if isinstance(file, types.StringType):
222 openedFile = file = open(file, "w")
Guido van Rossumd1d584f2001-10-01 13:46:55 +0000223
224 resp = self.getresp()
225 if resp[:3] not in LONGRESP:
226 raise NNTPReplyError(resp)
227 list = []
228 while 1:
229 line = self.getline()
230 if line == '.':
231 break
232 if line[:2] == '..':
233 line = line[1:]
Fredrik Lundha5e61652001-10-18 20:58:25 +0000234 if file:
235 file.write(line + "\n")
Guido van Rossumd1d584f2001-10-01 13:46:55 +0000236 else:
237 list.append(line)
238 finally:
239 # If this method created the file, then it must close it
240 if openedFile:
241 openedFile.close()
242
Tim Peters2344fae2001-01-15 00:50:52 +0000243 return resp, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000244
Tim Peters2344fae2001-01-15 00:50:52 +0000245 def shortcmd(self, line):
246 """Internal: send a command and get the response."""
247 self.putcmd(line)
248 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000249
Fredrik Lundha5e61652001-10-18 20:58:25 +0000250 def longcmd(self, line, file=None):
Tim Peters2344fae2001-01-15 00:50:52 +0000251 """Internal: send a command and get the response plus following text."""
252 self.putcmd(line)
Fredrik Lundha5e61652001-10-18 20:58:25 +0000253 return self.getlongresp(file)
Guido van Rossumc629d341992-11-05 10:43:02 +0000254
Tim Peters2344fae2001-01-15 00:50:52 +0000255 def newgroups(self, date, time):
256 """Process a NEWGROUPS command. Arguments:
257 - date: string 'yymmdd' indicating the date
258 - time: string 'hhmmss' indicating the time
259 Return:
260 - resp: server response if successful
261 - list: list of newsgroup names"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000262
Tim Peters2344fae2001-01-15 00:50:52 +0000263 return self.longcmd('NEWGROUPS ' + date + ' ' + time)
Guido van Rossumc629d341992-11-05 10:43:02 +0000264
Tim Peters2344fae2001-01-15 00:50:52 +0000265 def newnews(self, group, date, time):
266 """Process a NEWNEWS command. Arguments:
267 - group: group name or '*'
268 - date: string 'yymmdd' indicating the date
269 - time: string 'hhmmss' indicating the time
270 Return:
271 - resp: server response if successful
272 - list: list of article ids"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000273
Tim Peters2344fae2001-01-15 00:50:52 +0000274 cmd = 'NEWNEWS ' + group + ' ' + date + ' ' + time
275 return self.longcmd(cmd)
Guido van Rossumc629d341992-11-05 10:43:02 +0000276
Tim Peters2344fae2001-01-15 00:50:52 +0000277 def list(self):
278 """Process a LIST command. Return:
279 - resp: server response if successful
280 - list: list of (group, last, first, flag) (strings)"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000281
Tim Peters2344fae2001-01-15 00:50:52 +0000282 resp, list = self.longcmd('LIST')
283 for i in range(len(list)):
284 # Parse lines into "group last first flag"
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000285 list[i] = tuple(list[i].split())
Tim Peters2344fae2001-01-15 00:50:52 +0000286 return resp, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000287
Tim Peters2344fae2001-01-15 00:50:52 +0000288 def group(self, name):
289 """Process a GROUP command. Argument:
290 - group: the group name
291 Returns:
292 - resp: server response if successful
293 - count: number of articles (string)
294 - first: first article number (string)
295 - last: last article number (string)
296 - name: the group name"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000297
Tim Peters2344fae2001-01-15 00:50:52 +0000298 resp = self.shortcmd('GROUP ' + name)
299 if resp[:3] != '211':
300 raise NNTPReplyError(resp)
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000301 words = resp.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000302 count = first = last = 0
303 n = len(words)
304 if n > 1:
305 count = words[1]
306 if n > 2:
307 first = words[2]
308 if n > 3:
309 last = words[3]
310 if n > 4:
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000311 name = words[4].lower()
Tim Peters2344fae2001-01-15 00:50:52 +0000312 return resp, count, first, last, name
Guido van Rossumc629d341992-11-05 10:43:02 +0000313
Tim Peters2344fae2001-01-15 00:50:52 +0000314 def help(self):
315 """Process a HELP command. Returns:
316 - resp: server response if successful
317 - list: list of strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000318
Tim Peters2344fae2001-01-15 00:50:52 +0000319 return self.longcmd('HELP')
Guido van Rossumc629d341992-11-05 10:43:02 +0000320
Tim Peters2344fae2001-01-15 00:50:52 +0000321 def statparse(self, resp):
322 """Internal: parse the response of a STAT, NEXT or LAST command."""
323 if resp[:2] != '22':
324 raise NNTPReplyError(resp)
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000325 words = resp.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000326 nr = 0
327 id = ''
328 n = len(words)
329 if n > 1:
330 nr = words[1]
331 if n > 2:
332 id = words[2]
333 return resp, nr, id
Guido van Rossumc629d341992-11-05 10:43:02 +0000334
Tim Peters2344fae2001-01-15 00:50:52 +0000335 def statcmd(self, line):
336 """Internal: process a STAT, NEXT or LAST command."""
337 resp = self.shortcmd(line)
338 return self.statparse(resp)
Guido van Rossumc629d341992-11-05 10:43:02 +0000339
Tim Peters2344fae2001-01-15 00:50:52 +0000340 def stat(self, id):
341 """Process a STAT command. Argument:
342 - id: article number or message id
343 Returns:
344 - resp: server response if successful
345 - nr: the article number
346 - id: the article id"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000347
Tim Peters2344fae2001-01-15 00:50:52 +0000348 return self.statcmd('STAT ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000349
Tim Peters2344fae2001-01-15 00:50:52 +0000350 def next(self):
351 """Process a NEXT command. No arguments. Return as for STAT."""
352 return self.statcmd('NEXT')
Guido van Rossumc629d341992-11-05 10:43:02 +0000353
Tim Peters2344fae2001-01-15 00:50:52 +0000354 def last(self):
355 """Process a LAST command. No arguments. Return as for STAT."""
356 return self.statcmd('LAST')
Guido van Rossumc629d341992-11-05 10:43:02 +0000357
Fredrik Lundha5e61652001-10-18 20:58:25 +0000358 def artcmd(self, line, file=None):
Tim Peters2344fae2001-01-15 00:50:52 +0000359 """Internal: process a HEAD, BODY or ARTICLE command."""
Fredrik Lundha5e61652001-10-18 20:58:25 +0000360 resp, list = self.longcmd(line, file)
Tim Peters2344fae2001-01-15 00:50:52 +0000361 resp, nr, id = self.statparse(resp)
362 return resp, nr, id, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000363
Tim Peters2344fae2001-01-15 00:50:52 +0000364 def head(self, id):
365 """Process a HEAD command. Argument:
366 - id: article number or message id
367 Returns:
368 - resp: server response if successful
369 - nr: article number
370 - id: message id
371 - list: the lines of the article's header"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000372
Tim Peters2344fae2001-01-15 00:50:52 +0000373 return self.artcmd('HEAD ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000374
Fredrik Lundha5e61652001-10-18 20:58:25 +0000375 def body(self, id, file=None):
Tim Peters2344fae2001-01-15 00:50:52 +0000376 """Process a BODY command. Argument:
377 - id: article number or message id
Fredrik Lundha5e61652001-10-18 20:58:25 +0000378 - file: Filename string or file object to store the article in
Tim Peters2344fae2001-01-15 00:50:52 +0000379 Returns:
380 - resp: server response if successful
381 - nr: article number
382 - id: message id
Guido van Rossumd1d584f2001-10-01 13:46:55 +0000383 - list: the lines of the article's body or an empty list
Fredrik Lundha5e61652001-10-18 20:58:25 +0000384 if file was used"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000385
Fredrik Lundha5e61652001-10-18 20:58:25 +0000386 return self.artcmd('BODY ' + id, file)
Guido van Rossumc629d341992-11-05 10:43:02 +0000387
Tim Peters2344fae2001-01-15 00:50:52 +0000388 def article(self, id):
389 """Process an ARTICLE command. Argument:
390 - id: article number or message id
391 Returns:
392 - resp: server response if successful
393 - nr: article number
394 - id: message id
395 - list: the lines of the article"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000396
Tim Peters2344fae2001-01-15 00:50:52 +0000397 return self.artcmd('ARTICLE ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000398
Tim Peters2344fae2001-01-15 00:50:52 +0000399 def slave(self):
400 """Process a SLAVE command. Returns:
401 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000402
Tim Peters2344fae2001-01-15 00:50:52 +0000403 return self.shortcmd('SLAVE')
Guido van Rossumc629d341992-11-05 10:43:02 +0000404
Tim Peters2344fae2001-01-15 00:50:52 +0000405 def xhdr(self, hdr, str):
406 """Process an XHDR command (optional server extension). Arguments:
407 - hdr: the header type (e.g. 'subject')
408 - str: an article nr, a message id, or a range nr1-nr2
409 Returns:
410 - resp: server response if successful
411 - list: list of (nr, value) strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000412
Tim Peters2344fae2001-01-15 00:50:52 +0000413 pat = re.compile('^([0-9]+) ?(.*)\n?')
414 resp, lines = self.longcmd('XHDR ' + hdr + ' ' + str)
415 for i in range(len(lines)):
416 line = lines[i]
417 m = pat.match(line)
418 if m:
419 lines[i] = m.group(1, 2)
420 return resp, lines
Guido van Rossumc629d341992-11-05 10:43:02 +0000421
Tim Peters2344fae2001-01-15 00:50:52 +0000422 def xover(self,start,end):
423 """Process an XOVER command (optional server extension) Arguments:
424 - start: start of range
425 - end: end of range
426 Returns:
427 - resp: server response if successful
428 - list: list of (art-nr, subject, poster, date,
429 id, references, size, lines)"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000430
Tim Peters2344fae2001-01-15 00:50:52 +0000431 resp, lines = self.longcmd('XOVER ' + start + '-' + end)
432 xover_lines = []
433 for line in lines:
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000434 elem = line.split("\t")
Tim Peters2344fae2001-01-15 00:50:52 +0000435 try:
436 xover_lines.append((elem[0],
437 elem[1],
438 elem[2],
439 elem[3],
440 elem[4],
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000441 elem[5].split(),
Tim Peters2344fae2001-01-15 00:50:52 +0000442 elem[6],
443 elem[7]))
444 except IndexError:
445 raise NNTPDataError(line)
446 return resp,xover_lines
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000447
Tim Peters2344fae2001-01-15 00:50:52 +0000448 def xgtitle(self, group):
449 """Process an XGTITLE command (optional server extension) Arguments:
450 - group: group name wildcard (i.e. news.*)
451 Returns:
452 - resp: server response if successful
453 - list: list of (name,title) strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000454
Tim Peters2344fae2001-01-15 00:50:52 +0000455 line_pat = re.compile("^([^ \t]+)[ \t]+(.*)$")
456 resp, raw_lines = self.longcmd('XGTITLE ' + group)
457 lines = []
458 for raw_line in raw_lines:
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000459 match = line_pat.search(raw_line.strip())
Tim Peters2344fae2001-01-15 00:50:52 +0000460 if match:
461 lines.append(match.group(1, 2))
462 return resp, lines
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000463
Tim Peters2344fae2001-01-15 00:50:52 +0000464 def xpath(self,id):
465 """Process an XPATH command (optional server extension) Arguments:
466 - id: Message id of article
467 Returns:
468 resp: server response if successful
469 path: directory path to article"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000470
Tim Peters2344fae2001-01-15 00:50:52 +0000471 resp = self.shortcmd("XPATH " + id)
472 if resp[:3] != '223':
473 raise NNTPReplyError(resp)
474 try:
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000475 [resp_num, path] = resp.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000476 except ValueError:
477 raise NNTPReplyError(resp)
478 else:
479 return resp, path
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000480
Tim Peters2344fae2001-01-15 00:50:52 +0000481 def date (self):
482 """Process the DATE command. Arguments:
483 None
484 Returns:
485 resp: server response if successful
486 date: Date suitable for newnews/newgroups commands etc.
487 time: Time suitable for newnews/newgroups commands etc."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000488
Tim Peters2344fae2001-01-15 00:50:52 +0000489 resp = self.shortcmd("DATE")
490 if resp[:3] != '111':
491 raise NNTPReplyError(resp)
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000492 elem = resp.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000493 if len(elem) != 2:
494 raise NNTPDataError(resp)
495 date = elem[1][2:8]
496 time = elem[1][-6:]
497 if len(date) != 6 or len(time) != 6:
498 raise NNTPDataError(resp)
499 return resp, date, time
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000500
501
Tim Peters2344fae2001-01-15 00:50:52 +0000502 def post(self, f):
503 """Process a POST command. Arguments:
504 - f: file containing the article
505 Returns:
506 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000507
Tim Peters2344fae2001-01-15 00:50:52 +0000508 resp = self.shortcmd('POST')
509 # Raises error_??? if posting is not allowed
510 if resp[0] != '3':
511 raise NNTPReplyError(resp)
512 while 1:
513 line = f.readline()
514 if not line:
515 break
516 if line[-1] == '\n':
517 line = line[:-1]
518 if line[:1] == '.':
519 line = '.' + line
520 self.putline(line)
521 self.putline('.')
522 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000523
Tim Peters2344fae2001-01-15 00:50:52 +0000524 def ihave(self, id, f):
525 """Process an IHAVE command. Arguments:
526 - id: message-id of the article
527 - f: file containing the article
528 Returns:
529 - resp: server response if successful
530 Note that if the server refuses the article an exception is raised."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000531
Tim Peters2344fae2001-01-15 00:50:52 +0000532 resp = self.shortcmd('IHAVE ' + id)
533 # Raises error_??? if the server already has it
534 if resp[0] != '3':
535 raise NNTPReplyError(resp)
536 while 1:
537 line = f.readline()
538 if not line:
539 break
540 if line[-1] == '\n':
541 line = line[:-1]
542 if line[:1] == '.':
543 line = '.' + line
544 self.putline(line)
545 self.putline('.')
546 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000547
Tim Peters2344fae2001-01-15 00:50:52 +0000548 def quit(self):
549 """Process a QUIT command and close the socket. Returns:
550 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000551
Tim Peters2344fae2001-01-15 00:50:52 +0000552 resp = self.shortcmd('QUIT')
553 self.file.close()
554 self.sock.close()
555 del self.file, self.sock
556 return resp
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000557
558
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000559def _test():
Tim Peters2344fae2001-01-15 00:50:52 +0000560 """Minimal test function."""
561 s = NNTP('news', readermode='reader')
562 resp, count, first, last, name = s.group('comp.lang.python')
563 print resp
564 print 'Group', name, 'has', count, 'articles, range', first, 'to', last
565 resp, subs = s.xhdr('subject', first + '-' + last)
566 print resp
567 for item in subs:
568 print "%7s %s" % item
569 resp = s.quit()
570 print resp
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000571
572
573# Run the test when run as a script
574if __name__ == '__main__':
Tim Peters2344fae2001-01-15 00:50:52 +0000575 _test()