]> git.proxmox.com Git - mirror_edk2.git/blame - AppPkg/Applications/Python/Python-2.7.2/Lib/nntplib.py
EmbeddedPkg: Extend NvVarStoreFormattedLib LIBRARY_CLASS
[mirror_edk2.git] / AppPkg / Applications / Python / Python-2.7.2 / Lib / nntplib.py
CommitLineData
4710c53d 1"""An NNTP client class based on RFC 977: Network News Transfer Protocol.\r
2\r
3Example:\r
4\r
5>>> from nntplib import NNTP\r
6>>> s = NNTP('news')\r
7>>> resp, count, first, last, name = s.group('comp.lang.python')\r
8>>> print 'Group', name, 'has', count, 'articles, range', first, 'to', last\r
9Group comp.lang.python has 51 articles, range 5770 to 5821\r
10>>> resp, subs = s.xhdr('subject', first + '-' + last)\r
11>>> resp = s.quit()\r
12>>>\r
13\r
14Here 'resp' is the server response line.\r
15Error responses are turned into exceptions.\r
16\r
17To post an article from a file:\r
18>>> f = open(filename, 'r') # file containing article, including header\r
19>>> resp = s.post(f)\r
20>>>\r
21\r
22For descriptions of all methods, read the comments in the code below.\r
23Note that all arguments and return values representing article numbers\r
24are strings, not numbers, since they are rarely used for calculations.\r
25"""\r
26\r
27# RFC 977 by Brian Kantor and Phil Lapsley.\r
28# xover, xgtitle, xpath, date methods by Kevan Heydon\r
29\r
30\r
31# Imports\r
32import re\r
33import socket\r
34\r
35__all__ = ["NNTP","NNTPReplyError","NNTPTemporaryError",\r
36 "NNTPPermanentError","NNTPProtocolError","NNTPDataError",\r
37 "error_reply","error_temp","error_perm","error_proto",\r
38 "error_data",]\r
39\r
40# Exceptions raised when an error or invalid response is received\r
41class NNTPError(Exception):\r
42 """Base class for all nntplib exceptions"""\r
43 def __init__(self, *args):\r
44 Exception.__init__(self, *args)\r
45 try:\r
46 self.response = args[0]\r
47 except IndexError:\r
48 self.response = 'No response given'\r
49\r
50class NNTPReplyError(NNTPError):\r
51 """Unexpected [123]xx reply"""\r
52 pass\r
53\r
54class NNTPTemporaryError(NNTPError):\r
55 """4xx errors"""\r
56 pass\r
57\r
58class NNTPPermanentError(NNTPError):\r
59 """5xx errors"""\r
60 pass\r
61\r
62class NNTPProtocolError(NNTPError):\r
63 """Response does not begin with [1-5]"""\r
64 pass\r
65\r
66class NNTPDataError(NNTPError):\r
67 """Error in response data"""\r
68 pass\r
69\r
70# for backwards compatibility\r
71error_reply = NNTPReplyError\r
72error_temp = NNTPTemporaryError\r
73error_perm = NNTPPermanentError\r
74error_proto = NNTPProtocolError\r
75error_data = NNTPDataError\r
76\r
77\r
78\r
79# Standard port used by NNTP servers\r
80NNTP_PORT = 119\r
81\r
82\r
83# Response numbers that are followed by additional text (e.g. article)\r
84LONGRESP = ['100', '215', '220', '221', '222', '224', '230', '231', '282']\r
85\r
86\r
87# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)\r
88CRLF = '\r\n'\r
89\r
90\r
91\r
92# The class itself\r
93class NNTP:\r
94 def __init__(self, host, port=NNTP_PORT, user=None, password=None,\r
95 readermode=None, usenetrc=True):\r
96 """Initialize an instance. Arguments:\r
97 - host: hostname to connect to\r
98 - port: port to connect to (default the standard NNTP port)\r
99 - user: username to authenticate with\r
100 - password: password to use with username\r
101 - readermode: if true, send 'mode reader' command after\r
102 connecting.\r
103\r
104 readermode is sometimes necessary if you are connecting to an\r
105 NNTP server on the local machine and intend to call\r
106 reader-specific commands, such as `group'. If you get\r
107 unexpected NNTPPermanentErrors, you might need to set\r
108 readermode.\r
109 """\r
110 self.host = host\r
111 self.port = port\r
112 self.sock = socket.create_connection((host, port))\r
113 self.file = self.sock.makefile('rb')\r
114 self.debugging = 0\r
115 self.welcome = self.getresp()\r
116\r
117 # 'mode reader' is sometimes necessary to enable 'reader' mode.\r
118 # However, the order in which 'mode reader' and 'authinfo' need to\r
119 # arrive differs between some NNTP servers. Try to send\r
120 # 'mode reader', and if it fails with an authorization failed\r
121 # error, try again after sending authinfo.\r
122 readermode_afterauth = 0\r
123 if readermode:\r
124 try:\r
125 self.welcome = self.shortcmd('mode reader')\r
126 except NNTPPermanentError:\r
127 # error 500, probably 'not implemented'\r
128 pass\r
129 except NNTPTemporaryError, e:\r
130 if user and e.response[:3] == '480':\r
131 # Need authorization before 'mode reader'\r
132 readermode_afterauth = 1\r
133 else:\r
134 raise\r
135 # If no login/password was specified, try to get them from ~/.netrc\r
136 # Presume that if .netc has an entry, NNRP authentication is required.\r
137 try:\r
138 if usenetrc and not user:\r
139 import netrc\r
140 credentials = netrc.netrc()\r
141 auth = credentials.authenticators(host)\r
142 if auth:\r
143 user = auth[0]\r
144 password = auth[2]\r
145 except IOError:\r
146 pass\r
147 # Perform NNRP authentication if needed.\r
148 if user:\r
149 resp = self.shortcmd('authinfo user '+user)\r
150 if resp[:3] == '381':\r
151 if not password:\r
152 raise NNTPReplyError(resp)\r
153 else:\r
154 resp = self.shortcmd(\r
155 'authinfo pass '+password)\r
156 if resp[:3] != '281':\r
157 raise NNTPPermanentError(resp)\r
158 if readermode_afterauth:\r
159 try:\r
160 self.welcome = self.shortcmd('mode reader')\r
161 except NNTPPermanentError:\r
162 # error 500, probably 'not implemented'\r
163 pass\r
164\r
165\r
166 # Get the welcome message from the server\r
167 # (this is read and squirreled away by __init__()).\r
168 # If the response code is 200, posting is allowed;\r
169 # if it 201, posting is not allowed\r
170\r
171 def getwelcome(self):\r
172 """Get the welcome message from the server\r
173 (this is read and squirreled away by __init__()).\r
174 If the response code is 200, posting is allowed;\r
175 if it 201, posting is not allowed."""\r
176\r
177 if self.debugging: print '*welcome*', repr(self.welcome)\r
178 return self.welcome\r
179\r
180 def set_debuglevel(self, level):\r
181 """Set the debugging level. Argument 'level' means:\r
182 0: no debugging output (default)\r
183 1: print commands and responses but not body text etc.\r
184 2: also print raw lines read and sent before stripping CR/LF"""\r
185\r
186 self.debugging = level\r
187 debug = set_debuglevel\r
188\r
189 def putline(self, line):\r
190 """Internal: send one line to the server, appending CRLF."""\r
191 line = line + CRLF\r
192 if self.debugging > 1: print '*put*', repr(line)\r
193 self.sock.sendall(line)\r
194\r
195 def putcmd(self, line):\r
196 """Internal: send one command to the server (through putline())."""\r
197 if self.debugging: print '*cmd*', repr(line)\r
198 self.putline(line)\r
199\r
200 def getline(self):\r
201 """Internal: return one line from the server, stripping CRLF.\r
202 Raise EOFError if the connection is closed."""\r
203 line = self.file.readline()\r
204 if self.debugging > 1:\r
205 print '*get*', repr(line)\r
206 if not line: raise EOFError\r
207 if line[-2:] == CRLF: line = line[:-2]\r
208 elif line[-1:] in CRLF: line = line[:-1]\r
209 return line\r
210\r
211 def getresp(self):\r
212 """Internal: get a response from the server.\r
213 Raise various errors if the response indicates an error."""\r
214 resp = self.getline()\r
215 if self.debugging: print '*resp*', repr(resp)\r
216 c = resp[:1]\r
217 if c == '4':\r
218 raise NNTPTemporaryError(resp)\r
219 if c == '5':\r
220 raise NNTPPermanentError(resp)\r
221 if c not in '123':\r
222 raise NNTPProtocolError(resp)\r
223 return resp\r
224\r
225 def getlongresp(self, file=None):\r
226 """Internal: get a response plus following text from the server.\r
227 Raise various errors if the response indicates an error."""\r
228\r
229 openedFile = None\r
230 try:\r
231 # If a string was passed then open a file with that name\r
232 if isinstance(file, str):\r
233 openedFile = file = open(file, "w")\r
234\r
235 resp = self.getresp()\r
236 if resp[:3] not in LONGRESP:\r
237 raise NNTPReplyError(resp)\r
238 list = []\r
239 while 1:\r
240 line = self.getline()\r
241 if line == '.':\r
242 break\r
243 if line[:2] == '..':\r
244 line = line[1:]\r
245 if file:\r
246 file.write(line + "\n")\r
247 else:\r
248 list.append(line)\r
249 finally:\r
250 # If this method created the file, then it must close it\r
251 if openedFile:\r
252 openedFile.close()\r
253\r
254 return resp, list\r
255\r
256 def shortcmd(self, line):\r
257 """Internal: send a command and get the response."""\r
258 self.putcmd(line)\r
259 return self.getresp()\r
260\r
261 def longcmd(self, line, file=None):\r
262 """Internal: send a command and get the response plus following text."""\r
263 self.putcmd(line)\r
264 return self.getlongresp(file)\r
265\r
266 def newgroups(self, date, time, file=None):\r
267 """Process a NEWGROUPS command. Arguments:\r
268 - date: string 'yymmdd' indicating the date\r
269 - time: string 'hhmmss' indicating the time\r
270 Return:\r
271 - resp: server response if successful\r
272 - list: list of newsgroup names"""\r
273\r
274 return self.longcmd('NEWGROUPS ' + date + ' ' + time, file)\r
275\r
276 def newnews(self, group, date, time, file=None):\r
277 """Process a NEWNEWS command. Arguments:\r
278 - group: group name or '*'\r
279 - date: string 'yymmdd' indicating the date\r
280 - time: string 'hhmmss' indicating the time\r
281 Return:\r
282 - resp: server response if successful\r
283 - list: list of message ids"""\r
284\r
285 cmd = 'NEWNEWS ' + group + ' ' + date + ' ' + time\r
286 return self.longcmd(cmd, file)\r
287\r
288 def list(self, file=None):\r
289 """Process a LIST command. Return:\r
290 - resp: server response if successful\r
291 - list: list of (group, last, first, flag) (strings)"""\r
292\r
293 resp, list = self.longcmd('LIST', file)\r
294 for i in range(len(list)):\r
295 # Parse lines into "group last first flag"\r
296 list[i] = tuple(list[i].split())\r
297 return resp, list\r
298\r
299 def description(self, group):\r
300\r
301 """Get a description for a single group. If more than one\r
302 group matches ('group' is a pattern), return the first. If no\r
303 group matches, return an empty string.\r
304\r
305 This elides the response code from the server, since it can\r
306 only be '215' or '285' (for xgtitle) anyway. If the response\r
307 code is needed, use the 'descriptions' method.\r
308\r
309 NOTE: This neither checks for a wildcard in 'group' nor does\r
310 it check whether the group actually exists."""\r
311\r
312 resp, lines = self.descriptions(group)\r
313 if len(lines) == 0:\r
314 return ""\r
315 else:\r
316 return lines[0][1]\r
317\r
318 def descriptions(self, group_pattern):\r
319 """Get descriptions for a range of groups."""\r
320 line_pat = re.compile("^(?P<group>[^ \t]+)[ \t]+(.*)$")\r
321 # Try the more std (acc. to RFC2980) LIST NEWSGROUPS first\r
322 resp, raw_lines = self.longcmd('LIST NEWSGROUPS ' + group_pattern)\r
323 if resp[:3] != "215":\r
324 # Now the deprecated XGTITLE. This either raises an error\r
325 # or succeeds with the same output structure as LIST\r
326 # NEWSGROUPS.\r
327 resp, raw_lines = self.longcmd('XGTITLE ' + group_pattern)\r
328 lines = []\r
329 for raw_line in raw_lines:\r
330 match = line_pat.search(raw_line.strip())\r
331 if match:\r
332 lines.append(match.group(1, 2))\r
333 return resp, lines\r
334\r
335 def group(self, name):\r
336 """Process a GROUP command. Argument:\r
337 - group: the group name\r
338 Returns:\r
339 - resp: server response if successful\r
340 - count: number of articles (string)\r
341 - first: first article number (string)\r
342 - last: last article number (string)\r
343 - name: the group name"""\r
344\r
345 resp = self.shortcmd('GROUP ' + name)\r
346 if resp[:3] != '211':\r
347 raise NNTPReplyError(resp)\r
348 words = resp.split()\r
349 count = first = last = 0\r
350 n = len(words)\r
351 if n > 1:\r
352 count = words[1]\r
353 if n > 2:\r
354 first = words[2]\r
355 if n > 3:\r
356 last = words[3]\r
357 if n > 4:\r
358 name = words[4].lower()\r
359 return resp, count, first, last, name\r
360\r
361 def help(self, file=None):\r
362 """Process a HELP command. Returns:\r
363 - resp: server response if successful\r
364 - list: list of strings"""\r
365\r
366 return self.longcmd('HELP',file)\r
367\r
368 def statparse(self, resp):\r
369 """Internal: parse the response of a STAT, NEXT or LAST command."""\r
370 if resp[:2] != '22':\r
371 raise NNTPReplyError(resp)\r
372 words = resp.split()\r
373 nr = 0\r
374 id = ''\r
375 n = len(words)\r
376 if n > 1:\r
377 nr = words[1]\r
378 if n > 2:\r
379 id = words[2]\r
380 return resp, nr, id\r
381\r
382 def statcmd(self, line):\r
383 """Internal: process a STAT, NEXT or LAST command."""\r
384 resp = self.shortcmd(line)\r
385 return self.statparse(resp)\r
386\r
387 def stat(self, id):\r
388 """Process a STAT command. Argument:\r
389 - id: article number or message id\r
390 Returns:\r
391 - resp: server response if successful\r
392 - nr: the article number\r
393 - id: the message id"""\r
394\r
395 return self.statcmd('STAT ' + id)\r
396\r
397 def next(self):\r
398 """Process a NEXT command. No arguments. Return as for STAT."""\r
399 return self.statcmd('NEXT')\r
400\r
401 def last(self):\r
402 """Process a LAST command. No arguments. Return as for STAT."""\r
403 return self.statcmd('LAST')\r
404\r
405 def artcmd(self, line, file=None):\r
406 """Internal: process a HEAD, BODY or ARTICLE command."""\r
407 resp, list = self.longcmd(line, file)\r
408 resp, nr, id = self.statparse(resp)\r
409 return resp, nr, id, list\r
410\r
411 def head(self, id):\r
412 """Process a HEAD command. Argument:\r
413 - id: article number or message id\r
414 Returns:\r
415 - resp: server response if successful\r
416 - nr: article number\r
417 - id: message id\r
418 - list: the lines of the article's header"""\r
419\r
420 return self.artcmd('HEAD ' + id)\r
421\r
422 def body(self, id, file=None):\r
423 """Process a BODY command. Argument:\r
424 - id: article number or message id\r
425 - file: Filename string or file object to store the article in\r
426 Returns:\r
427 - resp: server response if successful\r
428 - nr: article number\r
429 - id: message id\r
430 - list: the lines of the article's body or an empty list\r
431 if file was used"""\r
432\r
433 return self.artcmd('BODY ' + id, file)\r
434\r
435 def article(self, id):\r
436 """Process an ARTICLE command. Argument:\r
437 - id: article number or message id\r
438 Returns:\r
439 - resp: server response if successful\r
440 - nr: article number\r
441 - id: message id\r
442 - list: the lines of the article"""\r
443\r
444 return self.artcmd('ARTICLE ' + id)\r
445\r
446 def slave(self):\r
447 """Process a SLAVE command. Returns:\r
448 - resp: server response if successful"""\r
449\r
450 return self.shortcmd('SLAVE')\r
451\r
452 def xhdr(self, hdr, str, file=None):\r
453 """Process an XHDR command (optional server extension). Arguments:\r
454 - hdr: the header type (e.g. 'subject')\r
455 - str: an article nr, a message id, or a range nr1-nr2\r
456 Returns:\r
457 - resp: server response if successful\r
458 - list: list of (nr, value) strings"""\r
459\r
460 pat = re.compile('^([0-9]+) ?(.*)\n?')\r
461 resp, lines = self.longcmd('XHDR ' + hdr + ' ' + str, file)\r
462 for i in range(len(lines)):\r
463 line = lines[i]\r
464 m = pat.match(line)\r
465 if m:\r
466 lines[i] = m.group(1, 2)\r
467 return resp, lines\r
468\r
469 def xover(self, start, end, file=None):\r
470 """Process an XOVER command (optional server extension) Arguments:\r
471 - start: start of range\r
472 - end: end of range\r
473 Returns:\r
474 - resp: server response if successful\r
475 - list: list of (art-nr, subject, poster, date,\r
476 id, references, size, lines)"""\r
477\r
478 resp, lines = self.longcmd('XOVER ' + start + '-' + end, file)\r
479 xover_lines = []\r
480 for line in lines:\r
481 elem = line.split("\t")\r
482 try:\r
483 xover_lines.append((elem[0],\r
484 elem[1],\r
485 elem[2],\r
486 elem[3],\r
487 elem[4],\r
488 elem[5].split(),\r
489 elem[6],\r
490 elem[7]))\r
491 except IndexError:\r
492 raise NNTPDataError(line)\r
493 return resp,xover_lines\r
494\r
495 def xgtitle(self, group, file=None):\r
496 """Process an XGTITLE command (optional server extension) Arguments:\r
497 - group: group name wildcard (i.e. news.*)\r
498 Returns:\r
499 - resp: server response if successful\r
500 - list: list of (name,title) strings"""\r
501\r
502 line_pat = re.compile("^([^ \t]+)[ \t]+(.*)$")\r
503 resp, raw_lines = self.longcmd('XGTITLE ' + group, file)\r
504 lines = []\r
505 for raw_line in raw_lines:\r
506 match = line_pat.search(raw_line.strip())\r
507 if match:\r
508 lines.append(match.group(1, 2))\r
509 return resp, lines\r
510\r
511 def xpath(self,id):\r
512 """Process an XPATH command (optional server extension) Arguments:\r
513 - id: Message id of article\r
514 Returns:\r
515 resp: server response if successful\r
516 path: directory path to article"""\r
517\r
518 resp = self.shortcmd("XPATH " + id)\r
519 if resp[:3] != '223':\r
520 raise NNTPReplyError(resp)\r
521 try:\r
522 [resp_num, path] = resp.split()\r
523 except ValueError:\r
524 raise NNTPReplyError(resp)\r
525 else:\r
526 return resp, path\r
527\r
528 def date (self):\r
529 """Process the DATE command. Arguments:\r
530 None\r
531 Returns:\r
532 resp: server response if successful\r
533 date: Date suitable for newnews/newgroups commands etc.\r
534 time: Time suitable for newnews/newgroups commands etc."""\r
535\r
536 resp = self.shortcmd("DATE")\r
537 if resp[:3] != '111':\r
538 raise NNTPReplyError(resp)\r
539 elem = resp.split()\r
540 if len(elem) != 2:\r
541 raise NNTPDataError(resp)\r
542 date = elem[1][2:8]\r
543 time = elem[1][-6:]\r
544 if len(date) != 6 or len(time) != 6:\r
545 raise NNTPDataError(resp)\r
546 return resp, date, time\r
547\r
548\r
549 def post(self, f):\r
550 """Process a POST command. Arguments:\r
551 - f: file containing the article\r
552 Returns:\r
553 - resp: server response if successful"""\r
554\r
555 resp = self.shortcmd('POST')\r
556 # Raises error_??? if posting is not allowed\r
557 if resp[0] != '3':\r
558 raise NNTPReplyError(resp)\r
559 while 1:\r
560 line = f.readline()\r
561 if not line:\r
562 break\r
563 if line[-1] == '\n':\r
564 line = line[:-1]\r
565 if line[:1] == '.':\r
566 line = '.' + line\r
567 self.putline(line)\r
568 self.putline('.')\r
569 return self.getresp()\r
570\r
571 def ihave(self, id, f):\r
572 """Process an IHAVE command. Arguments:\r
573 - id: message-id of the article\r
574 - f: file containing the article\r
575 Returns:\r
576 - resp: server response if successful\r
577 Note that if the server refuses the article an exception is raised."""\r
578\r
579 resp = self.shortcmd('IHAVE ' + id)\r
580 # Raises error_??? if the server already has it\r
581 if resp[0] != '3':\r
582 raise NNTPReplyError(resp)\r
583 while 1:\r
584 line = f.readline()\r
585 if not line:\r
586 break\r
587 if line[-1] == '\n':\r
588 line = line[:-1]\r
589 if line[:1] == '.':\r
590 line = '.' + line\r
591 self.putline(line)\r
592 self.putline('.')\r
593 return self.getresp()\r
594\r
595 def quit(self):\r
596 """Process a QUIT command and close the socket. Returns:\r
597 - resp: server response if successful"""\r
598\r
599 resp = self.shortcmd('QUIT')\r
600 self.file.close()\r
601 self.sock.close()\r
602 del self.file, self.sock\r
603 return resp\r
604\r
605\r
606# Test retrieval when run as a script.\r
607# Assumption: if there's a local news server, it's called 'news'.\r
608# Assumption: if user queries a remote news server, it's named\r
609# in the environment variable NNTPSERVER (used by slrn and kin)\r
610# and we want readermode off.\r
611if __name__ == '__main__':\r
612 import os\r
613 newshost = 'news' and os.environ["NNTPSERVER"]\r
614 if newshost.find('.') == -1:\r
615 mode = 'readermode'\r
616 else:\r
617 mode = None\r
618 s = NNTP(newshost, readermode=mode)\r
619 resp, count, first, last, name = s.group('comp.lang.python')\r
620 print resp\r
621 print 'Group', name, 'has', count, 'articles, range', first, 'to', last\r
622 resp, subs = s.xhdr('subject', first + '-' + last)\r
623 print resp\r
624 for item in subs:\r
625 print "%7s %s" % item\r
626 resp = s.quit()\r
627 print resp\r