| Index: third_party/twisted_8_1/twisted/news/nntp.py
 | 
| diff --git a/third_party/twisted_8_1/twisted/news/nntp.py b/third_party/twisted_8_1/twisted/news/nntp.py
 | 
| deleted file mode 100644
 | 
| index 2e9861e5c7ca6c38786e7ba9336f8e1aff26a4c2..0000000000000000000000000000000000000000
 | 
| --- a/third_party/twisted_8_1/twisted/news/nntp.py
 | 
| +++ /dev/null
 | 
| @@ -1,1069 +0,0 @@
 | 
| -# -*- test-case-name: twisted.news.test.test_nntp -*-
 | 
| -# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
 | 
| -# See LICENSE for details.
 | 
| -
 | 
| -
 | 
| -"""
 | 
| -NNTP protocol support.
 | 
| -
 | 
| -Maintainer: U{Jp Calderone<mailto:exarkun@twistedmatrix.com>}
 | 
| -
 | 
| -The following protocol commands are currently understood::
 | 
| -
 | 
| -    LIST        LISTGROUP                  XOVER        XHDR
 | 
| -    POST        GROUP        ARTICLE       STAT         HEAD
 | 
| -    BODY        NEXT         MODE STREAM   MODE READER  SLAVE
 | 
| -    LAST        QUIT         HELP          IHAVE        XPATH
 | 
| -    XINDEX      XROVER       TAKETHIS      CHECK
 | 
| -    
 | 
| -The following protocol commands require implementation::
 | 
| -
 | 
| -                             NEWNEWS
 | 
| -                             XGTITLE                XPAT
 | 
| -                             XTHREAD       AUTHINFO NEWGROUPS
 | 
| -
 | 
| -
 | 
| -Other desired features:
 | 
| -
 | 
| -   - A real backend
 | 
| -   - More robust client input handling
 | 
| -   - A control protocol
 | 
| -"""
 | 
| -
 | 
| -import time
 | 
| -import types
 | 
| -
 | 
| -try:
 | 
| -    import cStringIO as StringIO
 | 
| -except:
 | 
| -    import StringIO
 | 
| -
 | 
| -from twisted.protocols import basic
 | 
| -from twisted.python import log
 | 
| -
 | 
| -def parseRange(text):
 | 
| -    articles = text.split('-')
 | 
| -    if len(articles) == 1:
 | 
| -        try:
 | 
| -            a = int(articles[0])
 | 
| -            return a, a
 | 
| -        except ValueError, e:
 | 
| -            return None, None
 | 
| -    elif len(articles) == 2:
 | 
| -        try:
 | 
| -            if len(articles[0]):
 | 
| -                l = int(articles[0])
 | 
| -            else:
 | 
| -                l = None
 | 
| -            if len(articles[1]):
 | 
| -                h = int(articles[1])
 | 
| -            else:
 | 
| -                h = None
 | 
| -        except ValueError, e:
 | 
| -            return None, None
 | 
| -    return l, h
 | 
| -
 | 
| -
 | 
| -def extractCode(line):
 | 
| -    line = line.split(' ', 1)
 | 
| -    if len(line) != 2:
 | 
| -        return None
 | 
| -    try:
 | 
| -        return int(line[0]), line[1]
 | 
| -    except ValueError:
 | 
| -        return None
 | 
| -
 | 
| -    
 | 
| -class NNTPError(Exception):
 | 
| -    def __init__(self, string):
 | 
| -        self.string = string
 | 
| -
 | 
| -    def __str__(self):
 | 
| -        return 'NNTPError: %s' % self.string
 | 
| -
 | 
| -
 | 
| -class NNTPClient(basic.LineReceiver):
 | 
| -    MAX_COMMAND_LENGTH = 510
 | 
| -
 | 
| -    def __init__(self):
 | 
| -        self.currentGroup = None
 | 
| -        
 | 
| -        self._state = []
 | 
| -        self._error = []
 | 
| -        self._inputBuffers = []
 | 
| -        self._responseCodes = []
 | 
| -        self._responseHandlers = []
 | 
| -        
 | 
| -        self._postText = []
 | 
| -        
 | 
| -        self._newState(self._statePassive, None, self._headerInitial)
 | 
| -
 | 
| -
 | 
| -    def gotAllGroups(self, groups):
 | 
| -        "Override for notification when fetchGroups() action is completed"
 | 
| -    
 | 
| -    
 | 
| -    def getAllGroupsFailed(self, error):
 | 
| -        "Override for notification when fetchGroups() action fails"
 | 
| -
 | 
| -
 | 
| -    def gotOverview(self, overview):
 | 
| -        "Override for notification when fetchOverview() action is completed"
 | 
| -
 | 
| -
 | 
| -    def getOverviewFailed(self, error):
 | 
| -        "Override for notification when fetchOverview() action fails"
 | 
| -
 | 
| -
 | 
| -    def gotSubscriptions(self, subscriptions):
 | 
| -        "Override for notification when fetchSubscriptions() action is completed"
 | 
| -
 | 
| -
 | 
| -    def getSubscriptionsFailed(self, error):
 | 
| -        "Override for notification when fetchSubscriptions() action fails"
 | 
| -
 | 
| -
 | 
| -    def gotGroup(self, group):
 | 
| -        "Override for notification when fetchGroup() action is completed"
 | 
| -
 | 
| -
 | 
| -    def getGroupFailed(self, error):
 | 
| -        "Override for notification when fetchGroup() action fails"
 | 
| -
 | 
| -
 | 
| -    def gotArticle(self, article):
 | 
| -        "Override for notification when fetchArticle() action is completed"
 | 
| -
 | 
| -
 | 
| -    def getArticleFailed(self, error):
 | 
| -        "Override for notification when fetchArticle() action fails"
 | 
| -
 | 
| -
 | 
| -    def gotHead(self, head):
 | 
