first import

first import between version 0.1.3 and 0.2

darcs-hash:20051127110300-684f5-0ed50cd0e86df9195cec2c1df070fdf24a6faeb5.gz
This commit is contained in:
dax
2005-11-27 12:03:00 +01:00
commit 9fa2df5563
27 changed files with 4351 additions and 0 deletions

340
COPYING Normal file
View File

@@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

25
README Normal file
View File

@@ -0,0 +1,25 @@
Installation :
- Get pyxmpp from http://pyxmpp.jabberstudio.org/ (work with snapshot pyxmpp-0.5.s20050506)
- edit jmc.xml to match jabber server configuration :
- for jabberd2, port must match router.xml port
- for ejabberd, add new listening port :
{5347, ejabberd_service, [{access, all},
{host, "jmc.localhost",
[{password, "secret"}]}]}
- then run python jmc.py -c jmc.xml (-D to get debug)
Usage :
- Now you can register new mail server connection with your favorite
jabber client.
- all connection are then listed under your service browser and you
can edit mail server parameters by registering on listed connection.
Feedback :
Send me feedback and comments to dax at happycoders dot org

44
TODO Normal file
View File

@@ -0,0 +1,44 @@
* Make real documentation
* i18n support in the same way as ejabberd and
PyMSNt, PyICQ-t and PyAIM-t: using xml:lang, but have an option in
the config to set the default language to show, when the client do
not support xml:lang.i18n support (use xml:lang + default)
* Database backend (default SQLLite)
* Checkbox and radio button options in the forms to set when and how
you like to get the messages (in offline, online, free for chat,
away,... presence).
So:
Online: <radio button for notifications> <radio button for retrieving
mails> <checkbox for digest (all new mails in 1 message)>
Away: <radio button for notifications> <radio button for retrieving
mails> <checkbox for digest (all new mails in 1 message)>
Free for Chat: <radio button for notifications> <radio button for
retrieving mails> <checkbox for digest (all new mails in 1
message)>
Extended Away: <radio button for notifications> <radio button for
retrieving mails> <checkbox for digest (all new mails in 1
message)>
Offline: <radio button for notifications> <radio button for
retrieving mails> <checkbox for digest (all new mails in 1
message)>
* Dropdown menu in the forms with configurable time-intervals. E.g.,
users can choose betwoon 1 minute, 2 minutes, 5 minutes, 15
minutes, 30 minutes, 60 minutes, 1 day. The admin can configure in
the configuration file of jmc which options are available.
* Support for Ad Hoc Commands (see PyMSNt cvs, avatar
branch). Interesting for statistics.
* Support for attachements with size limit and file format limit
(e.g. only png, jpeg,... but no exe, bat,...).
* Support for epoll, kpoll and kqueu (see PyMSNt cvs, avatar branch
code).
* make JMC run on windows

596
coverage.py Executable file
View File

@@ -0,0 +1,596 @@
#!/usr/bin/python
#
# Perforce Defect Tracking Integration Project
# <http://www.ravenbrook.com/project/p4dti/>
#
# COVERAGE.PY -- COVERAGE TESTING
#
# Gareth Rees, Ravenbrook Limited, 2001-12-04
#
#
# 1. INTRODUCTION
#
# This module provides coverage testing for Python code.
#
# The intended readership is all Python developers.
#
# This document is not confidential.
#
# See [GDR 2001-12-04a] for the command-line interface, programmatic
# interface and limitations. See [GDR 2001-12-04b] for requirements and
# design.
"""Usage:
coverage.py -x MODULE.py [ARG1 ARG2 ...]
Execute module, passing the given command-line arguments, collecting
coverage data.
coverage.py -e
Erase collected coverage data.
coverage.py -r [-m] FILE1 FILE2 ...
Report on the statement coverage for the given files. With the -m
option, show line numbers of the statements that weren't executed.
coverage.py -a [-d dir] FILE1 FILE2 ...
Make annotated copies of the given files, marking statements that
are executed with > and statements that are missed with !. With
the -d option, make the copies in that directory. Without the -d
option, make each copy in the same directory as the original.
Coverage data is saved in the file .coverage by default. Set the
COVERAGE_FILE environment variable to save it somewhere else."""
import os
import re
import string
import sys
import types
# 2. IMPLEMENTATION
#
# This uses the "singleton" pattern.
#
# The word "morf" means a module object (from which the source file can
# be deduced by suitable manipulation of the __file__ attribute) or a
# filename.
#
# When we generate a coverage report we have to canonicalize every
# filename in the coverage dictionary just in case it refers to the
# module we are reporting on. It seems a shame to throw away this
# information so the data in the coverage dictionary is transferred to
# the 'cexecuted' dictionary under the canonical filenames.
#
# The coverage dictionary is called "c" and the trace function "t". The
# reason for these short names is that Python looks up variables by name
# at runtime and so execution time depends on the length of variables!
# In the bottleneck of this application it's appropriate to abbreviate
# names to increase speed.
# A dictionary with an entry for (Python source file name, line number
# in that file) if that line has been executed.
c = {}
# t(f, x, y). This method is passed to sys.settrace as a trace
# function. See [van Rossum 2001-07-20b, 9.2] for an explanation of
# sys.settrace and the arguments and return value of the trace function.
# See [van Rossum 2001-07-20a, 3.2] for a description of frame and code
# objects.
def t(f, x, y):
c[(f.f_code.co_filename, f.f_lineno)] = 1
return t
the_coverage = None
class coverage:
error = "coverage error"
# Name of the cache file (unless environment variable is set).
cache_default = ".coverage"
# Environment variable naming the cache file.
cache_env = "COVERAGE_FILE"
# A map from canonical Python source file name to a dictionary in
# which there's an entry for each line number that has been
# executed.
cexecuted = {}
# Cache of results of calling the analysis() method, so that you can
# specify both -r and -a without doing double work.
analysis_cache = {}
# Cache of results of calling the canonical_filename() method, to
# avoid duplicating work.
canonical_filename_cache = {}
def __init__(self):
global the_coverage
if the_coverage:
raise self.error, "Only one coverage object allowed."
self.cache = os.environ.get(self.cache_env, self.cache_default)
self.restore()
self.analysis_cache = {}
def help(self, error=None):
if error:
print error
print
print __doc__
sys.exit(1)
def command_line(self):
import getopt
settings = {}
optmap = {
'-a': 'annotate',
'-d:': 'directory=',
'-e': 'erase',
'-h': 'help',
'-i': 'ignore-errors',
'-m': 'show-missing',
'-r': 'report',
'-x': 'execute',
}
short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '')
long_opts = optmap.values()
options, args = getopt.getopt(sys.argv[1:], short_opts,
long_opts)
for o, a in options:
if optmap.has_key(o):
settings[optmap[o]] = 1
elif optmap.has_key(o + ':'):
settings[optmap[o + ':']] = a
elif o[2:] in long_opts:
settings[o[2:]] = 1
elif o[2:] + '=' in long_opts:
settings[o[2:]] = a
else:
self.help("Unknown option: '%s'." % o)
if settings.get('help'):
self.help()
for i in ['erase', 'execute']:
for j in ['annotate', 'report']:
if settings.get(i) and settings.get(j):
self.help("You can't specify the '%s' and '%s' "
"options at the same time." % (i, j))
args_needed = (settings.get('execute')
or settings.get('annotate')
or settings.get('report'))
action = settings.get('erase') or args_needed
if not action:
self.help("You must specify at least one of -e, -x, -r, "
"or -a.")
if not args_needed and args:
self.help("Unexpected arguments %s." % args)
if settings.get('erase'):
self.erase()
if settings.get('execute'):
if not args:
self.help("Nothing to do.")
sys.argv = args
self.start()
import __main__
sys.path[0] = os.path.dirname(sys.argv[0])
execfile(sys.argv[0], __main__.__dict__)
if not args:
args = self.cexecuted.keys()
ignore_errors = settings.get('ignore-errors')
show_missing = settings.get('show-missing')
directory = settings.get('directory=')
if settings.get('report'):
self.report(args, show_missing, ignore_errors)
if settings.get('annotate'):
self.annotate(args, directory, ignore_errors)
def start(self):
sys.settrace(t)
def stop(self):
sys.settrace(None)
def erase(self):
global c
c = {}
self.analysis_cache = {}
self.cexecuted = {}
if os.path.exists(self.cache):
os.remove(self.cache)
# save(). Save coverage data to the coverage cache.
def save(self):
self.canonicalize_filenames()
cache = open(self.cache, 'wb')
import marshal
marshal.dump(self.cexecuted, cache)
cache.close()
# restore(). Restore coverage data from the coverage cache (if it
# exists).
def restore(self):
global c
c = {}
self.cexecuted = {}
if not os.path.exists(self.cache):
return
try:
cache = open(self.cache, 'rb')
import marshal
cexecuted = marshal.load(cache)
cache.close()
if isinstance(cexecuted, types.DictType):
self.cexecuted = cexecuted
except:
pass
# canonical_filename(filename). Return a canonical filename for the
# file (that is, an absolute path with no redundant components and
# normalized case). See [GDR 2001-12-04b, 3.3].
def canonical_filename(self, filename):
if not self.canonical_filename_cache.has_key(filename):
f = filename
if os.path.isabs(f) and not os.path.exists(f):
f = os.path.basename(f)
if not os.path.isabs(f):
for path in [os.curdir] + sys.path:
g = os.path.join(path, f)
if os.path.exists(g):
f = g
break
cf = os.path.normcase(os.path.abspath(f))
self.canonical_filename_cache[filename] = cf
return self.canonical_filename_cache[filename]
# canonicalize_filenames(). Copy results from "executed" to
# "cexecuted", canonicalizing filenames on the way. Clear the
# "executed" map.
def canonicalize_filenames(self):
global c
for filename, lineno in c.keys():
f = self.canonical_filename(filename)
if not self.cexecuted.has_key(f):
self.cexecuted[f] = {}
self.cexecuted[f][lineno] = 1
c = {}
# morf_filename(morf). Return the filename for a module or file.
def morf_filename(self, morf):
if isinstance(morf, types.ModuleType):
if not hasattr(morf, '__file__'):
raise self.error, "Module has no __file__ attribute."
file = morf.__file__
else:
file = morf
return self.canonical_filename(file)
# analyze_morf(morf). Analyze the module or filename passed as
# the argument. If the source code can't be found, raise an error.
# Otherwise, return a pair of (1) the canonical filename of the
# source code for the module, and (2) a list of lines of statements
# in the source code.
def analyze_morf(self, morf):
if self.analysis_cache.has_key(morf):
return self.analysis_cache[morf]
filename = self.morf_filename(morf)
ext = os.path.splitext(filename)[1]
if ext == '.pyc':
if not os.path.exists(filename[0:-1]):
raise self.error, ("No source for compiled code '%s'."
% filename)
filename = filename[0:-1]
elif ext != '.py':
raise self.error, "File '%s' not Python source." % filename
source = open(filename, 'r')
import parser
tree = parser.suite(source.read()).totuple(1)
source.close()
statements = {}
self.find_statements(tree, statements)
lines = statements.keys()
lines.sort()
result = filename, lines
self.analysis_cache[morf] = result
return result
# find_statements(tree, dict). Find each statement in the parse
# tree and record the line on which the statement starts in the
# dictionary (by assigning it to 1).
#
# It works by walking the whole tree depth-first. Every time it
# comes across a statement (symbol.stmt -- this includes compound
# statements like 'if' and 'while') it calls find_statement, which
# descends the tree below the statement to find the first terminal
# token in that statement and record the lines on which that token
# was found.
#
# This algorithm may find some lines several times (because of the
# grammar production statement -> compound statement -> statement),
# but that doesn't matter because we record lines as the keys of the
# dictionary.
#
# See also [GDR 2001-12-04b, 3.2].
def find_statements(self, tree, dict):
import symbol, token
if token.ISNONTERMINAL(tree[0]):
for t in tree[1:]:
self.find_statements(t, dict)
if tree[0] == symbol.stmt:
self.find_statement(tree[1], dict)
elif (tree[0] == token.NAME
and tree[1] in ['elif', 'except', 'finally']):
dict[tree[2]] = 1
def find_statement(self, tree, dict):
import token
while token.ISNONTERMINAL(tree[0]):
tree = tree[1]
dict[tree[2]] = 1
# format_lines(statements, lines). Format a list of line numbers
# for printing by coalescing groups of lines as long as the lines
# represent consecutive statements. This will coalesce even if
# there are gaps between statements, so if statements =
# [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then
# format_lines will return "1-2, 5-11, 13-14".
def format_lines(self, statements, lines):
pairs = []
i = 0
j = 0
start = None
pairs = []
while i < len(statements) and j < len(lines):
if statements[i] == lines[j]:
if start == None:
start = lines[j]
end = lines[j]
j = j + 1
elif start:
pairs.append((start, end))
start = None
i = i + 1
if start:
pairs.append((start, end))
def stringify(pair):
start, end = pair
if start == end:
return "%d" % start
else:
return "%d-%d" % (start, end)
import string
return string.join(map(stringify, pairs), ", ")
def analysis(self, morf):
filename, statements = self.analyze_morf(morf)
self.canonicalize_filenames()
if not self.cexecuted.has_key(filename):
self.cexecuted[filename] = {}
missing = []
for line in statements:
if not self.cexecuted[filename].has_key(line):
missing.append(line)
return (filename, statements, missing,
self.format_lines(statements, missing))
def morf_name(self, morf):
if isinstance(morf, types.ModuleType):
return morf.__name__
else:
return os.path.splitext(os.path.basename(morf))[0]
def report(self, morfs, show_missing=1, ignore_errors=0):
if not isinstance(morfs, types.ListType):
morfs = [morfs]
max_name = max([5,] + map(len, map(self.morf_name, morfs)))
fmt_name = "%%- %ds " % max_name
fmt_err = fmt_name + "%s: %s"
header = fmt_name % "Name" + " Stmts Exec Cover"
fmt_coverage = fmt_name + "% 6d % 6d % 5d%%"
if show_missing:
header = header + " Missing"
fmt_coverage = fmt_coverage + " %s"
print header
print "-" * len(header)
total_statements = 0
total_executed = 0
for morf in morfs:
name = self.morf_name(morf)
try:
_, statements, missing, readable = self.analysis(morf)
n = len(statements)
m = n - len(missing)
if n > 0:
pc = 100.0 * m / n
else:
pc = 100.0
args = (name, n, m, pc)
if show_missing:
args = args + (readable,)
print fmt_coverage % args
total_statements = total_statements + n
total_executed = total_executed + m
except KeyboardInterrupt:
raise
except:
if not ignore_errors:
type, msg = sys.exc_info()[0:2]
print fmt_err % (name, type, msg)
if len(morfs) > 1:
print "-" * len(header)
if total_statements > 0:
pc = 100.0 * total_executed / total_statements
else:
pc = 100.0
args = ("TOTAL", total_statements, total_executed, pc)
if show_missing:
args = args + ("",)
print fmt_coverage % args
# annotate(morfs, ignore_errors).
blank_re = re.compile("\\s*(#|$)")
else_re = re.compile("\\s*else\\s*:\\s*(#|$)")
def annotate(self, morfs, directory=None, ignore_errors=0):
for morf in morfs:
try:
filename, statements, missing, _ = self.analysis(morf)
source = open(filename, 'r')
if directory:
dest_file = os.path.join(directory,
os.path.basename(filename)
+ ',cover')
else:
dest_file = filename + ',cover'
dest = open(dest_file, 'w')
lineno = 0
i = 0
j = 0
covered = 1
while 1:
line = source.readline()
if line == '':
break
lineno = lineno + 1
while i < len(statements) and statements[i] < lineno:
i = i + 1
while j < len(missing) and missing[j] < lineno:
j = j + 1
if i < len(statements) and statements[i] == lineno:
covered = j >= len(missing) or missing[j] > lineno
if self.blank_re.match(line):
dest.write(' ')
elif self.else_re.match(line):
# Special logic for lines containing only
# 'else:'. See [GDR 2001-12-04b, 3.2].
if i >= len(statements) and j >= len(missing):
dest.write('! ')
elif i >= len(statements) or j >= len(missing):
dest.write('> ')
elif statements[i] == missing[j]:
dest.write('! ')
else:
dest.write('> ')
elif covered:
dest.write('> ')
else:
dest.write('! ')
dest.write(line)
source.close()
dest.close()
except KeyboardInterrupt:
raise
except:
if not ignore_errors:
raise
# Singleton object.
the_coverage = coverage()
# Module functions call methods in the singleton object.
def start(*args): return apply(the_coverage.start, args)
def stop(*args): return apply(the_coverage.stop, args)
def erase(*args): return apply(the_coverage.erase, args)
def analysis(*args): return apply(the_coverage.analysis, args)
def report(*args): return apply(the_coverage.report, args)
# Save coverage data when Python exits. (The atexit module wasn't
# introduced until Python 2.0, so use sys.exitfunc when it's not
# available.)
try:
import atexit
atexit.register(the_coverage.save)
except ImportError:
sys.exitfunc = the_coverage.save
# Command-line interface.
if __name__ == '__main__':
the_coverage.command_line()
# A. REFERENCES
#
# [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees;
# Ravenbrook Limited; 2001-12-04;
# <http://www.garethrees.org/2001/12/04/python-coverage/>.
#
# [GDR 2001-12-04b] "Statement coverage for Python: design and
# analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04;
# <http://www.garethrees.org/2001/12/04/python-coverage/design.html>.
#
# [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)";
# Guide van Rossum; 2001-07-20;
# <http://www.python.org/doc/2.1.1/ref/ref.html>.
#
# [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum;
# 2001-07-20; <http://www.python.org/doc/2.1.1/lib/lib.html>.
#
#
# B. DOCUMENT HISTORY
#
# 2001-12-04 GDR Created.
#
# 2001-12-06 GDR Added command-line interface and source code
# annotation.
#
# 2001-12-09 GDR Moved design and interface to separate documents.
#
# 2001-12-10 GDR Open cache file as binary on Windows. Allow
# simultaneous -e and -x, or -a and -r.
#
# 2001-12-12 GDR Added command-line help. Cache analysis so that it
# only needs to be done once when you specify -a and -r.
#
# 2001-12-13 GDR Improved speed while recording. Portable between
# Python 1.5.2 and 2.1.1.
#
# 2002-01-03 GDR Module-level functions work correctly.
#
# 2002-01-07 GDR Update sys.path when running a file with the -x option,
# so that it matches the value the program would get if it were run on
# its own.
#
#
# C. COPYRIGHT AND LICENCE
#
# Copyright 2001 Gareth Rees. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the
# distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.
#
#
#
# $Id: coverage.py,v 1.1 2005/07/11 20:39:31 dax Exp $

