blob: 618e4b8d0add031bc7d5bb196c1c4eac15683357 [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
Eric S. Raymondb2db5872002-11-13 23:05:35 +0000136 # If no login/password was specified, try to get them from ~/.netrc
137 # Presume that if .netc has an entry, NNRP authentication is required.
138 if not user:
139 import netrc
140 credentials = netrc.netrc()
141 auth = credentials.authenticators(host)
142 if auth:
143 user = auth[0]
144 password = auth[2]
145 # Perform NNRP authentication if needed.
Tim Peters2344fae2001-01-15 00:50:52 +0000146 if user:
147 resp = self.shortcmd('authinfo user '+user)
148 if resp[:3] == '381':
149 if not password:
150 raise NNTPReplyError(resp)
151 else:
152 resp = self.shortcmd(
153 'authinfo pass '+password)
154 if resp[:3] != '281':
155 raise NNTPPermanentError(resp)
Thomas Wouters47adcba2001-01-16 06:35:14 +0000156 if readermode_afterauth:
157 try:
158 self.welcome = self.shortcmd('mode reader')
159 except NNTPPermanentError:
160 # error 500, probably 'not implemented'
161 pass
Tim Petersdfb673b2001-01-16 07:12:46 +0000162
Barry Warsaw9dd78722000-02-10 20:25:53 +0000163
Tim Peters2344fae2001-01-15 00:50:52 +0000164 # Get the welcome message from the server
165 # (this is read and squirreled away by __init__()).
166 # If the response code is 200, posting is allowed;
167 # if it 201, posting is not allowed
Guido van Rossumc629d341992-11-05 10:43:02 +0000168
Tim Peters2344fae2001-01-15 00:50:52 +0000169 def getwelcome(self):
170 """Get the welcome message from the server
171 (this is read and squirreled away by __init__()).
172 If the response code is 200, posting is allowed;
173 if it 201, posting is not allowed."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000174
Tim Peters2344fae2001-01-15 00:50:52 +0000175 if self.debugging: print '*welcome*', `self.welcome`
176 return self.welcome
Guido van Rossumc629d341992-11-05 10:43:02 +0000177
Tim Peters2344fae2001-01-15 00:50:52 +0000178 def set_debuglevel(self, level):
179 """Set the debugging level. Argument 'level' means:
180 0: no debugging output (default)
181 1: print commands and responses but not body text etc.
182 2: also print raw lines read and sent before stripping CR/LF"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000183
Tim Peters2344fae2001-01-15 00:50:52 +0000184 self.debugging = level
185 debug = set_debuglevel
Guido van Rossumc629d341992-11-05 10:43:02 +0000186
Tim Peters2344fae2001-01-15 00:50:52 +0000187 def putline(self, line):
188 """Internal: send one line to the server, appending CRLF."""
189 line = line + CRLF
190 if self.debugging > 1: print '*put*', `line`
Martin v. Löwise12454f2002-02-16 23:06:19 +0000191 self.sock.sendall(line)
Guido van Rossumc629d341992-11-05 10:43:02 +0000192
Tim Peters2344fae2001-01-15 00:50:52 +0000193 def putcmd(self, line):
194 """Internal: send one command to the server (through putline())."""
195 if self.debugging: print '*cmd*', `line`
196 self.putline(line)
Guido van Rossumc629d341992-11-05 10:43:02 +0000197
Tim Peters2344fae2001-01-15 00:50:52 +0000198 def getline(self):
199 """Internal: return one line from the server, stripping CRLF.
200 Raise EOFError if the connection is closed."""
201 line = self.file.readline()
202 if self.debugging > 1:
203 print '*get*', `line`
204 if not line: raise EOFError
205 if line[-2:] == CRLF: line = line[:-2]
206 elif line[-1:] in CRLF: line = line[:-1]
207 return line
Guido van Rossumc629d341992-11-05 10:43:02 +0000208
Tim Peters2344fae2001-01-15 00:50:52 +0000209 def getresp(self):
210 """Internal: get a response from the server.
211 Raise various errors if the response indicates an error."""
212 resp = self.getline()
213 if self.debugging: print '*resp*', `resp`
214 c = resp[:1]
215 if c == '4':
216 raise NNTPTemporaryError(resp)
217 if c == '5':
218 raise NNTPPermanentError(resp)
219 if c not in '123':
220 raise NNTPProtocolError(resp)
221 return resp
Guido van Rossumc629d341992-11-05 10:43:02 +0000222
Fredrik Lundha5e61652001-10-18 20:58:25 +0000223 def getlongresp(self, file=None):
Tim Peters2344fae2001-01-15 00:50:52 +0000224 """Internal: get a response plus following text from the server.
225 Raise various errors if the response indicates an error."""
Guido van Rossumd1d584f2001-10-01 13:46:55 +0000226
227 openedFile = None
228 try:
229 # If a string was passed then open a file with that name
Walter Dörwald65230a22002-06-03 15:58:32 +0000230 if isinstance(file, str):
Fredrik Lundha5e61652001-10-18 20:58:25 +0000231 openedFile = file = open(file, "w")
Guido van Rossumd1d584f2001-10-01 13:46:55 +0000232
233 resp = self.getresp()
234 if resp[:3] not in LONGRESP:
235 raise NNTPReplyError(resp)
236 list = []
237 while 1:
238 line = self.getline()
239 if line == '.':
240 break
241 if line[:2] == '..':
242 line = line[1:]
Fredrik Lundha5e61652001-10-18 20:58:25 +0000243 if file:
244 file.write(line + "\n")
Guido van Rossumd1d584f2001-10-01 13:46:55 +0000245 else:
246 list.append(line)
247 finally:
248 # If this method created the file, then it must close it
249 if openedFile:
250 openedFile.close()
251
Tim Peters2344fae2001-01-15 00:50:52 +0000252 return resp, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000253
Tim Peters2344fae2001-01-15 00:50:52 +0000254 def shortcmd(self, line):
255 """Internal: send a command and get the response."""
256 self.putcmd(line)
257 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000258
Fredrik Lundha5e61652001-10-18 20:58:25 +0000259 def longcmd(self, line, file=None):
Tim Peters2344fae2001-01-15 00:50:52 +0000260 """Internal: send a command and get the response plus following text."""
261 self.putcmd(line)
Fredrik Lundha5e61652001-10-18 20:58:25 +0000262 return self.getlongresp(file)
Guido van Rossumc629d341992-11-05 10:43:02 +0000263
Tim Peters2344fae2001-01-15 00:50:52 +0000264 def newgroups(self, date, time):
265 """Process a NEWGROUPS command. Arguments:
266 - date: string 'yymmdd' indicating the date
267 - time: string 'hhmmss' indicating the time
268 Return:
269 - resp: server response if successful
270 - list: list of newsgroup names"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000271
Tim Peters2344fae2001-01-15 00:50:52 +0000272 return self.longcmd('NEWGROUPS ' + date + ' ' + time)
Guido van Rossumc629d341992-11-05 10:43:02 +0000273
Tim Peters2344fae2001-01-15 00:50:52 +0000274 def newnews(self, group, date, time):
275 """Process a NEWNEWS command. Arguments:
276 - group: group name or '*'
277 - date: string 'yymmdd' indicating the date
278 - time: string 'hhmmss' indicating the time
279 Return:
280 - resp: server response if successful
281 - list: list of article ids"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000282
Tim Peters2344fae2001-01-15 00:50:52 +0000283 cmd = 'NEWNEWS ' + group + ' ' + date + ' ' + time
284 return self.longcmd(cmd)
Guido van Rossumc629d341992-11-05 10:43:02 +0000285
Tim Peters2344fae2001-01-15 00:50:52 +0000286 def list(self):
287 """Process a LIST command. Return:
288 - resp: server response if successful
289 - list: list of (group, last, first, flag) (strings)"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000290
Tim Peters2344fae2001-01-15 00:50:52 +0000291 resp, list = self.longcmd('LIST')
292 for i in range(len(list)):
293 # Parse lines into "group last first flag"
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000294 list[i] = tuple(list[i].split())
Tim Peters2344fae2001-01-15 00:50:52 +0000295 return resp, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000296
Tim Peters2344fae2001-01-15 00:50:52 +0000297 def group(self, name):
298 """Process a GROUP command. Argument:
299 - group: the group name
300 Returns:
301 - resp: server response if successful
302 - count: number of articles (string)
303 - first: first article number (string)
304 - last: last article number (string)
305 - name: the group name"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000306
Tim Peters2344fae2001-01-15 00:50:52 +0000307 resp = self.shortcmd('GROUP ' + name)
308 if resp[:3] != '211':
309 raise NNTPReplyError(resp)
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000310 words = resp.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000311 count = first = last = 0
312 n = len(words)
313 if n > 1:
314 count = words[1]
315 if n > 2:
316 first = words[2]
317 if n > 3:
318 last = words[3]
319 if n > 4:
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000320 name = words[4].lower()
Tim Peters2344fae2001-01-15 00:50:52 +0000321 return resp, count, first, last, name
Guido van Rossumc629d341992-11-05 10:43:02 +0000322
Tim Peters2344fae2001-01-15 00:50:52 +0000323 def help(self):
324 """Process a HELP command. Returns:
325 - resp: server response if successful
326 - list: list of strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000327
Tim Peters2344fae2001-01-15 00:50:52 +0000328 return self.longcmd('HELP')
Guido van Rossumc629d341992-11-05 10:43:02 +0000329
Tim Peters2344fae2001-01-15 00:50:52 +0000330 def statparse(self, resp):
331 """Internal: parse the response of a STAT, NEXT or LAST command."""
332 if resp[:2] != '22':
333 raise NNTPReplyError(resp)
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000334 words = resp.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000335 nr = 0
336 id = ''
337 n = len(words)
338 if n > 1:
339 nr = words[1]
340 if n > 2:
341 id = words[2]
342 return resp, nr, id
Guido van Rossumc629d341992-11-05 10:43:02 +0000343
Tim Peters2344fae2001-01-15 00:50:52 +0000344 def statcmd(self, line):
345 """Internal: process a STAT, NEXT or LAST command."""
346 resp = self.shortcmd(line)
347 return self.statparse(resp)
Guido van Rossumc629d341992-11-05 10:43:02 +0000348
Tim Peters2344fae2001-01-15 00:50:52 +0000349 def stat(self, id):
350 """Process a STAT command. Argument:
351 - id: article number or message id
352 Returns:
353 - resp: server response if successful
354 - nr: the article number
355 - id: the article id"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000356
Tim Peters2344fae2001-01-15 00:50:52 +0000357 return self.statcmd('STAT ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000358
Tim Peters2344fae2001-01-15 00:50:52 +0000359 def next(self):
360 """Process a NEXT command. No arguments. Return as for STAT."""
361 return self.statcmd('NEXT')
Guido van Rossumc629d341992-11-05 10:43:02 +0000362
Tim Peters2344fae2001-01-15 00:50:52 +0000363 def last(self):
364 """Process a LAST command. No arguments. Return as for STAT."""
365 return self.statcmd('LAST')
Guido van Rossumc629d341992-11-05 10:43:02 +0000366
Fredrik Lundha5e61652001-10-18 20:58:25 +0000367 def artcmd(self, line, file=None):
Tim Peters2344fae2001-01-15 00:50:52 +0000368 """Internal: process a HEAD, BODY or ARTICLE command."""
Fredrik Lundha5e61652001-10-18 20:58:25 +0000369 resp, list = self.longcmd(line, file)
Tim Peters2344fae2001-01-15 00:50:52 +0000370 resp, nr, id = self.statparse(resp)
371 return resp, nr, id, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000372
Tim Peters2344fae2001-01-15 00:50:52 +0000373 def head(self, id):
374 """Process a HEAD command. Argument:
375 - id: article number or message id
376 Returns:
377 - resp: server response if successful
378 - nr: article number
379 - id: message id
380 - list: the lines of the article's header"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000381
Tim Peters2344fae2001-01-15 00:50:52 +0000382 return self.artcmd('HEAD ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000383
Fredrik Lundha5e61652001-10-18 20:58:25 +0000384 def body(self, id, file=None):
Tim Peters2344fae2001-01-15 00:50:52 +0000385 """Process a BODY command. Argument:
386 - id: article number or message id
Fredrik Lundha5e61652001-10-18 20:58:25 +0000387 - file: Filename string or file object to store the article in
Tim Peters2344fae2001-01-15 00:50:52 +0000388 Returns:
389 - resp: server response if successful
390 - nr: article number
391 - id: message id
Guido van Rossumd1d584f2001-10-01 13:46:55 +0000392 - list: the lines of the article's body or an empty list
Fredrik Lundha5e61652001-10-18 20:58:25 +0000393 if file was used"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000394
Fredrik Lundha5e61652001-10-18 20:58:25 +0000395 return self.artcmd('BODY ' + id, file)
Guido van Rossumc629d341992-11-05 10:43:02 +0000396
Tim Peters2344fae2001-01-15 00:50:52 +0000397 def article(self, id):
398 """Process an ARTICLE command. Argument:
399 - id: article number or message id
400 Returns:
401 - resp: server response if successful
402 - nr: article number
403 - id: message id
404 - list: the lines of the article"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000405
Tim Peters2344fae2001-01-15 00:50:52 +0000406 return self.artcmd('ARTICLE ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000407
Tim Peters2344fae2001-01-15 00:50:52 +0000408 def slave(self):
409 """Process a SLAVE command. Returns:
410 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000411
Tim Peters2344fae2001-01-15 00:50:52 +0000412 return self.shortcmd('SLAVE')
Guido van Rossumc629d341992-11-05 10:43:02 +0000413
Tim Peters2344fae2001-01-15 00:50:52 +0000414 def xhdr(self, hdr, str):
415 """Process an XHDR command (optional server extension). Arguments:
416 - hdr: the header type (e.g. 'subject')
417 - str: an article nr, a message id, or a range nr1-nr2
418 Returns:
419 - resp: server response if successful
420 - list: list of (nr, value) strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000421
Tim Peters2344fae2001-01-15 00:50:52 +0000422 pat = re.compile('^([0-9]+) ?(.*)\n?')
423 resp, lines = self.longcmd('XHDR ' + hdr + ' ' + str)
424 for i in range(len(lines)):
425 line = lines[i]
426 m = pat.match(line)
427 if m:
428 lines[i] = m.group(1, 2)
429 return resp, lines
Guido van Rossumc629d341992-11-05 10:43:02 +0000430
Tim Peters2344fae2001-01-15 00:50:52 +0000431 def xover(self,start,end):
432 """Process an XOVER command (optional server extension) Arguments:
433 - start: start of range
434 - end: end of range
435 Returns:
436 - resp: server response if successful
437 - list: list of (art-nr, subject, poster, date,
438 id, references, size, lines)"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000439
Tim Peters2344fae2001-01-15 00:50:52 +0000440 resp, lines = self.longcmd('XOVER ' + start + '-' + end)
441 xover_lines = []
442 for line in lines:
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000443 elem = line.split("\t")
Tim Peters2344fae2001-01-15 00:50:52 +0000444 try:
445 xover_lines.append((elem[0],
446 elem[1],
447 elem[2],
448 elem[3],
449 elem[4],
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000450 elem[5].split(),
Tim Peters2344fae2001-01-15 00:50:52 +0000451 elem[6],
452 elem[7]))
453 except IndexError:
454 raise NNTPDataError(line)
455 return resp,xover_lines
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000456
Tim Peters2344fae2001-01-15 00:50:52 +0000457 def xgtitle(self, group):
458 """Process an XGTITLE command (optional server extension) Arguments:
459 - group: group name wildcard (i.e. news.*)
460 Returns:
461 - resp: server response if successful
462 - list: list of (name,title) strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000463
Tim Peters2344fae2001-01-15 00:50:52 +0000464 line_pat = re.compile("^([^ \t]+)[ \t]+(.*)$")
465 resp, raw_lines = self.longcmd('XGTITLE ' + group)
466 lines = []
467 for raw_line in raw_lines:
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000468 match = line_pat.search(raw_line.strip())
Tim Peters2344fae2001-01-15 00:50:52 +0000469 if match:
470 lines.append(match.group(1, 2))
471 return resp, lines
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000472
Tim Peters2344fae2001-01-15 00:50:52 +0000473 def xpath(self,id):
474 """Process an XPATH command (optional server extension) Arguments:
475 - id: Message id of article
476 Returns:
477 resp: server response if successful
478 path: directory path to article"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000479
Tim Peters2344fae2001-01-15 00:50:52 +0000480 resp = self.shortcmd("XPATH " + id)
481 if resp[:3] != '223':
482 raise NNTPReplyError(resp)
483 try:
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000484 [resp_num, path] = resp.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000485 except ValueError:
486 raise NNTPReplyError(resp)
487 else:
488 return resp, path
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000489
Tim Peters2344fae2001-01-15 00:50:52 +0000490 def date (self):
491 """Process the DATE command. Arguments:
492 None
493 Returns:
494 resp: server response if successful
495 date: Date suitable for newnews/newgroups commands etc.
496 time: Time suitable for newnews/newgroups commands etc."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000497
Tim Peters2344fae2001-01-15 00:50:52 +0000498 resp = self.shortcmd("DATE")
499 if resp[:3] != '111':
500 raise NNTPReplyError(resp)
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000501 elem = resp.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000502 if len(elem) != 2:
503 raise NNTPDataError(resp)
504 date = elem[1][2:8]
505 time = elem[1][-6:]
506 if len(date) != 6 or len(time) != 6:
507 raise NNTPDataError(resp)
508 return resp, date, time
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000509
510
Tim Peters2344fae2001-01-15 00:50:52 +0000511 def post(self, f):
512 """Process a POST command. Arguments:
513 - f: file containing the article
514 Returns:
515 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000516
Tim Peters2344fae2001-01-15 00:50:52 +0000517 resp = self.shortcmd('POST')
518 # Raises error_??? if posting is not allowed
519 if resp[0] != '3':
520 raise NNTPReplyError(resp)
521 while 1:
522 line = f.readline()
523 if not line:
524 break
525 if line[-1] == '\n':
526 line = line[:-1]
527 if line[:1] == '.':
528 line = '.' + line
529 self.putline(line)
530 self.putline('.')
531 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000532
Tim Peters2344fae2001-01-15 00:50:52 +0000533 def ihave(self, id, f):
534 """Process an IHAVE command. Arguments:
535 - id: message-id of the article
536 - f: file containing the article
537 Returns:
538 - resp: server response if successful
539 Note that if the server refuses the article an exception is raised."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000540
Tim Peters2344fae2001-01-15 00:50:52 +0000541 resp = self.shortcmd('IHAVE ' + id)
542 # Raises error_??? if the server already has it
543 if resp[0] != '3':
544 raise NNTPReplyError(resp)
545 while 1:
546 line = f.readline()
547 if not line:
548 break
549 if line[-1] == '\n':
550 line = line[:-1]
551 if line[:1] == '.':
552 line = '.' + line
553 self.putline(line)
554 self.putline('.')
555 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000556
Tim Peters2344fae2001-01-15 00:50:52 +0000557 def quit(self):
558 """Process a QUIT command and close the socket. Returns:
559 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000560
Tim Peters2344fae2001-01-15 00:50:52 +0000561 resp = self.shortcmd('QUIT')
562 self.file.close()
563 self.sock.close()
564 del self.file, self.sock
565 return resp
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000566
567
Neal Norwitzef679562002-11-14 02:19:44 +0000568# Test retrieval when run as a script.
Eric S. Raymondb2db5872002-11-13 23:05:35 +0000569# Assumption: if there's a local news server, it's called 'news'.
570# Assumption: if user queries a remote news server, it's named
571# in the environment variable NNTPSERVER (used by slrn and kin)
572# and we want readermode off.
573if __name__ == '__main__':
574 import os
575 newshost = 'news' and os.environ["NNTPSERVER"]
576 if newshost.find('.') == -1:
577 mode = 'readermode'
578 else:
579 mode = None
580 s = NNTP(newshost, readermode=mode)
Tim Peters2344fae2001-01-15 00:50:52 +0000581 resp, count, first, last, name = s.group('comp.lang.python')
582 print resp
583 print 'Group', name, 'has', count, 'articles, range', first, 'to', last
584 resp, subs = s.xhdr('subject', first + '-' + last)
585 print resp
586 for item in subs:
587 print "%7s %s" % item
588 resp = s.quit()
589 print resp
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000590