| -        "Override for notification when fetchHead() action is completed"
 | 
| -
 | 
| -
 | 
| -    def getHeadFailed(self, error):
 | 
| -        "Override for notification when fetchHead() action fails"
 | 
| -
 | 
| -
 | 
| -    def gotBody(self, info):
 | 
| -        "Override for notification when fetchBody() action is completed"
 | 
| -
 | 
| -
 | 
| -    def getBodyFailed(self, body):
 | 
| -        "Override for notification when fetchBody() action fails"
 | 
| -
 | 
| -
 | 
| -    def postedOk(self):
 | 
| -        "Override for notification when postArticle() action is successful"
 | 
| -
 | 
| -    
 | 
| -    def postFailed(self, error):
 | 
| -        "Override for notification when postArticle() action fails"
 | 
| -
 | 
| -
 | 
| -    def gotXHeader(self, headers):
 | 
| -        "Override for notification when getXHeader() action is successful"
 | 
| -    
 | 
| -    
 | 
| -    def getXHeaderFailed(self, error):
 | 
| -        "Override for notification when getXHeader() action fails"
 | 
| -
 | 
| -
 | 
| -    def gotNewNews(self, news):
 | 
| -        "Override for notification when getNewNews() action is successful"
 | 
| -    
 | 
| -    
 | 
| -    def getNewNewsFailed(self, error):
 | 
| -        "Override for notification when getNewNews() action fails"
 | 
| -
 | 
| -
 | 
| -    def gotNewGroups(self, groups):
 | 
| -        "Override for notification when getNewGroups() action is successful"
 | 
| -    
 | 
| -    
 | 
| -    def getNewGroupsFailed(self, error):
 | 
| -        "Override for notification when getNewGroups() action fails"
 | 
| -
 | 
| -
 | 
| -    def setStreamSuccess(self):
 | 
| -        "Override for notification when setStream() action is successful"
 | 
| -
 | 
| -
 | 
| -    def setStreamFailed(self, error):
 | 
| -        "Override for notification when setStream() action fails"
 | 
| -
 | 
| -
 | 
| -    def fetchGroups(self):
 | 
| -        """
 | 
| -        Request a list of all news groups from the server.  gotAllGroups()
 | 
| -        is called on success, getGroupsFailed() on failure
 | 
| -        """
 | 
| -        self.sendLine('LIST')
 | 
| -        self._newState(self._stateList, self.getAllGroupsFailed)
 | 
| -
 | 
| -
 | 
| -    def fetchOverview(self):
 | 
| -        """
 | 
| -        Request the overview format from the server.  gotOverview() is called
 | 
| -        on success, getOverviewFailed() on failure
 | 
| -        """
 | 
| -        self.sendLine('LIST OVERVIEW.FMT')
 | 
| -        self._newState(self._stateOverview, self.getOverviewFailed)
 | 
| -
 | 
| -
 | 
| -    def fetchSubscriptions(self):
 | 
| -        """
 | 
| -        Request a list of the groups it is recommended a new user subscribe to.
 | 
| -        gotSubscriptions() is called on success, getSubscriptionsFailed() on
 | 
| -        failure
 | 
| -        """
 | 
| -        self.sendLine('LIST SUBSCRIPTIONS')
 | 
| -        self._newState(self._stateSubscriptions, self.getSubscriptionsFailed)
 | 
| -
 | 
| -
 | 
| -    def fetchGroup(self, group):
 | 
| -        """
 | 
| -        Get group information for the specified group from the server.  gotGroup()
 | 
| -        is called on success, getGroupFailed() on failure.
 | 
| -        """
 | 
| -        self.sendLine('GROUP %s' % (group,))
 | 
| -        self._newState(None, self.getGroupFailed, self._headerGroup)
 | 
| -
 | 
| -
 | 
| -    def fetchHead(self, index = ''):
 | 
| -        """
 | 
| -        Get the header for the specified article (or the currently selected
 | 
| -        article if index is '') from the server.  gotHead() is called on
 | 
| -        success, getHeadFailed() on failure
 | 
| -        """
 | 
| -        self.sendLine('HEAD %s' % (index,))
 | 
| -        self._newState(self._stateHead, self.getHeadFailed)
 | 
| -
 | 
| -        
 | 
| -    def fetchBody(self, index = ''):
 | 
| -        """
 | 
| -        Get the body for the specified article (or the currently selected
 | 
| -        article if index is '') from the server.  gotBody() is called on
 | 
| -        success, getBodyFailed() on failure
 | 
| -        """
 | 
| -        self.sendLine('BODY %s' % (index,))
 | 
| -        self._newState(self._stateBody, self.getBodyFailed)
 | 
| -
 | 
| -
 | 
| -    def fetchArticle(self, index = ''):
 | 
| -        """
 | 
| -        Get the complete article with the specified index (or the currently
 | 
| -        selected article if index is '') or Message-ID from the server.
 | 
| -        gotArticle() is called on success, getArticleFailed() on failure.
 | 
| -        """
 | 
| -        self.sendLine('ARTICLE %s' % (index,))
 | 
| -        self._newState(self._stateArticle, self.getArticleFailed)
 | 
| -
 | 
| -
 | 
| -    def postArticle(self, text):
 | 
| -        """
 | 
| -        Attempt to post an article with the specified text to the server.  'text'
 | 
| -        must consist of both head and body data, as specified by RFC 850.  If the
 | 
| -        article is posted successfully, postedOk() is called, otherwise postFailed()
 | 
| -        is called.
 | 
| -        """
 | 
| -        self.sendLine('POST')
 | 
| -        self._newState(None, self.postFailed, self._headerPost)
 | 
| -        self._postText.append(text)
 | 
| -
 | 
| -
 | 
| -    def fetchNewNews(self, groups, date, distributions = ''):
 | 
