USER_AGENT = "UniversalFeedParser/%s +http://feedparser.org/" % __version__
ACCEPT_HEADER = "application/atom+xml,application/rdf+xml,application/rss+xml,application/x-netcdf,application/xml;q=0.9,text/xml;q=0.2,*/*;q=0.1"
PREFERRED_XML_PARSERS = ["drv_libxml2"]
TIDY_MARKUP = 0
PREFERRED_TIDY_INTERFACES = ["uTidy", "mxTidy"]
RESOLVE_RELATIVE_URIS = 1
SANITIZE_HTML = 1
    _XML_AVAILABLE = 1
    _XML_AVAILABLE = 0
SUPPORTED_VERSIONS = {'': 'unknown',
                      'rss090': 'RSS 0.90',
                      'rss091n': 'RSS 0.91 (Netscape)',
                      'rss091u': 'RSS 0.91 (Userland)',
                      'rss092': 'RSS 0.92',
                      'rss093': 'RSS 0.93',
                      'rss094': 'RSS 0.94',
                      'rss20': 'RSS 2.0',
                      'rss10': 'RSS 1.0',
                      'rss': 'RSS (unknown version)',
                      'atom01': 'Atom 0.1',
                      'atom02': 'Atom 0.2',
                      'atom03': 'Atom 0.3',
                      'atom10': 'Atom 1.0',
                      'atom': 'Atom (unknown version)',
                      'cdf': 'CDF',
                      'hotrss': 'Hot RSS'
                      }
    STRING = 1
    DATE = 2
    URI = 3
    NODE = 4
    EMAIL = 5
    def _xmlescape(data,entities={}):
        def search(self,string,index=0):
        def __init__(self,match):
        def start(self,n):
    def dict(aList):
    def __getitem__(self, key):
    def __setitem__(self, key, value):
    def get(self, key, default=None):
    def setdefault(self, key, value):
    def has_key(self, key):
    def __getattr__(self, key):
    def __setattr__(self, key, value):
    def __contains__(self, key):
def zopeCompatibilityHack():
    def FeedParserDict(aDict=None):