0
jabber/__init__.py Normal file
View File

868
jabber/component.py Normal file
View File

@@ -0,0 +1,868 @@
##
## component.py
## Login : David Rousselie <dax@happycoders.org>
## Started on Fri Jan 7 11:06:42 2005
## $Id: component.py,v 1.12 2005/09/18 20:24:07 dax Exp $
##
## Copyright (C) 2005
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import re
import signal
import threading
import logging
import sys
import anydbm
import os
import mailconnection
from mailconnection import *
from x import *
from storage import *
import mailconnection_factory
import pyxmpp.jabberd
from pyxmpp.presence import Presence
from pyxmpp.message import Message
from pyxmpp.streambase import StreamError, FatalStreamError
from pyxmpp.jid import JID
from pyxmpp.jabber.disco import DiscoItems, DiscoItem, DiscoInfo, DiscoIdentity
from pyxmpp.jabberd.component import Component
class ComponentFatalError(RuntimeError):
pass
class MailComponent(Component):
def __init__(self, config):
Component.__init__(self, \
JID(config.get_content("config/jabber/service")), \
config.get_content("config/jabber/secret"), \
config.get_content("config/jabber/server"), \
int(config.get_content("config/jabber/port")), \
disco_category = "gateway", \
disco_type = "headline")
self.__logger = logging.getLogger("jabber.Component")
self.__shutdown = 0
# TODO : delete signals not known by Windows
signal.signal(signal.SIGINT, self.signal_handler)
signal.signal(signal.SIGPIPE, self.signal_handler)
signal.signal(signal.SIGHUP, self.signal_handler)
signal.signal(signal.SIGTERM, self.signal_handler)
signal.signal(signal.SIGALRM, self.time_handler)
self.__interval = int(config.get_content("config/check_interval"))
self.__config = config
# self.__registered = {}
try:
self.__storage = globals()[config.get_content("config/storage") + "Storage"]()
except:
print >>sys.stderr, "Cannot find " \
+ config.get_content("config/storage") + "Storage class"
exit(1)
self.__spool_dir = config.get_content("config/spooldir") + "/" + \
config.get_content("config/jabber/service")
self.__storage.spool_dir = self.__spool_dir
self.__storage.nb_pk_fields = 2
self.__reg_form = None
self.__reg_form_init = None
# dump registered accounts (save) at least every hour
self.__count = 60 / self.__interval
def __del__(self):
logging.shutdown()
""" Register Form creator """
def get_reg_form(self):
if self.__reg_form == None:
self.__reg_form = X()
self.__reg_form.xmlns = "jabber:x:data"
self.__reg_form.title = "Jabber Mail connection registration"
self.__reg_form.instructions = "Enter anything below"
self.__reg_form.type = "form"
self.__reg_form.add_field(type = "text-single", \
label = "Connection name", \
var = "name")
self.__reg_form.add_field(type = "text-single", \
label = "Login", \
var = "login")
self.__reg_form.add_field(type = "text-private", \
label = "Password", \
var = "password")
self.__reg_form.add_field(type = "text-single", \
label = "Host", \
var = "host")
self.__reg_form.add_field(type = "text-single", \
label = "Port", \
var = "port")
field = self.__reg_form.add_field(type = "list-single", \
label = "Mailbox type", \
var = "type")
field.add_option(label = "POP3", \
value = "pop3")
field.add_option(label = "POP3S", \
value = "pop3s")
field.add_option(label = "IMAP", \
value = "imap")
field.add_option(label = "IMAPS", \
value = "imaps")
self.__reg_form.add_field(type = "text-single", \
label = "Mailbox (IMAP)", \
var = "mailbox", \
value = "INBOX")
field = self.__reg_form.add_field(type = "list-single", \
label = "Action when state is 'Free For Chat'", \
var = "ffc_action", \
value = str(mailconnection.RETRIEVE))
field.add_option(label = "Do nothing", \
value = str(mailconnection.DO_NOTHING))
field.add_option(label = "Send mail digest", \
value = str(mailconnection.DIGEST))
field.add_option(label = "Retrieve mail", \
value = str(mailconnection.RETRIEVE))
field = self.__reg_form.add_field(type = "list-single", \
label = "Action when state is 'Online'", \
var = "online_action", \
value = str(mailconnection.RETRIEVE))
field.add_option(label = "Do nothing", \
value = str(mailconnection.DO_NOTHING))
field.add_option(label = "Send mail digest", \
value = str(mailconnection.DIGEST))
field.add_option(label = "Retrieve mail", \
value = str(mailconnection.RETRIEVE))
field = self.__reg_form.add_field(type = "list-single", \
label = "Action when state is 'Away'", \
var = "away_action", \
value = str(mailconnection.DIGEST))
field.add_option(label = "Do nothing", \
value = str(mailconnection.DO_NOTHING))
field.add_option(label = "Send mail digest", \
value = str(mailconnection.DIGEST))
field.add_option(label = "Retrieve mail", \
value = str(mailconnection.RETRIEVE))
field = self.__reg_form.add_field(type = "list-single", \
label = "Action when state is 'Extended Away'", \
var = "ea_action", \
value = str(mailconnection.DIGEST))
field.add_option(label = "Do nothing", \
value = str(mailconnection.DO_NOTHING))
field.add_option(label = "Send mail digest", \
value = str(mailconnection.DIGEST))
field.add_option(label = "Retrieve mail", \
value = str(mailconnection.RETRIEVE))
field = self.__reg_form.add_field(type = "list-single", \
label = "Action when state is 'Offline'", \
var = "offline_action", \
value = str(mailconnection.DO_NOTHING))
field.add_option(label = "Do nothing", \
value = str(mailconnection.DO_NOTHING))
field.add_option(label = "Send mail digest", \
value = str(mailconnection.DIGEST))
field.add_option(label = "Retrieve mail", \
value = str(mailconnection.RETRIEVE))
# default interval in config file
self.__reg_form.add_field(type = "text-single", \
label = "Mail check interval (in minutes)", \
var = "interval", \
value = "5")
return self.__reg_form
""" Register Form modifier for existing accounts """
def get_reg_form_init(self, jid, name):
if not self.__storage.has_key((jid, name)):
return None
account = self.__storage[(jid, name)]
if self.__reg_form_init == None:
self.__reg_form_init = X()
self.__reg_form_init.xmlns = "jabber:x:data"
self.__reg_form_init.title = "Jabber mail connection Modifier"
self.__reg_form_init.instructions = "Modifier for connection " + \
name
self.__reg_form_init.type = "form"
self.__reg_form_init.add_field(type = "fixed", \
label = "Connection name", \
var = "name", \
value = name)
self.__reg_form_init.add_field(type = "text-single", \
label = "Login", \
var = "login", \
value = account.login)
self.__reg_form_init.add_field(type = "text-private", \
label = "Password", \
var = "password", \
value = account.password)
self.__reg_form_init.add_field(type = "text-single", \
label = "Host", \
var = "host", \
value = account.host)
self.__reg_form_init.add_field(type = "text-single", \
label = "Port", \
var = "port", \
value = str(account.port))
field = self.__reg_form_init.add_field(type = "list-single", \
label = "Mailbox type", \
var = "type", \
value = account.get_type())
field.add_option(label = "POP3", \
value = "pop3")
field.add_option(label = "POP3S", \
value = "pop3s")
field.add_option(label = "IMAP", \
value = "imap")
field.add_option(label = "IMAPS", \
value = "imaps")
self.__reg_form_init.add_field(type = "text-single", \
label = "Mailbox (IMAP)", \
var = "mailbox")
field = self.__reg_form_init.add_field(type = "list-single", \
label = "Action when state is 'Free For Chat'", \
var = "ffc_action", \
value = str(account.ffc_action))
field.add_option(label = "Do nothing", \
value = str(mailconnection.DO_NOTHING))
field.add_option(label = "Send mail digest", \
value = str(mailconnection.DIGEST))
field.add_option(label = "Retrieve mail", \
value = str(mailconnection.RETRIEVE))
field = self.__reg_form_init.add_field(type = "list-single", \
label = "Action when state is 'Online'", \
var = "online_action", \
value = str(account.online_action))
field.add_option(label = "Do nothing", \
value = str(mailconnection.DO_NOTHING))
field.add_option(label = "Send mail digest", \
value = str(mailconnection.DIGEST))
field.add_option(label = "Retrieve mail", \
value = str(mailconnection.RETRIEVE))
field = self.__reg_form_init.add_field(type = "list-single", \
label = "Action when state is 'Away'", \
var = "away_action", \
value = str(account.away_action))
field.add_option(label = "Do nothing", \
value = str(mailconnection.DO_NOTHING))
field.add_option(label = "Send mail digest", \
value = str(mailconnection.DIGEST))
field.add_option(label = "Retrieve mail", \
value = str(mailconnection.RETRIEVE))
field = self.__reg_form_init.add_field(type = "list-single", \
label = "Action when state is 'Extended Away'", \
var = "ea_action", \
value = str(account.ea_action))
field.add_option(label = "Do nothing", \
value = str(mailconnection.DO_NOTHING))
field.add_option(label = "Send mail digest", \
value = str(mailconnection.DIGEST))
field.add_option(label = "Retrieve mail", \
value = str(mailconnection.RETRIEVE))
field = self.__reg_form_init.add_field(type = "list-single", \
label = "Action when state is 'Offline'", \
var = "offline_action", \
value = str(account.offline_action))
field.add_option(label = "Do nothing", \
value = str(mailconnection.DO_NOTHING))
field.add_option(label = "Send mail digest", \
value = str(mailconnection.DIGEST))
field.add_option(label = "Retrieve mail", \
value = str(mailconnection.RETRIEVE))
self.__reg_form_init.add_field(type = "text-single", \
label = "Mail check interval (in minutes)", \
var = "interval", \
value = str(account.interval))
else:
self.__reg_form_init.fields["login"].value = account.login
self.__reg_form_init.fields["password"].value = account.password
self.__reg_form_init.fields["host"].value = account.host
self.__reg_form_init.fields["port"].value = str(account.port)
self.__reg_form_init.fields["type"].value = account.get_type()
self.__reg_form_init.fields["ffc_action"].value = str(account.ffc_action)
self.__reg_form_init.fields["online_action"].value = str(account.online_action)
self.__reg_form_init.fields["away_action"].value = str(account.away_action)
self.__reg_form_init.fields["ea_action"].value = str(account.ea_action)
self.__reg_form_init.fields["offline_action"].value = str(account.offline_action)
self.__reg_form_init.fields["interval"].value = str(account.interval)
if account.get_type()[0:4] == "imap":
self.__reg_form_init.fields["mailbox"].value = account.mailbox
else:
self.__reg_form_init.fields["mailbox"].value = "INBOX"
return self.__reg_form_init
""" Looping method """
def run(self, timeout):
self.connect()
# Set check mail timer
threading.Timer(self.__interval * 60, self.time_handler)
try:
while (not self.__shutdown and self.stream
and not self.stream.eof and self.stream.socket is not None):
try:
self.stream.loop_iter(timeout)
except (KeyboardInterrupt, SystemExit, FatalStreamError, \
StreamError):
raise
except:
self.__logger.exception("Exception cought:")
finally:
## TODO : for jid in self.__storage.keys(())
## for name in self.__storage.keys((jid,))
# for jid in self.__storage.keys(()):
# p = Presence(from_jid = str(self.jid), to_jid = jid, \
# stanza_type = "unavailable")
# self.stream.send(p)
# for name in self.__registered[jid].keys():
# if self.__storage[(jid, name)].status != "offline":
# p = Presence(from_jid = name + "@" + str(self.jid), \
# to_jid = jid, \
# stanza_type = "unavailable")
# self.stream.send(p)
threads = threading.enumerate()
for th in threads:
try:
th.join(10 * timeout)
except:
pass
for th in threads:
try:
th.join(timeout)
except:
pass
self.disconnect()
del self.__storage
self.__logger.debug("Exitting normally")
""" Stop method handler """
def signal_handler(self, signum, frame):
self.__logger.debug("Signal %i received, shutting down..." % (signum,))
self.__shutdown = 1
""" SIGALRM signal handler """
def time_handler(self, signum, frame):
self.__logger.debug("Signal %i received, checking mail..." % (signum,))
self.check_all_mail()
self.__logger.debug("Resetting alarm signal")
threading.Timer(self.__interval * 60, self.time_handler)
if self.__count == 0:
self.__logger.debug("Dumping registered accounts Database")
self.__storage.sync()
self.__count = 60 / self.__interval
else:
self.__count -= 1
""" Component authentication handler """
def authenticated(self):
self.__logger.debug("AUTHENTICATED")
Component.authenticated(self)
# for jid in self.__registered.keys():
# p = Presence(from_jid = str(self.jid), \
# to_jid = jid, stanza_type = "probe")
# self.stream.send(p)
self.stream.set_iq_get_handler("query", "jabber:iq:version", \
self.get_version)
self.stream.set_iq_get_handler("query", "jabber:iq:register", \
self.get_register)
self.stream.set_iq_set_handler("query", "jabber:iq:register", \
self.set_register)
self.stream.set_presence_handler("available", \
self.presence_available)
self.stream.set_presence_handler("probe", \
self.presence_available)
self.stream.set_presence_handler("unavailable", \
self.presence_unavailable)
self.stream.set_presence_handler("unsubscribe", \
self.presence_unsubscribe)
self.stream.set_presence_handler("unsubscribed", \
self.presence_unsubscribed)
self.stream.set_presence_handler("subscribe", \
self.presence_subscribe)
self.stream.set_presence_handler("subscribed", \
self.presence_subscribed)
self.stream.set_message_handler("normal", \
self.message)
def stream_state_changed(self,state,arg):
print "*** State changed: %s %r ***" % (state,arg)
""" Discovery get info handler """
def disco_get_info(self, node, iq):
self.__logger.debug("DISCO_GET_INFO")
di = DiscoInfo()
if node is None:
di.add_feature("jabber:iq:version")
di.add_feature("jabber:iq:register")
DiscoIdentity(di, "Jabber Mail Component", "headline", "mail")
else:
di.add_feature("jabber:iq:register")
return di
""" Discovery get nested nodes handler """
def disco_get_items(self, node, iq):
self.__logger.debug("DISCO_GET_ITEMS")
base_from_jid = str(iq.get_from().bare())
di = DiscoItems()
if not node and self.__registered.has_key(base_from_jid):
for name in self.__registered[base_from_jid].keys():
account = self.__registered[base_from_jid][name]
str_name = account.get_type() + " connection " + name
if account.get_type()[0:4] == "imap":
str_name += " (" + account.mailbox + ")"
DiscoItem(di, JID(name + "@" + str(self.jid)), \
name, str_name)
return di
""" Get Version handler """
def get_version(self, iq):
self.__logger.debug("GET_VERSION")
iq = iq.make_result_response()
q = iq.new_query("jabber:iq:version")
q.newTextChild(q.ns(), "name", "Jabber Mail Component")
q.newTextChild(q.ns(), "version", "0.1")
self.stream.send(iq)
return 1
""" Send back register form to user """
def get_register(self, iq):
self.__logger.debug("GET_REGISTER")
base_from_jid = str(iq.get_from().bare())
to = iq.get_to()
iq = iq.make_result_response()
q = iq.new_query("jabber:iq:register")
if to and to != self.jid:
self.get_reg_form_init(base_from_jid, to.node).attach_xml(q)
else:
self.get_reg_form().attach_xml(q)
self.stream.send(iq)
return 1
""" Handle user registration response """
def set_register(self, iq):
self.__logger.debug("SET_REGISTER")
to = iq.get_to()
from_jid = iq.get_from()
base_from_jid = str(from_jid.bare())
remove = iq.xpath_eval("r:query/r:remove", \
{"r" : "jabber:iq:register"})
if remove:
if self.__registered.has_key(base_from_jid):
for name in self.__registered[base_from_jid].keys():
p = Presence(from_jid = name + "@" + str(self.jid), \
to_jid = from_jid, \
stanza_type = "unsubscribe")
self.stream.send(p)
p = Presence(from_jid = name + "@" + str(self.jid), \
to_jid = from_jid, \
stanza_type = "unsubscribed")
self.stream.send(p)
del self.__registered[base_from_jid]
# self.__storage.
p = Presence(from_jid = self.jid, to_jid = from_jid, \
stanza_type = "unsubscribe")
self.stream.send(p)
p = Presence(from_jid = self.jid, to_jid = from_jid, \
stanza_type = "unsubscribed")
self.stream.send(p)
return 1
query = iq.get_query()
x = X()
x.from_xml(query.children)
if x.fields.has_key("name"):
name = x.fields["name"].value.lower()
else:
name = u""
if x.fields.has_key("login"):
login = x.fields["login"].value
else:
login = u""
if x.fields.has_key("password"):
password = x.fields["password"].value
else:
password = u""
if x.fields.has_key("host"):
host = x.fields["host"].value
else:
host = u""
if x.fields.has_key("mailbox"):
mailbox = x.fields["mailbox"].value
else:
mailbox = u""
if x.fields.has_key("type"):
type = x.fields["type"].value
else:
type = u"pop3"
if x.fields.has_key("port") and x.fields["port"].value != "":
port = int(x.fields["port"].value)
else:
port = None
if x.fields.has_key("ffc_action") and x.fields["ffc_action"].value != "":
ffc_action = int(x.fields["ffc_action"].value)
else:
ffc_action = mailconnection.DO_NOTHING
if x.fields.has_key("online_action") and x.fields["online_action"].value != "":
online_action = int(x.fields["online_action"].value)
else:
online_action = mailconnection.DO_NOTHING
if x.fields.has_key("away_action") and x.fields["away_action"].value != "":
away_action = int(x.fields["away_action"].value)
else:
away_action = mailconnection.DO_NOTHING
if x.fields.has_key("ea_action") and x.fields["ea_action"].value != "":
ea_action = int(x.fields["ea_action"].value)
else:
ea_action = mailconnection.DO_NOTHING
if x.fields.has_key("offline_action") and x.fields["offline_action"].value != "":
offline_action = int(x.fields["offline_action"].value)
else:
offline_action = mailconnection.DO_NOTHING
if x.fields.has_key("interval") and x.fields["interval"].value != "":
interval = int(x.fields["interval"].value)
else:
interval = None
self.__logger.debug(u"New Account: %s, %s, %s, %s, %s, %s, %s %i %i %i %i %i %i" \
% (name, login, password, host, str(port), \
mailbox, type, ffc_action, online_action, away_action, \
ea_action, offline_action, interval))
iq = iq.make_result_response()
self.stream.send(iq)
if not self.__registered.has_key(base_from_jid):
self.__registered[base_from_jid] = {}
p = Presence(from_jid = self.jid, to_jid = from_jid.bare(), \
stanza_type="subscribe")
self.stream.send(p)
## Update account
if port != None:
socket = host + ":" + str(port)
else:
socket = host
if self.__registered[base_from_jid].has_key(name):
m = Message(from_jid = self.jid, to_jid = from_jid, \
stanza_type = "message", \
body = u"Updated %s connection '%s': Registered with "\
"username '%s' and password '%s' on '%s'" \
% (type, name, login, password, socket))
self.stream.send(m)
else:
m = Message(from_jid = self.jid, to_jid = from_jid, \
stanza_type = "message", \
body = u"New %s connection '%s': Registered with " \
"username '%s' and password '%s' on '%s'" \
% (type, name, login, password, socket))
self.stream.send(m)
p = Presence(from_jid = name + "@" + str(self.jid), \
to_jid = from_jid.bare(), \
stanza_type="subscribe")
self.stream.send(p)
self.__registered[base_from_jid][name] = \
mailconnection_factory.get_new_mail_connection(type)
self.__registered[base_from_jid][name].login = login
self.__registered[base_from_jid][name].password = password
self.__registered[base_from_jid][name].host = host
self.__registered[base_from_jid][name].ffc_action = ffc_action
self.__registered[base_from_jid][name].online_action = online_action
self.__registered[base_from_jid][name].away_action = away_action
self.__registered[base_from_jid][name].ea_action = ea_action
self.__registered[base_from_jid][name].offline_action = offline_action
if port:
self.__registered[base_from_jid][name].port = port
if interval:
self.__registered[base_from_jid][name].interval = interval
if type[0:4] == "imap":
self.__registered[base_from_jid][name].mailbox = mailbox
self.__storage.add([base_from_jid, name], self.__registered[base_from_jid][name])
return 1
""" Handle presence availability """
def presence_available(self, stanza):
self.__logger.debug("PRESENCE_AVAILABLE")
from_jid = stanza.get_from()
base_from_jid = str(from_jid.bare())
name = stanza.get_to().node
show = stanza.get_show()
self.__logger.debug("SHOW : " + str(show))
if self.__registered.has_key(base_from_jid):
if not name:
p = Presence(from_jid = self.jid, \
to_jid = from_jid, \
status = \
str(len(self.__registered[base_from_jid])) \
+ " accounts registered.", \
show = show, \
stanza_type = "available")
self.stream.send(p)
for name in self.__registered[base_from_jid].keys():
account = self.__registered[base_from_jid][name]
# Make available to receive mail only when online
account.status = "offline" # TODO get real status available = (not show)
p = Presence(from_jid = name + "@" + \
str(self.jid), \
to_jid = from_jid, \
status = account.get_status(), \
show = show, \
stanza_type = "available")
self.stream.send(p)
elif self.__registered[base_from_jid].has_key(name):
account = self.__registered[base_from_jid][name]
# Make available to receive mail only when online
account.status = "offline" # TODO get real status = (not show)
p = Presence(from_jid = name + "@" + \
str(self.jid), \
to_jid = from_jid, \
status = account.get_status(), \
show = show, \
stanza_type = "available")
self.stream.send(p)
return 1
""" handle presence unavailability """
def presence_unavailable(self, stanza):
self.__logger.debug("PRESENCE_UNAVAILABLE")
from_jid = stanza.get_from()
base_from_jid = str(from_jid.bare())
if stanza.get_to() == str(self.jid) \
and self.__registered.has_key(base_from_jid):
for name in self.__registered[base_from_jid].keys():
self.__registered[base_from_jid][name].status = "offline" # TODO get real status
p = Presence(from_jid = name + "@" + str(self.jid), \
to_jid = from_jid, \
stanza_type = "unavailable")
self.stream.send(p)
p = Presence(from_jid = stanza.get_to(), to_jid = from_jid, \
stanza_type = "unavailable")
self.stream.send(p)
return 1
""" handle subscribe presence from user """
def presence_subscribe(self, stanza):
self.__logger.debug("PRESENCE_SUBSCRIBE")
p = stanza.make_accept_response()
self.stream.send(p)
return 1
""" handle subscribed presence from user """
def presence_subscribed(self, stanza):
self.__logger.debug("PRESENCE_SUBSCRIBED")
name = stanza.get_to().node
from_jid = stanza.get_from()
base_from_jid = str(from_jid.bare())
if self.__registered.has_key(base_from_jid) \
and self.__registered[base_from_jid].has_key(name):
account = self.__registered[base_from_jid][name]
account.status = "online" # TODO retrieve real status
p = Presence(from_jid = stanza.get_to(), to_jid = from_jid, \
status = account.get_status(), \
stanza_type = "available")
self.stream.send(p)
return 1
""" handle unsubscribe presence from user """
def presence_unsubscribe(self, stanza):
self.__logger.debug("PRESENCE_UNSUBSCRIBE")
name = stanza.get_to().node
from_jid = stanza.get_from()
base_from_jid = str(from_jid.bare())
if self.__registered.has_key(base_from_jid) \
and self.__registered[base_from_jid].has_key(name):
del self.__registered[base_from_jid][name]
p = Presence(from_jid = stanza.get_to(), to_jid = from_jid, \
stanza_type = "unsubscribe")
self.stream.send(p)
p = stanza.make_accept_response()
self.stream.send(p)
return 1
""" handle unsubscribed presence from user """
def presence_unsubscribed(self, stanza):
self.__logger.debug("PRESENCE_UNSUBSCRIBED")
p = Presence(from_jid = stanza.get_to(), to_jid = stanza.get_from(), \
stanza_type = "unavailable")
self.stream.send(p)
self.__storage.sync()
return 1
""" Handle new message """
def message(self, message):
self.__logger.debug("MESSAGE: " + message.get_body())
name = message.get_to().node
base_from_jid = str(message.get_from().bare())
if name and self.__registered.has_key(base_from_jid):
body = message.get_body()
cmd = body.split(' ')
if cmd[0] == "check":
self.check_mail(base_from_jid, name)
elif cmd[0] == "dump":
body = ""
for jid in self.__registered.keys():
for name in self.__registered[jid].keys():
body += name + " for user " + jid
msg = Message(from_jid = self.jid, to_jid = base_from_jid, \
stanza_type = "message", \
body = body)
self.stream.send(msg)
return 1
# """ Store registered sessions """
# def store_registered(self):
# self.__logger.debug("STORE_REGISTERED")
# try:
# if not os.path.isdir(self.__spool_dir):
# os.makedirs(self.__spool_dir)
# str_registered = anydbm.open(self.__spool_dir + "/registered.db", \
# 'n')
# self.__logger.debug("Saving registered sessions")
# for jid in self.__registered.keys():
# for name in self.__registered[jid].keys():
# self.__logger.debug("\t" + jid + ", _" + name + "_")
# str_registered[jid + '#' + name] = \
# str(self.__storage[(jid, name)])
# str_registered.close()
# except Exception, e:
# print >>sys.stderr, "Cannot save to registered.db : "
# print >>sys.stderr, e
# """ Load previously registered sessions """
# def load_registered(self):
# self.__logger.debug("LOAD_REGISTERED")
# try:
# if not os.path.isdir(self.__spool_dir):
# os.makedirs(self.__spool_dir)
# str_registered = anydbm.open(self.__spool_dir + "/registered.db", \
# 'c')
# self.__logger.debug("Loading previously registered sessions")
# for key in str_registered.keys():
# jid, name = key.split('#')
# if not self.__registered.has_key(jid):
# self.__registered[jid] = {}
# self.__storage[(jid, name)] = \
# mailconnection_factory.str_to_mail_connection(str_registered[key])
# # self.__storage[(jid, name)].name = name
# self.__logger.debug("Loaded data for %s on %s :" % (jid, name))
# self.__logger.debug("\t %s" % (self.__storage[(jid, name)]))
# str_registered.close()
# except Exception, e:
# print >>sys.stderr, "Cannot load registered.db : "
# print >>sys.stderr, e
""" Check mail account """
def check_mail(self, jid, name):
self.__logger.debug("CHECK_MAIL")
account = self.__storage[(jid, name)]
action = account.action
if action != "nothing":
try:
self.__logger.debug("Checking " \
+ name)
self.__logger.debug("\t" + account.login \
+ "@" + account.host)
account.connect()
mail_list = account.get_mail_list()
if not mail_list or mail_list[0] == '':
num = 0
else:
num = len(mail_list)
# unseen mails checked by external client
if num < account.lastcheck:
account.lastcheck = 0
if action == "retrieve":
while account.lastcheck < num:
body = account.get_mail(int(mail_list[account.lastcheck]))
mesg = Message(from_jid = name + "@" + \
str(self.jid), \
to_jid = jid, \
stanza_type = "message", \
body = body)
self.stream.send(mesg)
account.lastcheck += 1
else:
body = ""
while account.lastcheck < num:
body += \
account.get_mail_summary(int(mail_list[account.lastcheck])) \
+ "\n----------------------------------\n"
account.lastcheck += 1
if body != "":
mesg = Message(from_jid = name + "@" + \
str(self.jid), \
to_jid = jid, \
stanza_type = "headline", \
body = body)
self.stream.send(mesg)
account.disconnect()
except Exception,e:
self.__logger.debug("Error while checking mail : %s" \
% (e))
""" check mail handler """
def check_all_mail(self):
self.__logger.debug("CHECK_ALL_MAIL")
for jid in self.__registered.keys():
for name in self.__registered[jid].keys():
self.check_mail(jid, name)