| -        """
 | 
| -        Get the Message-IDs for all new news posted to any of the given
 | 
| -        groups since the specified date - in seconds since the epoch, GMT -
 | 
| -        optionally restricted to the given distributions.  gotNewNews() is
 | 
| -        called on success, getNewNewsFailed() on failure.
 | 
| -        
 | 
| -        One invocation of this function may result in multiple invocations
 | 
| -        of gotNewNews()/getNewNewsFailed().
 | 
| -        """
 | 
| -        date, timeStr = time.strftime('%y%m%d %H%M%S', time.gmtime(date)).split()
 | 
| -        line = 'NEWNEWS %%s %s %s %s' % (date, timeStr, distributions)
 | 
| -        groupPart = ''
 | 
| -        while len(groups) and len(line) + len(groupPart) + len(groups[-1]) + 1 < NNTPClient.MAX_COMMAND_LENGTH:
 | 
| -            group = groups.pop()
 | 
| -            groupPart = groupPart + ',' + group
 | 
| -        
 | 
| -        self.sendLine(line % (groupPart,))
 | 
| -        self._newState(self._stateNewNews, self.getNewNewsFailed)
 | 
| -        
 | 
| -        if len(groups):
 | 
| -            self.fetchNewNews(groups, date, distributions)
 | 
| -    
 | 
| -    
 | 
| -    def fetchNewGroups(self, date, distributions):
 | 
| -        """
 | 
| -        Get the names of all new groups created/added to the server since
 | 
| -        the specified date - in seconds since the ecpoh, GMT - optionally
 | 
| -        restricted to the given distributions.  gotNewGroups() is called
 | 
| -        on success, getNewGroupsFailed() on failure.
 | 
| -        """
 | 
| -        date, timeStr = time.strftime('%y%m%d %H%M%S', time.gmtime(date)).split()
 | 
| -        self.sendLine('NEWGROUPS %s %s %s' % (date, timeStr, distributions))
 | 
| -        self._newState(self._stateNewGroups, self.getNewGroupsFailed)
 | 
| -
 | 
| -
 | 
| -    def fetchXHeader(self, header, low = None, high = None, id = None):
 | 
| -        """
 | 
| -        Request a specific header from the server for an article or range
 | 
| -        of articles.  If 'id' is not None, a header for only the article
 | 
| -        with that Message-ID will be requested.  If both low and high are
 | 
| -        None, a header for the currently selected article will be selected;
 | 
| -        If both low and high are zero-length strings, headers for all articles
 | 
| -        in the currently selected group will be requested;  Otherwise, high
 | 
| -        and low will be used as bounds - if one is None the first or last
 | 
| -        article index will be substituted, as appropriate.
 | 
| -        """
 | 
| -        if id is not None:
 | 
| -            r = header + ' <%s>' % (id,)
 | 
| -        elif low is high is None:
 | 
| -            r = header
 | 
| -        elif high is None:
 | 
| -            r = header + ' %d-' % (low,)
 | 
| -        elif low is None:
 | 
| -            r = header + ' -%d' % (high,)
 | 
| -        else:
 | 
| -            r = header + ' %d-%d' % (low, high)
 | 
| -        self.sendLine('XHDR ' + r)
 | 
| -        self._newState(self._stateXHDR, self.getXHeaderFailed)
 | 
| -
 | 
| -
 | 
| -    def setStream(self):
 | 
| -        """
 | 
| -        Set the mode to STREAM, suspending the normal "lock-step" mode of
 | 
| -        communications.  setStreamSuccess() is called on success,
 | 
| -        setStreamFailed() on failure.
 | 
| -        """ 
 | 
| -        self.sendLine('MODE STREAM')
 | 
| -        self._newState(None, self.setStreamFailed, self._headerMode)
 | 
| -
 | 
| -
 | 
| -    def quit(self):
 | 
| -        self.sendLine('QUIT')
 | 
| -        self.transport.loseConnection()
 | 
| -
 | 
| -
 | 
| -    def _newState(self, method, error, responseHandler = None):
 | 
| -        self._inputBuffers.append([])
 | 
| -        self._responseCodes.append(None)
 | 
| -        self._state.append(method)
 | 
| -        self._error.append(error)
 | 
| -        self._responseHandlers.append(responseHandler)
 | 
| -
 | 
| -
 | 
| -    def _endState(self):
 | 
| -        buf = self._inputBuffers[0]
 | 
| -        del self._responseCodes[0]
 | 
| -        del self._inputBuffers[0]
 | 
| -        del self._state[0]
 | 
| -        del self._error[0]
 | 
| -        del self._responseHandlers[0]
 | 
| -        return buf
 | 
| -
 | 
| -
 | 
| -    def _newLine(self, line, check = 1):
 | 
| -        if check and line and line[0] == '.':
 | 
| -            line = line[1:]
 | 
| -        self._inputBuffers[0].append(line)
 | 
| -
 | 
| -
 | 
| -    def _setResponseCode(self, code):
 | 
| -        self._responseCodes[0] = code
 | 
| -    
 | 
| -    
 | 
| -    def _getResponseCode(self):
 | 
| -        return self._responseCodes[0]
 | 
| -
 | 
| -
 | 
| -    def lineReceived(self, line):
 | 
| -        if not len(self._state):
 | 
| -            self._statePassive(line)
 | 
| -        elif self._getResponseCode() is None:
 | 
| -            code = extractCode(line)
 | 
| -            if code is None or not (200 <= code[0] < 400):    # An error!
 | 
| -                self._error[0](line)
 | 
| -                self._endState()
 | 
| -            else:
 | 
| -                self._setResponseCode(code)
 | 
| -                if self._responseHandlers[0]:
 | 
| -                    self._responseHandlers[0](code)
 | 
| -        else:
 | 
| -            self._state[0](line)
 | 
| -
 | 
| -
 | 
| -    def _statePassive(self, line):
 | 