def _ebcdic_to_ascii(s):
def _urljoin(base, uri):
    def __init__(self, baseuri=None, baselang=None, encoding='utf-8'):
    def unknown_starttag(self, tag, attrs):
    def unknown_endtag(self, tag):
    def handle_charref(self, ref):
    def handle_entityref(self, ref):
    def handle_data(self, text, escape=1):
    def handle_comment(self, text):
    def handle_pi(self, text):
    def handle_decl(self, text):
    def parse_declaration(self, i):
    def mapContentType(self, contentType):
    def trackNamespace(self, prefix, uri):
    def resolveURI(self, uri):
    def decodeEntities(self, element, data):
    def strattrs(self, attrs):
    def push(self, element, expectingText):
    def pop(self, element, stripWhitespace=1):
    def pushContent(self, tag, attrsD, defaultContentType, expectingText):
    def popContent(self, tag):
    def lookslikehtml(self, str):
    def _mapToStandardPrefix(self, name):
    def _getAttribute(self, attrsD, name):
    def _isBase64(self, attrsD, contentparams):
    def _itsAnHrefDamnIt(self, attrsD):
    def _save(self, key, value):
    def _start_rss(self, attrsD):
    def _start_dlhottitles(self, attrsD):
    def _start_channel(self, attrsD):
    def _cdf_common(self, attrsD):
    def _start_feed(self, attrsD):
    def _end_channel(self):
    def _start_image(self, attrsD):
    def _end_image(self):
    def _start_textinput(self, attrsD):
    def _end_textinput(self):
    def _start_author(self, attrsD):
    def _end_author(self):
    def _start_itunes_owner(self, attrsD):
    def _end_itunes_owner(self):
    def _start_contributor(self, attrsD):
    def _end_contributor(self):
    def _start_dc_contributor(self, attrsD):
    def _end_dc_contributor(self):
    def _start_name(self, attrsD):
    def _end_name(self):
    def _start_width(self, attrsD):
    def _end_width(self):
    def _start_height(self, attrsD):
    def _end_height(self):
    def _start_url(self, attrsD):
    def _end_url(self):
    def _start_email(self, attrsD):
    def _end_email(self):
    def _getContext(self):
    def _save_author(self, key, value, prefix='author'):
    def _save_contributor(self, key, value):
    def _sync_author_detail(self, key='author'):
    def _start_subtitle(self, attrsD):
    def _end_subtitle(self):
    def _start_rights(self, attrsD):
    def _end_rights(self):
    def _start_item(self, attrsD):
    def _end_item(self):
    def _start_dc_language(self, attrsD):
    def _end_dc_language(self):
    def _start_dc_publisher(self, attrsD):
    def _end_dc_publisher(self):
    def _start_published(self, attrsD):
    def _end_published(self):
    def _start_updated(self, attrsD):
    def _end_updated(self):
    def _start_created(self, attrsD):
    def _end_created(self):
    def _start_expirationdate(self, attrsD):
    def _end_expirationdate(self):
    def _start_cc_license(self, attrsD):
    def _start_creativecommons_license(self, attrsD):
    def _end_creativecommons_license(self):
    def _addXFN(self, relationships, href, name):
    def _addTag(self, term, scheme, label):
    def _start_category(self, attrsD):
    def _end_itunes_keywords(self):
    def _start_itunes_category(self, attrsD):
    def _end_category(self):
    def _start_cloud(self, attrsD):
    def _start_link(self, attrsD):
    def _end_link(self):
    def _start_guid(self, attrsD):
    def _end_guid(self):
    def _start_title(self, attrsD):
    def _end_title(self):
    def _end_media_title(self):
    def _start_description(self, attrsD):
    def _start_abstract(self, attrsD):
    def _end_description(self):
    def _start_info(self, attrsD):
    def _end_info(self):
    def _start_generator(self, attrsD):
    def _end_generator(self):
    def _start_admin_generatoragent(self, attrsD):
    def _start_admin_errorreportsto(self, attrsD):
    def _start_summary(self, attrsD):
    def _end_summary(self):
    def _start_enclosure(self, attrsD):
    def _start_source(self, attrsD):
    def _end_source(self):
    def _start_content(self, attrsD):
    def _start_prodlink(self, attrsD):
    def _start_body(self, attrsD):
    def _start_content_encoded(self, attrsD):
    def _end_content(self):
    def _start_itunes_image(self, attrsD):
    def _end_itunes_block(self):
    def _end_itunes_explicit(self):
    def _start_media_content(self, attrsD):
    def _start_media_thumbnail(self, attrsD):
    def _end_media_thumbnail(self):
    def _start_media_player(self, attrsD):
    def _end_media_player(self):
        def __init__(self, baseuri, baselang, encoding):
        def startPrefixMapping(self, prefix, uri):
        def startElementNS(self, name, qname, attrs):
        def characters(self, text):
        def endElementNS(self, name, qname):
        def error(self, exc):
        def fatalError(self, exc):
    def __init__(self, encoding, type):
    def reset(self):
    def _shorttag_replace(self, match):
    def parse_starttag(self,i):
    def feed(self, data):
    def normalize_attrs(self, attrs):
    def unknown_starttag(self, tag, attrs):
    def unknown_endtag(self, tag):
    def handle_charref(self, ref):
    def handle_entityref(self, ref):
    def handle_data(self, text):
    def handle_comment(self, text):
    def handle_pi(self, text):
    def handle_decl(self, text):
    def _scan_name(self, i, declstartpos):
    def convert_charref(self, name):
    def convert_entityref(self, name):
    def output(self):
    def __init__(self, baseuri, baselang, encoding, entities):
    def decodeEntities(self, element, data):
    def strattrs(self, attrs):
    def __init__(self, data, baseuri, encoding):
    def vcardEscape(self, s):
    def vcardFold(self, s):
    def normalize(self, s):
    def unique(self, aList):
    def toISO8601(self, dt):
    def getPropertyValue(self, elmRoot, sProperty, iPropertyType=4, bAllowMultiple=0, bAutoEscape=0):
    def findVCards(self, elmRoot, bAgentParsing=0):
            def processSingleString(sProperty):
            def processSingleURI(sProperty):
            def processTypeValue(sProperty, arDefaultType, arForceType=None):
    def isProbablyDownloadable(self, elm):
    def findTags(self):
    def findEnclosures(self):
    def findXFN(self):
def _parseMicroformats(htmlSource, baseURI, encoding):
    def __init__(self, baseuri, encoding, type):
    def resolveURI(self, uri):
    def unknown_starttag(self, tag, attrs):
def _resolveRelativeURIs(htmlSource, baseURI, encoding, type):
    def reset(self):
    def unknown_starttag(self, tag, attrs):
    def unknown_endtag(self, tag):
    def handle_pi(self, text):
    def handle_decl(self, text):
    def handle_data(self, text):
    def sanitize_style(self, style):
def _sanitizeHTML(htmlSource, encoding, type):
                    def _tidy(data, **kwargs):
                    def _tidy(data, **kwargs):
    def http_error_default(self, req, fp, code, msg, headers):
    def http_error_302(self, req, fp, code, msg, headers):
    def http_error_301(self, req, fp, code, msg, headers):
    def http_error_401(self, req, fp, code, msg, headers):