47
jabber/config.py Normal file
View File

@@ -0,0 +1,47 @@
##
## config.py
## Login : David Rousselie <dax@happycoders.org>
## Started on Fri Jan 7 11:06:42 2005
## $Id: config.py,v 1.2 2005/03/13 11:39:36 dax Exp $
##
## Copyright (C) 2005
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import libxml2
import os
from pyxmpp.jid import JID
from component import ComponentFatalError
class Config:
def __init__(self, config_file):
self.doc = None
self.config_file = config_file
# libxml2.initializeCatalog()
# libxml2.loadCatalog(os.path.join(data_dir, "catalog.xml"))
parser = libxml2.createFileParserCtxt(config_file)
# parser.validate(1)
parser.parseDocument()
if not parser.isValid():
raise ComponentFatalError, "Invalid configuration"
self.doc = parser.doc()
def get_content(self, xpath):
return self.doc.xpathEval(xpath)[0].getContent()
def __del__(self):
if self.doc:
self.doc.freeDoc()

373
jabber/mailconnection.py Normal file
View File

@@ -0,0 +1,373 @@
##
## mailconnection.py
## Login : David Rousselie <dax@happycoders.org>
## Started on Fri Jan 7 11:06:42 2005
## $Id: mailconnection.py,v 1.11 2005/09/18 20:24:07 dax Exp $
##
## Copyright (C) 2005
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import sys
import email
import email.Header
import poplib
import imaplib
import socket
IMAP4_TIMEOUT = 10
POP3_TIMEOUT = 10
DO_NOTHING = 0
DIGEST = 1
RETRIEVE = 2
## All MY* classes are implemented to add a timeout (settimeout)
## while connecting
class MYIMAP4(imaplib.IMAP4):
def open(self, host = '', port = imaplib.IMAP4_PORT):
"""Setup connection to remote server on "host:port"
(default: localhost:standard IMAP4 port).
This connection will be used by the routines:
read, readline, send, shutdown.
"""
self.host = host
self.port = port
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(IMAP4_TIMEOUT)
self.sock.connect((host, port))
self.sock.settimeout(None)
self.file = self.sock.makefile('rb')
class MYIMAP4_SSL(imaplib.IMAP4_SSL):
def open(self, host = '', port = imaplib.IMAP4_SSL_PORT):
"""Setup connection to remote server on "host:port".
(default: localhost:standard IMAP4 SSL port).
This connection will be used by the routines:
read, readline, send, shutdown.
"""
self.host = host
self.port = port
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(IMAP4_TIMEOUT)
self.sock.connect((host, port))
self.sock.settimeout(None)
self.sslobj = socket.ssl(self.sock, self.keyfile, self.certfile)
class MYPOP3(poplib.POP3):
def __init__(self, host, port = poplib.POP3_PORT):
self.host = host
self.port = port
msg = "getaddrinfo returns an empty list"
self.sock = None
for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM):
af, socktype, proto, canonname, sa = res
try:
self.sock = socket.socket(af, socktype, proto)
self.sock.settimeout(POP3_TIMEOUT)
self.sock.connect(sa)
self.sock.settimeout(None)
except socket.error, msg:
if self.sock:
self.sock.close()
self.sock = None
continue
break
if not self.sock:
raise socket.error, msg
self.file = self.sock.makefile('rb')
self._debugging = 0
self.welcome = self._getresp()
class MYPOP3_SSL(poplib.POP3_SSL):
def __init__(self, host, port = poplib.POP3_SSL_PORT, keyfile = None, certfile = None):
self.host = host
self.port = port
self.keyfile = keyfile
self.certfile = certfile
self.buffer = ""
msg = "getaddrinfo returns an empty list"
self.sock = None
for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM):
af, socktype, proto, canonname, sa = res
try:
self.sock = socket.socket(af, socktype, proto)
self.sock.settimeout(POP3_TIMEOUT)
self.sock.connect(sa)
self.sock.settimeout(None)
except socket.error, msg:
if self.sock:
self.sock.close()
self.sock = None
continue
break
if not self.sock:
raise socket.error, msg
self.file = self.sock.makefile('rb')
self.sslobj = socket.ssl(self.sock, self.keyfile, self.certfile)
self._debugging = 0
self.welcome = self._getresp()
class MailConnection(object):
""" Wrapper to mail connection and action.
Abstract class, do not represent real mail connection type"""
def __init__(self, login = "", password = "", host = "", \
port = 110, ssl = False):
""" Initialize MailConnection object for common parameters of all
connections types
:Parameters:
- 'login': login used to connect mail server
- 'password': password associated with 'login' to connect mail server
- 'host': mail server hostname
- 'port': mail server port
- 'ssl': activate ssl to connect server
:Types:
- 'login': string
- 'password': string
- 'host': string
- 'port': int
- 'ssl': boolean"""
self.login = login
self.password = password
self.host = host
self.port = port
self.ssl = ssl
self.lastcheck = 0
self.status = "offline"
self.connection = None
self.ffc_action = RETRIEVE
self.online_action = RETRIEVE
self.away_action = RETRIEVE
self.ea_action = RETRIEVE
self.offline_action = DO_NOTHING
def __eq__(self, other):
return self.get_type() == other.get_type() \
and self.login == other.login \
and self.password == other.password \
and self.host == other.host \
and self.port == other.port \
and self.ssl == other.ssl \
and self.ffc_action == other.ffc_action \
and self.online_action == other.online_action \
and self.away_action == other.away_action \
and self.ea_action == other.ea_action \
and self.offline_action == other.offline_action
def __str__(self):
return self.get_type() + "#" + self.login + "#" + self.password + "#" \
+ self.host + "#" + str(self.port) + "#" + str(self.ffc_action) + "#" \
+ str(self.online_action) + "#" + str(self.away_action) + "#" + \
str(self.ea_action) + "#" + str(self.offline_action)
def get_decoded_part(self, part):
content_charset = part.get_content_charset()
if content_charset:
return part.get_payload(decode=True).decode(content_charset)
else:
return part.get_payload(decode=True)
def format_message(self, email_msg, include_body = True):
from_decoded = email.Header.decode_header(email_msg["From"])
result = u"From : "
for i in range(len(from_decoded)):
if from_decoded[i][1]:
result += from_decoded[i][0].decode(from_decoded[i][1])
else:
result += from_decoded[i][0]
result += "\n"
subject_decoded = email.Header.decode_header(email_msg["Subject"])
result += u"Subject : "
for i in range(len(subject_decoded)):
if subject_decoded[i][1]:
result += subject_decoded[i][0].decode(subject_decoded[i][1])
else:
result += subject_decoded[i][0]
result += "\n\n"
if include_body:
action = {
"text/plain" : lambda part: self.get_decoded_part(part),
"text/html" : lambda part: "\n<<<HTML part skipped>>>\n"
}
for part in email_msg.walk():
content_type = part.get_content_type()
if action.has_key(content_type):
result += action[content_type](part) + '\n'
return result
def format_message_summary(self, email_msg):
return self.format_message(email_msg, False)
def get_type(self):
return "UNKNOWN"
def get_status(self):
return self.get_type() + "://" + self.login + "@" + self.host + ":" + \
str(self.port)
def connect(self):
pass
def disconnect(self):
pass
def get_mail_list(self):
return 0
def get_mail(self, index):
return None
def get_mail_summary(self, index):
return None
def get_action(self):
mapping = {"online": self.online_action,
"ffc": self.ffc_action,
"away": self.away_action,
"ea": self.ea_action,
"offline": self.offline_action}
if mapping.has_key(self.status):
return mapping[self.status]
return "nothing"
action = property(get_action)
class IMAPConnection(MailConnection):
def __init__(self, login = "", password = "", host = "", \
port = None, ssl = False, mailbox = "INBOX"):
if not port:
if ssl:
port = 993
else:
port = 143
MailConnection.__init__(self, login, password, host, port, ssl)
self.mailbox = mailbox
def __eq__(self, other):
return MailConnection.__eq__(self, other) \
and self.mailbox == other.mailbox
def __str__(self):
return MailConnection.__str__(self) + "#" + self.mailbox
def get_type(self):
if self.ssl:
return "imaps"
return "imap"
def get_status(self):
return MailConnection.get_status(self) + "/" + self.mailbox
def connect(self):
print >>sys.stderr, "Connecting to IMAP server " \
+ self.login + "@" + self.host + ":" + str(self.port) \
+ " (" + self.mailbox + "). SSL=" \
+ str(self.ssl)
if self.ssl:
self.connection = MYIMAP4_SSL(self.host, self.port)
else:
self.connection = MYIMAP4(self.host, self.port)
self.connection.login(self.login, self.password)
def disconnect(self):
print >>sys.stderr, "Disconnecting from IMAP server " \
+ self.host
self.connection.logout()
def get_mail_list(self):
print >>sys.stderr, "Getting mail list"
typ, data = self.connection.select(self.mailbox)
typ, data = self.connection.search(None, 'UNSEEN')
if typ == 'OK':
return data[0].split(' ')
return None
def get_mail(self, index):
print >>sys.stderr, "Getting mail " + str(index)
typ, data = self.connection.select(self.mailbox)
typ, data = self.connection.fetch(index, '(RFC822)')
self.connection.store(index, "FLAGS", "UNSEEN")
if typ == 'OK':
return self.format_message(email.message_from_string(data[0][1]))
return u"Error while fetching mail " + str(index)
def get_mail_summary(self, index):
print >>sys.stderr, "Getting mail summary " + str(index)
typ, data = self.connection.select(self.mailbox)
typ, data = self.connection.fetch(index, '(RFC822)')
self.connection.store(index, "FLAGS", "UNSEEN")
if typ == 'OK':
return self.format_message_summary(email.message_from_string(data[0][1]))
return u"Error while fetching mail " + str(index)
class POP3Connection(MailConnection):
def __init__(self, login = "", password = "", host = "", \
port = None, ssl = False):
if not port:
if ssl:
port = 995
else:
port = 110
MailConnection.__init__(self, login, password, host, port, ssl)
def get_type(self):
if self.ssl:
return "pop3s"
return "pop3"
def connect(self):
print >>sys.stderr, "Connecting to POP3 server " \
+ self.login + "@" + self.host + ":" + str(self.port)\
+ ". SSL=" + str(self.ssl)
if self.ssl:
self.connection = MYPOP3_SSL(self.host, self.port)
else:
self.connection = MYPOP3(self.host, self.port)
try:
self.connection.apop(self.login, self.password)
except:
self.connection.user(self.login)
self.connection.pass_(self.password)
def disconnect(self):
print >>sys.stderr, "Disconnecting from POP3 server " \
+ self.host
self.connection.quit()
def get_mail_list(self):
print >>sys.stderr, "Getting mail list"
count, size = self.connection.stat()
return [str(i) for i in range(1, count + 1)]
def get_mail(self, index):
print >>sys.stderr, "Getting mail " + str(index)
ret, data, size = self.connection.retr(index)
if ret[0:3] == '+OK':
return self.format_message(email.message_from_string('\n'.join(data)))
return u"Error while fetching mail " + str(index)
def get_mail_summary(self, index):
print >>sys.stderr, "Getting mail summary " + str(index)
ret, data, size = self.connection.retr(index)
if ret[0:3] == '+OK':
return self.format_message_summary(email.message_from_string('\n'.join(data)))
return u"Error while fetching mail " + str(index)