| -        log.msg('Server said: %s' % line)
 | 
| -
 | 
| -
 | 
| -    def _passiveError(self, error):
 | 
| -        log.err('Passive Error: %s' % (error,))
 | 
| -
 | 
| -
 | 
| -    def _headerInitial(self, (code, message)):
 | 
| -        if code == 200:
 | 
| -            self.canPost = 1
 | 
| -        else:
 | 
| -            self.canPost = 0
 | 
| -        self._endState()
 | 
| -
 | 
| -
 | 
| -    def _stateList(self, line):
 | 
| -        if line != '.':
 | 
| -            data = filter(None, line.strip().split())
 | 
| -            self._newLine((data[0], int(data[1]), int(data[2]), data[3]), 0)
 | 
| -        else:
 | 
| -            self.gotAllGroups(self._endState())
 | 
| -
 | 
| -
 | 
| -    def _stateOverview(self, line):
 | 
| -        if line != '.':
 | 
| -            self._newLine(filter(None, line.strip().split()), 0)
 | 
| -        else:
 | 
| -            self.gotOverview(self._endState())
 | 
| -
 | 
| -
 | 
| -    def _stateSubscriptions(self, line):
 | 
| -        if line != '.':
 | 
| -            self._newLine(line.strip(), 0)
 | 
| -        else:
 | 
| -            self.gotSubscriptions(self._endState())
 | 
| -
 | 
| -
 | 
| -    def _headerGroup(self, (code, line)):
 | 
| -        self.gotGroup(tuple(line.split()))
 | 
| -        self._endState()
 | 
| -
 | 
| -
 | 
| -    def _stateArticle(self, line):
 | 
| -        if line != '.':
 | 
| -            if line.startswith('.'):
 | 
| -                line = line[1:]
 | 
| -            self._newLine(line, 0)
 | 
| -        else:
 | 
| -            self.gotArticle('\n'.join(self._endState())+'\n')
 | 
| -
 | 
| -
 | 
| -    def _stateHead(self, line):
 | 
| -        if line != '.':
 | 
| -            self._newLine(line, 0)
 | 
| -        else:
 | 
| -            self.gotHead('\n'.join(self._endState()))
 | 
| -
 | 
| -
 | 
| -    def _stateBody(self, line):
 | 
| -        if line != '.':
 | 
| -            if line.startswith('.'):
 | 
| -                line = line[1:]
 | 
| -            self._newLine(line, 0)
 | 
| -        else:
 | 
| -            self.gotBody('\n'.join(self._endState())+'\n')
 | 
| -
 | 
| -
 | 
| -    def _headerPost(self, (code, message)):
 | 
| -        if code == 340:
 | 
| -            self.transport.write(self._postText[0].replace('\n', '\r\n').replace('\r\n.', '\r\n..'))
 | 
| -            if self._postText[0][-1:] != '\n':
 | 
| -                self.sendLine('')
 | 
| -            self.sendLine('.')
 | 
| -            del self._postText[0]
 | 
| -            self._newState(None, self.postFailed, self._headerPosted)
 | 
| -        else:
 | 
| -            self.postFailed('%d %s' % (code, message))
 | 
| -        self._endState()
 | 
| -
 | 
| -
 | 
| -    def _headerPosted(self, (code, message)):
 | 
| -        if code == 240:
 | 
| -            self.postedOk()
 | 
| -        else:
 | 
| -            self.postFailed('%d %s' % (code, message))
 | 
| -        self._endState()
 | 
| -
 | 
| -
 | 
| -    def _stateXHDR(self, line):
 | 
| -        if line != '.':
 | 
| -            self._newLine(line.split(), 0)
 | 
| -        else:
 | 
| -            self._gotXHeader(self._endState())
 | 
| -    
 | 
| -    
 | 
| -    def _stateNewNews(self, line):
 | 
| -        if line != '.':
 | 
| -            self._newLine(line, 0)
 | 
| -        else:
 | 
| -            self.gotNewNews(self._endState())
 | 
| -    
 | 
| -    
 | 
| -    def _stateNewGroups(self, line):
 | 
| -        if line != '.':
 | 
| -            self._newLine(line, 0)
 | 
| -        else:
 | 
| -            self.gotNewGroups(self._endState())
 | 
| -
 | 
| -
 | 
| -    def _headerMode(self, (code, message)):
 | 
| -        if code == 203:
 | 
| -            self.setStreamSuccess()
 | 
| -        else:
 | 
| -            self.setStreamFailed((code, message))
 | 
| -        self._endState()
 | 
| -
 | 
| -
 | 
| -class NNTPServer(basic.LineReceiver):
 | 
| -    COMMANDS = [
 | 
| -        'LIST', 'GROUP', 'ARTICLE', 'STAT', 'MODE', 'LISTGROUP', 'XOVER',
 | 
| -        'XHDR', 'HEAD', 'BODY', 'NEXT', 'LAST', 'POST', 'QUIT', 'IHAVE',
 | 
| -        'HELP', 'SLAVE', 'XPATH', 'XINDEX', 'XROVER', 'TAKETHIS', 'CHECK'
 | 
| -    ]
 | 
| -
 | 
| -    def __init__(self):
 | 
| -        self.servingSlave = 0
 | 
| -
 | 
| -
 | 
| -    def connectionMade(self):
 | 
| -        self.inputHandler = None
 | 
| -        self.currentGroup = None
 | 
| -        self.currentIndex = None
 | 
| -        self.sendLine('200 server ready - posting allowed')
 | 
| -
 | 
| -    def lineReceived(self, line):
 | 
| -        if self.inputHandler is not None:
 | 
| -            self.inputHandler(line)
 | 
| -        else:
 | 
| -            parts = line.strip().split()
 | 
| -            if len(parts):
 | 
| -                cmd, parts = parts[0].upper(), parts[1:]
 | 