def _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers):
def registerDateHandler(func):
def _parse_date_iso8601(dateString):
def _parse_date_onblog(dateString):
def _parse_date_nate(dateString):
def _parse_date_mssql(dateString):
def _parse_date_greek(dateString):
def _parse_date_hungarian(dateString):
def _parse_date_w3dtf(dateString):
    def __extract_date(m):
    def __extract_time(m):
    def __extract_tzd(m):
def _parse_date_rfc822(dateString):
def _parse_date_perforce(aDateString):
def _parse_date(dateString):
def _getCharacterEncoding(http_headers, xml_data):
    def _parseHTTPContentType(content_type):
def _toUTF8(data, encoding):
def _stripDoctype(data):
def parse(url_file_stream_or_string, etag=None, modified=None, agent=None, referrer=None, handlers=[], header_defaults=None):
    def __init__(self, results):
    def write(self, stream=sys.stdout):
    def _writer(self, stream, node, prefix):
    def write(self, stream=sys.stdout):
class ThingsNobodyCaresAboutButMe(Exception): pass
class CharacterEncodingOverride(ThingsNobodyCaresAboutButMe): pass
class CharacterEncodingUnknown(ThingsNobodyCaresAboutButMe): pass
class NonXMLContentType(ThingsNobodyCaresAboutButMe): pass
class UndeclaredNamespace(Exception): pass
    class EndBracketHack:
    class EndBracketMatch:
class FeedParserDict(UserDict):
class _FeedParserMixin:
    class _StrictFeedParser(_FeedParserMixin, xml.sax.handler.ContentHandler):
class _BaseHTMLProcessor(sgmllib.SGMLParser):
class _LooseFeedParser(_FeedParserMixin, _BaseHTMLProcessor):
class _MicroformatsParser:
class _RelativeURIResolver(_BaseHTMLProcessor):
class _HTMLSanitizer(_BaseHTMLProcessor):
class _FeedURLHandler(urllib2.HTTPDigestAuthHandler, urllib2.HTTPRedirectHandler, urllib2.HTTPDefaultErrorHandler):
class Serializer:
class TextSerializer(Serializer):
class PprintSerializer(Serializer):
        class _Options: def create_mailbox(couchdb, dbname):
    """
    create a mailbox on the given server with the given name.
    
    raises PreconditionFailed if a database with the given name 
           already exists.
    raises ValueError if name is not a valid mailbox name.
    """
    db = couchdb.create(dbname)
    bless_mailbox(db)
    return db def is_mailbox(db):
    try:
        # check for the existance of the 
        # mailboxinfo document.
        info = db[MAILBOXINFO_ID]
        # check that the type field of the info doc 
        # matches the expected type.
        if info.get('type', None) != MAILBOXINFO_TYPE:
            return False
        # could do other validation here...
        return True
    except ResourceNotFound:
        # if it's not there, not a mailbox
        return False class Message(DowncastDoc):
    """
    A message in a mailbox.
    """
    
    type = TextField(default=MESSAGE_TYPE)
    message_type = TextField()
    fingerprint = TextField()
    timestamp = DateTimeField()
    source = DictField(SourceInfo)

    # helpful view constants
    by_timestamp = '_design/mailbox/_view/messages_by_timestamp'
    by_subscription = '_design/mailbox/_view/messages_by_subscription'
    
    SUBTYPE_PLUGIN = 'radar.mailbox.mailbox_subtype'
    SUBTYPE_FIELD = 'message_type' MESSAGE_TYPE = 'message' class MailboxInfo(RadarDocument):
    """
    General metadata about a mailbox.
    """
    type = TextField(default=MAILBOXINFO_TYPE)
    version = TextField(default="0.0.1")
    title = TextField()
    
    def __init__(self, **values):
        Document.__init__(self, id=MAILBOXINFO_ID, **values)
    
    @classmethod
    def get(cls, mailbox):
        return MailboxInfo.load(mailbox, MAILBOXINFO_ID)

    user_updatable = ('title', ) class Subscription(DowncastDoc):
    """
    Represents a subscription to a particular
    source of messages by a mailbox.
    """
    type = TextField(default=SUBSCRIPTION_TYPE)
    subscription_type = TextField()
    title = TextField()
    status = TextField()
    last_update = DateTimeField()

    # helpful view constants
    by_type = '_design/mailbox/_view/subscriptions_by_type'

    # status constants
    STATUS_OK        = 'ok'
    STATUS_ERROR     = 'error'
    STATUS_UNCHANGED = 'unchanged'
    
    SUBTYPE_PLUGIN = 'radar.mailbox.subscription_subtype'
    SUBTYPE_FIELD = 'subscription_type'

    user_updatable = ('title', )

    def reset(self):
        """
        called to reset any subscription state associated with this
        subscription.
        """
        self.last_update = None
        self.status = None SUBSCRIPTION_TYPE = 'subscription' ENTRY_POINT = 'radarpost_plugins'