View File

@@ -0,0 +1,108 @@
##
## mailconnection_factory.py
## Login : <adro8400@claralinux>
## Started on Fri May 20 10:41:46 2005 adro8400
## $Id: mailconnection_factory.py,v 1.2 2005/09/18 20:24:07 dax Exp $
##
## Copyright (C) 2005 adro8400
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
from mailconnection import IMAPConnection, POP3Connection
""" Static method to return an empty MailConnection object of given type
:Parameters:
- 'type': type of connection to return : 'imap', 'imaps', 'pop3', 'pop3s'
:return: MailConnection of given type in parameter, None if unknown type
:returntype: 'MailConnection'"""
def get_new_mail_connection(type):
if type == "imap":
return IMAPConnection()
elif type == "imaps":
return IMAPConnection(ssl = True)
elif type == "pop3":
return POP3Connection()
elif type == "pop3s":
return POP3Connection(ssl = True)
raise Exception, "Connection type \"" + type + "\" unknown"
""" Static methode to create a MailConnection object filled from string
:Parameters:
- 'connection_string': string containing MailConnection parameters separated
by '#'. ex: 'pop3#login#password#host#110#True'
:Types:
- 'connection_string': string
:return: MailConnection of given type found in string parameter
:returntype: 'MailConnection'"""
def str_to_mail_connection(connection_string):
arg_list = connection_string.split("#")
# optionals values must be the at the beginning of the list to pop them
# last
arg_list.reverse()
type = arg_list.pop()
login = arg_list.pop()
password = arg_list.pop()
host = arg_list.pop()
port = int(arg_list.pop())
ffc_action = int(arg_list.pop())
online_action = int(arg_list.pop())
away_action = int(arg_list.pop())
ea_action = int(arg_list.pop())
offline_action = int(arg_list.pop())
result = None
if type == "imap":
mailbox = arg_list.pop()
result = IMAPConnection(login = login, \
password = password, \
host = host, \
ssl = False, \
port = port, \
mailbox = mailbox)
elif type == "imaps":
mailbox = arg_list.pop()
result = IMAPConnection(login = login, \
password = password, \
host = host, \
port = port, \
ssl = True, \
mailbox = mailbox)
elif type == "pop3":
result = POP3Connection(login = login, \
password = password, \
host = host, \
port = port, \
ssl = False)
elif type == "pop3s":
result = POP3Connection(login = login, \
password = password, \
host = host, \
port = port, \
ssl = True)
if result is None:
raise Exception, "Connection type \"" + type + "\" unknown"
result.ffc_action = ffc_action
result.online_action = online_action
result.away_action = away_action
result.ea_action = ea_action
result.offline_action = offline_action
return result