| -                if cmd in NNTPServer.COMMANDS:
 | 
| -                    func = getattr(self, 'do_%s' % cmd)
 | 
| -                    try:
 | 
| -                        func(*parts)
 | 
| -                    except TypeError:
 | 
| -                        self.sendLine('501 command syntax error')
 | 
| -                        log.msg("501 command syntax error")
 | 
| -                        log.msg("command was", line)
 | 
| -                        log.deferr()
 | 
| -                    except:
 | 
| -                        self.sendLine('503 program fault - command not performed')
 | 
| -                        log.msg("503 program fault")
 | 
| -                        log.msg("command was", line)
 | 
| -                        log.deferr()
 | 
| -                else:
 | 
| -                    self.sendLine('500 command not recognized')
 | 
| -
 | 
| -
 | 
| -    def do_LIST(self, subcmd = '', *dummy):
 | 
| -        subcmd = subcmd.strip().lower()
 | 
| -        if subcmd == 'newsgroups':
 | 
| -            # XXX - this could use a real implementation, eh?
 | 
| -            self.sendLine('215 Descriptions in form "group description"')
 | 
| -            self.sendLine('.')
 | 
| -        elif subcmd == 'overview.fmt':
 | 
| -            defer = self.factory.backend.overviewRequest()
 | 
| -            defer.addCallbacks(self._gotOverview, self._errOverview)
 | 
| -            log.msg('overview')
 | 
| -        elif subcmd == 'subscriptions':
 | 
| -            defer = self.factory.backend.subscriptionRequest()
 | 
| -            defer.addCallbacks(self._gotSubscription, self._errSubscription)
 | 
| -            log.msg('subscriptions')
 | 
| -        elif subcmd == '':
 | 
| -            defer = self.factory.backend.listRequest()
 | 
| -            defer.addCallbacks(self._gotList, self._errList)
 | 
| -        else:
 | 
| -            self.sendLine('500 command not recognized')
 | 
| -
 | 
| -
 | 
| -    def _gotList(self, list):
 | 
| -        self.sendLine('215 newsgroups in form "group high low flags"')
 | 
| -        for i in list:
 | 
| -            self.sendLine('%s %d %d %s' % tuple(i))
 | 
| -        self.sendLine('.')
 | 
| -
 | 
| -
 | 
| -    def _errList(self, failure):
 | 
| -        print 'LIST failed: ', failure
 | 
| -        self.sendLine('503 program fault - command not performed')
 | 
| -
 | 
| -
 | 
| -    def _gotSubscription(self, parts):
 | 
| -        self.sendLine('215 information follows')
 | 
| -        for i in parts:
 | 
| -            self.sendLine(i)
 | 
| -        self.sendLine('.')
 | 
| -
 | 
| -
 | 
| -    def _errSubscription(self, failure):
 | 
| -        print 'SUBSCRIPTIONS failed: ', failure
 | 
| -        self.sendLine('503 program fault - comand not performed')
 | 
| -
 | 
| -
 | 
| -    def _gotOverview(self, parts):
 | 
| -        self.sendLine('215 Order of fields in overview database.')
 | 
| -        for i in parts:
 | 
| -            self.sendLine(i + ':')
 | 
| -        self.sendLine('.')
 | 
| -
 | 
| -
 | 
| -    def _errOverview(self, failure):
 | 
| -        print 'LIST OVERVIEW.FMT failed: ', failure
 | 
| -        self.sendLine('503 program fault - command not performed')
 | 
| -
 | 
| -
 | 
| -    def do_LISTGROUP(self, group = None):
 | 
| -        group = group or self.currentGroup
 | 
| -        if group is None:
 | 
| -            self.sendLine('412 Not currently in newsgroup')
 | 
| -        else:
 | 
| -            defer = self.factory.backend.listGroupRequest(group)
 | 
| -            defer.addCallbacks(self._gotListGroup, self._errListGroup)
 | 
| -
 | 
| -
 | 
| -    def _gotListGroup(self, (group, articles)):
 | 
| -        self.currentGroup = group
 | 
| -        if len(articles):
 | 
| -            self.currentIndex = int(articles[0])
 | 
| -        else:
 | 
| -            self.currentIndex = None
 | 
| -
 | 
| -        self.sendLine('211 list of article numbers follow')
 | 
| -        for i in articles:
 | 
| -            self.sendLine(str(i))
 | 
| -        self.sendLine('.')
 | 
| -
 | 
| -
 | 
| -    def _errListGroup(self, failure):
 | 
| -        print 'LISTGROUP failed: ', failure
 | 
| -        self.sendLine('502 no permission')
 | 
| -
 | 
| -
 | 
| -    def do_XOVER(self, range):
 | 
| -        if self.currentGroup is None:
 | 
| -            self.sendLine('412 No news group currently selected')
 | 
| -        else:
 | 
| -            l, h = parseRange(range)
 | 
| -            defer = self.factory.backend.xoverRequest(self.currentGroup, l, h)
 | 
| -            defer.addCallbacks(self._gotXOver, self._errXOver)
 | 
| -
 | 
| -
 | 
| -    def _gotXOver(self, parts):
 | 
| -        self.sendLine('224 Overview information follows')
 | 
| -        for i in parts:
 | 
| -            self.sendLine('\t'.join(map(str, i)))
 | 
| -        self.sendLine('.')
 | 
| -
 | 
| -
 | 
| -    def _errXOver(self, failure):
 | 
| -        print 'XOVER failed: ', failure
 | 
| -        self.sendLine('420 No article(s) selected')
 | 
| -
 | 
| -
 | 
| -    def xhdrWork(self, header, range):
 | 
| -        if self.currentGroup is None:
 | 
| -            self.sendLine('412 No news group currently selected')
 | 
| -        else:
 | 
| -            if range is None:
 | 
| -                if self.currentIndex is None:
 | 
| -                    self.sendLine('420 No current article selected')
 | 