def get(slot):
def _get(slot):
def register(thing, slot):
def plugin(slot):
    def dd(thing):
def _search(): def plugin(slot):
    """
    decorator to declare a plugin
    """
    def dd(thing):
        register(thing, slot)
        return thing
    return dd class FeedSubscription(Subscription):
    """
    Represents a subscription of a mailbox to a particular 
    remote rss/atom/etc feed
    """
    subscription_type = TextField(default=FEED_SUBSCRIPTION_TYPE)

    url = TextField()
    last_ids = ListField(TextField)

    # digest of last fetched content
    last_digest = TextField()

    # helpful view constants
    by_url = '_design/feed/_view/feeds_by_url'

    user_updatable = Subscription.user_updatable + ('url', )

    def reset(self):
        super(FeedSubscription, self).reset()
        self.last_ids = []
        self.last_digest = None FEED_SUBSCRIPTION_TYPE = 'feed' def create_client(cfg):
def close_all(client):
def parse_http_config(cfg): class User(Document):
    """
    This is a representation of a 'standard' couchdb user
    document.  Creating one of these in the couchdb users
    database (usually _users) adds a database user.  They 
    can be created elsewhere if database level access is 
    not desirable.
    
    See http://wiki.apache.org/couchdb/Security_Features_Overview
    
    """
    def __init__(self, username=None, password=None):
        """
        User may be initialized with the special keyword arguments
        username and password for creating a new User:
        
        u = User(username=joe, password="bl0w")
        """
        Document.__init__(self)

        if 'username' is not None: 
            self.id = 'org.couchdb.user:%s' % username
            self['name'] = username
    
        if password is not None:
            self.set_password(password)
        else: 
            self.lock_password()

    @classmethod
    def get_by_username(cls, db, username):
        natid = 'org.couchdb.user:%s' % username
        return cls.load(db, natid)

    @property
    def username(self):
        return self['name']

    def get_public_id(self):
        # same for now.
        return self.username

    type = TextField(default="user")
    roles = ListField(TextField)
    password_sha = TextField()
    salt = TextField()

    def set_password(self, plaintext_password):
        self.salt = _generate_salt()
        self.password_sha = _password_hash(plaintext_password, self.salt)

    def lock_password(self):
        self.password_sha = LOCKED_PASSWORD
        
    def check_password(self, plaintext_password):
        if self.password_sha == LOCKED_PASSWORD:
            return False
        hashed_pw = _password_hash(plaintext_password, self.salt)
        return self.password_sha == hashed_pw

    def is_anonymous(self):
        return False

    def has_role(self, role, obj=None):
        return role in self.roles

    def has_perm(self, perm, obj=None):
        if self.has_role(ROLE_ADMIN):
            return True
        if perm == PERM_READ:
            return True
        return False ROLE_ADMIN = 'radar:role:admin' PERM_CREATE = 'radar:perm:create' PERM_READ   = 'radar:perm:read' PERM_UPDATE = 'radar:perm:update' PERM_DELETE = 'radar:perm:delete' PERM_CREATE_MAILBOX = 'radar:perm:create:mailbox' class TemplateContext(dict):
    def __init__(self, request, ctx):
        self.update(ctx)
        self.request = request
        for proc in plugins.get(TEMPLATE_CONTEXT_PROCESSORS):
            proc(request, self) def check_etag(request, etag):
    """
    returns a response if the request contains an if-none-match 
    header that matches the given etag.  returns None if the 
    request should proceed as normal. 
    """
    
    rtags = request.headers.get('if-none-match', None)
    if rtags is None: 
        return None
    
    # spec requires that only GET and HEAD may be used
    if request.method not in ['GET', 'HEAD']: 
        return HttpResponse(status=412) # precondition failed

    matcher = ETagMatcher.parse(rtags)
    if etag in matcher:
        return HttpResponse(status=304, headers=[('etag', etag)])
    else: 
        return None def get_mailbox_etag(mailbox):
    """
    generate a string that uniquely identifies the current
    state of the given mailbox.
    """
    info = mailbox.info()
    # digest of "dbname@update_seq"
    return md5("%s@%d" % (info['db_name'], info['update_seq'])).hexdigest()
