home *** CD-ROM | disk | FTP | other *** search
/ The Datafile PD-CD 5 / DATAFILE_PDCD5.iso / utilities / p / python / !Python / Lib / NetLib / py / nntplib < prev    next >
Text File  |  1996-02-14  |  12KB  |  449 lines

  1. # An NNTP client class.  Based on RFC 977: Network News Transfer
  2. # Protocol, by Brian Kantor and Phil Lapsley.
  3.  
  4.  
  5. # Example:
  6. #
  7. # >>> from nntplib import NNTP
  8. # >>> s = NNTP('news')
  9. # >>> resp, count, first, last, name = s.group('comp.lang.python')
  10. # >>> print 'Group', name, 'has', count, 'articles, range', first, 'to', last
  11. # Group comp.lang.python has 51 articles, range 5770 to 5821
  12. # >>> resp, subs = s.xhdr('subject', first + '-' + last)
  13. # >>> resp = s.quit()
  14. # >>>
  15. #
  16. # Here 'resp' is the server response line.
  17. # Error responses are turned into exceptions.
  18. #
  19. # To post an article from a file:
  20. # >>> f = open(filename, 'r') # file containing article, including header
  21. # >>> resp = s.post(f)
  22. # >>>
  23. #
  24. # For descriptions of all methods, read the comments in the code below.
  25. # Note that all arguments and return values representing article numbers
  26. # are strings, not numbers, since they are rarely used for calculations.
  27.  
  28. # (xover, xgtitle, xpath, date methods by Kevan Heydon)
  29.  
  30.  
  31. # Imports
  32. import regex
  33. import socket
  34. import string
  35.  
  36.  
  37. # Exception raised when an error or invalid response is received
  38.  
  39. error_reply = 'nntplib.error_reply'    # unexpected [123]xx reply
  40. error_temp = 'nntplib.error_temp'    # 4xx errors
  41. error_perm = 'nntplib.error_perm'    # 5xx errors
  42. error_proto = 'nntplib.error_proto'    # response does not begin with [1-5]
  43. error_data = 'nntplib.error_data'    # error in response data
  44.  
  45.  
  46. # Standard port used by NNTP servers
  47. NNTP_PORT = 119
  48.  
  49.  
  50. # Response numbers that are followed by additional text (e.g. article)
  51. LONGRESP = ['100', '215', '220', '221', '222', '224', '230', '231', '282']
  52.  
  53.  
  54. # Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
  55. CRLF = '\r\n'
  56.  
  57.  
  58. # The class itself
  59.  
  60. class NNTP:
  61.  
  62.     # Initialize an instance.  Arguments:
  63.     # - host: hostname to connect to
  64.     # - port: port to connect to (default the standard NNTP port)
  65.  
  66.     def __init__(self, host, port = NNTP_PORT):
  67.         self.host = host
  68.         self.port = port
  69.         self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  70.         self.sock.connect(self.host, self.port)
  71.         self.file = self.sock.makefile('rb')
  72.         self.debugging = 0
  73.         self.welcome = self.getresp()
  74.  
  75.     # Get the welcome message from the server
  76.     # (this is read and squirreled away by __init__()).
  77.     # If the response code is 200, posting is allowed;
  78.     # if it 201, posting is not allowed
  79.  
  80.     def getwelcome(self):
  81.         if self.debugging: print '*welcome*', `self.welcome`
  82.         return self.welcome
  83.  
  84.     # Set the debugging level.  Argument level means:
  85.     # 0: no debugging output (default)
  86.     # 1: print commands and responses but not body text etc.
  87.     # 2: also print raw lines read and sent before stripping CR/LF
  88.  
  89.     def set_debuglevel(self, level):
  90.         self.debugging = level
  91.     debug = set_debuglevel
  92.  
  93.     # Internal: send one line to the server, appending CRLF
  94.     def putline(self, line):
  95.         line = line + CRLF
  96.         if self.debugging > 1: print '*put*', `line`
  97.         self.sock.send(line)
  98.  
  99.     # Internal: send one command to the server (through putline())
  100.     def putcmd(self, line):
  101.         if self.debugging: print '*cmd*', `line`
  102.         self.putline(line)
  103.  
  104.     # Internal: return one line from the server, stripping CRLF.
  105.     # Raise EOFError if the connection is closed
  106.     def getline(self):
  107.         line = self.file.readline()
  108.         if self.debugging > 1:
  109.             print '*get*', `line`
  110.         if not line: raise EOFError
  111.         if line[-2:] == CRLF: line = line[:-2]
  112.         elif line[-1:] in CRLF: line = line[:-1]
  113.         return line
  114.  
  115.     # Internal: get a response from the server.
  116.     # Raise various errors if the response indicates an error
  117.     def getresp(self):
  118.         resp = self.getline()
  119.         if self.debugging: print '*resp*', `resp`
  120.         c = resp[:1]
  121.         if c == '4':
  122.             raise error_temp, resp
  123.         if c == '5':
  124.             raise error_perm, resp
  125.         if c not in '123':
  126.             raise error_proto, resp
  127.         return resp
  128.  
  129.     # Internal: get a response plus following text from the server.
  130.     # Raise various errors if the response indicates an error
  131.     def getlongresp(self):
  132.         resp = self.getresp()
  133.         if resp[:3] not in LONGRESP:
  134.             raise error_reply, resp
  135.         list = []
  136.         while 1:
  137.             line = self.getline()
  138.             if line == '.':
  139.                 break
  140.             list.append(line)
  141.         return resp, list
  142.  
  143.     # Internal: send a command and get the response
  144.     def shortcmd(self, line):
  145.         self.putcmd(line)
  146.         return self.getresp()
  147.  
  148.     # Internal: send a command and get the response plus following text
  149.     def longcmd(self, line):
  150.         self.putcmd(line)
  151.         return self.getlongresp()
  152.  
  153.     # Process a NEWGROUPS command.  Arguments:
  154.     # - date: string 'yymmdd' indicating the date
  155.     # - time: string 'hhmmss' indicating the time
  156.     # Return:
  157.     # - resp: server response if succesful
  158.     # - list: list of newsgroup names
  159.  
  160.     def newgroups(self, date, time):
  161.         return self.longcmd('NEWGROUPS ' + date + ' ' + time)
  162.  
  163.     # Process a NEWNEWS command.  Arguments:
  164.     # - group: group name or '*'
  165.     # - date: string 'yymmdd' indicating the date
  166.     # - time: string 'hhmmss' indicating the time
  167.     # Return:
  168.     # - resp: server response if succesful
  169.     # - list: list of article ids
  170.  
  171.     def newnews(self, group, date, time):
  172.         cmd = 'NEWNEWS ' + group + ' ' + date + ' ' + time
  173.         return self.longcmd(cmd)
  174.  
  175.     # Process a LIST command.  Return:
  176.     # - resp: server response if succesful
  177.     # - list: list of (group, last, first, flag) (strings)
  178.  
  179.     def list(self):
  180.         resp, list = self.longcmd('LIST')
  181.         for i in range(len(list)):
  182.             # Parse lines into "group last first flag"
  183.             list[i] = string.split(list[i])
  184.         return resp, list
  185.  
  186.     # Process a GROUP command.  Argument:
  187.     # - group: the group name
  188.     # Returns:
  189.     # - resp: server response if succesful
  190.     # - count: number of articles (string)
  191.     # - first: first article number (string)
  192.     # - last: last article number (string)
  193.     # - name: the group name
  194.  
  195.     def group(self, name):
  196.         resp = self.shortcmd('GROUP ' + name)
  197.         if resp[:3] <> '211':
  198.             raise error_reply, resp
  199.         words = string.split(resp)
  200.         count = first = last = 0
  201.         n = len(words)
  202.         if n > 1:
  203.             count = words[1]
  204.             if n > 2:
  205.                 first = words[2]
  206.                 if n > 3:
  207.                     last = words[3]
  208.                     if n > 4:
  209.                         name = string.lower(words[4])
  210.         return resp, count, first, last, name
  211.  
  212.     # Process a HELP command.  Returns:
  213.     # - resp: server response if succesful
  214.     # - list: list of strings
  215.  
  216.     def help(self):
  217.         return self.longcmd('HELP')
  218.  
  219.     # Internal: parse the response of a STAT, NEXT or LAST command
  220.     def statparse(self, resp):
  221.         if resp[:2] <> '22':
  222.             raise error_reply, resp
  223.         words = string.split(resp)
  224.         nr = 0
  225.         id = ''
  226.         n = len(words)
  227.         if n > 1:
  228.             nr = words[1]
  229.             if n > 2:
  230.                 id = string.lower(words[2])
  231.         return resp, nr, id
  232.  
  233.     # Internal: process a STAT, NEXT or LAST command
  234.     def statcmd(self, line):
  235.         resp = self.shortcmd(line)
  236.         return self.statparse(resp)
  237.  
  238.     # Process a STAT command.  Argument:
  239.     # - id: article number or message id
  240.     # Returns:
  241.     # - resp: server response if succesful
  242.     # - nr:   the article number
  243.     # - id:   the article id
  244.  
  245.     def stat(self, id):
  246.         return self.statcmd('STAT ' + id)
  247.  
  248.     # Process a NEXT command.  No arguments.  Return as for STAT
  249.  
  250.     def next(self):
  251.         return self.statcmd('NEXT')
  252.  
  253.     # Process a LAST command.  No arguments.  Return as for STAT
  254.  
  255.     def last(self):
  256.         return self.statcmd('LAST')
  257.  
  258.     # Internal: process a HEAD, BODY or ARTICLE command
  259.     def artcmd(self, line):
  260.         resp, list = self.longcmd(line)
  261.         resp, nr, id = self.statparse(resp)
  262.         return resp, nr, id, list
  263.  
  264.     # Process a HEAD command.  Argument:
  265.     # - id: article number or message id
  266.     # Returns:
  267.     # - resp: server response if succesful
  268.     # - list: the lines of the article's header
  269.  
  270.     def head(self, id):
  271.         return self.artcmd('HEAD ' + id)
  272.  
  273.     # Process a BODY command.  Argument:
  274.     # - id: article number or message id
  275.     # Returns:
  276.     # - resp: server response if succesful
  277.     # - list: the lines of the article's body
  278.  
  279.     def body(self, id):
  280.         return self.artcmd('BODY ' + id)
  281.  
  282.     # Process an ARTICLE command.  Argument:
  283.     # - id: article number or message id
  284.     # Returns:
  285.     # - resp: server response if succesful
  286.     # - list: the lines of the article
  287.  
  288.     def article(self, id):
  289.         return self.artcmd('ARTICLE ' + id)
  290.  
  291.     # Process a SLAVE command.  Returns:
  292.     # - resp: server response if succesful
  293.  
  294.     def slave(self):
  295.         return self.shortcmd('SLAVE')
  296.  
  297.     # Process an XHDR command (optional server extension).  Arguments:
  298.     # - hdr: the header type (e.g. 'subject')
  299.     # - str: an article nr