| -                    return
 | 
| -                else:
 | 
| -                    l = h = self.currentIndex
 | 
| -            else:
 | 
| -                # FIXME: articles may be a message-id
 | 
| -                l, h = parseRange(range)
 | 
| -            
 | 
| -            if l is h is None:
 | 
| -                self.sendLine('430 no such article')
 | 
| -            else:
 | 
| -                return self.factory.backend.xhdrRequest(self.currentGroup, l, h, header)
 | 
| -
 | 
| -
 | 
| -    def do_XHDR(self, header, range = None):
 | 
| -        d = self.xhdrWork(header, range)
 | 
| -        if d:
 | 
| -            d.addCallbacks(self._gotXHDR, self._errXHDR)
 | 
| -
 | 
| -
 | 
| -    def _gotXHDR(self, parts):
 | 
| -        self.sendLine('221 Header follows')
 | 
| -        for i in parts:
 | 
| -            self.sendLine('%d %s' % i)
 | 
| -        self.sendLine('.')
 | 
| -
 | 
| -    def _errXHDR(self, failure):
 | 
| -        print 'XHDR failed: ', failure
 | 
| -        self.sendLine('502 no permission')
 | 
| -
 | 
| -
 | 
| -    def do_XROVER(self, header, range = None):
 | 
| -        d = self.xhdrWork(header, range)
 | 
| -        if d:
 | 
| -            d.addCallbacks(self._gotXROVER, self._errXROVER)
 | 
| -    
 | 
| -    
 | 
| -    def _gotXROVER(self, parts):
 | 
| -        self.sendLine('224 Overview information follows')
 | 
| -        for i in parts:
 | 
| -            self.sendLine('%d %s' % i)
 | 
| -        self.sendLine('.')
 | 
| -
 | 
| -
 | 
| -    def _errXROVER(self, failure):
 | 
| -        print 'XROVER failed: ',
 | 
| -        self._errXHDR(failure)
 | 
| -
 | 
| -
 | 
| -    def do_POST(self):
 | 
| -        self.inputHandler = self._doingPost
 | 
| -        self.message = ''
 | 
| -        self.sendLine('340 send article to be posted.  End with <CR-LF>.<CR-LF>')
 | 
| -
 | 
| -
 | 
| -    def _doingPost(self, line):
 | 
| -        if line == '.':
 | 
| -            self.inputHandler = None
 | 
| -            group, article = self.currentGroup, self.message
 | 
| -            self.message = ''
 | 
| -
 | 
| -            defer = self.factory.backend.postRequest(article)
 | 
| -            defer.addCallbacks(self._gotPost, self._errPost)
 | 
| -        else:
 | 
| -            self.message = self.message + line + '\r\n'
 | 
| -
 | 
| -
 | 
| -    def _gotPost(self, parts):
 | 
| -        self.sendLine('240 article posted ok')
 | 
| -        
 | 
| -    
 | 
| -    def _errPost(self, failure):
 | 
| -        print 'POST failed: ', failure
 | 
| -        self.sendLine('441 posting failed')
 | 
| -
 | 
| -
 | 
| -    def do_CHECK(self, id):
 | 
| -        d = self.factory.backend.articleExistsRequest(id)
 | 
| -        d.addCallbacks(self._gotCheck, self._errCheck)
 | 
| -    
 | 
| -    
 | 
| -    def _gotCheck(self, result):
 | 
| -        if result:
 | 
| -            self.sendLine("438 already have it, please don't send it to me")
 | 
| -        else:
 | 
| -            self.sendLine('238 no such article found, please send it to me')
 | 
| -    
 | 
| -    
 | 
| -    def _errCheck(self, failure):
 | 
| -        print 'CHECK failed: ', failure
 | 
| -        self.sendLine('431 try sending it again later')
 | 
| -
 | 
| -
 | 
| -    def do_TAKETHIS(self, id):
 | 
| -        self.inputHandler = self._doingTakeThis
 | 
| -        self.message = ''
 | 
| -    
 | 
| -    
 | 
| -    def _doingTakeThis(self, line):
 | 
| -        if line == '.':
 | 
| -            self.inputHandler = None
 | 
| -            article = self.message
 | 
| -            self.message = ''
 | 
| -            d = self.factory.backend.postRequest(article)
 | 
| -            d.addCallbacks(self._didTakeThis, self._errTakeThis)
 | 
| -        else:
 | 
| -            self.message = self.message + line + '\r\n'
 | 
| -
 | 
| -
 | 
| -    def _didTakeThis(self, result):
 | 
| -        self.sendLine('239 article transferred ok')
 | 
| -    
 | 
| -    
 | 
| -    def _errTakeThis(self, failure):
 | 
| -        print 'TAKETHIS failed: ', failure
 | 
| -        self.sendLine('439 article transfer failed')
 | 
| -
 | 
| -
 | 
| -    def do_GROUP(self, group):
 | 
| -        defer = self.factory.backend.groupRequest(group)
 | 
| -        defer.addCallbacks(self._gotGroup, self._errGroup)
 | 
| -
 | 
| -    
 | 
| -    def _gotGroup(self, (name, num, high, low, flags)):
 | 
| -        self.currentGroup = name
 | 
| -        self.currentIndex = low
 | 
| -        self.sendLine('211 %d %d %d %s group selected' % (num, low, high, name))
 | 
| -    
 | 
| -    
 | 
| -    def _errGroup(self, failure):
 | 
| -        print 'GROUP failed: ', failure
 | 
| -        self.sendLine('411 no such group')
 | 
| -
 | 
| -
 | 
| -    def articleWork(self, article, cmd, func):
 | 
| -        if self.currentGroup is None:
 | 
| -            self.sendLine('412 no newsgroup has been selected')
 | 
| -        else:
 | 
| -            if not article:
 | 
| -                if self.currentIndex is None:
 | 