import copy
import html5lib
import json
import logging
import re
import traceback
from couchdb import ResourceConflict
from couchdb import ResourceNotFound, PreconditionFailed
from datetime import datetime
from html5lib import treebuilders
from xml.etree import cElementTree as etree
from webob import Response as HttpResponse
from radarpost.lib import feedparser
from radarpost.mailbox import create_mailbox as _create_mailbox, is_mailbox
from radarpost.mailbox import Message, MESSAGE_TYPE, MailboxInfo
from radarpost.mailbox import Subscription, SUBSCRIPTION_TYPE
from radarpost import plugins
from radarpost.plugins import plugin
from radarpost.feed import FeedSubscription, FEED_SUBSCRIPTION_TYPE
from radarpost import http
from radarpost.user import User, ROLE_ADMIN
from radarpost.user import PERM_CREATE, PERM_READ, PERM_UPDATE, PERM_DELETE
from radarpost.user import PERM_CREATE_MAILBOX
from radarpost.web.context import TemplateContext, check_etag, get_mailbox_etag
def _mailbox_info_json(request):
    info = {}
    raw_info = json.loads(request.body)
    title = raw_info.get('title')
    if title:
        if VALID_TITLE_PAT.match(title) is None:
            raise ValueError()
        info['title'] = raw_info['title']
    return info

def delete_mailbox(request, mailbox_slug):
    """
    destroys a mailbox.
    """
    ctx = request.context
    couchdb = ctx.get_couchdb_server()
    try:
        dbname = ctx.get_database_name(mailbox_slug)
        mb = couchdb[dbname]
        if not is_mailbox(mb):
            return HttpResponse(status=404)
        if not ctx.user.has_perm(PERM_DELETE, mb):
            return HttpResponse(status=401)
        del couchdb[dbname]
        return HttpResponse()
    except ResourceNotFound:
        return HttpResponse(status=404)

##############################################
#
# Feed output 
#
###############################################

ATOM_RENDERER_PLUGIN = 'radarpost.web.api.atom_renderer'
"""
This slot represents plugins that can render a Message
into an Atom entry.  

Most types can be handled by just creating a template called 
radar/atom/entry/<message_type>.xml

Specifically, the slot is filled with callables that accept a Message 
and a Request, and produce a zero argument callable returning the text
of an atom entry representing the Message.  If the Message cannot be handled,
None should be returned. eg: 

@plugin(ATOM_RENDERER_PLUGIN)
def _render_empty(message, request):
    def render(): 
        return "<entry></entry>"
    return render
"""

DEFAULT_ATOM_ENTRIES = 25
MAX_ATOM_ENTRIES = 100
def atom_feed_latest(request, mailbox_slug):
    """
    renders the mailbox as an atom feed
    """
    ctx = request.context
    mb = ctx.get_mailbox(mailbox_slug)
    if mb is None:
        return HttpResponse(status=404)

    if not ctx.user.has_perm(PERM_READ, mb):
        return HttpResponse(status=401)

    etag = get_mailbox_etag(mb)
    cached = check_etag(request, etag)
    if cached is not None: 
        return cached

    # number of entries
    try:
        limit = min(int(request.GET.get('limit', DEFAULT_ATOM_ENTRIES)), 
                    MAX_ATOM_ENTRIES)
    except:
        return HttpResponse(status=400)

    params = {'limit': limit, 
              'include_docs': True,
              'reduce': False,
              'descending': True}

    # starting point in time
    if 'startkey' in request.GET:
        params['startkey'] = request.GET['startkey']

    latest_messages = Message.view(mb, Message.by_timestamp, **params) 
    return _render_atom_feed(request, mb, latest_messages)

def _render_atom_feed(request, mb, messages):    
    entries = []
    for message in messages:
        renderer = _get_atom_renderer(message, request)
        if renderer is not None:
            entries.append(renderer)

    info = MailboxInfo.get(mb)
    
    # absolute url for requested feed
    feed_url = request.url
    template_info = TemplateContext(request,
          {'id': feed_url,
           'self_link': feed_url,
           'updated': datetime.utcnow(), # XXX
           'title': info.title or request.context.get_mailbox_slug(mb.name),
           'entries': entries,
          })

    res = HttpResponse(content_type='application/atom+xml')
    res.charset = 'utf-8'
    res.unicode_body = request.context.render('radar/atom/atom.xml', template_info)
    res.headers['etag'] = get_mailbox_etag(mb)
    
    return res


def _get_atom_renderer(message, request):

    for renderer in plugins.get(ATOM_RENDERER_PLUGIN):