180
jabber/storage.py Normal file
View File

@@ -0,0 +1,180 @@
##
## storage.py
## Login : <dax@happycoders.org>
## Started on Wed Jul 20 20:26:53 2005 dax
## $Id: storage.py,v 1.1 2005/09/18 20:24:07 dax Exp $
##
## Copyright (C) 2005 dax
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import os
import re
import os.path
import sys
import anydbm
import mailconnection_factory
from UserDict import UserDict
class Storage(UserDict):
def __init__(self, nb_pk_fields = 1, spool_dir = "."):
UserDict.__init__(self)
self._spool_dir = ""
self.set_spool_dir(spool_dir)
self.nb_pk_fields = nb_pk_fields
self.file = self._spool_dir + "/registered.db"
# return hash of hash (jid and name)
def load(self):
pass
def sync(self):
pass
def store(self, nb_pk_fields, registered, pk):
pass
def add(self, key_list, obj):
pass
def remove(self, key_list):
pass
def get_spool_dir(self):
return self._spool_dir
def set_spool_dir(self, spool_dir):
self._spool_dir = spool_dir
if not os.path.isdir(self._spool_dir):
os.makedirs(self._spool_dir)
spool_dir = property(get_spool_dir, set_spool_dir)
class DBMStorage(Storage):
def __init__(self, nb_pk_fields = 1, spool_dir = "."):
# print "DBM INIT"
Storage.__init__(self, nb_pk_fields, spool_dir)
self.__str_registered = anydbm.open(self.file, \
'c')
def __del__(self):
# print "DBM STOP"
self.__str_registered.close()
def load(self):
# print "DBM LOAD"
result = {}
try:
for pk in self.__str_registered.keys():
pk_list = pk.split('#')
obj = result
key = None
while pk_list:
key = pk_list.pop(0)
if pk_list:
if not obj.has_key(key):
obj[key] = {}
obj = obj[key]
obj[key] = mailconnection_factory.str_to_mail_connection(self.__str_registered[pk])
except Exception, e:
print >>sys.stderr, "Cannot load registered.db : "
print >>sys.stderr, e
return result
def sync(self):
# print "DBM SYNC"
self.__str_registered.close()
self.__str_registered = anydbm.open(self.file, \
'c')
def __store(self, nb_pk_fields, registered, pk):
if nb_pk_fields > 0:
for key in registered.keys():
if pk:
self.__store(nb_pk_fields - 1, \
registered[key], pk + "#" + key)
else:
self.__store(nb_pk_fields - 1, \
registered[key], key)
else:
self.__str_registered[pk] = str(registered)
print "STORING : " + pk + " = " + str(registered)
def store(self, registered):
# print "DBM STORE"
try:
self.__store(self.nb_pk_fields, registered, "")
# Force file synchronisation
self.sync()
except Exception, e:
print >>sys.stderr, "Cannot save to registered.db : "
print >>sys.stderr, e
def __setitem__(self, pk_tuple, obj):
print "Adding " + "#".join(pk_tuple) + " = " + str(obj)
self.__str_registered["#".join(pk_tuple)] = str(obj)
self.sync()
def __getitem__(self, pk_tuple):
print "Getting " + "#".join(pk_tuple)
if len(pk_tuple) == self.nb_pk_fields:
return mailconnection_factory.str_to_mail_connection(self.__str_registered["#".join(pk_tuple)])
else:
partial_key = "#".join(pk_tuple)
regexp = re.compile(partial_key)
return [mailconnection_factory.str_to_mail_connection(self.__str_registered[key])
for key in self.__str_registered.keys()
if regexp.search(key)]
def __delitem__(self, pk_tuple):
print "Deleting " + "#".join(pk_tuple)
del self.__str_registered["#".join(pk_tuple)]
self.sync()
def has_key(self, pk_tuple):
return self.__str_registered.has_key("#".join(pk_tuple))
def keys(self, pk_tuple = None):
if pk_tuple is None:
return [tuple(key.split("#")) for key in self.__str_registered.keys()]
else:
level = len(pk_tuple)
partial_key = "#".join(pk_tuple)
regexp = re.compile("^" + partial_key)
result = {}
for key in self.__str_registered.keys():
if regexp.search(key):
result[key.split("#")[level]] = None
return result.keys()
class SQLiteStorage(Storage):
def __init__(self, nb_pk_fields = 1, spool_dir = "."):
pass
def load(self):
pass
def sync(self):
pass
def store(self, nb_pk_fields, registered, pk):
pass
def add(self, key_list, obj):
pass
def remove(self, key_list):
pass

129
jabber/x.py Normal file
View File

@@ -0,0 +1,129 @@
##
## x.py
## Login : David Rousselie <dax@happycoders.org>
## Started on Fri Jan 7 11:06:42 2005
## $Id: x.py,v 1.3 2005/09/18 20:24:07 dax Exp $
##
## Copyright (C) 2005
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import sys
from pyxmpp.stanza import common_doc
class Option(object):
def __init__(self, label, value):
self.__label = label
self.__value = value
def get_xml(self, parent):
if parent is None:
option = common_doc.newChild(None, "option", None)
else:
option = parent.newChild(None, "option", None)
option.setProp("label", self.__label)
option.newChild(None, "value", self.__value)
return option
class Field(object):
def __init__(self, type, label, var, value):
self.__type = type
self.__label = label
self.__var = var
self.value = value
self.__options = []
def add_option(self, label, value):
option = Option(label, value)
self.__options.append(option)
return option
def get_xml(self, parent):
if parent is None:
raise Exception, "parent field should not be None"
else:
field = parent.newChild(None, "field", None)
field.setProp("type", self.__type)
if not self.__label is None:
field.setProp("label", self.__label)
if not self.__var is None:
field.setProp("var", self.__var)
if self.value:
field.newChild(None, "value", self.value)
for option in self.__options:
option.get_xml(field)
return field
class X(object):
def __init__(self):
self.fields = {}
self.fields_tab = []
self.title = None
self.instructions = None
self.type = None
self.xmlns = None
def add_field(self, type = "fixed", label = None, var = None, value = ""):
field = Field(type, label, var, value)
self.fields[var] = field
# fields_tab exist to keep added fields order
self.fields_tab.append(field)
return field
def attach_xml(self, iq):
node = iq.newChild(None, "x", None)
_ns = node.newNs(self.xmlns, None)
node.setNs(_ns)
if not self.title is None:
node.newTextChild(None, "title", self.title)
if not self.instructions is None:
node.newTextChild(None, "instructions", self.instructions)
for field in self.fields_tab:
field.get_xml(node)
return node
def from_xml(self, node):
## TODO : test node type and ns and clean that loop !!!!
while node and node.type != "element":
node = node.next
child = node.children
while child:
## TODO : test child type (element) and ns (jabber:x:data)
if child.type == "element" and child.name == "field":
if child.hasProp("type"):
type = child.prop("type")
else:
type = ""
if child.hasProp("label"):
label = child.prop("label")
else:
label = ""
if child.hasProp("var"):
var = child.prop("var")
else:
var = ""
xval = child.children
while xval and xval.name != "value":
xval = xval.next
if xval:
value = xval.getContent()
else:
value = ""
field = Field(type, label, var, value)
self.fields[var] = field
child = child.next

74
jmc.py Executable file
View File

@@ -0,0 +1,74 @@
#!/usr/bin/python -u
##
## Jabber Mail Component
## jmc.py
## Login : David Rousselie <dax@happycoders.org>
## Started on Fri Jan 7 11:06:42 2005
## $Id: jmc.py,v 1.3 2005/07/11 20:39:31 dax Exp $
##
## Copyright (C) 2005
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import sys
import os.path
import logging
from jabber.component import MailComponent, ComponentFatalError
from jabber.config import Config
def main(config_file = "jmc.xml", isDebug = 0):
try:
logger = logging.getLogger()
logger.addHandler(logging.StreamHandler())
if isDebug > 0:
logger.setLevel(logging.DEBUG)
try:
logger.debug("Loading config file " + config_file)
config = Config(config_file)
except:
print >>sys.stderr, "Couldn't load config file:", \
str(sys.exc_value)
sys.exit(1)
print "creating component..."
mailcomp = MailComponent(config)
print "starting..."
mailcomp.run(1)
except ComponentFatalError,e:
print e
print "Aborting."
sys.exit(1)
if __name__ == "__main__":
var_option = 0
file_num = 0
index = 0
debug_level = 0
for opt in sys.argv:
if var_option == 0 and len(opt) == 2 and opt == "-c":
var_option += 1
elif (var_option & 1) == 1 and len(opt) > 0:
var_option += 1
file_num = index
if len(opt) == 2 and opt == "-D":
debug_level = 1
index += 1
if (var_option & 2) == 2:
main(sys.argv[file_num], debug_level)
else:
main("jmc.xml", debug_level)

18
jmc.xml Normal file
View File

@@ -0,0 +1,18 @@
<config>
<jabber>
<server>127.0.0.1</server>
<port>5347</port>
<secret>secret</secret>
<service>jmc.localhost</service>
<connectsleep>5</connectsleep>
<language>fr</language>
<vCard>
<FN>Jabber Mail Component</FN>
<DESC>A Jabber mail server component</DESC>
<URL>http://people.happycoders.org/dax/jabber/jmc/</URL>
</vCard>
</jabber>
<storage>DBM</storage>
<spooldir>/var/spool/jabber</spooldir>
<check_interval>1</check_interval> <!-- in minutes -->
</config>

70
run_test.py Normal file
View File

@@ -0,0 +1,70 @@
##
## test.py
## Login : <adro8400@claralinux>
## Started on Wed May 18 13:33:03 2005 adro8400
## $Id: run_test.py,v 1.2 2005/09/18 20:24:07 dax Exp $
##
## Copyright (C) 2005 adro8400
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import coverage
coverage.erase()
coverage.start()
import unittest
import sys
import tests
from tests.test_mailconnection import *
from tests.test_mailconnection_factory import *
from tests.test_component import *
from tests.test_storage import *
from test import test_support
import jabber
if __name__ == '__main__':
mail_connection_suite = unittest.makeSuite(MailConnection_TestCase, \
"test")
pop3_connection_suite = unittest.makeSuite(POP3Connection_TestCase, \
"test")
imap_connection_suite = unittest.makeSuite(IMAPConnection_TestCase, \
"test")
mc_factory_suite = unittest.makeSuite(MailConnectionFactory_TestCase, \
"test")
component_suite = unittest.makeSuite(MailComponent_TestCase_Basic, \
"test")
component2_suite = unittest.makeSuite(MailComponent_TestCase_NoReg, \
"test")
storage_suite = unittest.makeSuite(Storage_TestCase, \
"test")
dbmstorage_suite = unittest.makeSuite(DBMStorage_TestCase, \
"test")
jmc_suite = unittest.TestSuite((mail_connection_suite, \
pop3_connection_suite, \
imap_connection_suite, \
mc_factory_suite, \
component_suite, \
component2_suite, \
storage_suite, \
dbmstorage_suite))
test_support.run_suite(component_suite)
# coverage.stop()
# coverage.analysis(jabber.mailconnection_factory)
# coverage.analysis(jabber.mailconnection)
# coverage.analysis(jabber.component)
# coverage.analysis(jabber.x)
# coverage.report([jabber.mailconnection_factory, jabber.mailconnection, \
# jabber.component, jabber.x])

0
tests/__init__.py Normal file
View File

169
tests/dummy_server.py Normal file
View File

