home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / pyth_os2.zip / python-1.0.2 / Lib / nntplib.py < prev    next >
Text File  |  1993-12-29  |  10KB  |  368 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('charon')
  9. # >>> resp, count, first, last, name = s.group('nlnet.misc')
  10. # >>> print 'Group', name, 'has', count, 'articles, range', first, 'to', last
  11. # Group nlnet.misc has 525 articles, range 6960 to 7485
  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.  
  29. # Imports
  30. import regex
  31. import socket
  32. import string
  33.  
  34.  
  35. # Exception raised when an error or invalid response is received
  36.  
  37. error_reply = 'nntplib.error_reply'    # unexpected [123]xx reply
  38. error_temp = 'nntplib.error_temp'    # 4xx errors
  39. error_perm = 'nntplib.error_perm'    # 5xx errors
  40. error_proto = 'nntplib.error_proto'    # response does not begin with [1-5]
  41.  
  42.  
  43. # Standard port used by NNTP servers
  44. NNTP_PORT = 119
  45.  
  46.  
  47. # Response numbers that are followed by additional text (e.g. article)
  48. LONGRESP = ['100', '215', '220', '221', '222', '230', '231']
  49.  
  50.  
  51. # Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
  52. CRLF = '\r\n'
  53.  
  54.  
  55. # The class itself
  56.  
  57. class NNTP:
  58.  
  59.     # Initialize an instance.  Arguments:
  60.     # - host: hostname to connect to
  61.     # - port: port to connect to (default the standard NNTP port)
  62.  
  63.     def __init__(self, host, *args):
  64.         if len(args) > 1: raise TypeError, 'too many args'
  65.         if args: port = args[0]
  66.         else: 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('r')
  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 debug(self, level):
  90.         self.debugging = level
  91.  
  92.     # Internal: send one line to the server, appending CRLF
  93.     def putline(self, line):
  94.         line = line + CRLF
  95.         if self.debugging > 1: print '*put*', `line`
  96.         self.sock.send(line)
  97.  
  98.     # Internal: send one command to the server (through putline())
  99.     def putcmd(self, line):
  100.         if self.debugging: print '*cmd*', `line`
  101.         self.putline(line)
  102.  
  103.     # Internal: return one line from the server, stripping CRLF.
  104.     # Raise EOFError if the connection is closed
  105.     def getline(self):
  106.         line = self.file.readline()
  107.         if self.debugging > 1:
  108.             print '*get*', `line`
  109.         if not line: raise EOFError
  110.         if line[-2:] == CRLF: line = line[:-2]
  111.         elif line[-1:] in CRLF: line = line[:-1]
  112.         return line
  113.  
  114.     # Internal: get a response from the server.
  115.     # Raise various errors if the response indicates an error
  116.     def getresp(self):
  117.         resp = self.getline()
  118.         if self.debugging: print '*resp*', `resp`
  119.         c = resp[:1]
  120.         if c == '4':
  121.             raise error_temp, resp
  122.         if c == '5':
  123.             raise error_perm, resp
  124.         if c not in '123':
  125.             raise error_proto, resp
  126.         return resp
  127.  
  128.     # Internal: get a response plus following text from the server.
  129.     # Raise various errors if the response indicates an error
  130.     def getlongresp(self):
  131.         resp = self.getresp()
  132.         if resp[:3] not in LONGRESP:
  133.             raise error_reply, resp
  134.         list = []
  135.         while 1:
  136.             line = self.getline()
  137.             if line == '.':
  138.                 break
  139.             list.append(line)
  140.         return resp, list
  141.  
  142.     # Internal: send a command and get the response
  143.     def shortcmd(self, line):
  144.         self.putcmd(line)
  145.         return self.getresp()
  146.  
  147.     # Internal: send a command and get the response plus following text
  148.     def longcmd(self, line):
  149.         self.putcmd(line)
  150.         return self.getlongresp()
  151.  
  152.     # Process a NEWGROUPS command.  Arguments:
  153.     # - date: string 'yymmdd' indicating the date
  154.     # - time: string 'hhmmss' indicating the time
  155.     # Return:
  156.     # - resp: server response if succesful
  157.     # - list: list of newsgroup names
  158.  
  159.     def newgroups(self, date, time):
  160.         return self.longcmd('NEWGROUPS ' + date + ' ' + time)
  161.  
  162.     # Process a NEWNEWS command.  Arguments:
  163.     # - group: group name or '*'
  164.     # - date: string 'yymmdd' indicating the date
  165.     # - time: string 'hhmmss' indicating the time
  166.     # Return:
  167.     # - resp: server response if succesful
  168.     # - list: list of article ids
  169.  
  170.     def newnews(self, group, date, time):
  171.         cmd = 'NEWNEWS ' + group + ' ' + date + ' ' + time
  172.         return self.longcmd(cmd)
  173.  
  174.     # Process a LIST command.  Return:
  175.     # - resp: server response if succesful
  176.     # - list: list of (group, first, last, flag) (strings)
  177.  
  178.     def list(self):
  179.         resp, list = self.longcmd('LIST')
  180.         for i in range(len(list)):
  181.             # Parse lines into "group first last flag"
  182.             list[i] = string.split(list[i])
  183.         return resp, list
  184.  
  185.     # Process a GROUP command.  Argument:
  186.     # - group: the group name
  187.     # Returns:
  188.     # - resp: server response if succesful
  189.     # - count: number of articles (string)
  190.     # - first: first article number (string)
  191.     # - last: last article number (string)
  192.     # - name: the group name
  193.  
  194.     def group(self, name):
  195.         resp = self.shortcmd('GROUP ' + name)
  196.         if resp[:3] <> '211':
  197.             raise error_reply, resp
  198.         words = string.split(resp)
  199.         count = first = last = 0
  200.         n = len(words)
  201.         if n > 1:
  202.             count = words[1]
  203.             if n > 2:
  204.                 first = words[2]
  205.                 if n > 3:
  206.                     last = words[3]
  207.                     if n > 4:
  208.                         name = string.lower(words[4])
  209.         return resp, count, first, last, name
  210.  
  211.     # Process a HELP command.  Returns:
  212.     # - resp: server response if succesful
  213.     # - list: list of strings
  214.  
  215.     def help(self):
  216.         return self.longcmd('HELP')
  217.  
  218.     # Internal: parse the response of a STAT, NEXT or LAST command
  219.     def statparse(self, resp):
  220.         if resp[:2] <> '22':
  221.             raise error_reply, resp
  222.         words = string.split(resp)
  223.         nr = 0
  224.         id = ''
  225.         n = len(words)
  226.         if n > 1:
  227.             nr = words[1]
  228.             if n > 2:
  229.                 id = string.lower(words[2])
  230.         return resp, nr, id
  231.  
  232.     # Internal: process a STAT, NEXT or LAST command
  233.     def statcmd(self, line):
  234.         resp = self.shortcmd(line)
  235.         return self.statparse(resp)
  236.  
  237.     # Process a STAT command.  Argument:
  238.     # - id: article number or message id
  239.     # Returns:
  240.     # - resp: server response if succesful
  241.     # - nr:   the article number
  242.     # - id:   the article id
  243.  
  244.     def stat(self, id):
  245.         return self.statcmd('STAT ' + id)
  246.  
  247.     # Process a NEXT command.  No arguments.  Return as for STAT
  248.  
  249.     def next(self):
  250.         return self.statcmd('NEXT')
  251.  
  252.     # Process a LAST command.  No arguments.  Return as for STAT
  253.  
  254.     def last(self):
  255.         return self.statcmd('LAST')
  256.  
  257.     # Internal: process a HEAD, BODY or ARTICLE command
  258.     def artcmd(self, line):
  259.         resp, list = self.longcmd(line)
  260.         resp, nr, id = self.statparse(resp)
  261.         return resp, nr, id, list
  262.  
  263.     # Process a HEAD command.  Argument:
  264.     # - id: article number or message id
  265.     # Returns:
  266.     # - resp: server response if succesful
  267.     # - list: the lines of the article's header
  268.  
  269.     def head(self, id):
  270.         return self.artcmd('HEAD ' + id)
  271.  
  272.     # Process a BODY command.  Argument:
  273.     # - id: article number or message id
  274.     # Returns:
  275.     # - resp: server response if succesful
  276.     # - list: the lines of the article's body
  277.  
  278.     def body(self, id):
  279.         return self.artcmd('BODY ' + id)
  280.  
  281.     # Process an ARTICLE command.  Argument:
  282.     # - id: article number or message id
  283.     # Returns:
  284.     # - resp: server response if succesful
  285.     # - list: the lines of the article
  286.  
  287.     def article(self, id):
  288.         return self.artcmd('ARTICLE ' + id)
  289.  
  290.     # Process a SLAVE command.  Returns:
  291.     # - resp: server response if succesful
  292.  
  293.     def slave(self):
  294.         return self.shortcmd('SLAVE')
  295.  
  296.     # Process an XHDR command (optional server extension).  Arguments:
  297.     # - hdr: the header type (e.g. 'subject')
  298.     # - str: an article nr, a message id, or a range nr1-nr2
  299.     # Returns:
  300.     # - resp: server response if succesful
  301.     # - list: list of (nr, value) strings
  302.  
  303.     def xhdr(self, hdr, str):
  304.         resp, lines = self.longcmd('XHDR ' + hdr + ' ' + str)
  305.         for i in range(len(lines)):
  306.             line = lines[i]
  307.             n = regex.match('^[0-9]+', line)
  308.             nr = line[:n]
  309.             if n < len(line) and line[n] == ' ': n = n+1
  310.             lines[i] = (nr, line[n:])
  311.         return resp, lines
  312.  
  313.     # Process a POST command.  Arguments:
  314.     # - f: file containing the article
  315.     # Returns:
  316.     # - resp: server response if succesful
  317.  
  318.     def post(self, f):
  319.         resp = self.shortcmd('POST')
  320.         # Raises error_??? if posting is not allowed
  321.         if resp[0] <> '3':
  322.             raise error_reply, resp
  323.         while 1:
  324.             line = f.readline()
  325.             if not line:
  326.                 break
  327.             if line[-1] == '\n':
  328.                 line = line[:-1]
  329.             if line == '.':
  330.                 line = '..'
  331.             self.putline(line)
  332.         self.putline('.')
  333.         return self.getresp()
  334.  
  335.     # Process an IHAVE command.  Arguments:
  336.     # - id: message-id of the article
  337.     # - f:  file containing the article
  338.     # Returns:
  339.     # - resp: server response if succesful
  340.     # Note that if the server refuses the article an exception is raised
  341.  
  342.     def ihave(self, id, f):
  343.         resp = self.shortcmd('IHAVE ' + id)
  344.         # Raises error_??? if the server already has it
  345.         if resp[0] <> '3':
  346.             raise error_reply, resp
  347.         while 1:
  348.             line = f.readline()
  349.             if not line:
  350.                 break
  351.             if line[-1] == '\n':
  352.                 line = line[:-1]
  353.             if line == '.':
  354.                 line = '..'
  355.             self.putline(line)
  356.         self.putline('.')
  357.         return self.getresp()
  358.  
  359.      # Process a QUIT command and close the socket.  Returns:
  360.      # - resp: server response if succesful
  361.  
  362.     def quit(self):
  363.         resp = self.shortcmd('QUIT')
  364.         self.file.close()
  365.         self.sock.close()
  366.         del self.file, self.sock
  367.         return resp
  368.