blob: 81449b03d10a7fb7348df507b24dc0ba142197cd [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
34import string
35
36
Barry Warsaw9dd78722000-02-10 20:25:53 +000037
38# Exceptions raised when an error or invalid response is received
39class NNTPError(Exception):
40 """Base class for all nntplib exceptions"""
41 def __init__(self, *args):
42 apply(Exception.__init__, (self,)+args)
43 try:
44 self.response = args[0]
45 except IndexError:
46 self.response = 'No response given'
47
48class NNTPReplyError(NNTPError):
49 """Unexpected [123]xx reply"""
50 pass
51
52class NNTPTemporaryError(NNTPError):
53 """4xx errors"""
54 pass
55
56class NNTPPermanentError(NNTPError):
57 """5xx errors"""
58 pass
59
60class NNTPProtocolError(NNTPError):
61 """Response does not begin with [1-5]"""
62 pass
63
64class NNTPDataError(NNTPError):
65 """Error in response data"""
66 pass
67
68# for backwards compatibility
69error_reply = NNTPReplyError
70error_temp = NNTPTemporaryError
71error_perm = NNTPPermanentError
72error_proto = NNTPProtocolError
73error_data = NNTPDataError
Guido van Rossumc629d341992-11-05 10:43:02 +000074
75
Barry Warsaw9dd78722000-02-10 20:25:53 +000076
Guido van Rossumc629d341992-11-05 10:43:02 +000077# Standard port used by NNTP servers
78NNTP_PORT = 119
79
80
81# Response numbers that are followed by additional text (e.g. article)
Guido van Rossum8421c4e1995-09-22 00:52:38 +000082LONGRESP = ['100', '215', '220', '221', '222', '224', '230', '231', '282']
Guido van Rossumc629d341992-11-05 10:43:02 +000083
84
85# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
86CRLF = '\r\n'
87
88
Barry Warsaw9dd78722000-02-10 20:25:53 +000089
Guido van Rossumc629d341992-11-05 10:43:02 +000090# The class itself
Guido van Rossumc629d341992-11-05 10:43:02 +000091class NNTP:
Barry Warsaw9dd78722000-02-10 20:25:53 +000092 def __init__(self, host, port=NNTP_PORT, user=None, password=None,
93 readermode=None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +000094 """Initialize an instance. Arguments:
95 - host: hostname to connect to
Barry Warsaw9dd78722000-02-10 20:25:53 +000096 - port: port to connect to (default the standard NNTP port)
97 - user: username to authenticate with
98 - password: password to use with username
99 - readermode: if true, send 'mode reader' command after
100 connecting.
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000101
Barry Warsaw9dd78722000-02-10 20:25:53 +0000102 readermode is sometimes necessary if you are connecting to an
103 NNTP server on the local machine and intend to call
104 reader-specific comamnds, such as `group'. If you get
105 unexpected NNTPPermanentErrors, you might need to set
106 readermode.
107 """
Guido van Rossumc629d341992-11-05 10:43:02 +0000108 self.host = host
109 self.port = port
110 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Guido van Rossum93a7c0f2000-03-28 21:45:46 +0000111 self.sock.connect((self.host, self.port))
Jack Jansen2bb57b81996-02-14 16:06:24 +0000112 self.file = self.sock.makefile('rb')
Guido van Rossumc629d341992-11-05 10:43:02 +0000113 self.debugging = 0
114 self.welcome = self.getresp()
Barry Warsaw9dd78722000-02-10 20:25:53 +0000115 if readermode:
116 try:
117 self.welcome = self.shortcmd('mode reader')
118 except NNTPPermanentError:
119 # error 500, probably 'not implemented'
120 pass
Guido van Rossumdd659751997-10-20 23:29:44 +0000121 if user:
Guido van Rossum8ca84201998-03-26 20:56:10 +0000122 resp = self.shortcmd('authinfo user '+user)
123 if resp[:3] == '381':
124 if not password:
Barry Warsaw9dd78722000-02-10 20:25:53 +0000125 raise NNTPReplyError(resp)
Guido van Rossum8ca84201998-03-26 20:56:10 +0000126 else:
127 resp = self.shortcmd(
128 'authinfo pass '+password)
129 if resp[:3] != '281':
Barry Warsaw9dd78722000-02-10 20:25:53 +0000130 raise NNTPPermanentError(resp)
131
132 # Get the welcome message from the server
133 # (this is read and squirreled away by __init__()).
134 # If the response code is 200, posting is allowed;
135 # if it 201, posting is not allowed
Guido van Rossumc629d341992-11-05 10:43:02 +0000136
Guido van Rossumc629d341992-11-05 10:43:02 +0000137 def getwelcome(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000138 """Get the welcome message from the server
139 (this is read and squirreled away by __init__()).
140 If the response code is 200, posting is allowed;
141 if it 201, posting is not allowed."""
142
Guido van Rossumc629d341992-11-05 10:43:02 +0000143 if self.debugging: print '*welcome*', `self.welcome`
144 return self.welcome
145
Guido van Rossumcf5394f1995-03-30 10:42:34 +0000146 def set_debuglevel(self, level):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000147 """Set the debugging level. Argument 'level' means:
148 0: no debugging output (default)
149 1: print commands and responses but not body text etc.
150 2: also print raw lines read and sent before stripping CR/LF"""
151
Guido van Rossumc629d341992-11-05 10:43:02 +0000152 self.debugging = level
Guido van Rossumcf5394f1995-03-30 10:42:34 +0000153 debug = set_debuglevel
Guido van Rossumc629d341992-11-05 10:43:02 +0000154
Guido van Rossumc629d341992-11-05 10:43:02 +0000155 def putline(self, line):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000156 """Internal: send one line to the server, appending CRLF."""
Guido van Rossumc629d341992-11-05 10:43:02 +0000157 line = line + CRLF
158 if self.debugging > 1: print '*put*', `line`
159 self.sock.send(line)
160
Guido van Rossumc629d341992-11-05 10:43:02 +0000161 def putcmd(self, line):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000162 """Internal: send one command to the server (through putline())."""
Guido van Rossumc629d341992-11-05 10:43:02 +0000163 if self.debugging: print '*cmd*', `line`
164 self.putline(line)
165
Guido van Rossumc629d341992-11-05 10:43:02 +0000166 def getline(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000167 """Internal: return one line from the server, stripping CRLF.
168 Raise EOFError if the connection is closed."""
Guido van Rossumc629d341992-11-05 10:43:02 +0000169 line = self.file.readline()
170 if self.debugging > 1:
171 print '*get*', `line`
172 if not line: raise EOFError
173 if line[-2:] == CRLF: line = line[:-2]
174 elif line[-1:] in CRLF: line = line[:-1]
175 return line
176
Guido van Rossumc629d341992-11-05 10:43:02 +0000177 def getresp(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000178 """Internal: get a response from the server.
179 Raise various errors if the response indicates an error."""
Guido van Rossumc629d341992-11-05 10:43:02 +0000180 resp = self.getline()
181 if self.debugging: print '*resp*', `resp`
182 c = resp[:1]
183 if c == '4':
Barry Warsaw9dd78722000-02-10 20:25:53 +0000184 raise NNTPTemporaryError(resp)
Guido van Rossumc629d341992-11-05 10:43:02 +0000185 if c == '5':
Barry Warsaw9dd78722000-02-10 20:25:53 +0000186 raise NNTPPermanentError(resp)
Guido van Rossumc629d341992-11-05 10:43:02 +0000187 if c not in '123':
Barry Warsaw9dd78722000-02-10 20:25:53 +0000188 raise NNTPProtocolError(resp)
Guido van Rossumc629d341992-11-05 10:43:02 +0000189 return resp
190
Guido van Rossumc629d341992-11-05 10:43:02 +0000191 def getlongresp(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000192 """Internal: get a response plus following text from the server.
193 Raise various errors if the response indicates an error."""
Guido van Rossumc629d341992-11-05 10:43:02 +0000194 resp = self.getresp()
195 if resp[:3] not in LONGRESP:
Barry Warsaw9dd78722000-02-10 20:25:53 +0000196 raise NNTPReplyError(resp)
Guido van Rossumc629d341992-11-05 10:43:02 +0000197 list = []
198 while 1:
199 line = self.getline()
200 if line == '.':
201 break
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000202 if line[:2] == '..':
203 line = line[1:]
Guido van Rossumc629d341992-11-05 10:43:02 +0000204 list.append(line)
205 return resp, list
206
Guido van Rossumc629d341992-11-05 10:43:02 +0000207 def shortcmd(self, line):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000208 """Internal: send a command and get the response."""
Guido van Rossumc629d341992-11-05 10:43:02 +0000209 self.putcmd(line)
210 return self.getresp()
211
Guido van Rossumc629d341992-11-05 10:43:02 +0000212 def longcmd(self, line):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000213 """Internal: send a command and get the response plus following text."""
Guido van Rossumc629d341992-11-05 10:43:02 +0000214 self.putcmd(line)
215 return self.getlongresp()
216
Guido van Rossumc629d341992-11-05 10:43:02 +0000217 def newgroups(self, date, time):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000218 """Process a NEWGROUPS command. Arguments:
219 - date: string 'yymmdd' indicating the date
220 - time: string 'hhmmss' indicating the time
221 Return:
222 - resp: server response if succesful
223 - list: list of newsgroup names"""
224
Guido van Rossumc629d341992-11-05 10:43:02 +0000225 return self.longcmd('NEWGROUPS ' + date + ' ' + time)
226
Guido van Rossumc629d341992-11-05 10:43:02 +0000227 def newnews(self, group, date, time):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000228 """Process a NEWNEWS command. Arguments:
229 - group: group name or '*'
230 - date: string 'yymmdd' indicating the date
231 - time: string 'hhmmss' indicating the time
232 Return:
233 - resp: server response if succesful
234 - list: list of article ids"""
235
Guido van Rossumc629d341992-11-05 10:43:02 +0000236 cmd = 'NEWNEWS ' + group + ' ' + date + ' ' + time
237 return self.longcmd(cmd)
238
Guido van Rossumc629d341992-11-05 10:43:02 +0000239 def list(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000240 """Process a LIST command. Return:
241 - resp: server response if succesful
242 - list: list of (group, last, first, flag) (strings)"""
243
Guido van Rossumc629d341992-11-05 10:43:02 +0000244 resp, list = self.longcmd('LIST')
245 for i in range(len(list)):
Guido van Rossumbe9f2121995-01-10 10:35:55 +0000246 # Parse lines into "group last first flag"
Guido van Rossumc69955341997-03-14 04:18:20 +0000247 list[i] = tuple(string.split(list[i]))
Guido van Rossumc629d341992-11-05 10:43:02 +0000248 return resp, list
249
Guido van Rossumc629d341992-11-05 10:43:02 +0000250 def group(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000251 """Process a GROUP command. Argument:
252 - group: the group name
253 Returns:
254 - resp: server response if succesful
255 - count: number of articles (string)
256 - first: first article number (string)
257 - last: last article number (string)
258 - name: the group name"""
259
Guido van Rossumc629d341992-11-05 10:43:02 +0000260 resp = self.shortcmd('GROUP ' + name)
261 if resp[:3] <> '211':
Barry Warsaw9dd78722000-02-10 20:25:53 +0000262 raise NNTPReplyError(resp)
Guido van Rossumc629d341992-11-05 10:43:02 +0000263 words = string.split(resp)
264 count = first = last = 0
265 n = len(words)
266 if n > 1:
267 count = words[1]
268 if n > 2:
269 first = words[2]
270 if n > 3:
271 last = words[3]
272 if n > 4:
273 name = string.lower(words[4])
274 return resp, count, first, last, name
275
Guido van Rossumc629d341992-11-05 10:43:02 +0000276 def help(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000277 """Process a HELP command. Returns:
278 - resp: server response if succesful
279 - list: list of strings"""
280
Guido van Rossumc629d341992-11-05 10:43:02 +0000281 return self.longcmd('HELP')
282
Guido van Rossumc629d341992-11-05 10:43:02 +0000283 def statparse(self, resp):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000284 """Internal: parse the response of a STAT, NEXT or LAST command."""
Guido van Rossumc629d341992-11-05 10:43:02 +0000285 if resp[:2] <> '22':
Barry Warsaw9dd78722000-02-10 20:25:53 +0000286 raise NNTPReplyError(resp)
Guido van Rossumc629d341992-11-05 10:43:02 +0000287 words = string.split(resp)
288 nr = 0
289 id = ''
290 n = len(words)
291 if n > 1:
292 nr = words[1]
293 if n > 2:
Guido van Rossum98c17b31998-12-21 18:51:23 +0000294 id = words[2]
Guido van Rossumc629d341992-11-05 10:43:02 +0000295 return resp, nr, id
296
Guido van Rossumc629d341992-11-05 10:43:02 +0000297 def statcmd(self, line):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000298 """Internal: process a STAT, NEXT or LAST command."""
Guido van Rossumc629d341992-11-05 10:43:02 +0000299 resp = self.shortcmd(line)
300 return self.statparse(resp)
301
Guido van Rossumc629d341992-11-05 10:43:02 +0000302 def stat(self, id):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000303 """Process a STAT command. Argument:
304 - id: article number or message id
305 Returns:
306 - resp: server response if succesful
307 - nr: the article number
308 - id: the article id"""
309
Guido van Rossumc629d341992-11-05 10:43:02 +0000310 return self.statcmd('STAT ' + id)
311
Guido van Rossumc629d341992-11-05 10:43:02 +0000312 def next(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000313 """Process a NEXT command. No arguments. Return as for STAT."""
Guido van Rossumc629d341992-11-05 10:43:02 +0000314 return self.statcmd('NEXT')
315
Guido van Rossumc629d341992-11-05 10:43:02 +0000316 def last(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000317 """Process a LAST command. No arguments. Return as for STAT."""
Guido van Rossumc629d341992-11-05 10:43:02 +0000318 return self.statcmd('LAST')
319
Guido van Rossumc629d341992-11-05 10:43:02 +0000320 def artcmd(self, line):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000321 """Internal: process a HEAD, BODY or ARTICLE command."""
Guido van Rossumc629d341992-11-05 10:43:02 +0000322 resp, list = self.longcmd(line)
323 resp, nr, id = self.statparse(resp)
324 return resp, nr, id, list
325
Guido van Rossumc629d341992-11-05 10:43:02 +0000326 def head(self, id):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000327 """Process a HEAD command. Argument:
328 - id: article number or message id
329 Returns:
330 - resp: server response if succesful
331 - nr: article number
332 - id: message id
333 - list: the lines of the article's header"""
334
Guido van Rossumc629d341992-11-05 10:43:02 +0000335 return self.artcmd('HEAD ' + id)
336
Guido van Rossumc629d341992-11-05 10:43:02 +0000337 def body(self, id):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000338 """Process a BODY command. Argument:
339 - id: article number or message id
340 Returns:
341 - resp: server response if succesful
342 - nr: article number
343 - id: message id
344 - list: the lines of the article's body"""
345
Guido van Rossumc629d341992-11-05 10:43:02 +0000346 return self.artcmd('BODY ' + id)
347
Guido van Rossumc629d341992-11-05 10:43:02 +0000348 def article(self, id):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000349 """Process an ARTICLE command. Argument:
350 - id: article number or message id
351 Returns:
352 - resp: server response if succesful
353 - nr: article number
354 - id: message id
355 - list: the lines of the article"""
356
Guido van Rossumc629d341992-11-05 10:43:02 +0000357 return self.artcmd('ARTICLE ' + id)
358
Guido van Rossumc629d341992-11-05 10:43:02 +0000359 def slave(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000360 """Process a SLAVE command. Returns:
361 - resp: server response if succesful"""
362
Guido van Rossumc629d341992-11-05 10:43:02 +0000363 return self.shortcmd('SLAVE')
364
Guido van Rossumc629d341992-11-05 10:43:02 +0000365 def xhdr(self, hdr, str):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000366 """Process an XHDR command (optional server extension). Arguments:
367 - hdr: the header type (e.g. 'subject')
368 - str: an article nr, a message id, or a range nr1-nr2
369 Returns:
370 - resp: server response if succesful
371 - list: list of (nr, value) strings"""
372
Guido van Rossum9694fca1997-10-22 21:00:49 +0000373 pat = re.compile('^([0-9]+) ?(.*)\n?')
Guido van Rossumc629d341992-11-05 10:43:02 +0000374 resp, lines = self.longcmd('XHDR ' + hdr + ' ' + str)
375 for i in range(len(lines)):
376 line = lines[i]
Guido van Rossum9694fca1997-10-22 21:00:49 +0000377 m = pat.match(line)
378 if m:
379 lines[i] = m.group(1, 2)
Guido van Rossumc629d341992-11-05 10:43:02 +0000380 return resp, lines
381
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000382 def xover(self,start,end):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000383 """Process an XOVER command (optional server extension) Arguments:
384 - start: start of range
385 - end: end of range
386 Returns:
387 - resp: server response if succesful
388 - list: list of (art-nr, subject, poster, date,
389 id, references, size, lines)"""
390
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000391 resp, lines = self.longcmd('XOVER ' + start + '-' + end)
392 xover_lines = []
393 for line in lines:
394 elem = string.splitfields(line,"\t")
395 try:
Guido van Rossumc3fb88b1997-07-17 15:21:52 +0000396 xover_lines.append((elem[0],
397 elem[1],
398 elem[2],
399 elem[3],
400 elem[4],
401 string.split(elem[5]),
402 elem[6],
403 elem[7]))
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000404 except IndexError:
Barry Warsaw9dd78722000-02-10 20:25:53 +0000405 raise NNTPDataError(line)
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000406 return resp,xover_lines
407
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000408 def xgtitle(self, group):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000409 """Process an XGTITLE command (optional server extension) Arguments:
410 - group: group name wildcard (i.e. news.*)
411 Returns:
412 - resp: server response if succesful
413 - list: list of (name,title) strings"""
414
Guido van Rossum9694fca1997-10-22 21:00:49 +0000415 line_pat = re.compile("^([^ \t]+)[ \t]+(.*)$")
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000416 resp, raw_lines = self.longcmd('XGTITLE ' + group)
417 lines = []
418 for raw_line in raw_lines:
Guido van Rossum9694fca1997-10-22 21:00:49 +0000419 match = line_pat.search(string.strip(raw_line))
420 if match:
421 lines.append(match.group(1, 2))
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000422 return resp, lines
423
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000424 def xpath(self,id):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000425 """Process an XPATH command (optional server extension) Arguments:
426 - id: Message id of article
427 Returns:
428 resp: server response if succesful
429 path: directory path to article"""
430
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000431 resp = self.shortcmd("XPATH " + id)
432 if resp[:3] <> '223':
Barry Warsaw9dd78722000-02-10 20:25:53 +0000433 raise NNTPReplyError(resp)
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000434 try:
435 [resp_num, path] = string.split(resp)
436 except ValueError:
Barry Warsaw9dd78722000-02-10 20:25:53 +0000437 raise NNTPReplyError(resp)
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000438 else:
439 return resp, path
440
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000441 def date (self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000442 """Process the DATE command. Arguments:
443 None
444 Returns:
445 resp: server response if succesful
446 date: Date suitable for newnews/newgroups commands etc.
447 time: Time suitable for newnews/newgroups commands etc."""
448
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000449 resp = self.shortcmd("DATE")
450 if resp[:3] <> '111':
Barry Warsaw9dd78722000-02-10 20:25:53 +0000451 raise NNTPReplyError(resp)
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000452 elem = string.split(resp)
453 if len(elem) != 2:
Barry Warsaw9dd78722000-02-10 20:25:53 +0000454 raise NNTPDataError(resp)
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000455 date = elem[1][2:8]
456 time = elem[1][-6:]
457 if len(date) != 6 or len(time) != 6:
Barry Warsaw9dd78722000-02-10 20:25:53 +0000458 raise NNTPDataError(resp)
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000459 return resp, date, time
460
461
Guido van Rossumc629d341992-11-05 10:43:02 +0000462 def post(self, f):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000463 """Process a POST command. Arguments:
464 - f: file containing the article
465 Returns:
466 - resp: server response if succesful"""
467
Guido van Rossumc629d341992-11-05 10:43:02 +0000468 resp = self.shortcmd('POST')
469 # Raises error_??? if posting is not allowed
470 if resp[0] <> '3':
Barry Warsaw9dd78722000-02-10 20:25:53 +0000471 raise NNTPReplyError(resp)
Guido van Rossumc629d341992-11-05 10:43:02 +0000472 while 1:
473 line = f.readline()
474 if not line:
475 break
476 if line[-1] == '\n':
477 line = line[:-1]
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000478 if line[:1] == '.':
479 line = '.' + line
Guido van Rossumc629d341992-11-05 10:43:02 +0000480 self.putline(line)
481 self.putline('.')
482 return self.getresp()
483
Guido van Rossumc629d341992-11-05 10:43:02 +0000484 def ihave(self, id, f):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000485 """Process an IHAVE command. Arguments:
486 - id: message-id of the article
487 - f: file containing the article
488 Returns:
489 - resp: server response if succesful
490 Note that if the server refuses the article an exception is raised."""
491
Guido van Rossumc629d341992-11-05 10:43:02 +0000492 resp = self.shortcmd('IHAVE ' + id)
Guido van Rossum18fc5691992-11-26 09:17:19 +0000493 # Raises error_??? if the server already has it
Guido van Rossumc629d341992-11-05 10:43:02 +0000494 if resp[0] <> '3':
Barry Warsaw9dd78722000-02-10 20:25:53 +0000495 raise NNTPReplyError(resp)
Guido van Rossumc629d341992-11-05 10:43:02 +0000496 while 1:
497 line = f.readline()
498 if not line:
499 break
500 if line[-1] == '\n':
501 line = line[:-1]
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000502 if line[:1] == '.':
503 line = '.' + line
Guido van Rossumc629d341992-11-05 10:43:02 +0000504 self.putline(line)
505 self.putline('.')
506 return self.getresp()
507
Guido van Rossumc629d341992-11-05 10:43:02 +0000508 def quit(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000509 """Process a QUIT command and close the socket. Returns:
510 - resp: server response if succesful"""
511
Guido van Rossumc629d341992-11-05 10:43:02 +0000512 resp = self.shortcmd('QUIT')
513 self.file.close()
514 self.sock.close()
515 del self.file, self.sock
516 return resp
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000517
518
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000519def _test():
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000520 """Minimal test function."""
Barry Warsaw9dd78722000-02-10 20:25:53 +0000521 s = NNTP('news', readermode='reader')
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000522 resp, count, first, last, name = s.group('comp.lang.python')
523 print resp
524 print 'Group', name, 'has', count, 'articles, range', first, 'to', last
525 resp, subs = s.xhdr('subject', first + '-' + last)
526 print resp
527 for item in subs:
528 print "%7s %s" % item
529 resp = s.quit()
530 print resp
531
532
533# Run the test when run as a script
534if __name__ == '__main__':
535 _test()