@@ -0,0 +1,169 @@
##
## dummy_server.py
## Login : <adro8400@claralinux>
## Started on Fri May 13 12:53:17 2005 adro8400
## $Id: dummy_server.py,v 1.1 2005/07/11 20:39:31 dax Exp $
##
## Copyright (C) 2005 adro8400
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import sys
import time
import traceback
import re
import socket
import types
import select
import xml.dom.minidom
import utils
from pyxmpp import xmlextra
class DummyServer:
def __init__(self, host, port, responses = None):
for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
af, socktype, proto, canonname, sa = res
try:
s = socket.socket(af, socktype, proto)
except socket.error, msg:
s = None
continue
try:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(sa)
s.listen(1)
except socket.error, msg:
s.close()
s = None
continue
break
self.socket = s
self.responses = None
self.queries = None
self.real_queries = []
def __del__(self):
self.socket.close()
def serve(self):
conn, addr = self.socket.accept()
rfile = conn.makefile('rb', -1)
if self.responses:
data = None
for idx in range(len(self.responses)):
# if response is a function apply it (it must return a string)
# it is given previous received data
if isinstance(self.responses[idx], types.FunctionType):
response = self.responses[idx](data)
else:
response = self.responses[idx]
if response is not None:
print >>sys.stderr, 'Sending : ', response
conn.send(response)
data = rfile.readline()
if not data:
break
else:
self.real_queries.append(data)
print >>sys.stderr, 'Receive : ', data
conn.close()
def verify_queries(self):
result = True
queries_len = len(self.queries)
if queries_len == len(self.real_queries):
for idx in range(queries_len):
real_query = self.real_queries[idx]
match = (re.compile(self.queries[idx], re.M).match(real_query) is not None)
if not match:
result = False
print >>sys.stderr, "Unexpected query :\n" + \
"Expected query : _" + self.queries[idx] + "_\n" + \
"Receive query : _" + real_query + "_\n"
else:
result = False
print >>sys.stderr, "Expected " + str(queries_len) + \
" queries, got " + str(len(self.real_queries))
return result
class XMLDummyServer(DummyServer):
def __init__(self, host, port, responses, stream_handler):
DummyServer.__init__(self, host, port, responses)
self._reader = xmlextra.StreamReader(stream_handler)
def serve(self):
try:
conn, addr = self.socket.accept()
if self.responses:
data = None
for idx in range(len(self.responses)):
try:
# TODO : this approximation is not clean
# received size is based on the expected size in self.queries
data = conn.recv(1024 + len(self.queries[idx]))
if data:
print "-----------RECEIVE " + data
r = self._reader.feed(data)
except:
type, value, stack = sys.exc_info()
print "".join (traceback.format_exception
(type, value, stack, 5))
raise
# TODO verify got all data </stream>
if data:
self.real_queries.append(data)
# if response is a function apply it (it must return a string)
# it is given previous received data
if isinstance(self.responses[idx], types.FunctionType):
response = self.responses[idx](data)
else:
response = self.responses[idx]
if response is not None:
print >>sys.stderr, '---------SENDING : ', response
conn.send(response)
conn.close()
except:
type, value, stack = sys.exc_info()
print "".join (traceback.format_exception
(type, value, stack, 5))
raise
def verify_queries(self):
result = True
queries_len = len(self.queries)
if queries_len == len(self.real_queries):
for idx in range(queries_len):
real_query = xml.dom.minidom.parseString(self.real_queries[idx])
recv_query = xml.dom.minidom.parseString(self.queries[idx])
if not utils.xmldiff(real_query, recv_query):
result = False
print >>sys.stderr, "Unexpected query :\n" + \
"Expected query : _" + self.queries[idx] + "_\n" + \
"Receive query : _" + self.real_queries[idx] + "_\n"
else:
result = False
print >>sys.stderr, "Expected " + str(queries_len) + \
" queries, got " + str(len(self.real_queries))
return result
def test():
server = DummyServer(("localhost", 4242))
server.responses = ["rep1\n", "rep2\n"]
server.serve()
if __name__ == '__main__':
test()

65
tests/email_generator.py Normal file
View File

@@ -0,0 +1,65 @@
##
# -*- coding: iso-8859-1 -*-
## email_generator.py
## Login : <adro8400@claralinux>
## Started on Tue May 17 15:33:35 2005 adro8400
## $Id: email_generator.py,v 1.1 2005/07/11 20:39:31 dax Exp $
##
## Copyright (C) 2005 adro8400
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
from email.Header import Header
from email.MIMEText import MIMEText
from email.MIMEMultipart import MIMEMultipart
def _create_multipart(encoded):
msg = MIMEMultipart()
if encoded:
part1 = MIMEText("Encoded multipart1 with 'iso-8859-15' charset (<28><><EFBFBD>)", \
_charset = "iso-8859-15")
msg.attach(part1)
part2 = MIMEText("Encoded multipart2 with 'iso-8859-15' charset (<28><><EFBFBD>)", \
_charset = "iso-8859-15")
msg.attach(part2)
else:
part1 = MIMEText("Not encoded multipart1")
msg.attach(part1)
part2 = MIMEText("Not encoded multipart2")
msg.attach(part2)
return msg
def _create_singlepart(encoded):
if encoded:
return MIMEText("Encoded single part with 'iso-8859-15' charset (<28><><EFBFBD>)", \
_charset = "iso-8859-15")
else:
return MIMEText("Not encoded single part")
def generate(encoded, multipart, header):
msg = None
if multipart:
msg = _create_multipart(encoded)
else:
msg = _create_singlepart(encoded)
if header:
if encoded:
msg['Subject'] = Header("encoded subject (<28><><EFBFBD>)", "iso-8859-15")
msg['From'] = Header("encoded from (<28><><EFBFBD>)", "iso-8859-15")
else:
msg['Subject'] = Header("not encoded subject")
msg['From'] = Header("not encoded from")
return msg

18
tests/jmc-test.xml Normal file
View File

@@ -0,0 +1,18 @@
<config>
<jabber>
<server>127.0.0.1</server>
<port>55555</port>
<secret>secret</secret>
<service>jmc.localhost</service>
<connectsleep>5</connectsleep>
<language>fr</language>
<vCard>
<FN>Jabber Mail Component</FN>
<DESC>A Jabber mail server component</DESC>
<URL>http://people.happycoders.org/dax/jabber/jmc/</URL>
</vCard>
</jabber>
<storage>DBM</storage>
<spooldir>.</spooldir>
<check_interval>1</check_interval> <!-- in minutes -->
</config>

275
tests/test_component.py Normal file
View File

@@ -0,0 +1,275 @@
##
## test_mailconnection_factory.py
## Login : <adro8400@claralinux>
## Started on Fri May 20 10:46:58 2005 adro8400
## $Id: test_component.py,v 1.2 2005/09/18 20:24:07 dax Exp $
##
## Copyright (C) 2005 adro8400
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import thread
import unittest
import dummy_server
import time
import traceback
from pyxmpp import xmlextra
from jabber.component import *
from jabber.config import Config
class TestStreamHandler(xmlextra.StreamHandler):
def __init__(self, expected_balises = []):
xmlextra.StreamHandler.__init__(self)
self.expected_balises = expected_balises
def stream_start(self, doc):
pass
def stream_end(self, doc):
pass
def stanza(self, notused, node):
pass
class MailComponent_TestCase_NoConnection(unittest.TestCase):
def setUp(self):
self.mail_component = MailComponent(Config("tests/jmc-test.xml"))
def tearDown(self):
self.mail_component = None
def test_get_reg_form(self):
reg_form = self.mail_component.get_reg_form()
reg_form2 = self.mail_component.get_reg_form()
self.assertTrue(reg_form is reg_form2)
#TODO
class MailComponent_TestCase_Basic(unittest.TestCase):
def setUp(self):
self.handler = TestStreamHandler()
self.mail_component = MailComponent(Config("tests/jmc-test.xml"))
self.server = dummy_server.XMLDummyServer("localhost", 55555, None, self.handler)
thread.start_new_thread(self.server.serve, ())
def tearDown(self):
self.server = None
self.mail_component = None
def test_run(self):
self.server.responses = ["<?xml version='1.0'?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:component:accept' id='4258238724' from='localhost'>", \
"<handshake/></stream:stream>"]
# TODO : concatenate all queries to parse xml
self.server.queries = ["<?xml version='1.0' encoding='UTF-8'?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:component:accept' to='jmc.localhost' version='1.0'>", \
"<handshake></handshake>"]
# self.server.queries = ["<\?xml version=\"1.0\" encoding=\"UTF-8\"\?>\s<stream:stream xmlns:stream=\"http://etherx.jabber.org/streams\" xmlns=\"jabber:component:accept\" to=\"jmc.localhost\" version=\"1.0\">", \
# "<handshake>\s*</handshake>"]
self.mail_component.run(1)
self.failUnless(self.server.verify_queries())
# TODO : more assertion
class MailComponent_TestCase_NoReg(unittest.TestCase):
def setUp(self):
self.handler = TestStreamHandler()
logger = logging.getLogger()
logger.addHandler(logging.StreamHandler())
logger.setLevel(logging.DEBUG)
self.mail_component = MailComponent(Config("tests/jmc-test.xml"))
self.server = dummy_server.XMLDummyServer("localhost", 55555, None, self.handler)
thread.start_new_thread(self.server.serve, ())
def tearDown(self):
self.server = None
self.mail_component = None
def test_disco_get_items(self):
self.server.responses = ["<?xml version='1.0'?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:component:accept' id='4258238724' from='localhost'>", \
"<handshake/><iq type='get' to='jmc.localhost' id='aabca'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>", \
"</stream:stream>"]
self.server.queries = ["<\?xml version=\"1.0\" encoding=\"UTF-8\"\?>\s?" + \
"<stream:stream xmlns:stream=\"http://etherx.jabber.org/streams\" xmlns=\"jabber:component:accept\" to=\"jmc.localhost\" version=\"1.0\">", \
"<handshake>\s*</handshake>", \
"<iq from=\"jmc.localhost\" type=\"result\" id=\"aabca\"><query xmlns=\"http://jabber.org/protocol/disco\#info\"><feature var=\"jabber:iq:version\"/><feature var=\"jabber:iq:register\"/><identity name=\"Jabber Mail Component\" category=\"headline\" type=\"mail\"/></query></iq>"]
self.mail_component.run(1)
self.failUnless(self.server.verify_queries())
def test_get_register(self):
pass
self.server.responses = ["<?xml version='1.0'?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:component:accept' id='4258238724' from='localhost'>", \
"<handshake/><iq type='get' to='jmc.localhost' from='test@localhost/test' id='aad9a'><query xmlns='jabber:iq:register'/></iq>", \
"</stream:stream>"]
self.server.queries = ["<\?xml version=\"1.0\" encoding=\"UTF-8\"\?>\s?" + \
"<stream:stream xmlns:stream=\"http://etherx.jabber.org/streams\" xmlns=\"jabber:component:accept\" to=\"jmc.localhost\" version=\"1.0\">", \
"<handshake>\s*</handshake>", \
"<iq from=\"jmc.localhost\" to=\"test@localhost/test\" type=\"result\" id=\"aad9a\">\s?" + \
"<query xmlns=\"jabber:iq:register\">\s?" + \
"<x xmlns=\"jabber:x:data\">\s?" + \
"<title>Jabber Mail connection registration</title>\s?" + \
"<instructions>Enter anything below</instructions>\s?" + \
"<field type=\"text-single\" label=\"Connection name\" var=\"name\"/>\s?" + \
"<field type=\"text-single\" label=\"Login\" var=\"login\"/>\s?" + \
"<field type=\"text-private\" label=\"Password\" var=\"password\"/>\s?" + \
"<field type=\"text-single\" label=\"Host\" var=\"host\"/>\s?" + \
"<field type=\"text-single\" label=\"Port\" var=\"port\"/>\s?" + \
"<field type=\"list-single\" label=\"Mailbox type\" var=\"type\">\s?" + \
"<option label=\"POP3\">\s?" + \
"<value>pop3</value>\s?" + \
"</option>\s?" + \
"<option label=\"POP3S\">\s?" + \
"<value>pop3s</value>\s?" + \
"</option>\s?" + \
"<option label=\"IMAP\">\s?" + \
"<value>imap</value>\s?" + \
"</option>\s?" + \
"<option label=\"IMAPS\">\s?" + \
"<value>imaps</value>\s?" + \
"</option>\s?" + \
"</field>\s?" + \
"<field type=\"text-single\" label=\"Mailbox (IMAP)\" var=\"mailbox\">\s?" + \
"<value>INBOX</value>\s?" + \
"</field>\s?" + \
"<field type=\"list-single\" label=\"Action when state is 'Free For Chat'\" var=\"ffc_action\">\s?" + \
"<value>2</value>\s?" + \
"<option label=\"Do nothing\">\s?" + \
"<value>0</value>\s?" + \
"</option>\s?" + \
"<option label=\"Send mail digest\">\s?" + \
"<value>1</value>\s?" + \
"</option>\s?" + \
"<option label=\"Retrieve mail\">\s?" + \
"<value>2</value>\s?" + \
"</option>\s?" + \
"</field>\s?" + \
"<field type=\"list-single\" label=\"Action when state is 'Online'\" var=\"online_action\">\s?" + \
"<value>2</value>\s?" + \
"<option label=\"Do nothing\">\s?" + \
"<value>0</value>\s?" + \
"</option>\s?" + \
"<option label=\"Send mail digest\">\s?" + \
"<value>1</value>\s?" + \
"</option>\s?" + \
"<option label=\"Retrieve mail\">\s?" + \
"<value>2</value>\s?" + \
"</option>\s?" + \
"</field>\s?" + \
"<field type=\"list-single\" label=\"Action when state is 'Away'\" var=\"away_action\">\s?" + \
"<value>1</value>\s?" + \
"<option label=\"Do nothing\">\s?" + \
"<value>0</value>\s?" + \
"</option>\s?" + \
"<option label=\"Send mail digest\">\s?" + \
"<value>1</value>\s?" + \
"</option>\s?" + \
"<option label=\"Retrieve mail\">\s?" + \
"<value>2</value>\s?" + \
"</option>\s?" + \
"</field>\s?" + \
"<field type=\"list-single\" label=\"Action when state is 'Extended Away'\" var=\"ea_action\">\s?" + \
"<value>1</value>\s?" + \
"<option label=\"Do nothing\">\s?" + \
"<value>0</value>\s?" + \
"</option>\s?" + \
"<option label=\"Send mail digest\">\s?" + \
"<value>1</value>\s?" + \
"</option>\s?" + \
"<option label=\"Retrieve mail\">\s?" + \
"<value>2</value>\s?" + \
"</option>\s?" + \
"</field>\s?" + \
"<field type=\"list-single\" label=\"Action when state is 'Offline'\" var=\"offline_action\">\s?" + \
"<value>0</value>\s?" + \
"<option label=\"Do nothing\">\s?" + \
"<value>0</value>\s?" + \
"</option>\s?" + \
"<option label=\"Send mail digest\">\s?" + \
"<value>1</value>\s?" + \
"</option>\s?" + \
"<option label=\"Retrieve mail\">\s?" + \
"<value>2</value>\s?" + \
"</option>\s?" + \
"</field>\s?" + \
"<field type=\"text-single\" label=\"Mail check interval (in minutes)\" var=\"interval\">\s?" + \
"<value>5</value>\s?" + \
"</field>\s?" + \
"</x>\s?" + \
"</query>\s?" + \
"</iq>"]
self.mail_component.run(1)
self.failUnless(self.server.verify_queries())
#self.mail_component.get_version()
# def test_disco_get_info(self):
# pass
# def test_get_register(self):
# pass
# def test_set_register(self):
# pass
class MailComponent_TestCase_Reg(unittest.TestCase):
def setUp(self):
self.mail_component = MailComponent(Config("tests/jmc-test.xml"))
self.mail_component.set_register(iq = None)
def test_get_reg_form_init(self):
pass
def test_get_reg_form_init_2pass(self):
pass
def test_disco_get_items(self):
pass
def test_get_register(self):
pass
def test_set_register_update(self):
pass
def test_set_register_remove(self):
pass
def test_presence_available_transport(self):
pass
def test_presence_available_mailbox(self):
pass
def test_presence_unavailable_transport(self):
pass
def test_presence_unavailable_mailbox(self):
pass
def test_presence_subscribe(self):
pass
def test_presence_subscribed_transport(self):
pass
def test_presence_subscribed_mailbox(self):
pass
def test_presence_unsubscribe(self):
pass
def test_presence_unsubscribed(self):
pass
def test_check_mail(self):
pass

