blob: eddd31afe7a53363d53f8ee8e0e533821352bf0b [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
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
Tim Peters2344fae2001-01-15 00:50:52 +0000214 def getlongresp(self):
215 """Internal: get a response plus following text from the server.
216 Raise various errors if the response indicates an error."""
217 resp = self.getresp()
218 if resp[:3] not in LONGRESP:
219 raise NNTPReplyError(resp)
220 list = []
221 while 1:
222 line = self.getline()
223 if line == '.':
224 break
225 if line[:2] == '..':
226 line = line[1:]
227 list.append(line)
228 return resp, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000229
Tim Peters2344fae2001-01-15 00:50:52 +0000230 def shortcmd(self, line):
231 """Internal: send a command and get the response."""
232 self.putcmd(line)
233 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000234
Tim Peters2344fae2001-01-15 00:50:52 +0000235 def longcmd(self, line):
236 """Internal: send a command and get the response plus following text."""
237 self.putcmd(line)
238 return self.getlongresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000239
Tim Peters2344fae2001-01-15 00:50:52 +0000240 def newgroups(self, date, time):
241 """Process a NEWGROUPS command. Arguments:
242 - date: string 'yymmdd' indicating the date
243 - time: string 'hhmmss' indicating the time
244 Return:
245 - resp: server response if successful
246 - list: list of newsgroup names"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000247
Tim Peters2344fae2001-01-15 00:50:52 +0000248 return self.longcmd('NEWGROUPS ' + date + ' ' + time)
Guido van Rossumc629d341992-11-05 10:43:02 +0000249
Tim Peters2344fae2001-01-15 00:50:52 +0000250 def newnews(self, group, date, time):
251 """Process a NEWNEWS command. Arguments:
252 - group: group name or '*'
253 - date: string 'yymmdd' indicating the date
254 - time: string 'hhmmss' indicating the time
255 Return:
256 - resp: server response if successful
257 - list: list of article ids"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000258
Tim Peters2344fae2001-01-15 00:50:52 +0000259 cmd = 'NEWNEWS ' + group + ' ' + date + ' ' + time
260 return self.longcmd(cmd)
Guido van Rossumc629d341992-11-05 10:43:02 +0000261
Tim Peters2344fae2001-01-15 00:50:52 +0000262 def list(self):
263 """Process a LIST command. Return:
264 - resp: server response if successful
265 - list: list of (group, last, first, flag) (strings)"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000266
Tim Peters2344fae2001-01-15 00:50:52 +0000267 resp, list = self.longcmd('LIST')
268 for i in range(len(list)):
269 # Parse lines into "group last first flag"
270 list[i] = tuple(string.split(list[i]))
271 return resp, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000272
Tim Peters2344fae2001-01-15 00:50:52 +0000273 def group(self, name):
274 """Process a GROUP command. Argument:
275 - group: the group name
276 Returns:
277 - resp: server response if successful
278 - count: number of articles (string)
279 - first: first article number (string)
280 - last: last article number (string)
281 - name: the group name"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000282
Tim Peters2344fae2001-01-15 00:50:52 +0000283 resp = self.shortcmd('GROUP ' + name)
284 if resp[:3] != '211':
285 raise NNTPReplyError(resp)
286 words = string.split(resp)
287 count = first = last = 0
288 n = len(words)
289 if n > 1:
290 count = words[1]
291 if n > 2:
292 first = words[2]
293 if n > 3:
294 last = words[3]
295 if n > 4:
296 name = string.lower(words[4])
297 return resp, count, first, last, name
Guido van Rossumc629d341992-11-05 10:43:02 +0000298
Tim Peters2344fae2001-01-15 00:50:52 +0000299 def help(self):
300 """Process a HELP command. Returns:
301 - resp: server response if successful
302 - list: list of strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000303
Tim Peters2344fae2001-01-15 00:50:52 +0000304 return self.longcmd('HELP')
Guido van Rossumc629d341992-11-05 10:43:02 +0000305
Tim Peters2344fae2001-01-15 00:50:52 +0000306 def statparse(self, resp):
307 """Internal: parse the response of a STAT, NEXT or LAST command."""
308 if resp[:2] != '22':
309 raise NNTPReplyError(resp)
310 words = string.split(resp)
311 nr = 0
312 id = ''
313 n = len(words)
314 if n > 1:
315 nr = words[1]
316 if n > 2:
317 id = words[2]
318 return resp, nr, id
Guido van Rossumc629d341992-11-05 10:43:02 +0000319
Tim Peters2344fae2001-01-15 00:50:52 +0000320 def statcmd(self, line):
321 """Internal: process a STAT, NEXT or LAST command."""
322 resp = self.shortcmd(line)
323 return self.statparse(resp)
Guido van Rossumc629d341992-11-05 10:43:02 +0000324
Tim Peters2344fae2001-01-15 00:50:52 +0000325 def stat(self, id):
326 """Process a STAT command. Argument:
327 - id: article number or message id
328 Returns:
329 - resp: server response if successful
330 - nr: the article number
331 - id: the article id"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000332
Tim Peters2344fae2001-01-15 00:50:52 +0000333 return self.statcmd('STAT ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000334
Tim Peters2344fae2001-01-15 00:50:52 +0000335 def next(self):
336 """Process a NEXT command. No arguments. Return as for STAT."""
337 return self.statcmd('NEXT')
Guido van Rossumc629d341992-11-05 10:43:02 +0000338
Tim Peters2344fae2001-01-15 00:50:52 +0000339 def last(self):
340 """Process a LAST command. No arguments. Return as for STAT."""
341 return self.statcmd('LAST')
Guido van Rossumc629d341992-11-05 10:43:02 +0000342
Tim Peters2344fae2001-01-15 00:50:52 +0000343 def artcmd(self, line):
344 """Internal: process a HEAD, BODY or ARTICLE command."""
345 resp, list = self.longcmd(line)
346 resp, nr, id = self.statparse(resp)
347 return resp, nr, id, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000348
Tim Peters2344fae2001-01-15 00:50:52 +0000349 def head(self, id):
350 """Process a HEAD command. Argument:
351 - id: article number or message id
352 Returns:
353 - resp: server response if successful
354 - nr: article number
355 - id: message id
356 - list: the lines of the article's header"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000357
Tim Peters2344fae2001-01-15 00:50:52 +0000358 return self.artcmd('HEAD ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000359
Tim Peters2344fae2001-01-15 00:50:52 +0000360 def body(self, id):
361 """Process a BODY command. Argument:
362 - id: article number or message id
363 Returns:
364 - resp: server response if successful
365 - nr: article number
366 - id: message id
367 - list: the lines of the article's body"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000368
Tim Peters2344fae2001-01-15 00:50:52 +0000369 return self.artcmd('BODY ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000370
Tim Peters2344fae2001-01-15 00:50:52 +0000371 def article(self, id):
372 """Process an ARTICLE command. Argument:
373 - id: article number or message id
374 Returns:
375 - resp: server response if successful
376 - nr: article number
377 - id: message id
378 - list: the lines of the article"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000379
Tim Peters2344fae2001-01-15 00:50:52 +0000380 return self.artcmd('ARTICLE ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000381
Tim Peters2344fae2001-01-15 00:50:52 +0000382 def slave(self):
383 """Process a SLAVE command. Returns:
384 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000385
Tim Peters2344fae2001-01-15 00:50:52 +0000386 return self.shortcmd('SLAVE')
Guido van Rossumc629d341992-11-05 10:43:02 +0000387
Tim Peters2344fae2001-01-15 00:50:52 +0000388 def xhdr(self, hdr, str):
389 """Process an XHDR command (optional server extension). Arguments:
390 - hdr: the header type (e.g. 'subject')
391 - str: an article nr, a message id, or a range nr1-nr2
392 Returns:
393 - resp: server response if successful
394 - list: list of (nr, value) strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000395
Tim Peters2344fae2001-01-15 00:50:52 +0000396 pat = re.compile('^([0-9]+) ?(.*)\n?')
397 resp, lines = self.longcmd('XHDR ' + hdr + ' ' + str)
398 for i in range(len(lines)):
399 line = lines[i]
400 m = pat.match(line)
401 if m:
402 lines[i] = m.group(1, 2)
403 return resp, lines
Guido van Rossumc629d341992-11-05 10:43:02 +0000404
Tim Peters2344fae2001-01-15 00:50:52 +0000405 def xover(self,start,end):
406 """Process an XOVER command (optional server extension) Arguments:
407 - start: start of range
408 - end: end of range
409 Returns:
410 - resp: server response if successful
411 - list: list of (art-nr, subject, poster, date,
412 id, references, size, lines)"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000413
Tim Peters2344fae2001-01-15 00:50:52 +0000414 resp, lines = self.longcmd('XOVER ' + start + '-' + end)
415 xover_lines = []
416 for line in lines:
417 elem = string.splitfields(line,"\t")
418 try:
419 xover_lines.append((elem[0],
420 elem[1],
421 elem[2],
422 elem[3],
423 elem[4],
424 string.split(elem[5]),
425 elem[6],
426 elem[7]))
427 except IndexError:
428 raise NNTPDataError(line)
429 return resp,xover_lines
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000430
Tim Peters2344fae2001-01-15 00:50:52 +0000431 def xgtitle(self, group):
432 """Process an XGTITLE command (optional server extension) Arguments:
433 - group: group name wildcard (i.e. news.*)
434 Returns:
435 - resp: server response if successful
436 - list: list of (name,title) strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000437
Tim Peters2344fae2001-01-15 00:50:52 +0000438 line_pat = re.compile("^([^ \t]+)[ \t]+(.*)$")
439 resp, raw_lines = self.longcmd('XGTITLE ' + group)
440 lines = []
441 for raw_line in raw_lines:
442 match = line_pat.search(string.strip(raw_line))
443 if match:
444 lines.append(match.group(1, 2))
445 return resp, lines
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000446
Tim Peters2344fae2001-01-15 00:50:52 +0000447 def xpath(self,id):
448 """Process an XPATH command (optional server extension) Arguments:
449 - id: Message id of article
450 Returns:
451 resp: server response if successful
452 path: directory path to article"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000453
Tim Peters2344fae2001-01-15 00:50:52 +0000454 resp = self.shortcmd("XPATH " + id)
455 if resp[:3] != '223':
456 raise NNTPReplyError(resp)
457 try:
458 [resp_num, path] = string.split(resp)
459 except ValueError:
460 raise NNTPReplyError(resp)
461 else:
462 return resp, path
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000463
Tim Peters2344fae2001-01-15 00:50:52 +0000464 def date (self):
465 """Process the DATE command. Arguments:
466 None
467 Returns:
468 resp: server response if successful
469 date: Date suitable for newnews/newgroups commands etc.
470 time: Time suitable for newnews/newgroups commands etc."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000471
Tim Peters2344fae2001-01-15 00:50:52 +0000472 resp = self.shortcmd("DATE")
473 if resp[:3] != '111':
474 raise NNTPReplyError(resp)
475 elem = string.split(resp)
476 if len(elem) != 2:
477 raise NNTPDataError(resp)
478 date = elem[1][2:8]
479 time = elem[1][-6:]
480 if len(date) != 6 or len(time) != 6:
481 raise NNTPDataError(resp)
482 return resp, date, time
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000483
484
Tim Peters2344fae2001-01-15 00:50:52 +0000485 def post(self, f):
486 """Process a POST command. Arguments:
487 - f: file containing the article
488 Returns:
489 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000490
Tim Peters2344fae2001-01-15 00:50:52 +0000491 resp = self.shortcmd('POST')
492 # Raises error_??? if posting is not allowed
493 if resp[0] != '3':
494 raise NNTPReplyError(resp)
495 while 1:
496 line = f.readline()
497 if not line:
498 break
499 if line[-1] == '\n':
500 line = line[:-1]
501 if line[:1] == '.':
502 line = '.' + line
503 self.putline(line)
504 self.putline('.')
505 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000506
Tim Peters2344fae2001-01-15 00:50:52 +0000507 def ihave(self, id, f):
508 """Process an IHAVE command. Arguments:
509 - id: message-id of the article
510 - f: file containing the article
511 Returns:
512 - resp: server response if successful
513 Note that if the server refuses the article an exception is raised."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000514
Tim Peters2344fae2001-01-15 00:50:52 +0000515 resp = self.shortcmd('IHAVE ' + id)
516 # Raises error_??? if the server already has it
517 if resp[0] != '3':
518 raise NNTPReplyError(resp)
519 while 1:
520 line = f.readline()
521 if not line:
522 break
523 if line[-1] == '\n':
524 line = line[:-1]
525 if line[:1] == '.':
526 line = '.' + line
527 self.putline(line)
528 self.putline('.')
529 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000530
Tim Peters2344fae2001-01-15 00:50:52 +0000531 def quit(self):
532 """Process a QUIT command and close the socket. Returns:
533 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000534
Tim Peters2344fae2001-01-15 00:50:52 +0000535 resp = self.shortcmd('QUIT')
536 self.file.close()
537 self.sock.close()
538 del self.file, self.sock
539 return resp
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000540
541
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000542def _test():
Tim Peters2344fae2001-01-15 00:50:52 +0000543 """Minimal test function."""
544 s = NNTP('news', readermode='reader')
545 resp, count, first, last, name = s.group('comp.lang.python')
546 print resp
547 print 'Group', name, 'has', count, 'articles, range', first, 'to', last
548 resp, subs = s.xhdr('subject', first + '-' + last)
549 print resp
550 for item in subs:
551 print "%7s %s" % item
552 resp = s.quit()
553 print resp
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000554
555
556# Run the test when run as a script
557if __name__ == '__main__':
Tim Peters2344fae2001-01-15 00:50:52 +0000558 _test()