| -                    self.sendLine('420 no current article has been selected')
 | 
| -                else:
 | 
| -                    article = self.currentIndex
 | 
| -            else:
 | 
| -                if article[0] == '<':
 | 
| -                    return func(self.currentGroup, index = None, id = article)
 | 
| -                else:
 | 
| -                    try:
 | 
| -                        article = int(article)
 | 
| -                        return func(self.currentGroup, article) 
 | 
| -                    except ValueError, e:
 | 
| -                        self.sendLine('501 command syntax error')
 | 
| -
 | 
| -
 | 
| -    def do_ARTICLE(self, article = None):
 | 
| -        defer = self.articleWork(article, 'ARTICLE', self.factory.backend.articleRequest)
 | 
| -        if defer:
 | 
| -            defer.addCallbacks(self._gotArticle, self._errArticle)
 | 
| -
 | 
| -
 | 
| -    def _gotArticle(self, (index, id, article)):
 | 
| -        if isinstance(article, types.StringType):
 | 
| -            import warnings
 | 
| -            warnings.warn(
 | 
| -                "Returning the article as a string from `articleRequest' "
 | 
| -                "is deprecated.  Return a file-like object instead."
 | 
| -            )
 | 
| -            article = StringIO.StringIO(article)
 | 
| -        self.currentIndex = index
 | 
| -        self.sendLine('220 %d %s article' % (index, id))
 | 
| -        s = basic.FileSender()
 | 
| -        d = s.beginFileTransfer(article, self.transport)
 | 
| -        d.addCallback(self.finishedFileTransfer)
 | 
| -    
 | 
| -    ##   
 | 
| -    ## Helper for FileSender
 | 
| -    ##
 | 
| -    def finishedFileTransfer(self, lastsent):
 | 
| -        if lastsent != '\n':
 | 
| -            line = '\r\n.'
 | 
| -        else:
 | 
| -            line = '.'
 | 
| -        self.sendLine(line)
 | 
| -    ##
 | 
| -
 | 
| -    def _errArticle(self, failure):
 | 
| -        print 'ARTICLE failed: ', failure
 | 
| -        self.sendLine('423 bad article number')
 | 
| -
 | 
| -
 | 
| -    def do_STAT(self, article = None):
 | 
| -        defer = self.articleWork(article, 'STAT', self.factory.backend.articleRequest)
 | 
| -        if defer:
 | 
| -            defer.addCallbacks(self._gotStat, self._errStat)
 | 
| -    
 | 
| -    
 | 
| -    def _gotStat(self, (index, id, article)):
 | 
| -        self.currentIndex = index
 | 
| -        self.sendLine('223 %d %s article retreived - request text separately' % (index, id))
 | 
| -
 | 
| -
 | 
| -    def _errStat(self, failure):
 | 
| -        print 'STAT failed: ', failure
 | 
| -        self.sendLine('423 bad article number')
 | 
| -
 | 
| -
 | 
| -    def do_HEAD(self, article = None):
 | 
| -        defer = self.articleWork(article, 'HEAD', self.factory.backend.headRequest)
 | 
| -        if defer:
 | 
| -            defer.addCallbacks(self._gotHead, self._errHead)
 | 
| -    
 | 
| -    
 | 
| -    def _gotHead(self, (index, id, head)):
 | 
| -        self.currentIndex = index
 | 
| -        self.sendLine('221 %d %s article retrieved' % (index, id))
 | 
| -        self.transport.write(head + '\r\n')
 | 
| -        self.sendLine('.')
 | 
| -    
 | 
| -    
 | 
| -    def _errHead(self, failure):
 | 
| -        print 'HEAD failed: ', failure
 | 
| -        self.sendLine('423 no such article number in this group')
 | 
| -
 | 
| -
 | 
| -    def do_BODY(self, article):
 | 
| -        defer = self.articleWork(article, 'BODY', self.factory.backend.bodyRequest)
 | 
| -        if defer:
 | 
| -            defer.addCallbacks(self._gotBody, self._errBody)
 | 
| -
 | 
| -
 | 
| -    def _gotBody(self, (index, id, body)):
 | 
| -        if isinstance(body, types.StringType):
 | 
| -            import warnings
 | 
| -            warnings.warn(
 | 
| -                "Returning the article as a string from `articleRequest' "
 | 
| -                "is deprecated.  Return a file-like object instead."
 | 
| -            )
 | 
| -            body = StringIO.StringIO(body)
 | 
| -        self.currentIndex = index
 | 
| -        self.sendLine('221 %d %s article retrieved' % (index, id))
 | 
| -        self.lastsent = ''
 | 
| -        s = basic.FileSender()
 | 
| -        d = s.beginFileTransfer(body, self.transport)
 | 
| -        d.addCallback(self.finishedFileTransfer)
 | 
| -
 | 
| -    def _errBody(self, failure):
 | 
| -        print 'BODY failed: ', failure
 | 
| -        self.sendLine('423 no such article number in this group')
 | 
| -
 | 
| -
 | 
| -    # NEXT and LAST are just STATs that increment currentIndex first.
 | 
| -    # Accordingly, use the STAT callbacks.
 | 
| -    def do_NEXT(self):
 | 
| -        i = self.currentIndex + 1
 | 
| -        defer = self.factory.backend.articleRequest(self.currentGroup, i)
 | 
| -        defer.addCallbacks(self._gotStat, self._errStat)
 | 
| -
 | 
| -
 | 
| -    def do_LAST(self):
 | 
| -        i = self.currentIndex - 1
 | 
| -        defer = self.factory.backend.articleRequest(self.currentGroup, i)
 | 
| -        defer.addCallbacks(self._gotStat, self._errStat)
 | 
| -
 | 
| -
 | 
| -    def do_MODE(self, cmd):
 | 
| -        cmd = cmd.strip().upper()
 | 