View File

@@ -0,0 +1,264 @@
# -*- coding: iso-8859-15 -*-
##
## mailconnection_test.py
## Login : <adro8400@claralinux>
## Started on Fri May 13 11:32:51 2005 adro8400
## $Id: test_mailconnection.py,v 1.2 2005/09/18 20:24:07 dax Exp $
##
## Copyright (C) 2005 adro8400
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import unittest
from jabber.mailconnection import IMAPConnection, \
POP3Connection, \
MailConnection
import dummy_server
import email_generator
import thread
import re
import sys
import string
class MailConnection_TestCase(unittest.TestCase):
def setUp(self):
self.connection = MailConnection()
def make_test(email_type, tested_func, expected_res):
def inner(self):
encoded, multipart, header = email_type
email = email_generator.generate(encoded, \
multipart, \
header)
part = tested_func(self, email)
self.assertEquals(part, expected_res)
return inner
test_get_decoded_part_not_encoded = \
make_test((False, False, False), \
lambda self, email: self.connection.get_decoded_part(email), \
u"Not encoded single part")
test_get_decoded_part_encoded = \
make_test((True, False, False), \
lambda self, email: self.connection.get_decoded_part(email), \
u"Encoded single part with 'iso-8859-15' charset (<28><><EFBFBD>)")
test_format_message_summary_not_encoded = \
make_test((False, False, True), \
lambda self, email: self.connection.format_message_summary(email), \
u"From : not encoded from\nSubject : not encoded subject\n\n")
test_format_message_summary_encoded = \
make_test((True, False, True), \
lambda self, email: self.connection.format_message_summary(email), \
u"From : encoded from (<28><><EFBFBD>)\nSubject : encoded subject " + \
u"(<28><><EFBFBD>)\n\n")
test_format_message_summary_partial_encoded = \
make_test((True, False, True), \
lambda self, email: \
email.replace_header("Subject", \
"\"" + str(email["Subject"]) \
+ "\" not encoded part") or \
email.replace_header("From", \
"\"" + str(email["From"]) \
+ "\" not encoded part") or \
self.connection.format_message_summary(email), \
u"From : \"encoded from (<28><><EFBFBD>)\" not encoded part\nSubject " + \
u": \"encoded subject (<28><><EFBFBD>)\" not encoded part\n\n")
test_format_message_single_not_encoded = \
make_test((False, False, True), \
lambda self, email: self.connection.format_message(email), \
u"From : not encoded from\nSubject : not encoded subject" + \
u"\n\nNot encoded single part\n")
test_format_message_single_encoded = \
make_test((True, False, True), \
lambda self, email: self.connection.format_message(email), \
u"From : encoded from (<28><><EFBFBD>)\nSubject : encoded subject " + \
u"(<28><><EFBFBD>)\n\nEncoded single part with 'iso-8859-15' charset" + \
u" (\xe9\xe0\xea)\n")
test_format_message_multi_not_encoded = \
make_test((False, True, True), \
lambda self, email: self.connection.format_message(email), \
u"From : not encoded from\nSubject : not encoded subject" + \
u"\n\nNot encoded multipart1\nNot encoded multipart2\n")
test_format_message_multi_encoded = \
make_test((True, True, True), \
lambda self, email: self.connection.format_message(email), \
u"From : encoded from (<28><><EFBFBD>)\nSubject : encoded subject (<28><>" + \
u"<EFBFBD>)\n\nEncoded multipart1 with 'iso-8859-15' charset (<28><><EFBFBD>" + \
u")\nEncoded multipart2 with 'iso-8859-15' charset (<28><><EFBFBD>)\n")
class POP3Connection_TestCase(unittest.TestCase):
def setUp(self):
self.server = dummy_server.DummyServer("localhost", 1110)
thread.start_new_thread(self.server.serve, ())
self.pop3connection = POP3Connection("login", \
"pass", \
"localhost", \
1110, \
ssl = False)
def tearDown(self):
self.server = None
self.pop3connection = None
def make_test(responses = None, queries = None, core = None):
def inner(self):
self.server.responses = ["+OK connected\r\n", \
"+OK name is a valid mailbox\r\n", \
"+OK pass\r\n"]
if responses:
self.server.responses += responses
self.server.queries = ["USER login\r\n", \
"PASS pass\r\n"]
if queries:
self.server.queries += queries
self.server.queries += ["QUIT\r\n"]
self.pop3connection.connect()
self.failUnless(self.pop3connection.connection, \
"Cannot establish connection")
if core:
core(self)
self.pop3connection.disconnect()
self.failUnless(self.server.verify_queries(), \
"Sended queries does not match expected queries.")
return inner
test_connection = make_test()
test_get_mail_list = \
make_test(["+OK 2 20\r\n"], \
["STAT\r\n"], \
lambda self: \
self.assertEquals(self.pop3connection.get_mail_list(), \
["1", "2"]))
test_get_mail_summary = \
make_test(["+OK 10 octets\r\n" + \
"From: user@test.com\r\n" + \
"Subject: subject test\r\n\r\n" + \
"mymessage\r\n.\r\n"], \
["RETR 1\r\n"], \
lambda self: \
self.assertEquals(self.pop3connection.get_mail_summary(1), \
"From : user@test.com\n" + \
"Subject : subject test\n\n"))
test_get_mail = \
make_test(["+OK 10 octets\r\n" + \
"From: user@test.com\r\n" + \
"Subject: subject test\r\n\r\n" + \
"mymessage\r\n.\r\n"], \
["RETR 1\r\n"], \
lambda self: \
self.assertEquals(self.pop3connection.get_mail(1), \
"From : user@test.com\n" + \
"Subject : subject test\n\n" + \
"mymessage\n"))
class IMAPConnection_TestCase(unittest.TestCase):
def setUp(self):
self.server = dummy_server.DummyServer("localhost", 1143)
thread.start_new_thread(self.server.serve, ())
self.imap_connection = IMAPConnection("login", \
"pass", \
"localhost", \
1143, \
ssl = False)
def tearDown(self):
self.server = None
self.imap_connection = None
def make_test(responses = None, queries = None, core = None):
def inner(self):
self.server.responses = ["* OK [CAPABILITY IMAP4 LOGIN-REFERRALS " + \
"AUTH=PLAIN]\n", \
lambda data: "* CAPABILITY IMAP4 " + \
"LOGIN-REFERRALS AUTH=PLAIN\n" + \
data.split()[0] + \
" OK CAPABILITY completed\n", \
lambda data: data.split()[0] + \
" OK LOGIN completed\n"]
if responses:
self.server.responses += responses
self.server.queries = ["^[^ ]* CAPABILITY", \
"^[^ ]* LOGIN login \"pass\""]
if queries:
self.server.queries += queries
self.server.queries += ["^[^ ]* LOGOUT"]
self.imap_connection.connect()
self.failUnless(self.imap_connection.connection, \
"Cannot establish connection")
if core:
core(self)
self.imap_connection.disconnect()
self.failUnless(self.server.verify_queries("Sended queries does not match expected queries."))
return inner
test_connection = make_test()
test_get_mail_list = make_test([lambda data: "* 42 EXISTS\n* 1 RECENT\n* OK" +\
" [UNSEEN 9]\n* FLAGS (\Deleted \Seen\*)\n*" +\
" OK [PERMANENTFLAGS (\Deleted \Seen\*)\n" + \
data.split()[0] + \
" OK [READ-WRITE] SELECT completed\n", \
lambda data: "* SEARCH 9 10 \n" + \
data.split()[0] + " OK SEARCH completed\n"], \
["^[^ ]* SELECT INBOX", \
"^[^ ]* SEARCH UNSEEN"], \
lambda self: \
self.assertEquals(self.imap_connection.get_mail_list(), ['9', '10']))
test_get_mail_summary = make_test([lambda data: "* 42 EXISTS\r\n* 1 RECENT\r\n* OK" +\
" [UNSEEN 9]\r\n* FLAGS (\Deleted \Seen\*)\r\n*" +\
" OK [PERMANENTFLAGS (\Deleted \Seen\*)\r\n" + \
data.split()[0] + \
" OK [READ-WRITE] SELECT completed\r\n", \
lambda data: "* 1 FETCH ((RFC822) {12}\r\nbody" + \
" text\r\n)\r\n" + \
data.split()[0] + " OK FETCH completed\r\n", \
lambda data: "* 1 FETCH (FLAGS (\UNSEEN))\r\n" + \
data.split()[0] + " OK STORE completed\r\n"], \
["^[^ ]* SELECT INBOX", \
"^[^ ]* FETCH 1 \(RFC822\)", \
"^[^ ]* STORE 1 FLAGS \(UNSEEN\)"], \
lambda self: self.assertEquals(self.imap_connection.get_mail_summary(1), \
"From : None\nSubject : None\n\n"))
test_get_mail = make_test([lambda data: "* 42 EXISTS\r\n* 1 RECENT\r\n* OK" +\
" [UNSEEN 9]\r\n* FLAGS (\Deleted \Seen\*)\r\n*" +\
" OK [PERMANENTFLAGS (\Deleted \Seen\*)\r\n" + \
data.split()[0] + \
" OK [READ-WRITE] SELECT completed\r\n", \
lambda data: "* 1 FETCH ((RFC822) {11}\r\nbody" + \
" text\r\n)\r\n" + \
data.split()[0] + " OK FETCH completed\r\n", \
lambda data: "* 1 FETCH (FLAGS (\UNSEEN))\r\n" + \
data.split()[0] + " OK STORE completed\r\n"], \
["^[^ ]* SELECT INBOX", \
"^[^ ]* FETCH 1 \(RFC822\)", \
"^[^ ]* STORE 1 FLAGS \(UNSEEN\)"], \
lambda self: self.assertEquals(self.imap_connection.get_mail(1), \
"From : None\nSubject : None\n\nbody text\r\n\n"))

View File

@@ -0,0 +1,96 @@
##
## test_mailconnection_factory.py
## Login : <adro8400@claralinux>
## Started on Fri May 20 10:46:58 2005 adro8400
## $Id: test_mailconnection_factory.py,v 1.1 2005/07/11 20:39:31 dax Exp $
##
## Copyright (C) 2005 adro8400
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import unittest
from jabber.mailconnection_factory import *
class MailConnectionFactory_TestCase(unittest.TestCase):
def test_new_mail_connection_imap(self):
mc = get_new_mail_connection("imap")
self.assertEquals(mc, mc)
def test_new_mail_connection_imaps(self):
mc = get_new_mail_connection("imaps")
self.assertEquals(mc, mc)
def test_new_mail_connection_pop3(self):
mc = get_new_mail_connection("pop3")
self.assertEquals(mc, mc)
def test_new_mail_connection_pop3s(self):
mc = get_new_mail_connection("pop3s")
self.assertEquals(mc, mc)
def test_str_to_mail_connection_imap(self):
mc = str_to_mail_connection("imap#login#passwd#host#193#nothing#nothing#nothing#nothing#retrieve#INBOX")
self.assertEquals(mc.get_type(), "imap")
self.assertEquals(mc.login, "login")
self.assertEquals(mc.password, "passwd")
self.assertEquals(mc.host, "host")
self.assertEquals(mc.port, 193)
self.assertEquals(mc.mailbox, "INBOX")
self.assertEquals(mc.ffc_action, "nothing")
self.assertEquals(mc.online_action, "nothing")
self.assertEquals(mc.away_action, "nothing")
self.assertEquals(mc.ea_action, "nothing")
self.assertEquals(mc.offline_action, "retrieve")
def test_str_to_mail_connection_imaps(self):
mc = str_to_mail_connection("imaps#login#passwd#host#993#nothing#nothing#nothing#nothing#retrieve#INBOX.SubDir")
self.assertEquals(mc.get_type(), "imaps")
self.assertEquals(mc.login, "login")
self.assertEquals(mc.password, "passwd")
self.assertEquals(mc.host, "host")
self.assertEquals(mc.port, 993)
self.assertEquals(mc.mailbox, "INBOX.SubDir")
self.assertEquals(mc.ffc_action, "nothing")
self.assertEquals(mc.online_action, "nothing")
self.assertEquals(mc.away_action, "nothing")
self.assertEquals(mc.ea_action, "nothing")
self.assertEquals(mc.offline_action, "retrieve")
def test_str_to_mail_connection_pop3(self):
mc = str_to_mail_connection("pop3#login#passwd#host#110#nothing#nothing#nothing#nothing#retrieve")
self.assertEquals(mc.get_type(), "pop3")
self.assertEquals(mc.login, "login")
self.assertEquals(mc.password, "passwd")
self.assertEquals(mc.host, "host")
self.assertEquals(mc.port, 110)
self.assertEquals(mc.ffc_action, "nothing")
self.assertEquals(mc.online_action, "nothing")
self.assertEquals(mc.away_action, "nothing")
self.assertEquals(mc.ea_action, "nothing")
self.assertEquals(mc.offline_action, "retrieve")
def test_str_to_mail_connection_pop3s(self):
mc = str_to_mail_connection("pop3s#login#passwd#host#995#nothing#nothing#nothing#nothing#retrieve")
self.assertEquals(mc.get_type(), "pop3s")
self.assertEquals(mc.login, "login")
self.assertEquals(mc.password, "passwd")
self.assertEquals(mc.host, "host")
self.assertEquals(mc.port, 995)
self.assertEquals(mc.ffc_action, "nothing")
self.assertEquals(mc.online_action, "nothing")
self.assertEquals(mc.away_action, "nothing")
self.assertEquals(mc.ea_action, "nothing")
self.assertEquals(mc.offline_action, "retrieve")

150
tests/test_storage.py Normal file
View File

