blob: ea831cf525251ed29c1dcb59da0f7829b79922c4 [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.
Eric S. Raymond782d9402002-11-17 17:53:12 +0000138 try:
139 if not user:
140 import netrc
141 credentials = netrc.netrc()
142 auth = credentials.authenticators(host)
143 if auth:
144 user = auth[0]
145 password = auth[2]
146 except IOError:
147 pass
Eric S. Raymondb2db5872002-11-13 23:05:35 +0000148 # Perform NNRP authentication if needed.
Tim Peters2344fae2001-01-15 00:50:52 +0000149 if user:
150 resp = self.shortcmd('authinfo user '+user)
151 if resp[:3] == '381':
152 if not password:
153 raise NNTPReplyError(resp)
154 else:
155 resp = self.shortcmd(
156 'authinfo pass '+password)
157 if resp[:3] != '281':
158 raise NNTPPermanentError(resp)
Thomas Wouters47adcba2001-01-16 06:35:14 +0000159 if readermode_afterauth:
160 try:
161 self.welcome = self.shortcmd('mode reader')
162 except NNTPPermanentError:
163 # error 500, probably 'not implemented'
164 pass
Tim Petersdfb673b2001-01-16 07:12:46 +0000165
Barry Warsaw9dd78722000-02-10 20:25:53 +0000166
Tim Peters2344fae2001-01-15 00:50:52 +0000167 # Get the welcome message from the server
168 # (this is read and squirreled away by __init__()).
169 # If the response code is 200, posting is allowed;
170 # if it 201, posting is not allowed
Guido van Rossumc629d341992-11-05 10:43:02 +0000171
Tim Peters2344fae2001-01-15 00:50:52 +0000172 def getwelcome(self):
173 """Get the welcome message from the server
174 (this is read and squirreled away by __init__()).
175 If the response code is 200, posting is allowed;
176 if it 201, posting is not allowed."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000177
Tim Peters2344fae2001-01-15 00:50:52 +0000178 if self.debugging: print '*welcome*', `self.welcome`
179 return self.welcome
Guido van Rossumc629d341992-11-05 10:43:02 +0000180
Tim Peters2344fae2001-01-15 00:50:52 +0000181 def set_debuglevel(self, level):
182 """Set the debugging level. Argument 'level' means:
183 0: no debugging output (default)
184 1: print commands and responses but not body text etc.
185 2: also print raw lines read and sent before stripping CR/LF"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000186
Tim Peters2344fae2001-01-15 00:50:52 +0000187 self.debugging = level
188 debug = set_debuglevel
Guido van Rossumc629d341992-11-05 10:43:02 +0000189
Tim Peters2344fae2001-01-15 00:50:52 +0000190 def putline(self, line):
191 """Internal: send one line to the server, appending CRLF."""
192 line = line + CRLF
193 if self.debugging > 1: print '*put*', `line`
Martin v. Löwise12454f2002-02-16 23:06:19 +0000194 self.sock.sendall(line)
Guido van Rossumc629d341992-11-05 10:43:02 +0000195
Tim Peters2344fae2001-01-15 00:50:52 +0000196 def putcmd(self, line):
197 """Internal: send one command to the server (through putline())."""
198 if self.debugging: print '*cmd*', `line`
199 self.putline(line)
Guido van Rossumc629d341992-11-05 10:43:02 +0000200
Tim Peters2344fae2001-01-15 00:50:52 +0000201 def getline(self):
202 """Internal: return one line from the server, stripping CRLF.
203 Raise EOFError if the connection is closed."""
204 line = self.file.readline()
205 if self.debugging > 1:
206 print '*get*', `line`
207 if not line: raise EOFError
208 if line[-2:] == CRLF: line = line[:-2]
209 elif line[-1:] in CRLF: line = line[:-1]
210 return line
Guido van Rossumc629d341992-11-05 10:43:02 +0000211
Tim Peters2344fae2001-01-15 00:50:52 +0000212 def getresp(self):
213 """Internal: get a response from the server.
214 Raise various errors if the response indicates an error."""
215 resp = self.getline()
216 if self.debugging: print '*resp*', `resp`
217 c = resp[:1]
218 if c == '4':
219 raise NNTPTemporaryError(resp)
220 if c == '5':
221 raise NNTPPermanentError(resp)
222 if c not in '123':
223 raise NNTPProtocolError(resp)
224 return resp
Guido van Rossumc629d341992-11-05 10:43:02 +0000225
Fredrik Lundha5e61652001-10-18 20:58:25 +0000226 def getlongresp(self, file=None):
Tim Peters2344fae2001-01-15 00:50:52 +0000227 """Internal: get a response plus following text from the server.
228 Raise various errors if the response indicates an error."""
Guido van Rossumd1d584f2001-10-01 13:46:55 +0000229
230 openedFile = None
231 try:
232 # If a string was passed then open a file with that name
Walter Dörwald65230a22002-06-03 15:58:32 +0000233 if isinstance(file, str):
Fredrik Lundha5e61652001-10-18 20:58:25 +0000234 openedFile = file = open(file, "w")
Guido van Rossumd1d584f2001-10-01 13:46:55 +0000235
236 resp = self.getresp()
237 if resp[:3] not in LONGRESP:
238 raise NNTPReplyError(resp)
239 list = []
240 while 1:
241 line = self.getline()
242 if line == '.':
243 break
244 if line[:2] == '..':
245 line = line[1:]
Fredrik Lundha5e61652001-10-18 20:58:25 +0000246 if file:
247 file.write(line + "\n")
Guido van Rossumd1d584f2001-10-01 13:46:55 +0000248 else:
249 list.append(line)
250 finally:
251 # If this method created the file, then it must close it
252 if openedFile:
253 openedFile.close()
254
Tim Peters2344fae2001-01-15 00:50:52 +0000255 return resp, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000256
Tim Peters2344fae2001-01-15 00:50:52 +0000257 def shortcmd(self, line):
258 """Internal: send a command and get the response."""
259 self.putcmd(line)
260 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000261
Fredrik Lundha5e61652001-10-18 20:58:25 +0000262 def longcmd(self, line, file=None):
Tim Peters2344fae2001-01-15 00:50:52 +0000263 """Internal: send a command and get the response plus following text."""
264 self.putcmd(line)
Fredrik Lundha5e61652001-10-18 20:58:25 +0000265 return self.getlongresp(file)
Guido van Rossumc629d341992-11-05 10:43:02 +0000266
Tim Peters2344fae2001-01-15 00:50:52 +0000267 def newgroups(self, date, time):
268 """Process a NEWGROUPS command. Arguments:
269 - date: string 'yymmdd' indicating the date
270 - time: string 'hhmmss' indicating the time
271 Return:
272 - resp: server response if successful
273 - list: list of newsgroup names"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000274
Tim Peters2344fae2001-01-15 00:50:52 +0000275 return self.longcmd('NEWGROUPS ' + date + ' ' + time)
Guido van Rossumc629d341992-11-05 10:43:02 +0000276
Tim Peters2344fae2001-01-15 00:50:52 +0000277 def newnews(self, group, date, time):
278 """Process a NEWNEWS command. Arguments:
279 - group: group name or '*'
280 - date: string 'yymmdd' indicating the date
281 - time: string 'hhmmss' indicating the time
282 Return:
283 - resp: server response if successful
284 - list: list of article ids"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000285
Tim Peters2344fae2001-01-15 00:50:52 +0000286 cmd = 'NEWNEWS ' + group + ' ' + date + ' ' + time
287 return self.longcmd(cmd)
Guido van Rossumc629d341992-11-05 10:43:02 +0000288
Tim Peters2344fae2001-01-15 00:50:52 +0000289 def list(self):
290 """Process a LIST command. Return:
291 - resp: server response if successful
292 - list: list of (group, last, first, flag) (strings)"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000293
Tim Peters2344fae2001-01-15 00:50:52 +0000294 resp, list = self.longcmd('LIST')
295 for i in range(len(list)):
296 # Parse lines into "group last first flag"
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000297 list[i] = tuple(list[i].split())
Tim Peters2344fae2001-01-15 00:50:52 +0000298 return resp, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000299
Tim Peters2344fae2001-01-15 00:50:52 +0000300 def group(self, name):
301 """Process a GROUP command. Argument:
302 - group: the group name
303 Returns:
304 - resp: server response if successful
305 - count: number of articles (string)
306 - first: first article number (string)
307 - last: last article number (string)
308 - name: the group name"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000309
Tim Peters2344fae2001-01-15 00:50:52 +0000310 resp = self.shortcmd('GROUP ' + name)
311 if resp[:3] != '211':
312 raise NNTPReplyError(resp)
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000313 words = resp.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000314 count = first = last = 0
315 n = len(words)
316 if n > 1:
317 count = words[1]
318 if n > 2:
319 first = words[2]
320 if n > 3:
321 last = words[3]
322 if n > 4:
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000323 name = words[4].lower()
Tim Peters2344fae2001-01-15 00:50:52 +0000324 return resp, count, first, last, name
Guido van Rossumc629d341992-11-05 10:43:02 +0000325
Tim Peters2344fae2001-01-15 00:50:52 +0000326 def help(self):
327 """Process a HELP command. Returns:
328 - resp: server response if successful
329 - list: list of strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000330
Tim Peters2344fae2001-01-15 00:50:52 +0000331 return self.longcmd('HELP')
Guido van Rossumc629d341992-11-05 10:43:02 +0000332
Tim Peters2344fae2001-01-15 00:50:52 +0000333 def statparse(self, resp):
334 """Internal: parse the response of a STAT, NEXT or LAST command."""
335 if resp[:2] != '22':
336 raise NNTPReplyError(resp)
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000337 words = resp.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000338 nr = 0
339 id = ''
340 n = len(words)
341 if n > 1:
342 nr = words[1]
343 if n > 2:
344 id = words[2]
345 return resp, nr, id
Guido van Rossumc629d341992-11-05 10:43:02 +0000346
Tim Peters2344fae2001-01-15 00:50:52 +0000347 def statcmd(self, line):
348 """Internal: process a STAT, NEXT or LAST command."""
349 resp = self.shortcmd(line)
350 return self.statparse(resp)
Guido van Rossumc629d341992-11-05 10:43:02 +0000351
Tim Peters2344fae2001-01-15 00:50:52 +0000352 def stat(self, id):
353 """Process a STAT command. Argument:
354 - id: article number or message id
355 Returns:
356 - resp: server response if successful
357 - nr: the article number
358 - id: the article id"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000359
Tim Peters2344fae2001-01-15 00:50:52 +0000360 return self.statcmd('STAT ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000361
Tim Peters2344fae2001-01-15 00:50:52 +0000362 def next(self):
363 """Process a NEXT command. No arguments. Return as for STAT."""
364 return self.statcmd('NEXT')
Guido van Rossumc629d341992-11-05 10:43:02 +0000365
Tim Peters2344fae2001-01-15 00:50:52 +0000366 def last(self):
367 """Process a LAST command. No arguments. Return as for STAT."""
368 return self.statcmd('LAST')
Guido van Rossumc629d341992-11-05 10:43:02 +0000369
Fredrik Lundha5e61652001-10-18 20:58:25 +0000370 def artcmd(self, line, file=None):
Tim Peters2344fae2001-01-15 00:50:52 +0000371 """Internal: process a HEAD, BODY or ARTICLE command."""
Fredrik Lundha5e61652001-10-18 20:58:25 +0000372 resp, list = self.longcmd(line, file)
Tim Peters2344fae2001-01-15 00:50:52 +0000373 resp, nr, id = self.statparse(resp)
374 return resp, nr, id, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000375
Tim Peters2344fae2001-01-15 00:50:52 +0000376 def head(self, id):
377 """Process a HEAD command. Argument:
378 - id: article number or message id
379 Returns:
380 - resp: server response if successful
381 - nr: article number
382 - id: message id
383 - list: the lines of the article's header"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000384
Tim Peters2344fae2001-01-15 00:50:52 +0000385 return self.artcmd('HEAD ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000386
Fredrik Lundha5e61652001-10-18 20:58:25 +0000387 def body(self, id, file=None):
Tim Peters2344fae2001-01-15 00:50:52 +0000388 """Process a BODY command. Argument:
389 - id: article number or message id
Fredrik Lundha5e61652001-10-18 20:58:25 +0000390 - file: Filename string or file object to store the article in
Tim Peters2344fae2001-01-15 00:50:52 +0000391 Returns:
392 - resp: server response if successful
393 - nr: article number
394 - id: message id
Guido van Rossumd1d584f2001-10-01 13:46:55 +0000395 - list: the lines of the article's body or an empty list
Fredrik Lundha5e61652001-10-18 20:58:25 +0000396 if file was used"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000397
Fredrik Lundha5e61652001-10-18 20:58:25 +0000398 return self.artcmd('BODY ' + id, file)
Guido van Rossumc629d341992-11-05 10:43:02 +0000399
Tim Peters2344fae2001-01-15 00:50:52 +0000400 def article(self, id):
401 """Process an ARTICLE command. Argument:
402 - id: article number or message id
403 Returns:
404 - resp: server response if successful
405 - nr: article number
406 - id: message id
407 - list: the lines of the article"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000408
Tim Peters2344fae2001-01-15 00:50:52 +0000409 return self.artcmd('ARTICLE ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000410
Tim Peters2344fae2001-01-15 00:50:52 +0000411 def slave(self):
412 """Process a SLAVE command. Returns:
413 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000414
Tim Peters2344fae2001-01-15 00:50:52 +0000415 return self.shortcmd('SLAVE')
Guido van Rossumc629d341992-11-05 10:43:02 +0000416
Tim Peters2344fae2001-01-15 00:50:52 +0000417 def xhdr(self, hdr, str):
418 """Process an XHDR command (optional server extension). Arguments:
419 - hdr: the header type (e.g. 'subject')
420 - str: an article nr, a message id, or a range nr1-nr2
421 Returns:
422 - resp: server response if successful
423 - list: list of (nr, value) strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000424
Tim Peters2344fae2001-01-15 00:50:52 +0000425 pat = re.compile('^([0-9]+) ?(.*)\n?')
426 resp, lines = self.longcmd('XHDR ' + hdr + ' ' + str)
427 for i in range(len(lines)):
428 line = lines[i]
429 m = pat.match(line)
430 if m:
431 lines[i] = m.group(1, 2)
432 return resp, lines
Guido van Rossumc629d341992-11-05 10:43:02 +0000433
Tim Peters2344fae2001-01-15 00:50:52 +0000434 def xover(self,start,end):
435 """Process an XOVER command (optional server extension) Arguments:
436 - start: start of range
437 - end: end of range
438 Returns:
439 - resp: server response if successful
440 - list: list of (art-nr, subject, poster, date,
441 id, references, size, lines)"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000442
Tim Peters2344fae2001-01-15 00:50:52 +0000443 resp, lines = self.longcmd('XOVER ' + start + '-' + end)
444 xover_lines = []
445 for line in lines:
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000446 elem = line.split("\t")
Tim Peters2344fae2001-01-15 00:50:52 +0000447 try:
448 xover_lines.append((elem[0],
449 elem[1],
450 elem[2],
451 elem[3],
452 elem[4],
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000453 elem[5].split(),
Tim Peters2344fae2001-01-15 00:50:52 +0000454 elem[6],
455 elem[7]))
456 except IndexError:
457 raise NNTPDataError(line)
458 return resp,xover_lines
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000459
Tim Peters2344fae2001-01-15 00:50:52 +0000460 def xgtitle(self, group):
461 """Process an XGTITLE command (optional server extension) Arguments:
462 - group: group name wildcard (i.e. news.*)
463 Returns:
464 - resp: server response if successful
465 - list: list of (name,title) strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000466
Tim Peters2344fae2001-01-15 00:50:52 +0000467 line_pat = re.compile("^([^ \t]+)[ \t]+(.*)$")
468 resp, raw_lines = self.longcmd('XGTITLE ' + group)
469 lines = []
470 for raw_line in raw_lines:
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000471 match = line_pat.search(raw_line.strip())
Tim Peters2344fae2001-01-15 00:50:52 +0000472 if match:
473 lines.append(match.group(1, 2))
474 return resp, lines
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000475
Tim Peters2344fae2001-01-15 00:50:52 +0000476 def xpath(self,id):
477 """Process an XPATH command (optional server extension) Arguments:
478 - id: Message id of article
479 Returns:
480 resp: server response if successful
481 path: directory path to article"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000482
Tim Peters2344fae2001-01-15 00:50:52 +0000483 resp = self.shortcmd("XPATH " + id)
484 if resp[:3] != '223':
485 raise NNTPReplyError(resp)
486 try:
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000487 [resp_num, path] = resp.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000488 except ValueError:
489 raise NNTPReplyError(resp)
490 else:
491 return resp, path
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000492
Tim Peters2344fae2001-01-15 00:50:52 +0000493 def date (self):
494 """Process the DATE command. Arguments:
495 None
496 Returns:
497 resp: server response if successful
498 date: Date suitable for newnews/newgroups commands etc.
499 time: Time suitable for newnews/newgroups commands etc."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000500
Tim Peters2344fae2001-01-15 00:50:52 +0000501 resp = self.shortcmd("DATE")
502 if resp[:3] != '111':
503 raise NNTPReplyError(resp)
Eric S. Raymondb9c24fb2001-02-09 07:02:17 +0000504 elem = resp.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000505 if len(elem) != 2:
506 raise NNTPDataError(resp)
507 date = elem[1][2:8]
508 time = elem[1][-6:]
509 if len(date) != 6 or len(time) != 6:
510 raise NNTPDataError(resp)
511 return resp, date, time
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000512
513
Tim Peters2344fae2001-01-15 00:50:52 +0000514 def post(self, f):
515 """Process a POST command. Arguments:
516 - f: file containing the article
517 Returns:
518 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000519
Tim Peters2344fae2001-01-15 00:50:52 +0000520 resp = self.shortcmd('POST')
521 # Raises error_??? if posting is not allowed
522 if resp[0] != '3':
523 raise NNTPReplyError(resp)
524 while 1:
525 line = f.readline()
526 if not line:
527 break
528 if line[-1] == '\n':
529 line = line[:-1]
530 if line[:1] == '.':
531 line = '.' + line
532 self.putline(line)
533 self.putline('.')
534 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000535
Tim Peters2344fae2001-01-15 00:50:52 +0000536 def ihave(self, id, f):
537 """Process an IHAVE command. Arguments:
538 - id: message-id of the article
539 - f: file containing the article
540 Returns:
541 - resp: server response if successful
542 Note that if the server refuses the article an exception is raised."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000543
Tim Peters2344fae2001-01-15 00:50:52 +0000544 resp = self.shortcmd('IHAVE ' + id)
545 # Raises error_??? if the server already has it
546 if resp[0] != '3':
547 raise NNTPReplyError(resp)
548 while 1:
549 line = f.readline()
550 if not line:
551 break
552 if line[-1] == '\n':
553 line = line[:-1]
554 if line[:1] == '.':
555 line = '.' + line
556 self.putline(line)
557 self.putline('.')
558 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000559
Tim Peters2344fae2001-01-15 00:50:52 +0000560 def quit(self):
561 """Process a QUIT command and close the socket. Returns:
562 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000563
Tim Peters2344fae2001-01-15 00:50:52 +0000564 resp = self.shortcmd('QUIT')
565 self.file.close()
566 self.sock.close()
567 del self.file, self.sock
568 return resp
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000569
570
Neal Norwitzef679562002-11-14 02:19:44 +0000571# Test retrieval when run as a script.
Eric S. Raymondb2db5872002-11-13 23:05:35 +0000572# Assumption: if there's a local news server, it's called 'news'.
573# Assumption: if user queries a remote news server, it's named
574# in the environment variable NNTPSERVER (used by slrn and kin)
575# and we want readermode off.
576if __name__ == '__main__':
577 import os
578 newshost = 'news' and os.environ["NNTPSERVER"]
579 if newshost.find('.') == -1:
580 mode = 'readermode'
581 else:
582 mode = None
583 s = NNTP(newshost, readermode=mode)
Tim Peters2344fae2001-01-15 00:50:52 +0000584 resp, count, first, last, name = s.group('comp.lang.python')
585 print resp
586 print 'Group', name, 'has', count, 'articles, range', first, 'to', last
587 resp, subs = s.xhdr('subject', first + '-' + last)
588 print resp
589 for item in subs:
590 print "%7s %s" % item
591 resp = s.quit()
592 print resp
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000593