| -        if cmd == 'READER':
 | 
| -            self.servingSlave = 0
 | 
| -            self.sendLine('200 Hello, you can post')
 | 
| -        elif cmd == 'STREAM':
 | 
| -            self.sendLine('500 Command not understood')
 | 
| -        else:
 | 
| -            # This is not a mistake
 | 
| -            self.sendLine('500 Command not understood')
 | 
| -
 | 
| -
 | 
| -    def do_QUIT(self):
 | 
| -        self.sendLine('205 goodbye')
 | 
| -        self.transport.loseConnection()
 | 
| -
 | 
| -    
 | 
| -    def do_HELP(self):
 | 
| -        self.sendLine('100 help text follows')
 | 
| -        self.sendLine('Read the RFC.')
 | 
| -        self.sendLine('.')
 | 
| -    
 | 
| -    
 | 
| -    def do_SLAVE(self):
 | 
| -        self.sendLine('202 slave status noted')
 | 
| -        self.servingeSlave = 1
 | 
| -
 | 
| -
 | 
| -    def do_XPATH(self, article):
 | 
| -        # XPATH is a silly thing to have.  No client has the right to ask
 | 
| -        # for this piece of information from me, and so that is what I'll
 | 
| -        # tell them.
 | 
| -        self.sendLine('502 access restriction or permission denied')
 | 
| -
 | 
| -
 | 
| -    def do_XINDEX(self, article):
 | 
| -        # XINDEX is another silly command.  The RFC suggests it be relegated
 | 
| -        # to the history books, and who am I to disagree?
 | 
| -        self.sendLine('502 access restriction or permission denied')
 | 
| -
 | 
| -
 | 
| -    def do_XROVER(self, range = None):
 | 
| -        self.do_XHDR(self, 'References', range)
 | 
| -
 | 
| -
 | 
| -    def do_IHAVE(self, id):
 | 
| -        self.factory.backend.articleExistsRequest(id).addCallback(self._foundArticle)
 | 
| -
 | 
| -    
 | 
| -    def _foundArticle(self, result):
 | 
| -        if result:
 | 
| -            self.sendLine('437 article rejected - do not try again')
 | 
| -        else:
 | 
| -            self.sendLine('335 send article to be transferred.  End with <CR-LF>.<CR-LF>')
 | 
| -            self.inputHandler = self._handleIHAVE
 | 
| -            self.message = ''
 | 
| -    
 | 
| -    
 | 
| -    def _handleIHAVE(self, line):
 | 
| -        if line == '.':
 | 
| -            self.inputHandler = None
 | 
| -            self.factory.backend.postRequest(
 | 
| -                self.message
 | 
| -            ).addCallbacks(self._gotIHAVE, self._errIHAVE)
 | 
| -            
 | 
| -            self.message = ''
 | 
| -        else:
 | 
| -            self.message = self.message + line + '\r\n'
 | 
| -
 | 
| -
 | 
| -    def _gotIHAVE(self, result):
 | 
| -        self.sendLine('235 article transferred ok')
 | 
| -    
 | 
| -    
 | 
| -    def _errIHAVE(self, failure):
 | 
| -        print 'IHAVE failed: ', failure
 | 
| -        self.sendLine('436 transfer failed - try again later')
 | 
| -
 | 
| -
 | 
| -class UsenetClientProtocol(NNTPClient):
 | 
| -    """
 | 
| -    A client that connects to an NNTP server and asks for articles new
 | 
| -    since a certain time.
 | 
| -    """
 | 
| -    
 | 
| -    def __init__(self, groups, date, storage):
 | 
| -        """
 | 
| -        Fetch all new articles from the given groups since the
 | 
| -        given date and dump them into the given storage.  groups
 | 
| -        is a list of group names.  date is an integer or floating
 | 
| -        point representing seconds since the epoch (GMT).  storage is
 | 
| -        any object that implements the NewsStorage interface.
 | 
| -        """
 | 
| -        NNTPClient.__init__(self)
 | 
| -        self.groups, self.date, self.storage = groups, date, storage
 | 
| -
 | 
| -
 | 
| -    def connectionMade(self):
 | 
| -        NNTPClient.connectionMade(self)
 | 
| -        log.msg("Initiating update with remote host: " + str(self.transport.getPeer()))
 | 
| -        self.setStream()
 | 
| -        self.fetchNewNews(self.groups, self.date, '')
 | 
| -
 | 
| -
 | 
| -    def articleExists(self, exists, article):
 | 
| -        if exists:
 | 
| -            self.fetchArticle(article)
 | 
| -        else:
 | 
| -            self.count = self.count - 1
 | 
| -            self.disregard = self.disregard + 1
 | 
| -
 | 
| -
 | 
| -    def gotNewNews(self, news):
 | 
| -        self.disregard = 0
 | 
| -        self.count = len(news)
 | 
| -        log.msg("Transfering " + str(self.count) + " articles from remote host: " + str(self.transport.getPeer()))
 | 
| -        for i in news:
 | 
| -            self.storage.articleExistsRequest(i).addCallback(self.articleExists, i)
 | 
| -
 | 
| -
 | 
| -    def getNewNewsFailed(self, reason):
 | 
| -        log.msg("Updated failed (" + reason + ") with remote host: " + str(self.transport.getPeer()))
 | 
| -        self.quit()
 | 
| -
 | 
| -
 | 
| -    def gotArticle(self, article):
 | 
| -        self.storage.postRequest(article)
 | 
| -        self.count = self.count - 1
 | 
| -        if not self.count:
 | 
| -            log.msg("Completed update with remote host: " + str(self.transport.getPeer()))
 | 
| -            if self.disregard:
 | 
| -                log.msg("Disregarded %d articles." % (self.disregard,))
 | 
| -            self.factory.updateChecks(self.transport.getPeer())
 | 
| -            self.quit()
 | 
| 
 |