@@ -0,0 +1,150 @@
##
## test_storage.py
## Login : <dax@happycoders.org>
## Started on Fri May 20 10:46:58 2005 dax
## $Id: test_component.py,v 1.1 2005/07/11 20:39:31 dax Exp $
##
## Copyright (C) 2005 adro8400
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import os
import unittest
import dummy_server
from jabber.storage import *
from jabber import mailconnection
from jabber.mailconnection import *
class Storage_TestCase(unittest.TestCase):
def test_init(self):
spool_dir = "./spool/test"
self._storage = Storage(spool_dir = spool_dir)
self.assertTrue(os.access(spool_dir, os.F_OK))
self.assertEquals(self._storage.spool_dir, spool_dir)
os.removedirs(spool_dir)
# TODO
class SQLiteStorage_TestCase(unittest.TestCase):
def setUp(self):
self._storage = SQLiteStorage()
def tearDown(self):
pass
class DBMStorage_TestCase(unittest.TestCase):
def setUp(self):
self._storage = DBMStorage(nb_pk_fields = 2)
self._account1 = IMAPConnection(login = "login1",
password = "password1",
host = "host1",
port = 993,
ssl = True,
mailbox = "INBOX.box1")
self._account1.ffc_action = mailconnection.DO_NOTHING
self._account1.onlline_action = mailconnection.DO_NOTHING
self._account1.away_action = mailconnection.DO_NOTHING
self._account1.ea_action = mailconnection.DO_NOTHING
self._account1.offline_action = mailconnection.DO_NOTHING
self._account2 = IMAPConnection(login = "login2",
password = "password2",
host = "host2",
port = 1993,
ssl = False,
mailbox = "INBOX.box2")
self._account2.ffc_action = mailconnection.DO_NOTHING
self._account2.onlline_action = mailconnection.DO_NOTHING
self._account2.away_action = mailconnection.DO_NOTHING
self._account2.ea_action = mailconnection.DO_NOTHING
self._account2.offline_action = mailconnection.DO_NOTHING
def tearDown(self):
os.remove(self._storage.file)
self._storage = None
def test_set_get(self):
self._storage[("test@localhost", "account1")] = self._account1
self._storage[("test@localhost", "account2")] = self._account2
self.assertEquals(self._storage[("test@localhost", "account1")], self._account1)
self.assertEquals(self._storage[("test@localhost", "account2")], self._account2)
def test_set_sync_get(self):
self._storage[("test@localhost", "account1")] = self._account1
self._storage[("test@localhost", "account2")] = self._account2
loaded_storage = DBMStorage(nb_pk_fields = 2)
self.assertEquals(loaded_storage[("test@localhost", "account1")], self._account1)
self.assertEquals(loaded_storage[("test@localhost", "account2")], self._account2)
def test_set_del_get(self):
self._storage[("test@localhost", "account2")] = self._account2
del self._storage[("test@localhost", "account2")]
try:
self._storage[("test@localhost", "account2")]
except KeyError:
return
self.fail("KeyError was expected")
def test_haskey(self):
self._storage[("test@localhost", "account2")] = self._account2
self.assertTrue(self._storage.has_key(("test@localhost", "account2")))
def test_get_filtered(self):
self._storage[("test@localhost", "account1")] = self._account1
self._storage[("test@localhost", "account2")] = self._account2
result = self._storage[("test@localhost",)]
self.assertEquals(type(result), list)
self.assertEquals(len(result), 2)
self.assertEquals(result[0], self._account1)
self.assertEquals(result[1], self._account2)
def test_get_filtered2(self):
self._storage[("test@localhost", "account1")] = self._account1
self._storage[("test@localhost", "account2")] = self._account2
result = self._storage[("account1",)]
self.assertEquals(type(result), list)
self.assertEquals(len(result), 1)
self.assertEquals(result[0], self._account1)
def test_keys(self):
self._storage[("test@localhost", "account1")] = self._account1
self._storage[("test@localhost", "account2")] = self._account2
result = self._storage.keys()
self.assertEquals(type(result), list)
self.assertEquals(len(result), 2)
self.assertEquals(type(result[0]), tuple)
self.assertEquals(len(result[0]), 2)
self.assertEquals(result[0][0], "test@localhost")
self.assertEquals(result[0][1], "account1")
self.assertEquals(type(result[1]), tuple)
self.assertEquals(len(result[1]), 2)
self.assertEquals(result[1][0], "test@localhost")
self.assertEquals(result[1][1], "account2")
def test_keys_filtered(self):
self._storage[("test@localhost", "account1")] = self._account1
self._storage[("test@localhost", "account2")] = self._account2
result = self._storage.keys(())
self.assertEquals(type(result), list)
self.assertEquals(len(result), 1)
self.assertEquals(result[0], "test@localhost")
def test_keys_filtered2(self):
self._storage[("test@localhost", "account1")] = self._account1
self._storage[("test@localhost", "account2")] = self._account2
result = self._storage.keys(("test@localhost",))
self.assertEquals(type(result), list)
self.assertEquals(len(result), 2)
self.assertEquals(result[0], "account2")
self.assertEquals(result[1], "account1")

72
tests/test_x.py Normal file
View File

@@ -0,0 +1,72 @@
##
## test_x.py
## Login : <adro8400@claralinux>
## Started on Fri May 20 10:46:58 2005 adro8400
## $Id: test_x.py,v 1.1 2005/07/11 20:39:31 dax Exp $
##
## Copyright (C) 2005 adro8400
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import unittest
from jabber.x import *
class X_TestCase(unittest.TestCase):
def setUp(self):
self.mail_component = MailComponent()
def test_get_form(self):
self.reg_form.add_field(type = "text-single", \
label = "Connection name", \
var = "name")
self.reg_form.add_field(type = "text-single", \
label = "Login", \
var = "login")
self.reg_form.add_field(type = "text-private", \
label = "password", \
var = "password")
self.reg_form.add_field(type = "text-single", \
label = "Host", \
var = "host")
self.reg_form.add_field(type = "text-single", \
label = "Port", \
var = "port")
field = self.reg_form.add_field(type = "list-single", \
label = "Mailbox type", \
var = "type")
field.add_option(label = "POP3", \
value = "pop3")
field.add_option(label = "POP3S", \
value = "pop3s")
field.add_option(label = "IMAP", \
value = "imap")
field.add_option(label = "IMAPS", \
value = "imaps")
self.reg_form.add_field(type = "text-single", \
label = "Mailbox (IMAP)", \
var = "mailbox", \
value = "INBOX")
self.reg_form.add_field(type = "boolean", \
label = "Retrieve mail", \
var = "retrieve")
pass

28
tests/tmp.py Normal file
View File

@@ -0,0 +1,28 @@
import dummy_server
server = dummy_server.DummyServer(("localhost", 1143))
server.responses = ["* OK [CAPABILITY IMAP4 LOGIN-REFERRALS " + \
"AUTH=PLAIN]\r\n", \
lambda data: "* CAPABILITY IMAP4 " + \
"LOGIN-REFERRALS AUTH=PLAIN\r\n" + \
data.split()[0] + \
" OK CAPABILITY completed\r\n", \
lambda data: data.split()[0] + \
" OK LOGIN completed\r\n", \
lambda data: "* 42 EXISTS\r\n* 1 RECENT\r\n* OK" +\
" [UNSEEN 9]\r\n* FLAGS (\Deleted \Seen\*)\r\n*" +\
" OK [PERMANENTFLAGS (\Deleted \Seen\*)\r\n" + \
data.split()[0] + \
" OK [READ-WRITE] SELECT completed\r\n", \
lambda data: "* 1 FETCH ((RFC822) {12}\r\nbody text\r\n)\r\n" + \
data.split()[0] + " OK FETCH completed\r\n", \
lambda data: "* 1 FETCH (FLAGS (\UNSEEN))\r\n" + \
data.split()[0] + " OK STORE completed\r\n"]
server.queries = ["CAPABILITY", \
"LOGIN login \"pass\"", \
"SELECT INBOX", \
"FETCH 1 (RFC822)", \
"STORE 1 FLAGS (UNSEEN)", \
"LOGOUT"]
server.serve()
#server.verify_queries()

121
tests/tmp1 Normal file
View File

@@ -0,0 +1,121 @@
<iq from="jmc.localhost" to="test@localhost/test" type="result" id="aad9a">
<query xmlns="jabber:iq:register">
<x xmlns="jabber:x:data">
<title>
Jabber Mail connection registration</title>
<instructions>
Enter anything below</instructions>
<field type="text-single" label="Connection name" var="name"/>
<field type="text-single" label="Login" var="login"/>
<field type="text-private" label="Password" var="password"/>
<field type="text-single" label="Host" var="host"/>
<field type="text-single" label="Port" var="port"/>
<field type="list-single" label="Mailbox type" var="type">
<option label="POP3">
<value>
pop3</value>
</option>
<option label="POP3S">
<value>
pop3s</value>
</option>
<option label="IMAP">
<value>
imap</value>
</option>
<option label="IMAPS">
<value>
imaps</value>
</option>
</field>
<field type="text-single" label="Mailbox (IMAP)" var="mailbox">
<value>
INBOX</value>
</field>
<field type="list-single" label="Action when state is 'Free For Chat'" var="ffc_action">
<value>
2</value>
<option label="Do nothing">
<value>
0</value>
</option>
<option label="Send mail digest">
<value>
1</value>
</option>
<option label="Retrieve mail">
<value>
2</value>
</option>
</field>
<field type="list-single" label="Action when state is 'Online'" var="online_action">
<value>
2</value>
<option label="Do nothing">
<value>
0</value>
</option>
<option label="Send mail digest">
<value>
1</value>
</option>
<option label="Retrieve mail">
<value>
2</value>
</option>
</field>
<field type="list-single" label="Action when state is 'Away'" var="away_action">
<value>
1</value>
<option label="Do nothing">
<value>
0</value>
</option>
<option label="Send mail digest">
<value>
1</value>
</option>
<option label="Retrieve mail">
<value>
2</value>
</option>
</field>
<field type="list-single" label="Action when state is 'Extended Away'" var="ea_action">
<value>
1</value>
<option label="Do nothing">
<value>
0</value>
</option>
<option label="Send mail digest">
<value>
1</value>
</option>
<option label="Retrieve mail">
<value>
2</value>
</option>
</field>
<field type="list-single" label="Action when state is 'Offline'" var="offline_action">
<value>
0</value>
<option label="Do nothing">
<value>
0</value>
</option>
<option label="Send mail digest">
<value>
1</value>
</option>
<option label="Retrieve mail">
<value>
2</value>
</option>
</field>
<field type="text-single" label="Mail check interval (in minutes)" var="interval">
<value>
5</value>
</field>
</x>
</query>
</iq>

121
tests/tmp2 Normal file
View File

@@ -0,0 +1,121 @@
<iq from="jmc.localhost" to="test@localhost/test" type="result" id="aad9a">
<query xmlns="jabber:iq:register">
<x xmlns="jabber:x:data">
<title>
Jabber Mail connection registration</title>
<instructions>
Enter anything below</instructions>
<field type="text-single" label="Connection name" var="name"/>
<field type="text-single" label="Login" var="login"/>
<field type="text-private" label="Password" var="password"/>
<field type="text-single" label="Host" var="host"/>
<field type="text-single" label="Port" var="port"/>
<field type="list-single" label="Mailbox type" var="type">
<option label="POP3">
<value>
pop3</value>
</option>
<option label="POP3S">
<value>
pop3s</value>
</option>
<option label="IMAP">
<value>
imap</value>
</option>
<option label="IMAPS">
<value>
imaps</value>
</option>
</field>
<field type="text-single" label="Mailbox (IMAP)" var="mailbox">
<value>
INBOX</value>
</field>
<field type="list-single" label="Action when state is 'Free For Chat'" var="ffc_action">
<value>
2</value>
<option label="Do nothing">
<value>
0</value>
</option>
<option label="Send mail digest">
<value>
1</value>
</option>
<option label="Retrieve mail">
<value>
2</value>
</option>
</field>
<field type="list-single" label="Action when state is 'Online'" var="online_action">
<value>
2</value>
<option label="Do nothing">
<value>
0</value>
</option>
<option label="Send mail digest">
<value>
1</value>
</option>
<option label="Retrieve mail">
<value>
2</value>
</option>
</field>
<field type="list-single" label="Action when state is 'Away'" var="away_action">
<value>
1</value>
<option label="Do nothing">
<value>
0</value>
</option>
<option label="Send mail digest">
<value>
1</value>
</option>
<option label="Retrieve mail">
<value>
2</value>
</option>
</field>
<field type="list-single" label="Action when state is 'Extended Away'" var="ea_action">
<value>
1</value>
<option label="Do nothing">
<value>
0</value>
</option>
<option label="Send mail digest">
<value>
1</value>
</option>
<option label="Retrieve mail">
<value>
2</value>
</option>
</field>
<field type="list-single" label="Action when state is 'Offline'" var="offline_action">
<value>
0</value>
<option label="Do nothing">
<value>
0</value>
</option>
<option label="Send mail digest">
<value>
1</value>
</option>
<option label="Retrieve mail">
<value>
2</value>
</option>
</field>
<field type="text-single" label="Mail check interval (in minutes)" var="interval">
<value>
5</value>
</field>
</x>
</query>
</iq>

100
tests/utils.py Normal file
View File

@@ -0,0 +1,100 @@
##
## utils.py
## Login : <dax@happycoders.org>
## Started on Mon Oct 24 21:44:43 2005 dax
## $Id$
##
## Copyright (C) 2005 dax
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import xml.dom.minidom
#document = "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:component:accept' id='4258238724' from='localhost'>"
# document = """\
# <slideshow attr='value'>
# <title>Demo slideshow</title>
# <slide><title>Slide title</title>
# <point>This is a demo</point>
# <point>Of a program for processing slides</point>
# </slide>
# <slide><title>Another demo slide</title>
# <point>It is important</point>
# <point>To have more than</point>
# <point>one slide</point>
# </slide>
# </slideshow>
# """
# document1 = """\
# <slideshow attr='value'>
# <title>Demo slideshow</title>
# <slide><title>Slide title</title>
# <point>This is a demo</point>
# <point>Of a program for processing slides</point>
# </slide>
# <slide><title>Another demo slide</title>
# <point>It is important1</point>
# <point>To have more than</point>
# <point>one slide</point>
# </slide>
# </slideshow>
# """
#dom = xml.dom.minidom.parseString(document)
#dom1 = xml.dom.minidom.parseString(document)
# def getText(nodelist):
# rc = ""
# for node in nodelist:
# if node.nodeType == node.TEXT_NODE:
# rc = rc + node.data
# return rc
def xmldiff(node1, node2):
if node1.nodeType == node1.TEXT_NODE:
if not node2.nodeType == node2.TEXT_NODE \
or node1.data != node2.data:
return False
elif node1.nodeType == node1.DOCUMENT_NODE:
if not node2.nodeType == node2.DOCUMENT_NODE:
return False
elif node1.tagName != node2.tagName:
return False
else:
for attr in node1._get_attributes().keys():
if not node2.hasAttribute(attr) \
or node1.getAttribute(attr) != node2.getAttribute(attr):
return False
for i in range(len(node1.childNodes)):
if not xmldiff(node1.childNodes[i], node2.childNodes[i]):
return False
return True
#print xmldiff(dom, dom1)
# def nodediff(node1, node2):
# if not node1.name == node2.name:
# return False
# for properties in node1.properties:
# if node2.hasAttribute(attr):
# def xmldiff(xpath, node1, node2):
# if not nodediff(node1, node2):
# return False
# for child in node1.children: