This commit is contained in:
tonio
2010-04-12 10:05:58 +02:00
100 changed files with 7068 additions and 614 deletions
+27 -1
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import popen2
import os
import sys
import time
from django.conf import settings
from django.template import RequestContext
@@ -41,6 +42,10 @@ def _render(request, tpl, user_context):
return render_to_response(tpl, user_context,
context_instance=RequestContext(request))
def _render_error(request, user_context):
return render_to_response("common/error.html", user_context,
context_instance=RequestContext(request))
def exec_as_vuser(cmd):
code, output = exec_pipe("sudo -u %s %s" % (settings.VIRTUAL_UID, cmd))
if code:
@@ -54,7 +59,17 @@ def _ctx_ok(url):
def _ctx_ko(tpl, ctx):
return {"status" : "ko", "content" : render_to_string(tpl, ctx)}
def decode(s, encodings=('ascii', 'utf8', 'latin1')):
def getctx(status, level=1, callback=None, **kwargs):
if not callback:
callername = sys._getframe(level).f_code.co_name
else:
callername = callback
ctx = {"status" : status, "callback" : callername}
for kw, v in kwargs.iteritems():
ctx[kw] = v
return ctx
def decode(s, encodings=('utf8', 'latin1', 'windows-1252', 'ascii')):
for encoding in encodings:
try:
return s.decode(encoding)
@@ -69,3 +84,14 @@ def getoption(name, default=None):
except AttributeError:
res = default
return res
class Singleton(type):
def __init__(cls, name, bases, dict):
super(Singleton, cls).__init__(name, bases, dict)
cls.instance = None
def __call__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = super(Singleton, cls).__call__(*args, **kwargs)
return cls.instance
+228
View File
@@ -0,0 +1,228 @@
# -*- coding: utf-8 -*-
import time
import re
from email.header import decode_header
import lxml
import lxml.html
from lxml import etree
from django.template.loader import render_to_string
from django.template import Template, Context
from django.utils.translation import ugettext as _
from mailng.lib import _render, decode
attached_map = {}
class MBconnector(object):
def __init__(self, address, port):
self.address = address
self.port = port
def messages_count(self, **kwargs):
pass
class Page(object):
def __init__(self, pageid, id_start, id_stop, items,
items_per_page, has_previous, has_next,
baseurl=None):
self.pageid = pageid
self.id_start = id_start
self.id_stop = id_stop
self.items = items
self.items_per_page = items_per_page
self.has_previous = has_previous
self.has_next = has_next
self.baseurl = baseurl
def previous_page(self):
if not self.has_previous:
return False
return self.pageid - 1
def next_page(self):
if not self.has_next:
return False
return self.pageid + 1
def last_page(self):
lid = self.items / self.items_per_page
if not lid:
return 1
if self.items % self.items_per_page:
lid += 1
return lid
class Paginator(object):
def __init__(self, total, elems_per_page):
self.total = total
self.elems_per_page = elems_per_page
self.baseurl = None
def _indexes(self, page):
id_start = self.elems_per_page * page + 1
id_stop = id_start + self.elems_per_page - 1
return (id_start, id_stop)
def getpage(self, page):
if page < 1:
return None
id_start, id_stop = self._indexes(page - 1)
has_previous = has_next = False
if id_start < self.total:
if page > 1:
has_previous = True
if id_stop < self.total:
has_next = True
else:
id_stop = self.total
else:
return None
p = Page(page, id_start, id_stop, self.total,
self.elems_per_page, has_previous, has_next)
if self.baseurl:
p.baseurl = self.baseurl
return p
class EmailListing:
def __init__(self, baseurl=None, folder=None, elems_per_page=40,
navparams={}, **kwargs):
self.folder = folder
self.elems_per_page = elems_per_page
self.navparams = navparams
order = "order" in kwargs.keys() and kwargs["order"] or None
self.empty = "empty" in kwargs.keys() and kwargs["empty"] or False
if not self.empty:
self.paginator = Paginator(self.mbc.messages_count(folder=self.folder,
order=order),
elems_per_page)
if baseurl:
self.paginator.baseurl = baseurl
def render_navbar(self, page):
if page is None:
return ""
return render_to_string("common/navbar.html", {
"page" : page
})
def getfolders(self):
return None
def fetch(self, request, id_start, id_stop):
table = self.tbltype(self.mbc.fetch(start=id_start, stop=id_stop,
folder=self.folder))
tpl = Template("""
<form method="POST" id="listingform">
{{ table }}
</form>""")
return tpl.render(Context({"table" : table.render(request)}))
def render(self, request, pageid=1, **kwargs):
listing = ""
page = None
if not self.empty:
page = self.paginator.getpage(pageid)
if not page:
listing = "Empty folder"
else:
listing = self.fetch(request, page.id_start, page.id_stop)
elapsed = kwargs.has_key("start") and time.time() - kwargs["start"] or 0
return _render(request, self.tpl, {
"listing" : listing,
"elapsed" : elapsed,
"navbar" : self.render_navbar(page),
"folders" : self.getfolders(),
"selection" : self.folder,
"navparams" : self.navparams,
"deflocation" : self.deflocation, "defcallback" : self.defcallback
})
class Email(object):
def __init__(self, msg, mode="plain", links="0"):
self.attached_map = {}
contents = {"html" : "", "plain" : ""}
self.headers = []
self.attachments = []
for part in msg.walk():
if part.get_content_maintype() == 'multipart':
continue
if part.has_key("Content-Disposition"):
if re.match("^attachment", part["Content-Disposition"]):
self.attachments += [part]
if part.get_content_type() in ("text/html", "text/plain"):
contents[part.get_content_subtype()] += part.get_payload(decode=True)
if mode != "html" or links == "0":
continue
if part.get_content_maintype() == "image":
if part.has_key("Content-Location"):
fname = part["Content-Location"]
if re.match("^http:", fname):
path = fname
else:
path = "/%s" % os.path.join("static/tmp", fname)
fp = open(path, "wb")
fp.write(part.get_payload(decode=True))
fp.close()
m = re.match("<(.+)>", part["Content-ID"])
if m:
self.attached_map[m.group(1)] = path
else:
self.attached_map[part["Content-ID"]] = path
if not contents.has_key(mode) or contents[mode] == "":
if mode == "html":
mode = "plain"
else:
mode = "html"
self.pre, self.body = \
getattr(self, "viewmail_%s" % mode)(contents[mode], links=links)
def map_cid(self, url):
import re
map = globals()["attached_map"]
m = re.match(".*cid:([^\"]+)", url)
if m:
if map.has_key(m.group(1)):
return map[m.group(1)]
return url
def render_headers(self, folder, mail_id):
attachments = []
for part in self.attachments:
decoded = decode_header(part.get_filename())
if decoded[0][1] is None:
attachments += [decoded[0][0]]
else:
attachments += [unicode(decoded[0][0], decoded[0][1])]
return render_to_string("webmail/headers.html", {
"headers" : self.headers,
"folder" : folder, "mail_id" : mail_id,
"attachments" : attachments != [] and attachments or None
})
def render(self, request):
return _render(request, 'common/getmailcontent.html', {
"body" : self.body, "pre" : self.pre
})
def viewmail_plain(self, content, **kwargs):
body = decode(content)
return (True, body)
def viewmail_html(self, content, **kwargs):
links = kwargs.has_key("links") and kwargs["links"] or "0"
html = lxml.html.fromstring(content)
if links == "0":
html.rewrite_links(lambda x: None)
else:
html.rewrite_links(self.map_cid)
body = html.find("body")
if body is None:
body = lxml.html.tostring(html)
else:
body = lxml.html.tostring(body)
body = re.sub("<(/?)body", lambda m: "<%sdiv" % m.group(1), body)
body = Template(decode(body)).render({})
return (False, body)
+3 -1
View File
@@ -13,7 +13,9 @@ events = ["CreateDomain",
"ModifyMailbox",
"UserMenuDisplay",
"AdminMenuDisplay"]
"AdminMenuDisplay",
"UserLogin"]
callbacks = {}
+210
View File
@@ -0,0 +1,210 @@
# -*- coding: latin-1 -*-
"""
Imap folder names are encoded using a special version of utf-7 as defined in RFC
2060 section 5.1.3.
5.1.3. Mailbox International Naming Convention
By convention, international mailbox names are specified using a
modified version of the UTF-7 encoding described in [UTF-7]. The
purpose of these modifications is to correct the following problems
with UTF-7:
1) UTF-7 uses the "+" character for shifting; this conflicts with
the common use of "+" in mailbox names, in particular USENET
newsgroup names.
2) UTF-7's encoding is BASE64 which uses the "/" character; this
conflicts with the use of "/" as a popular hierarchy delimiter.
3) UTF-7 prohibits the unencoded usage of "\"; this conflicts with
the use of "\" as a popular hierarchy delimiter.
4) UTF-7 prohibits the unencoded usage of "~"; this conflicts with
the use of "~" in some servers as a home directory indicator.
5) UTF-7 permits multiple alternate forms to represent the same
string; in particular, printable US-ASCII chararacters can be
represented in encoded form.
In modified UTF-7, printable US-ASCII characters except for "&"
represent themselves; that is, characters with octet values 0x20-0x25
and 0x27-0x7e. The character "&" (0x26) is represented by the two-
octet sequence "&-".
All other characters (octet values 0x00-0x1f, 0x7f-0xff, and all
Unicode 16-bit octets) are represented in modified BASE64, with a
further modification from [UTF-7] that "," is used instead of "/".
Modified BASE64 MUST NOT be used to represent any printing US-ASCII
character which can represent itself.
"&" is used to shift to modified BASE64 and "-" to shift back to US-
ASCII. All names start in US-ASCII, and MUST end in US-ASCII (that
is, a name that ends with a Unicode 16-bit octet MUST end with a "-
").
For example, here is a mailbox name which mixes English, Japanese,
and Chinese text: ~peter/mail/&ZeVnLIqe-/&U,BTFw-
Found here:
http://svn.plone.org/svn/collective/mxmImapClient/trunk/imapUTF7.py
"""
import binascii
import codecs
# encoding
def modified_base64(s):
s = s.encode('utf-16be')
return binascii.b2a_base64(s).rstrip('\n=').replace('/', ',')
def doB64(_in, r):
if _in:
r.append('&%s-' % modified_base64(''.join(_in)))
del _in[:]
def encoder(s):
r = []
_in = []
for c in s:
ordC = ord(c)
if 0x20 <= ordC <= 0x25 or 0x27 <= ordC <= 0x7e:
doB64(_in, r)
r.append(c)
elif c == '&':
doB64(_in, r)
r.append('&-')
else:
_in.append(c)
doB64(_in, r)
return (str(''.join(r)), len(s))
# decoding
def modified_unbase64(s):
b = binascii.a2b_base64(s.replace(',', '/') + '===')
return unicode(b, 'utf-16be')
def decoder(s):
r = []
decode = []
for c in s:
if c == '&' and not decode:
decode.append('&')
elif c == '-' and decode:
if len(decode) == 1:
r.append('&')
else:
r.append(modified_unbase64(''.join(decode[1:])))
decode = []
elif decode:
decode.append(c)
else:
r.append(c)
if decode:
r.append(modified_unbase64(''.join(decode[1:])))
bin_str = ''.join(r)
return (bin_str, len(s))
class StreamReader(codecs.StreamReader):
def decode(self, s, errors='strict'):
return decoder(s)
class StreamWriter(codecs.StreamWriter):
def decode(self, s, errors='strict'):
return encoder(s)
def imap4_utf_7(name):
if name == 'imap4-utf-7':
return (encoder, decoder, StreamReader, StreamWriter)
codecs.register(imap4_utf_7)
## testing methods
def imapUTF7Encode(ust):
"Returns imap utf-7 encoded version of string"
return ust.encode('imap4-utf-7')
def imapUTF7EncodeSequence(seq):
"Returns imap utf-7 encoded version of strings in sequence"
return [imapUTF7Encode(itm) for itm in seq]
def imapUTF7Decode(st):
"Returns utf7 encoded version of imap utf-7 string"
return st.decode('imap4-utf-7')
def imapUTF7DecodeSequence(seq):
"Returns utf7 encoded version of imap utf-7 strings in sequence"
return [imapUTF7Decode(itm) for itm in seq]
def utf8Decode(st):
"Returns utf7 encoded version of imap utf-7 string"
return st.decode('utf-8')
def utf7SequenceToUTF8(seq):
"Returns utf7 encoded version of imap utf-7 strings in sequence"
return [itm.decode('imap4-utf-7').encode('utf-8') for itm in seq]
__all__ = ['imapUTF7Encode', 'imapUTF7Decode', ]
if __name__=='__main__':
## print u'bøx'.encode('imap4-utf-7')
## print 'expected b&APg-x'
##
## print u'båx'.encode('imap4-utf-7')
## print 'expected b&AOU-x'
##
print '#######'
print 'bøx'
e = imapUTF7Encode(u'bøx')
print e
print imapUTF7Decode(e).encode('latin-1')
print '#######'
print 'båx'
e = imapUTF7Encode(u'båx')
print e
print imapUTF7Decode(e).encode('latin-1')
print '#######'
print '~/bågø'
e = imapUTF7Encode(u'~/bågø')
print e
print imapUTF7Decode(e).encode('latin-1')
print '#######'
print 'Ting & Såger'
e = imapUTF7Encode(u'Ting & Såger')
print e
print imapUTF7Decode(e).encode('latin-1')
## e = imapUTF7Decode('b&AOU-x')
## print e.encode('latin-1')
##
## e = imapUTF7Decode('b&APg-x')
## print e.encode('latin-1')
##
print '#######'
print '~/Følder/mailbåx & stuff + more'
n = u'~/Følder/mailbåx & stuff + more'
e = imapUTF7Encode(n)
print e
print imapUTF7Decode(e).encode('latin-1')
print '#######'
print '~peter/mail/&ZeVnLIqe-/&U,BTFw-'
print imapUTF7Decode('~peter/mail/&ZeVnLIqe-/&U,BTFw-').encode('utf-8')
+57
View File
@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
import string
from mailng.admin.models import Parameter
"""
This interface provides a simple way to declare and store parameters
in MailNG's database.
Core components or extensions can register their own parameters, which
will be available and modifiable directly from the web interface.
Only super users will be able to acces this part of the web interface.
"""
_params = {}
class NotDefined(Exception):
def __init__(self, app, name):
self.app = app
self.name = name
def __str__(self):
return "Application '%s' and/or parameter '%s' not defined" % (self.app,
self.name)
def register(app, name, type="string", deflt=None, help=None, **kwargs):
"""Register a new parameter.
app corresponds to a core component (admin, main) or an extension.
"""
if not app in _params.keys():
_params[app] = {}
_params[app][name] = {"type" : type, "default" : deflt, "help" : help}
def save(app, name, value):
if not app in _params.keys() or not name in _params[app].keys():
raise NotDefined(app, name)
fullname = "%s.%s" % (app, name)
try:
p = Parameter.objects.get(name=fullname)
except Parameter.DoesNotExist:
p = Parameter()
p.name = fullname
if p.value != value:
p.value = value
p.save()
return True
def get(app, name):
if not app in _params.keys() or not name in _params[app].keys():
raise NotDefined(app, name)
try:
p = Parameter.objects.get(name="%s.%s" % (app, name))
except Parameter.DoesNotExist:
return _params[app][name]["default"]
return p.value
+115
View File
@@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
import inspect
import lxml
from django.template import Template, Context
from django.template.loader import render_to_string
class Column:
sortable = True
def __init__(self, name, **kwargs):
self.name = name
for k, v in kwargs.iteritems():
setattr(self, k, v)
def __str__(self):
try:
label = getattr(self, "label")
except KeyError:
label = getattr(self, "name")
return label
class SelectionColumn(Column):
def __str__(self):
return "<input type='checkbox' name='selectall' id='selectall' />"
def render(self, value):
return "<input type='checkbox' name='%s' value='%s' />" \
% (self.name, value)
class ImgColumn(Column):
sortable = False
def __str__(self):
try:
return getattr(self, "header")
except AttributeError:
return ""
def render(self, value):
return "<img src='%s' />" % value
class Table(object):
tableid = ""
def __init__(self, rows={}):
self.columns = []
for m in inspect.getmembers(self):
if isinstance(m[1], Column):
try:
if getattr(m[1], "first"):
self.columns = [m[1]] + self.columns
except AttributeError:
self.columns += [m[1]]
self.rows = []
for row in rows:
nrow = {"id" : row[self.idkey], "cols" : []}
for c in self.columns:
newcol = {}
try:
newcol["width"] = getattr(c, "width")
except AttributeError:
pass
if isinstance(c, SelectionColumn):
newcol["value"] = c.render(row[self.idkey])
newcol["safe"] = True
if isinstance(c, ImgColumn):
key = "img_%s" % c.name
if key in row.keys():
newcol["value"] = c.render(row[key])
newcol["safe"] = True
if row.has_key(c.name):
try:
cssclass = getattr(c, "cssclass")
except AttributeError:
cssclass = ""
if "class" in row.keys():
cssclass += (cssclass != "") and ", %s" % row["class"] or row["class"]
newcol["value"] = self.parse(c.name, row[c.name])
newcol["class"] = cssclass
nrow["cols"] += [newcol]
self.rows += [nrow]
def render(self, request):
t = Template("""
<table id="{{ tableid }}">
{% include "common/table_head.html" %}
{% include "common/table_body.html" %}
</table>
""")
return t.render(Context({
"table" : self, "tableid" : self.tableid
}))
def render_head(self, request):
t = Template("""
<table id="{{ tableid }}">
{% include "common/table_head.html" %}
</table>
""")
return t.render(Context({
"table" : self, "tableid" : "%s_head" % self.tableid
}))
def render_body(self, request):
t = Template("""
<table id="{{ tableid }}">
{% include "common/table_body.html" %}
</table>
""")
return t.render(Context({
"table" : self, "tableid" : "%s_body" % self.tableid
}))