diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..45d1dfb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,30 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +max_line_length = 300 +tab_width = 4 + +# C++ Coding style +# I know these keys are unknown, regard it as documentation +[*.{cc,hh}] +indent_brace_style = K&R +curly_bracket_next_line = false +spaces_around_operators = true +spaces_around_brackets = true + +[*.py] +indent_size = 4 + +[Makefile] +indent_style = tab + +[*.ini] +indent_size = 2 + +[*.cfg] +indent_size = 2 diff --git a/.gitignore b/.gitignore index 280d2a5..819aecf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,6 @@ syntax: glob -local.conf -*.swp ws/ -tags/ -.DS_Store/ pEp.egg-info/ dist/ build/ @@ -34,3 +30,49 @@ settings.json docs/build/ .eggs/ .tox/ +======= +# Build config +local.conf + +# Python artifacts +/build/ +/dist/ +__pycache__/ +*.py[co] +/.tox +.pytest_cache +/venv/ +/_venv/ +*.egg-info/ +/*.egg-link +/*.egg +/.eggs/ +/pip-wheel-metadata/ + +# file generated by setuptools_scm +/src/pEp/__version__.py + +# Doc artifacts +/docs/build/ + +# Test artifacts +/test/Laptop +/test/Library +/test/Phone +/test/TestInbox +/test/Backup +/test/lib +/test/imap_settings.py + +# Editor artifacts and config +.idea/ +*~ +.\#* +\#*# +*.swp +.idea +.spyproject +*.code-workspace + +# platform artifacts +.DS_store diff --git a/.hgignore b/.hgignore deleted file mode 100644 index 8850bfe..0000000 --- a/.hgignore +++ /dev/null @@ -1,28 +0,0 @@ -syntax: glob - -local.conf -*.swp -ws -tags -.DS_Store -pEp.egg-info -dist -build -.pythonhist -.gnupg -.lldb -.pEp_management.db* -.python_history -__pycache__ -test/Laptop -test/Library -test/Phone -test/TestInbox -test/Backup -test/lib -test/imap_settings.py -venv -launch.json -settings.json -# Default ignored files -.idea/ diff --git a/AUTHORS.txt b/AUTHORS.txt new file mode 100644 index 0000000..efddf90 --- /dev/null +++ b/AUTHORS.txt @@ -0,0 +1,9 @@ +# This is the list of pEpPythonAdapter's significant contributors. +# +# This does not necessarily list everyone who has contributed code +# To see the full list of contributors, see the revision history in +# source control. +Volker Birk (original author and maintainer) +Heck (current maintainer and author) +Hartmut Goebel (contributions) +juga (contributions) diff --git a/Makefile b/Makefile index 1cce38f..494ad53 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,18 @@ include Makefile.conf -.PHONY: all dist dist-egg dist-whl install install-prefix install-sys compile clean devenv envtest - +.PHONY: all compile compile-inplace dist dist-egg dist-whl install install-user venv envtest install-test test develop docs clean clean-all clean-docs all: dist +# Build +# ===== +compile: + python3 setup.py build_ext $(DEBUG_OPT) $(PREFIX_OPT) + +compile-inplace: + python3 setup.py build_ext $(DEBUG_OPT) $(PREFIX_OPT) --inplace + +# Packaging +# ========= # create wheel and egg package in dist/ dist: dist-whl dist-egg @@ -16,34 +25,66 @@ dist-egg: compile python3 setup.py bdist_egg -# installs the package into the user home -install: compile - python3 setup.py install --force --user - -# installs the package into PREFIX path -install-prefix: compile - python3 setup.py install --force $(PREFIX_OPT) - +# Installation +# ============ # installs the package system wide -install-sys: compile - python3 setup.py install --force - +install: compile + pip3 install . -# build the module into build/ -compile: - python3 setup.py build_ext $(DEBUG_OPT) $(PREFIX_OPT) +# installs the package into your user home +install-user: compile + pip3 install . --user -clean: - rm -r $(BUILD_DIR) - rm -r $(DIST_DIR) -devenv: +# Envrionment +# =========== +# Creates and activates a new venv that has the LD_LIBRARY_PATH/DYLD_LIBRARY_PATH +# already set for the prefix specified in local.conf +# Only activates venv if already existing +venv: + python3 -m venv $(VENV_DIR) LD_LIBRARY_PATH=$(PREFIX)/lib \ DYLD_LIBRARY_PATH=$(PREFIX)/lib \ - PYTHONPATH=$PYTHONPATH:`pwd`/build/lib.linux-x86_64-3.7:\ - PYTHONPATH=$PYTHONPATH:`pwd`/build/lib.macosx-10.9-x86_64-3.8:\ - `pwd`/src \ - bash -l + bash --rcfile $(VENV_DIR)/bin/activate +# Tests if the current environment is able to load the pEp module envtest: - python3 -c 'import pEp' \ No newline at end of file + python3 -c 'import pEp' + +# Test +# ==== +# Use these targets only in venv created with 'make venv' +install-test: compile + pip3 install .[test] + +# TODO: maybe use setup.py test? +# --forked, because every test needs a separate process, see PYADPT-100 +test: + pytest + + +# Development +develop: compile + pip install -e . + + +# Documentation +# ============= +docs: compile-inplace + make html -C docs/ + + +# Housekeeping +# ============ +clean-all: clean + rm -rf $(VENV_DIR) + +clean: clean-docs + rm -rf $(BUILD_DIR) + rm -rf $(DIST_DIR) + rm -rf $(PYTHON_ARTIFACTS) + rm -rf $(VERSION_FILE) + rm -rf $(BUILD_INPLACE) + +clean-docs: + make clean -C docs/ diff --git a/Makefile.conf b/Makefile.conf index e83e1e8..126cc86 100644 --- a/Makefile.conf +++ b/Makefile.conf @@ -3,10 +3,16 @@ HERE:=$(dir $(lastword $(MAKEFILE_LIST))) # Constants BUILD_DIR = ./build DIST_DIR = ./dist +VERSION_FILE = ./src/pEp/__version__.py +BUILD_INPLACE = ./src/pEp/_pEp.cpython-38-darwin.so +PYTHON_ARTIFACTS += ./.eggs +PYTHON_ARTIFACTS += ./src/pEp.egg-info +PYTHON_ARTIFACTS += ./.pytest_cache +VENV_DIR = ./venv # Build config Defaults DEBUG=0 -PREFIX?=$(HOME) +PREFIX= ######### Overrides ######### -include $(HERE)local.conf @@ -15,4 +21,6 @@ ifeq ($(DEBUG),1) DEBUG_OPT+=--debug endif -PREFIX_OPT += --prefix=$(PREFIX) \ No newline at end of file +ifneq ($(PREFIX),) + PREFIX_OPT += --prefix=$(PREFIX) +endif diff --git a/README.md b/README.md deleted file mode 100644 index d6ae5ed..0000000 --- a/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# pEpPythonAdapter - -## Build Insttructions - -These build instructions should work on: - * Linux (Verified 26.4.20 - heck) - * MacOS (Verified 26.4.20 - heck) - * Windows - -### Build -To build against system wide pEp installation (libs/includes) -```bash -python3 setup.py build_ext -``` - -To build against a pEp installation in your home dir (libs/includes): -```bash -python3 setup.py build_ext --local -``` - -To build against a pEp installation in a custom installation root (libs/includes) -```bash -python3 setup.py build_ext --prefix= -``` - -### Install - -To install the extension module system wide, as root, run: -```bash -python3 setup.py install -``` - -To install the extension module into you home dir -```bash -python3 setup.py install --user -``` - -To install the extension module into a custom destination -```bash -python3 setup.py install --prefix= -``` -Attention: The ~ (tilde) does not get expanded, but env vars work ($HOME). \ No newline at end of file diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..80f9118 --- /dev/null +++ b/README.rst @@ -0,0 +1,22 @@ +pEpPythonAdapter +================ +Python adapter for the `pEpEngine `_ + + +Documentation +------------- +Please find the documentation in the ``docs`` directory. + + +Issues +------ +If you are not pEp internal, please send a mail to: heck@pep.foundation + +If you are pEp internal, please open a ticket in our `jira bugtracker . + + +License +------- +|GNU AFFERO GENERAL PUBLIC LICENSE +|Version 3, 19 November 2007 +|See LICENSE.txt diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..ba501f6 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,19 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..543c6b1 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/docs/source/api/pEp.rst b/docs/source/api/pEp.rst new file mode 100644 index 0000000..586e991 --- /dev/null +++ b/docs/source/api/pEp.rst @@ -0,0 +1,12 @@ +pEp package +=========== + +Module contents +--------------- + +.. automodule:: pEp + :members: + :imported-members: + :undoc-members: + :show-inheritance: + diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..3a966f5 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,206 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. + +import os +import sys +sys.path.insert(0, os.path.abspath('../../src')) +# -- Project information ----------------------------------------------------- + +project = "pEpPythonAdapter" +copyright = "2020, Volker Birk, heck, Hartmut Goebel, juga" +author = "Volker Birk, heck, Hartmut Goebel, juga" + +import pEp +# The full version, including alpha/beta/rc tags +release = pEp.__version__ +# The short X.Y version +version = ".".join(release.split(".")[:2]) + +# DEBUG +print("release:", release) +print("version:", version) +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.viewcode", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = ".rst" + +# The master toctree document. +master_doc = "index" + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = None + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "nature" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = "pEpPythonAdapterdoc" + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ( + master_doc, + "pEpPythonAdapter.tex", + "pEpPythonAdapter Documentation", + "2020, Volker Birk, heck, juga", + "manual", + ) +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ( + master_doc, + "peppythonadapter", + "pEpPythonAdapter Documentation", + [author], + 1, + ) +] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + master_doc, + "pEpPythonAdapter", + "pEpPythonAdapter Documentation", + author, + "pEpPythonAdapter", + "One line description of project.", + "Miscellaneous", + ) +] + + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ["search.html"] + + +# -- Extension configuration ------------------------------------------------- + +# -- Options for intersphinx extension --------------------------------------- + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {"https://docs.python.org/": None} + +# -- Options for todo extension ---------------------------------------------- + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..8c59365 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,21 @@ +.. pEpPythonAdapter documentation master file, created by + sphinx-quickstart on Wed Oct 21 12:35:11 2020. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to pEpPythonAdapter's documentation! +============================================ + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + install + software_using + api/pEp +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/source/install.rst b/docs/source/install.rst new file mode 100644 index 0000000..4b94108 --- /dev/null +++ b/docs/source/install.rst @@ -0,0 +1,143 @@ +Installation +============ + +Requirements +------------ +In order to build, use or install this extension module, you need to have some +system dependencies already installed: + +* pEp-base (sequoia, libetpan, asn1c, yml2, pEpEngine, libpEpAdapter) +* boost-python + +These `build instructions `_ will get you all setup. + +Additionally, there is a `build script `_ +that executes these build instructions automatically (Debian and MacOS): + +.. Note:: If you dont install pEp-base system wide, but under a prefix, like /home/foo/local/, + you will need to have LD_LIBARY_PATH/DYLD_LIBRARY_PATH adjusted for all the following operations. + + +Build +----- +The pEpPythonAdapter is a python extension module that contains C/C++ code that needs to be +compiled first. So, before any use or installation, the module needs to be built. + +Build config +~~~~~~~~~~~~ +Create a local build config by creating a 'local.conf' file. There is a 'local.conf.example' describing +all the build options. You can use this as a template. + +``cp local.conf.example local.conf`` + +If you have pEp-base installed under a custom prefix (e.g. /home/foo/local) it is important +that you specify "PREFIX". + +Build +~~~~~ +To build the module just type: + +``make`` + +This will compile the C/C++ parts of the module and create the python packages in the .egg and .wheel format +in the dist/ dir. + + +Virtualenv +---------- +We recommend using a venv to work on/with the pEpPythonAdapter. +There is a convenience make target that will create and activate a venv that already has the LD_LIBRARY_PATH +or DYLD_LIBRARY_PATH set according to your ``local.conf``. +If the venv does not exist yet it will be created and activated. +If the venv already exists it will only be activated. + +``make venv`` + +After that, to install the pEp module into the venv, do: + +``make install`` + + +Installation +------------ +You can install the module in the in the following ways. + +To install the extension module system wide or into a venv, use: + +``make install`` + +To install the extension module into your home dir, use: + +``make install-user`` + + +Test +---- +To run the whole testsuite you need to first create/activate the venv: + +``make venv`` + +then install the test-dependencies: + +``make install-test`` + +And finally run the test-suite: + +``make test`` + + +Module Development +------------------ +To develop on the module itself, first of all create and activate a venv: + +``make venv`` + +Then, in the venv install the module in development mode. + +``make develop`` + +While developing there are two levels of changes. Changes to the python part of the module (pEp), and +changes to the C/C++ part of the module (_pEp). If you change just python code, the changes are effective immediately. +If you do changes to the C/C++ part you need to issue ``make develop`` again, to recompile the extension and install +the new binary (.so/.dylib) of the module into the venv. + +Documentation +------------- +The documentation of the pEpPythonAdapter uses `Sphinx `_ +Refer to the `Sphinx installation instructions `_ to install it. + +To generate the documentation in the HTML format, there is a make target "docs" +But first, you need to create/activate the venv or set the LD_LIBRARY_PATH manually. + +``make venv`` + +``make docs`` + +You can see the generated HTML documentation in a browser opening the directory +`docs/build/html`. + +Housekeeping +------------ +There are the following "clean" targets. + +To delete all the generated documentation, run: + +``make docs-clean`` + +To delete all the "derived" files including eggs, wheels, shared libs, build files and caches, run: + +``make clean`` + +To delete all of make clean plus the venv (should equal a complete reset), run: +``make clean-all`` + + + +Docker +------ +If you know how to use docker, you can avoid having to install all +the dependencies using the image +https://registry.gitlab.com/juga0/pepdocker/peppythonadapter. + +.. Note:: This docker image is not officially maintained and it exists only + until there is an official Debian one. diff --git a/docs/source/software_using.rst b/docs/source/software_using.rst new file mode 100644 index 0000000..11274c0 --- /dev/null +++ b/docs/source/software_using.rst @@ -0,0 +1,7 @@ +Software using pEpPythonAdapter +=============================== + +- `pEpProxy `_ +- `command-line-tool `_ +- `pEpPythonMixnet `_ +- `pEpSimulator `_ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a25f0cb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,18 @@ + +[build-system] +# Preparing for PEP-517/PEP-518, but not in effect yet. +# These requires are not effective yet, setup.cfg is. +requires =[ + "setuptools >=39.2.0", + "setuptools_scm >= 4.1.2", + "wheel >= 0.35.1" ] + +build-backend = "setuptools.build_meta" + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = "-rP --forked" +testpaths = [ + "tests", +] + diff --git a/setup.cfg b/setup.cfg old mode 100644 new mode 100755 index bc1d57b..6a82799 --- a/setup.cfg +++ b/setup.cfg @@ -1,16 +1,18 @@ [metadata] name = pEp -version = 2.1.0-RC2 url = https://pep.foundation -author = Volker Birk +download_url = +ext_package = pEp +author = Volker Birk, heck, Hartmut Goebel, juga author_email = vb@pep-project.org -maintainer = Matthias Heckmann +maintainer = heck maintainer_email = heck@pep.foundation description = p≡p for Python long_description = file: README.md keywords = pEp, crypto, end-to-end, python adapter, key management, high-level license = GNU Affero General Public License -license_files = LICENSE +license_files = LICENSE.txt +platforms = linux, macOs classifiers = Intended Audience :: Developers Topic :: Utilities @@ -21,3 +23,21 @@ classifiers = Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Development Status :: 3 - Alpha + +[options] +zip_safe = false +include_package_data = true +python_requires = >= 3.6 +test_suite = tests +install_requires = +# deprecated/redundant with pyproject.toml, but lets keep both ways around for now +setup_requires = + setuptools >=39.2.0 + setuptools_scm >= 4.1.2 + wheel >= 0.35.1 +[options.extras_require] +# To install these dependencies, run pip install .[test] +test = + pytest + pytest-forked +doc = sphinx diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index 83a73e9..e1e8136 --- a/setup.py +++ b/setup.py @@ -27,18 +27,16 @@ def pEpLog(*msg): msgstr += str(m) msgstr += separator func = inspect.currentframe().f_back.f_code - print(func.co_filename + " : " + func.co_name + " : " + msgstr) + print(func.co_filename + " : " + func.co_name + " : " + msgstr) -class BuildExtCommand(build_ext): +class BuildExtCommand(build_ext): user_options = build_ext.user_options + [ - ('local', None, 'Use local pEp install in HOME/USERPROFILE for libs/includes'), - ('prefix=', None, 'Use local pEp install in prefix for libs/includes'), + ('prefix=', None, 'Use pEp-base installation in prefix (libs/includes)'), ] def initialize_options(self): build_ext.initialize_options(self) - self.local = None != environ.get('PER_USER_DIRECTORY') self.prefix = getattr(self, "prefix=", None) def windowsGetInstallLocation(self): @@ -139,7 +137,6 @@ class BuildExtCommand(build_ext): def finalize_options(self): build_ext.finalize_options(self) - pEpLog("local: ", self.local) pEpLog("prefix: ", self.prefix) pEpLog("sys.platform: ", sys.platform) @@ -162,18 +159,11 @@ class BuildExtCommand(build_ext): includes = [] libdirs = [] - # Append home-dir - if self.local: - pEpLog("local mode") - home_include=[ join(home, 'include') ] - home_libdirs=[ join(home, 'lib') ] - includes += home_include - libdirs += home_libdirs # Append prefix-dir if self.prefix: - prefix_include=[ join(self.prefix, 'include') ] - prefix_libdirs=[ join(self.prefix, 'lib') ] + prefix_include = [join(self.prefix, 'include')] + prefix_libdirs = [join(self.prefix, 'lib')] includes += prefix_include libdirs += prefix_libdirs @@ -206,33 +196,32 @@ if sys.platform == 'win32': if sys.version_info[0] < 3: FileNotFoundError = EnvironmentError - module_pEp = Extension( - 'native_pEp', - sources = [ - 'src/pEp/native_pEp/pEpmodule.cc', - 'src/pEp/native_pEp/basic_api.cc', - 'src/pEp/native_pEp/identity.cc', - 'src/pEp/native_pEp/message.cc', - 'src/pEp/native_pEp/message_api.cc', - 'src/pEp/native_pEp/str_attr.cc', - # 'src/pEp/native_pEp/user_interface.cc', - ], + 'pEp._pEp', + sources=[ + 'src/pEp/_pEp/pEpmodule.cc', + 'src/pEp/_pEp/basic_api.cc', + 'src/pEp/_pEp/identity.cc', + 'src/pEp/_pEp/message.cc', + 'src/pEp/_pEp/message_api.cc', + 'src/pEp/_pEp/str_attr.cc', + # 'src/pEp/_pEp/user_interface.cc', + ], ) # "MAIN" Function setup( - name='pEp', - version='2.1.0-RC2', - description='p≡p for Python', - author="Volker Birk", - author_email="vb@pep-project.org", - maintainer="Heck", - maintainer_email="heck@pep.foundation", - package_dir={'':'src'}, + package_dir={'': 'src'}, packages=['pEp'], ext_modules=[module_pEp], cmdclass={ 'build_ext': BuildExtCommand, }, + # While not using a pyproject.toml, support setuptools_scm setup.cfg usage, + # see https://github.com/pypa/setuptools_scm/#setupcfg-usage + use_scm_version={ + 'write_to': 'src/pEp/__version__.py', + #TODO: fallback_version does not seem to work in case os missing tag + 'fallback_version' : '0.0.0-RC0' + } ) diff --git a/src/pEp/__init__.py b/src/pEp/__init__.py old mode 100644 new mode 100755 index 8a32e83..596266b --- a/src/pEp/__init__.py +++ b/src/pEp/__init__.py @@ -1,20 +1,39 @@ +# -*- coding: UTF-8 -*- # pEp package -# This file is being exectued upon 'import pEp' # -# __all__ could be used to limit the symbols exported when using from import * - +# The names that are in _pEp that do not begin with an underscore, will be be imported into, and "re-exported" from this module. +# They are defined in boost-python/C++, and are directly part of the pEpPythonAdapter API +# The names that are in _pEp that DO begin with an underscore, will not be imported into this module, but will be accessible like _pEp._underscore_function(). +# They are not directly part of the pEpPythonAdapter API, and are meant to be wrapped in this module. +# Example: +# def underscore_function(): +# _pEp._underscore_function() -# Import all symbols EXCEPT the ones beginning with underscore into the current namespace -from native_pEp import * -# TODO: inter-pkg ref to make sure which native_pEp in sys.path gets loaded -# like: pEp.native_pEp -# import the module -import native_pEp +# __all__ could be used to limit the symbols exported when using from import * +try: + from .__version__ import version as __version__ +except ImportError: + import warnings + warnings.warn("Error loading build-time defined __version__.py, trying setuptools now...") + try: + import setuptools_scm + __version__ = setuptools_scm.get_version() + del setuptools_scm + except Exception: + warnings.warn('could not determine %s package version' % __name__) + __version__ = '0.0.dev0+unknown' + +# Imports all symbols EXCEPT the ones beginning with underscore +from ._pEp import * + +# import the native module into the current namespace because we also need to access the names beginning +# with an underscore (of _pEp), but we dont want to import them into this module +import pEp._pEp # Executed on module import def init(): print(init, "called") - native_pEp._init_after_main_module() + _pEp._init_after_main_module() def message_to_send(msg): @@ -41,12 +60,4 @@ def notify_handshake(me, partner, signal): print("overwrite this method") -# Executed when run as script -def main(): - print("I am being run as a script") - -# MAIN -if __name__ == "__main__": - main() -else: - init() \ No newline at end of file +init() diff --git a/src/pEp/_pEp/basic_api.cc b/src/pEp/_pEp/basic_api.cc new file mode 100644 index 0000000..50cc802 --- /dev/null +++ b/src/pEp/_pEp/basic_api.cc @@ -0,0 +1,161 @@ +// This file is under GNU Affero General Public License 3.0 +// see LICENSE.txt + +// System +#include + +// Engine +#include +#include +#include + +// local +#include "basic_api.hh" + +namespace pEp { + namespace PythonAdapter { + using namespace std; + + void update_identity(Identity &ident) { + if (ident.address() == "") + throw invalid_argument("address needed"); + if (ident.user_id() == PEP_OWN_USERID) + throw runtime_error("update_identity: '" + PEP_OWN_USERID + "' may only be used for own identities"); + + PEP_STATUS status = update_identity(Adapter::session(), ident); + _throw_status(status); + } + + void myself(Identity &ident) { + if (ident.address() == "") + throw invalid_argument("address needed"); + if (ident.username() == "") + throw invalid_argument("username needed"); + + if (ident.user_id() == "") + ident.user_id(ident.address()); + + PEP_STATUS status = myself(Adapter::session(), ident); + _throw_status(status); + } + + string _trustwords(Identity me, Identity partner, string lang, bool full) { + if (me.fpr() == "" || partner.fpr() == "") + throw invalid_argument("fingerprint needed in Identities"); + + if (lang == "" && me.lang() == partner.lang()) + lang = me.lang(); + + char *words = NULL; + size_t size = 0; + PEP_STATUS status = get_trustwords(Adapter::session(), me, partner, + lang.c_str(), &words, &size, full); + _throw_status(status); + return words; + } + + void trust_personal_key(Identity ident) { + if (ident.fpr() == "") + throw invalid_argument("fingerprint needed in Identities"); + if (ident.user_id() == "") + throw invalid_argument("user_id must be provided"); + + PEP_STATUS status = trust_personal_key(Adapter::session(), ident); + _throw_status(status); + } + + void set_identity_flags(Identity ident, identity_flags_t flags) { + if (ident.address() == "") + throw invalid_argument("address needed"); + if (ident.user_id() == "") + throw invalid_argument("user_id needed"); + + PEP_STATUS status = set_identity_flags(Adapter::session(), ident, flags); + _throw_status(status); + } + + void unset_identity_flags(Identity ident, identity_flags_t flags) { + if (ident.address() == "") + throw invalid_argument("address needed"); + if (ident.user_id() == "") + throw invalid_argument("user_id needed"); + + PEP_STATUS status = unset_identity_flags(Adapter::session(), ident, flags); + _throw_status(status); + } + + void key_reset_trust(Identity ident) { + if (ident.fpr() == "") + throw invalid_argument("fpr needed"); + if (ident.address() == "") + throw invalid_argument("address needed"); + if (ident.user_id() == "") + throw invalid_argument("user_id needed"); + + PEP_STATUS status = key_reset_trust(Adapter::session(), ident); + _throw_status(status); + } + + + boost::python::list import_key(string key_data) { + ::identity_list *private_keys = NULL; + PEP_STATUS status = ::import_key(Adapter::session(), key_data.c_str(), key_data.size(), &private_keys); + if (status && status != PEP_KEY_IMPORTED) + _throw_status(status); + + auto result = boost::python::list(); + for (::identity_list *il = private_keys; il && il->ident; il = il->next) { + ::pEp_identity *ident = ::identity_dup(il->ident); + if (!ident) { + free_identity_list(private_keys); + throw bad_alloc(); + } + result.append(Identity(ident)); + } + + free_identity_list(private_keys); + return result; + } + + string export_key(Identity ident) { + PEP_STATUS status = PEP_STATUS_OK; + char *key_data = NULL; + size_t size; + status = ::export_key(Adapter::session(), ident.fpr().c_str(), &key_data, &size); + + _throw_status(status); + return key_data; + } + + string export_secret_key(Identity ident) { + PEP_STATUS status = PEP_STATUS_OK; + char *key_data = NULL; + size_t size; + status = ::export_secret_key(Adapter::session(), ident.fpr().c_str(), &key_data, &size); + + _throw_status(status); + return key_data; + } + + void set_own_key(Identity &ident, string fpr) { + if (ident.address() == "") + throw invalid_argument("address needed"); + if (ident.username() == "") + throw invalid_argument("username needed"); + if (ident.user_id() == "") + throw invalid_argument("user_id needed"); + if (fpr == "") + throw invalid_argument("fpr needed"); + + + const char *fpr_c = fpr.c_str(); + PEP_STATUS status = set_own_key(Adapter::session(), ident, fpr_c); + _throw_status(status); + } + + } // namespace PythonAdapter +} // namespace pEp + + diff --git a/src/pEp/_pEp/basic_api.hh b/src/pEp/_pEp/basic_api.hh new file mode 100644 index 0000000..e4b99f2 --- /dev/null +++ b/src/pEp/_pEp/basic_api.hh @@ -0,0 +1,37 @@ +// This file is under GNU Affero General Public License 3.0 +// see LICENSE.txt + +#ifndef BASIC_API_HH +#define BASIC_API_HH + +#include "pEpmodule.hh" + +namespace pEp { + namespace PythonAdapter { + + void update_identity(Identity &ident); + + void myself(Identity &ident); + + string _trustwords(Identity me, Identity partner, string lang, bool full); + + void trust_personal_key(Identity ident); + + void set_identity_flags(Identity ident, identity_flags_t flags); + + void unset_identity_flags(Identity ident, identity_flags_t flags); + + void key_reset_trust(Identity ident); + + boost::python::list import_key(string key_data); + + string export_key(Identity ident); + + string export_secret_key(Identity ident); + + void set_own_key(Identity &ident, string fpr); + + } /* namespace PythonAdapter */ +} /* namespace pEp */ + +#endif /* BASIC_API_HH */ \ No newline at end of file diff --git a/src/pEp/_pEp/identity.cc b/src/pEp/_pEp/identity.cc new file mode 100644 index 0000000..5274c1a --- /dev/null +++ b/src/pEp/_pEp/identity.cc @@ -0,0 +1,257 @@ +// This file is under GNU Affero General Public License 3.0 +// see LICENSE.txt + +// System +#include +#include + +// Engine +#include +#include +#include + +// local +#include "identity.hh" +#include "pEpmodule.hh" +#include "basic_api.hh" +#include "message_api.hh" + +namespace pEp { + namespace PythonAdapter { + using namespace std; + using namespace boost::python; + + Identity::Identity(string address, string username, string user_id, + string fpr, int comm_type, string lang, identity_flags_t flags) + : _ident(new_identity(address.c_str(), fpr.c_str(), user_id.c_str(), + username.c_str()), &::free_identity) { + if (!_ident) + throw bad_alloc(); + _ident->comm_type = (PEP_comm_type) comm_type; + _ident->flags = (identity_flags_t) flags; + this->lang(lang); + } + + Identity::Identity(const Identity &second) + : _ident(second._ident) { + + } + + Identity::Identity(pEp_identity *ident) + : _ident(ident, &::free_identity) { + + } + + Identity::~Identity() { + + } + + Identity::operator pEp_identity *() { + return _ident.get(); + } + + Identity::operator const pEp_identity *() const { + return _ident.get(); + } + + string Identity::_repr() { + stringstream build; + build << "Identity("; + string address; + if (_ident->address) + address = string(_ident->address); + build << repr(address) << ", "; + string username; + if (_ident->username) + username = string(_ident->username); + build << repr(username) << ", "; + string user_id; + if (_ident->user_id) + user_id = string(_ident->user_id); + build << repr(user_id) << ", "; + string fpr; + if (_ident->fpr) + fpr = string(_ident->fpr); + build << repr(fpr) << ", "; + build << (int) _ident->comm_type << ", "; + string lang = _ident->lang; + build << repr(lang) << ")"; + return build.str(); + } + + string Identity::_str() { + if (!(_ident->address && _ident->address[0])) + return ""; + if (!(_ident->username && _ident->username[0])) + return _ident->address; + return string(_ident->username) + " <" + _ident->address + ">"; + } + + void Identity::username(string value) { + if (value.length() && value.length() < 5) + throw length_error("username must be at least 5 characters"); + + str_attr(_ident->username, value); + } + + void Identity::lang(string value) { + if (value == "") + memset(_ident->lang, 0, 3); + else if (value.length() != 2) + throw length_error("length of lang must be 2"); + else + memcpy(_ident->lang, value.c_str(), 3); + } + + string Identity::lang() { + return _ident->lang; + } + + int Identity::rating() { + if (!(_ident->address)) + throw invalid_argument("address must be given"); + + PEP_rating rating = PEP_rating_undefined; + PEP_STATUS status = ::identity_rating(Adapter::session(), _ident.get(), &rating); + _throw_status(status); + + return (int) rating; + } + + PEP_color Identity::color() { + return _color(rating()); + } + + Identity Identity::copy() { + pEp_identity *dup = ::identity_dup(*this); + if (!dup) + throw bad_alloc(); + + return Identity(dup); + } + + Identity Identity::deepcopy(dict &) { + return copy(); + } + + void Identity::update() { + update_identity(*this); + } + + void Identity::key_reset(string fpr) { + PEP_STATUS status = ::key_reset_identity(Adapter::session(), *this, + fpr != "" ? fpr.c_str() : nullptr); + _throw_status(status); + } + + void Identity::key_mistrusted() { + PEP_STATUS status = ::key_mistrusted(Adapter::session(), *this); + _throw_status(status); + } + + bool Identity::is_pEp_user() { + bool result; + PEP_STATUS status = ::is_pEp_user(Adapter::session(), *this, &result); + _throw_status(status); + return result; + } + + void Identity::enable_for_sync() { + PEP_STATUS status = ::enable_identity_for_sync(Adapter::session(), *this); + _throw_status(status); + } + + void Identity::disable_for_sync() { + PEP_STATUS status = ::disable_identity_for_sync(Adapter::session(), *this); + _throw_status(status); + } + +// Myself::Myself(string address, string username, string user_id, string lang) +// : Identity(address, username, user_id, "", 0, lang) { +// if (!(address.length() && username.length())) +// throw invalid_argument("address and username must be set"); +// if (lang.length() && lang.length() != 2) +// throw length_error("lang must be an ISO 639-1 language code or empty"); +// +// // FIXME: should set .me +// // _ident->me = true; +// if (user_id.length()) +// throw runtime_error("user_id feature not yet implemented for Myself"); +// } + +// void Myself::update() { +// pEp::PythonAdapter::myself(*this); +// } + + Identity identity_attr(pEp_identity *&ident) { + if (!ident) + throw out_of_range("no identity assigned"); + + pEp_identity *_dup = ::identity_dup(ident); + if (!_dup) + throw bad_alloc(); + + Identity _ident(_dup); + return _ident; + } + + void identity_attr(pEp_identity *&ident, object value) { + Identity &_ident = extract(value); + pEp_identity *_dup = ::identity_dup(_ident); + if (!_dup) + throw bad_alloc(); + PEP_STATUS status = ::update_identity(Adapter::session(), _dup); + _throw_status(status); + ::free_identity(ident); + ident = _dup; + } + + boost::python::list identitylist_attr(identity_list *&il) { + boost::python::list result; + + for (identity_list *_il = il; _il && _il->ident; _il = _il->next) { + pEp_identity *ident = ::identity_dup(_il->ident); + if (!ident) + throw bad_alloc(); + result.append(object(Identity(ident))); + } + + return result; + } + + void identitylist_attr(identity_list *&il, boost::python::list value) { + identity_list *_il = ::new_identity_list(NULL); + if (!_il) + throw bad_alloc(); + + identity_list *_i = _il; + for (int i = 0; i < len(value); i++) { + extract < Identity & > extract_identity(value[i]); + if (!extract_identity.check()) { + free_identity_list(_il); + } + pEp_identity *_ident = extract_identity(); + pEp_identity *_dup = ::identity_dup(_ident); + if (!_dup) { + ::free_identity_list(_il); + throw bad_alloc(); + } + PEP_STATUS status = ::update_identity(Adapter::session(), _dup); + if (status != PEP_STATUS_OK) { + ::free_identity_list(_il); + _throw_status(status); + } + _i = ::identity_list_add(_i, _dup); + if (!_i) { + ::free_identity_list(_il); + throw bad_alloc(); + } + } + + ::free_identity_list(il); + il = _il; + } + + } // namespace PythonAdapter +} // namespace pEp + diff --git a/src/pEp/_pEp/identity.hh b/src/pEp/_pEp/identity.hh new file mode 100644 index 0000000..9149aaf --- /dev/null +++ b/src/pEp/_pEp/identity.hh @@ -0,0 +1,121 @@ +// This file is under GNU Affero General Public License 3.0 +// see LICENSE.txt + +#ifndef IDENTITY_HH +#define IDENTITY_HH + +// System +#include +#include +#include +#include + +// Engine +#include +#include + +//libpEpAdapter +#include "pEp/Adapter.hh" + +// local +#include "str_attr.hh" + +namespace pEp { + namespace PythonAdapter { + + using std::string; + using std::shared_ptr; + +// Identity is owning a pEp_identity + + class Identity { + protected: + shared_ptr _ident; + + public: + Identity(string address = "", string username = "", + string user_id = "", string fpr = "", int comm_type = 0, + string lang = "", identity_flags_t flags = 0); + + Identity(const Identity &second); + + Identity(pEp_identity *ident); + + virtual ~Identity(); + + operator pEp_identity *(); + + operator const pEp_identity *() const; + + string _repr(); + + string _str(); + + string address() { return str_attr(_ident->address); } + + void address(string value) { str_attr(_ident->address, value); } + + string fpr() { return str_attr(_ident->fpr); } + + void fpr(string value) { str_attr(_ident->fpr, value); } + + string user_id() { return str_attr(_ident->user_id); } + + void user_id(string value) { str_attr(_ident->user_id, value); } + + string username() { return str_attr(_ident->username); } + + void username(string value); + + PEP_comm_type comm_type() { return _ident->comm_type; } + + void comm_type(PEP_comm_type value) { _ident->comm_type = value; }; + + std::string lang(); + + void lang(std::string value); + + identity_flags_t flags() { return _ident->flags; } + + void flags(identity_flags_t flags) { _ident->flags = flags; } + + int rating(); + + PEP_color color(); + + Identity copy(); + + Identity deepcopy(dict &memo); + + virtual void update(); + + void key_reset(string fpr = ""); + + void key_mistrusted(); + + bool is_pEp_user(); + + void enable_for_sync(); + + void disable_for_sync(); + }; + +// class Myself : public Identity { +// public: +// Myself(string address, string username, string user_id = "", string lang = ""); +// +// virtual void update(); +// }; + + Identity identity_attr(pEp_identity *&ident); + + void identity_attr(pEp_identity *&ident, object value); + + boost::python::list identitylist_attr(identity_list *&il); + + void identitylist_attr(identity_list *&il, boost::python::list value); + + } // namespace PythonAdapter +} // namespace pEp + +#endif /* IDENTITY_HH */ diff --git a/src/pEp/_pEp/message.cc b/src/pEp/_pEp/message.cc new file mode 100644 index 0000000..ffdcca7 --- /dev/null +++ b/src/pEp/_pEp/message.cc @@ -0,0 +1,385 @@ +// This file is under GNU Affero General Public License 3.0 +// see LICENSE.txt + +// System +#include +#include +#include +#include +#include +#include + +// Engine +#include +#include +#include + +// local +#include "message.hh" +#include "message_api.hh" + +namespace pEp { + namespace PythonAdapter { + using namespace std; + using namespace boost::python; + + Message::Blob::Blob(bloblist_t *bl, bool chained) : + _bl(bl), part_of_chain(chained) { + if (!_bl) + throw bad_alloc(); + } + + Message::Blob::Blob(object data, string mime_type, string filename) : + _bl(new_bloblist(NULL, 0, NULL, NULL)), part_of_chain(false) { + if (!_bl) + throw bad_alloc(); + + Py_buffer src; + int result = PyObject_GetBuffer(data.ptr(), &src, PyBUF_CONTIG_RO); + if (result) + throw invalid_argument("need a contiguous buffer to read"); + + char *mem = (char *) malloc(src.len); + if (!mem) { + PyBuffer_Release(&src); + throw bad_alloc(); + } + + memcpy(mem, src.buf, src.len); + free(_bl->value); + _bl->size = src.len; + _bl->value = mem; + + PyBuffer_Release(&src); + + this->mime_type(mime_type); + this->filename(filename); + } + + Message::Blob::Blob(const Message::Blob &second) : + _bl(second._bl), part_of_chain(true) { + + } + + Message::Blob::~Blob() { + if (!part_of_chain) { + free(_bl->value); + free(_bl); + } + } + + string Message::Blob::_repr() { + stringstream build; + build << "Blob("; + if (!_bl) { + build << "b'', '', ''"; + } else { + build << "bytes(" << _bl->size << "), "; + string mime_type; + if (_bl->mime_type) + mime_type = string(_bl->mime_type); + string filename; + if (_bl->filename) + filename = string(_bl->filename); + build << repr(mime_type) << ", "; + build << repr(filename); + } + build << ")"; + return build.str(); + } + + int Message::Blob::getbuffer(PyObject *self, Py_buffer *view, int flags) { + bloblist_t *bl = NULL; + + try { + Message::Blob &blob = extract(self); + bl = blob._bl; + } + catch (exception &e) { + PyErr_SetString(PyExc_RuntimeError, "extract not possible"); + view->obj = NULL; + return -1; + } + + if (!(bl && bl->value)) { + PyErr_SetString(PyExc_RuntimeError, "no data available"); + view->obj = NULL; + return -1; + } + + return PyBuffer_FillInfo(view, self, bl->value, bl->size, 0, flags); + } + + string Message::Blob::decode(string encoding) { + if (encoding == "") { + string _mime_type = _bl->mime_type ? _bl->mime_type : ""; + encoding = "ascii"; + + if (_mime_type == "application/pEp.sync") + encoding = "pep.sync"; + + if (_mime_type == "application/pEp.keyreset") + encoding = "pep.distribution"; + + } + object codecs = import("codecs"); + object _decode = codecs.attr("decode"); + return call(_decode.ptr(), this, encoding); + } + + PyBufferProcs Message::Blob::bp = {getbuffer, NULL}; + + Message::Message(int dir, Identity *from) + : _msg(new_message((PEP_msg_direction) dir), &free_message) { + if (!_msg) + throw bad_alloc(); + + if (from) { + _msg->from = ::identity_dup(*from); + if (!_msg->from) + throw bad_alloc(); + _msg->dir = (PEP_msg_direction) dir; + } + } + + Message::Message(string mimetext) + : _msg(NULL, &free_message) { + message *_cpy; + PEP_STATUS status = mime_decode_message(mimetext.c_str(), + mimetext.size(), &_cpy, NULL); + switch (status) { + case PEP_STATUS_OK: + if (_cpy) + _cpy->dir = PEP_dir_outgoing; + else + _cpy = new_message(PEP_dir_outgoing); + + if (!_cpy) + throw bad_alloc(); + + _msg = shared_ptr(_cpy); + break; + + case PEP_BUFFER_TOO_SMALL: + throw runtime_error("mime_decode_message: buffer too small"); + + case PEP_CANNOT_CREATE_TEMP_FILE: + throw runtime_error("mime_decode_message: cannot create temp file"); + + case PEP_OUT_OF_MEMORY: + throw bad_alloc(); + + default: + stringstream build; + build << "mime_decode_message: unknown error (" << (int) status << ")"; + throw runtime_error(build.str()); + } + } + + Message::Message(const Message &second) + : _msg(second._msg) { + if (!_msg.get()) + throw bad_alloc(); + } + + Message::Message(message *msg) + : _msg(::message_dup(msg), &free_message) { + + } + + Message::~Message() { + + } + + Message::operator message *() { + return _msg.get(); + } + + Message::operator const message *() const { + return _msg.get(); + } + + string Message::_str() { + if (!(_msg->from && _msg->from->address && _msg->from->address[0])) + throw out_of_range(".from_.address missing"); + + char *mimetext; + string result; + + PEP_STATUS status = mime_encode_message(*this, false, &mimetext, false); + switch (status) { + case PEP_STATUS_OK: + result = mimetext; + free(mimetext); + break; + + case PEP_BUFFER_TOO_SMALL: + throw runtime_error("mime_encode_message: buffer too small"); + + case PEP_CANNOT_CREATE_TEMP_FILE: + throw runtime_error("mime_encode_message: cannot create temp file"); + + case PEP_OUT_OF_MEMORY: + throw bad_alloc(); + + default: + stringstream build; + build << "mime_encode_message: unknown error (" << (int) status << ")"; + throw runtime_error(build.str()); + } + + return result; + } + + string Message::_repr() { + stringstream build; + build << "Message(" << repr(_str()) << ")"; + return build.str(); + } + + boost::python::tuple Message::attachments() { + boost::python::list l; + + for (bloblist_t *bl = _msg->attachments; bl && bl->value; bl = + bl->next) { + l.append(Blob(bl, true)); + } + + return boost::python::tuple(l); + } + + void Message::attachments(boost::python::list value) { + bloblist_t *bl = new_bloblist(NULL, 0, NULL, NULL); + if (!bl) + throw bad_alloc(); + + bloblist_t *_l = bl; + for (int i = 0; i < len(value); i++) { + Message::Blob &blob = extract(value[i]); + _l = bloblist_add(_l, blob._bl->value, blob._bl->size, + blob._bl->mime_type, blob._bl->filename); + if (!_l) { + for (_l = bl; _l && _l->value;) { + free(_l->mime_type); + free(_l->filename); + bloblist_t *_ll = _l; + _l = _l->next; + free(_ll); + } + throw bad_alloc(); + } + } + + for (int i = 0; i < len(value); i++) { + Message::Blob &blob = extract(value[i]); + blob._bl->value = NULL; + blob._bl->size = 0; + free(blob._bl->mime_type); + blob._bl->mime_type = NULL; + free(blob._bl->filename); + blob._bl->filename = NULL; + } + + free_bloblist(_msg->attachments); + _msg->attachments = bl; + } + + Message Message::encrypt() { + boost::python::list extra; + return encrypt_message(*this, extra, PEP_enc_PGP_MIME, 0); + } + + Message Message::_encrypt(boost::python::list extra, int enc_format, int flags) { + if (!enc_format) + enc_format = PEP_enc_PGP_MIME; + return encrypt_message(*this, extra, enc_format, flags); + } + + boost::python::tuple Message::decrypt(int flags) { + return pEp::PythonAdapter::decrypt_message(*this, flags); + } + + PEP_rating Message::outgoing_rating() { + if (_msg->dir != PEP_dir_outgoing) + throw invalid_argument("Message.dir must be outgoing"); + + if (from().address() == "") + throw invalid_argument("from.address needed"); + if (from().username() == "") + throw invalid_argument("from.username needed"); + + if (len(to()) + len(cc()) == 0) + throw invalid_argument("either to or cc needed"); + + PEP_STATUS status = myself(Adapter::session(), _msg->from); + _throw_status(status); + + PEP_rating rating = PEP_rating_undefined; + status = outgoing_message_rating(Adapter::session(), *this, &rating); + _throw_status(status); + + return rating; + } + + PEP_color Message::outgoing_color() { + return _color(outgoing_rating()); + } + + Message Message::copy() { + message *dup = message_dup(*this); + if (!dup) + throw bad_alloc(); + return Message(dup); + } + + Message Message::deepcopy(dict &) { + return copy(); + } + + Message outgoing_message(Identity me) { + if (me.address().empty() || me.user_id().empty()) + throw runtime_error("at least address and user_id of own user needed"); + + ::myself(Adapter::session(), me); + auto m = Message(PEP_dir_outgoing, &me); + return m; + } + + static object update(Identity ident) { + if (ident.address().empty()) + throw runtime_error("at least address needed"); + update_identity(Adapter::session(), ident); + return object(ident); + } + + static boost::python::list update(boost::python::list il) { + for (int i = 0; i < len(il); i++) { + update(extract(il[i])); + } + + return il; + } + + Message incoming_message(string mime_text) { + auto m = Message(mime_text); + m.dir(PEP_dir_incoming); + + try { + m.from(update(m.from())); + } + catch (out_of_range &) {} + + try { + m.recv_by(update(m.recv_by())); + } + catch (out_of_range &) {} + + m.to(update(m.to())); + m.cc(update(m.cc())); + m.reply_to(update(m.reply_to())); + + return m; + } + + } // namespace PythonAdapter +} // namespace pEp diff --git a/src/pEp/_pEp/message.hh b/src/pEp/_pEp/message.hh new file mode 100644 index 0000000..5eba845 --- /dev/null +++ b/src/pEp/_pEp/message.hh @@ -0,0 +1,194 @@ +// This file is under GNU Affero General Public License 3.0 +// see LICENSE.txt + +#ifndef MESSAGE_HH +#define MESSAGE_HH + +// System +#include +#include +#include + +// Engine +#include +#include + +// local +#include "str_attr.hh" +#include "identity.hh" + +namespace pEp { + namespace PythonAdapter { + using std::string; + using std::runtime_error; + using std::invalid_argument; + using boost::lexical_cast; + +// Message is owning a message struct + + class Message { + shared_ptr<::message> _msg; + + public: + // Blob is owning a bloblist_t struct - or not and just managing + // one depending on part_of_chain + + class Blob { + bloblist_t *_bl; + bool part_of_chain; + + public: + Blob(bloblist_t *bl = new_bloblist(NULL, 0, NULL, NULL), + bool chained = false); + + Blob(object data, string mime_type = "", string filename = ""); + + Blob(const Blob &second); + + ~Blob(); + + string _repr(); + + string mime_type() { return _bl ? str_attr(_bl->mime_type) : ""; } + + void mime_type(string value) { str_attr(_bl->mime_type, value); } + + string filename() { return str_attr(_bl->filename); } + + void filename(string value) { str_attr(_bl->filename, value); } + + size_t size() { return _bl->size; } + + string decode(string encoding); + + string decode() { return decode(""); } + + static PyBufferProcs bp; + + friend class Message; + + protected: + static int getbuffer(PyObject *self, Py_buffer *view, int flags); + }; + + Message(int dir = PEP_dir_outgoing, Identity *from = NULL); + + Message(string mimetext); + + Message(const Message &second); + + Message(message *msg); + + ~Message(); + + operator message *(); + + operator const message *() const; + + string _str(); + + string _repr(); + + PEP_msg_direction dir() { return _msg->dir; } + + void dir(PEP_msg_direction value) { _msg->dir = value; } + + string id() { return str_attr(_msg->id); } + + void id(string value) { str_attr(_msg->id, value); } + + string shortmsg() { return str_attr(_msg->shortmsg); } + + void shortmsg(string value) { str_attr(_msg->shortmsg, value); } + + string longmsg() { return str_attr(_msg->longmsg); } + + void longmsg(string value) { str_attr(_msg->longmsg, value); } + + string longmsg_formatted() { return str_attr(_msg->longmsg_formatted); } + + void longmsg_formatted(string value) { str_attr(_msg->longmsg_formatted, value); } + + boost::python::tuple attachments(); + + void attachments(boost::python::list value); + + time_t sent() { return timestamp_attr(_msg->sent); } + + void sent(time_t value) { timestamp_attr(_msg->sent, value); } + + time_t recv() { return timestamp_attr(_msg->recv); } + + void recv(time_t value) { timestamp_attr(_msg->recv, value); } + + Identity from() { return identity_attr(_msg->from); } + + void from(object value) { identity_attr(_msg->from, value); } + + boost::python::list to() { return identitylist_attr(_msg->to); } + + void to(boost::python::list value) { identitylist_attr(_msg->to, value); } + + Identity recv_by() { return identity_attr(_msg->recv_by); } + + void recv_by(object value) { identity_attr(_msg->recv_by, value); } + + boost::python::list cc() { return identitylist_attr(_msg->cc); } + + void cc(boost::python::list value) { identitylist_attr(_msg->cc, value); } + + boost::python::list bcc() { return identitylist_attr(_msg->bcc); } + + void bcc(boost::python::list value) { identitylist_attr(_msg->bcc, value); } + + boost::python::list reply_to() { return identitylist_attr(_msg->reply_to); } + + void reply_to(boost::python::list value) { identitylist_attr(_msg->reply_to, value); } + + boost::python::list in_reply_to() { return strlist_attr(_msg->in_reply_to); } + + void in_reply_to(boost::python::list value) { strlist_attr(_msg->in_reply_to, value); } + + boost::python::list references() { return strlist_attr(_msg->references); } + + void references(boost::python::list value) { strlist_attr(_msg->references, value); } + + boost::python::list keywords() { return strlist_attr(_msg->keywords); } + + void keywords(boost::python::list value) { strlist_attr(_msg->keywords, value); } + + string comments() { return str_attr(_msg->comments); } + + void comments(string value) { str_attr(_msg->comments, value); } + + dict opt_fields() { return strdict_attr(_msg->opt_fields); } + + void opt_fields(dict value) { return strdict_attr(_msg->opt_fields, value); } + + PEP_enc_format enc_format() { return _msg->enc_format; } + + void enc_format(PEP_enc_format value) { _msg->enc_format = value; } + + Message encrypt(); + + Message _encrypt(boost::python::list extra, int enc_format = 4, int flags = 0); + + boost::python::tuple decrypt(int flags = 0); + + PEP_rating outgoing_rating(); + + PEP_color outgoing_color(); + + Message deepcopy(dict &memo); + + Message copy(); + }; + + Message outgoing_message(Identity me); + + Message incoming_message(string mime_text); + + } /* namespace PythonAdapter */ +} /* namespace pEp */ + +#endif /* MESSAGE_HH */ \ No newline at end of file diff --git a/src/pEp/_pEp/message_api.cc b/src/pEp/_pEp/message_api.cc new file mode 100644 index 0000000..1c2e3c7 --- /dev/null +++ b/src/pEp/_pEp/message_api.cc @@ -0,0 +1,161 @@ +// This file is under GNU Affero General Public License 3.0 +// see LICENSE.txt + +// Engine +#include +#include +#include +#include +#include + +// local +#include "message_api.hh" +#include "basic_api.hh" + +namespace pEp { + namespace PythonAdapter { + using namespace std; + using namespace boost::python; + + Message encrypt_message(Message src, boost::python::list extra, int enc_format, int flags) { + Identity _from = src.from(); + if (_from.address() == "") + throw invalid_argument("encrypt_message: src.from_.address empty"); + if (_from.username() == "") + throw invalid_argument("encrypt_message: src.from_.username empty"); + + if (_from.user_id() == "") + src.from().user_id(_from.address()); + + stringlist_t *_extra = to_stringlist(extra); + PEP_enc_format _enc_format = (PEP_enc_format) enc_format; + PEP_encrypt_flags_t _flags = (PEP_encrypt_flags_t) flags; + message *_dst = NULL; + + message *_src = src; + PEP_STATUS status = encrypt_message(Adapter::session(), _src, _extra, &_dst, + _enc_format, _flags); + free_stringlist(_extra); + _throw_status(status); + + if (!_dst || _dst == _src) + return Message(_src); + + return Message(_dst); + } + + boost::python::tuple decrypt_message(Message src, int flags) { + message *_dst = NULL; + stringlist_t *_keylist = NULL; + PEP_rating _rating = PEP_rating_undefined; + PEP_decrypt_flags_t _flags = (PEP_decrypt_flags_t) flags; + message *_src = src; + + PEP_STATUS status = ::decrypt_message(Adapter::session(), _src, &_dst, &_keylist, + &_rating, &_flags); + _throw_status(status); + + boost::python::list keylist; + if (_keylist) { + keylist = from_stringlist(_keylist); + free_stringlist(_keylist); + } + + Message dst = _dst ? Message(_dst) : Message(src); + return boost::python::make_tuple(dst, keylist, _rating, _flags); + } + + PEP_color _color(int rating) { + return ::color_from_rating((PEP_rating) rating); + } + + boost::python::tuple sync_decode(object buffer) { + Py_buffer src; + int result = PyObject_GetBuffer(buffer.ptr(), &src, PyBUF_CONTIG_RO); + if (result) + throw invalid_argument("need a contiguous buffer to read"); + + char *dst = NULL; + PEP_STATUS status = PER_to_XER_Sync_msg((char *) src.buf, src.len, &dst); + PyBuffer_Release(&src); + _throw_status(status); + + string _dst(dst); + free(dst); + return boost::python::make_tuple(_dst, 0); + } + + static boost::python::tuple sync_encode(string text) { + char *data = NULL; + size_t size = 0; + PEP_STATUS status = XER_to_PER_Sync_msg(text.c_str(), &data, &size); + _throw_status(status); + + PyObject *ba = PyBytes_FromStringAndSize(data, size); + free(data); + if (!ba) + throw bad_alloc(); + + return boost::python::make_tuple(object(handle<>(ba)), 0); + } + + boost::python::tuple Distribution_decode(object buffer) { + Py_buffer src; + int result = PyObject_GetBuffer(buffer.ptr(), &src, PyBUF_CONTIG_RO); + if (result) + throw invalid_argument("need a contiguous buffer to read"); + + char *dst = NULL; + PEP_STATUS status = PER_to_XER_Distribution_msg((char *) src.buf, src.len, &dst); + PyBuffer_Release(&src); + _throw_status(status); + + string _dst(dst); + free(dst); + return boost::python::make_tuple(_dst, 0); + } + + static boost::python::tuple Distribution_encode(string text) { + char *data = NULL; + size_t size = 0; + PEP_STATUS status = XER_to_PER_Distribution_msg(text.c_str(), &data, &size); + _throw_status(status); + + PyObject *ba = PyBytes_FromStringAndSize(data, size); + free(data); + if (!ba) + throw bad_alloc(); + + return boost::python::make_tuple(object(handle<>(ba)), 0); + } + + object sync_search(string name) { + if (name != "pep.sync") { + return object(); + } else { + object codecs = import("codecs"); + object CodecInfo = codecs.attr("CodecInfo"); + + object _sync_decode = make_function(sync_decode); + object _sync_encode = make_function(sync_encode); + + return call(CodecInfo.ptr(), _sync_encode, _sync_decode); + } + } + + object distribution_search(string name) { + if (name != "pep.distribution") { + return object(); + } else { + object codecs = import("codecs"); + object CodecInfo = codecs.attr("CodecInfo"); + + object _distribution_decode = make_function(Distribution_decode); + object _distribution_encode = make_function(Distribution_encode); + + return call(CodecInfo.ptr(), _distribution_encode, _distribution_decode); + } + } + + } // namespace PythonAdapter +} // namespace pEp { diff --git a/src/pEp/_pEp/message_api.hh b/src/pEp/_pEp/message_api.hh new file mode 100644 index 0000000..2d153b6 --- /dev/null +++ b/src/pEp/_pEp/message_api.hh @@ -0,0 +1,30 @@ +// This file is under GNU Affero General Public License 3.0 +// see LICENSE.txt + +#ifndef MESSAGE_API_HH +#define MESSAGE_API_HH + +#include "pEpmodule.hh" + +namespace pEp { + namespace PythonAdapter { + + Message encrypt_message( + Message src, + boost::python::list extra = boost::python::list(), + int enc_format = 4, + int flags = 0 + ); + + boost::python::tuple decrypt_message(Message src, int flags = 0); + + PEP_color _color(int rating); + + object sync_search(string name); + + object distribution_search(string name); + + } /* namespace PythonAdapter */ +} /* namespace pEp */ + +#endif /* MESSAGE_API_HH */ \ No newline at end of file diff --git a/src/pEp/_pEp/pEpmodule.cc b/src/pEp/_pEp/pEpmodule.cc new file mode 100644 index 0000000..73c1969 --- /dev/null +++ b/src/pEp/_pEp/pEpmodule.cc @@ -0,0 +1,656 @@ +// This file is under GNU Affero General Public License 3.0 +// see LICENSE.txt + +// System +#include +#include +#include +#include +#include +#include + +// Engine +#include +#include +#include +#include + +// libpEpAdapter +#include +#include +#include + +// local +#include "pEpmodule.hh" +#include "basic_api.hh" +#include "message_api.hh" +//#include "user_interface.hh" + +namespace pEp { + namespace PythonAdapter { + using namespace std; + using namespace boost::python; + + static const char *version_string = "p≡p Python adapter version 0.3"; + + void init_before_main_module() { + pEpLog("called"); + } + +// hidden init function, wrapped by hello_world.init() + void _init_after_main_module() { + pEpLog("called"); + callback_dispatcher.add(_messageToSend, notifyHandshake, nullptr, nullptr); + Adapter::_messageToSend = CallbackDispatcher::messageToSend; + } + + + void config_passive_mode(bool enable) { + ::config_passive_mode(Adapter::session(), enable); + } + + void config_unencrypted_subject(bool enable) { + ::config_unencrypted_subject(Adapter::session(), enable); + } + + void key_reset_user(string user_id, string fpr) { + if (user_id == "") + throw invalid_argument("user_id required"); + + PEP_STATUS status = ::key_reset_user(Adapter::session(), + user_id.c_str(), fpr != "" ? fpr.c_str() : nullptr); + _throw_status(status); + } + + void key_reset_user2(string user_id) { + key_reset_user(user_id, ""); + } + + void key_reset_all_own_keys() { + PEP_STATUS status = ::key_reset_all_own_keys(Adapter::session()); + _throw_status(status); + } + + static string about() { + string version = string(version_string) + "\np≡p version " + + PEP_VERSION + "\n"; + return version; + } + + void _throw_status(PEP_STATUS status) { + if (status == PEP_STATUS_OK) + return; + if (status >= 0x400 && status <= 0x4ff) + return; + if (status == PEP_OUT_OF_MEMORY) + throw bad_alloc(); + if (status == PEP_ILLEGAL_VALUE) + throw invalid_argument("illegal value"); + + if (string(pEp_status_to_string(status)) == "unknown status code") { + stringstream build; + build << setfill('0') << "p≡p 0x" << setw(4) << hex << status; + throw runtime_error(build.str()); + } else { + throw runtime_error(pEp_status_to_string(status)); + } + } + + PEP_STATUS _messageToSend(::message *msg) { + pEpLog("called"); + try { + PyGILState_STATE gil = PyGILState_Ensure(); + pEpLog("GIL Aquired"); + object modref = import("pEp"); + object funcref = modref.attr("message_to_send"); + call(funcref.ptr(), Message()); + PyGILState_Release(gil); + pEpLog("GIL released"); + } catch (exception &e) {} + + return PEP_STATUS_OK; + } + + PEP_STATUS notifyHandshake(pEp_identity *me, pEp_identity *partner, sync_handshake_signal signal) { + pEpLog("called"); + try { + PyGILState_STATE gil = PyGILState_Ensure(); + pEpLog("GIL Aquired"); + object modref = import("pEp"); + object funcref = modref.attr("notify_handshake"); + call(funcref.ptr(), me, partner, signal); + PyGILState_Release(gil); + pEpLog("GIL released"); + } catch (exception &e) {} + + return PEP_STATUS_OK; + } + + + void start_sync() { + CallbackDispatcher::start_sync(); + } + + void shutdown_sync() { + CallbackDispatcher::stop_sync(); + } + + void debug_color(int ansi_color) { + ::set_debug_color(Adapter::session(), ansi_color); + } + + void leave_device_group() { + ::leave_device_group(Adapter::session()); + } + + bool is_sync_active() { + return Adapter::is_sync_running(); + } + + void testfunc() { + _messageToSend(NULL); + } + + void deliverHandshakeResult(int result, object identities) { + identity_list *shared_identities = nullptr; + if (identities != boost::python::api::object() && boost::python::len(identities)) { + shared_identities = new_identity_list(nullptr); + if (!shared_identities) + throw bad_alloc(); + + try { + identity_list *si = shared_identities; + for (int i = 0; i < boost::python::len(identities); ++i) { + Identity ident = extract(identities[i]); + si = identity_list_add(si, ident); + if (!si) + throw bad_alloc(); + } + } + catch (exception &ex) { + free_identity_list(shared_identities); + throw ex; + } + } + + PEP_STATUS status = ::deliverHandshakeResult(Adapter::session(), (sync_handshake_result) result, shared_identities); + free_identity_list(shared_identities); + _throw_status(status); + } + + BOOST_PYTHON_MODULE(_pEp) { + init_before_main_module(); + + // Module init function called by pEp.init() + def("_init_after_main_module", _init_after_main_module); + def("testfunc", &testfunc); + + docstring_options doc_options(true, false, false); + boost::locale::generator gen; + std::locale::global(gen("")); + + + scope().attr("about") = about(); + scope().attr("per_user_directory") = per_user_directory(); + scope().attr("per_machine_directory") = per_machine_directory(); + scope().attr("engine_version") = get_engine_version(); + scope().attr("protocol_version") = get_protocol_version(); + + def("passive_mode", config_passive_mode, + "do not attach pub keys to all messages"); + + def("unencrypted_subject", config_unencrypted_subject, + "do not encrypt the subject of messages"); + + def("key_reset", key_reset_user, + "reset the default database status for the user / keypair provided\n" + "This will effectively perform key_reset on each identity\n" + "associated with the key and user_id, if a key is provided, and for\n" + "each key (and all of their identities) if an fpr is not."); + + def("key_reset", key_reset_user2, + "reset the default database status for the user / keypair provided\n" + "This will effectively perform key_reset on each identity\n" + "associated with the key and user_id, if a key is provided, and for\n" + "each key (and all of their identities) if an fpr is not."); + + def("key_reset_all_own_keys", key_reset_all_own_keys, + "revoke and mistrust all own keys, generate new keys for all\n" + "own identities, and opportunistically communicate key reset\n" + "information to people we have recently contacted."); + + auto identity_class = class_("Identity", + "Identity(address, username, user_id='', fpr='', comm_type=0, lang='en')\n" + "\n" + "represents a p≡p identity\n" + "\n" + "an identity is a network address, under which a user is represented in\n" + "the network\n" + "\n" + " address network address, either an SMTP address or a URI\n" + " username real name or nickname for user\n" + " user_id ID this user is handled by the application\n" + " fpr full fingerprint of the key being used as key ID,\n" + " hex encoded\n" + " comm_type first rating level of this communication channel\n" + " lang ISO 639-1 language code for language being preferred\n" + " on this communication channel\n" + ) + .def(boost::python::init()) + .def(boost::python::init()) + .def(boost::python::init()) + .def(boost::python::init()) + .def(boost::python::init()) + .def(boost::python::init()) + .def("__repr__", &Identity::_repr) + .def("__str__", &Identity::_str, + "string representation of this identity\n" + "following the pattern 'username < address >'\n" + ) + .def("key_reset", &Identity::key_reset, + boost::python::arg("fpr")=object(""), + "reset the default database status for the identity / keypair provided. If this\n" + "corresponds to the own user and a private key, also revoke the key, generate a\n" + "new one, and communicate the reset to recently contacted pEp partners for this\n" + "identity. If it does not, remove the key from the keyring; the key's status is\n" + "completely fresh on next contact from the partner.") + + .def("key_mistrusted", &Identity::key_mistrusted, + boost::python::arg("fpr")=object(""), + "If you want updated trust on the identity, you ll have" + "to call update_identity or myself respectively after this." + "N.B. If you are calling this on a key that is the identity or user default," + "it will be removed as the default key for ANY identity and user for which" + "it is the default. Please keep in mind that the undo in undo_last_mistrust" + "will only undo the current identity's / it's user's default, not any" + "other identities which may be impacted (this will not affect most use cases)") + + .def("enable_for_sync", &Identity::enable_for_sync, + "Enable own identity for p≡p sync.\n\n" + "Only use this on own identities, which are used as accounts.\n") + .def("disable_for_sync", &Identity::disable_for_sync, + "Disable own identity for p≡p sync.\n\n" + "Only use this on own identities, which are used as accounts.\n") + + .add_property("address", (string(Identity::*)()) &Identity::address, + (void(Identity::*)(string)) &Identity::address, + "email address or URI") + .add_property("fpr", (string(Identity::*)()) &Identity::fpr, + (void(Identity::*)(string)) &Identity::fpr, + "key ID (full fingerprint, hex encoded)") + .add_property("user_id", (string(Identity::*)()) &Identity::user_id, + (void(Identity::*)(string)) &Identity::user_id, + "ID of person associated or 'pEp_own_userId' if own identity") + .add_property("username", (string(Identity::*)()) &Identity::username, + (void(Identity::*)(string)) &Identity::username, + "name in full of person associated") + .add_property("comm_type", (int(Identity::*)()) + (PEP_comm_type(Identity::*)()) &Identity::comm_type, + (void(Identity::*)(int)) + (void(Identity::*)(PEP_comm_type)) &Identity::comm_type, + "communication type, first rating level (p≡p internal)") + .add_property("lang", (string(Identity::*)()) &Identity::lang, + (void(Identity::*)(string)) &Identity::lang, + "ISO 639-1 language code") + .add_property("flags", (identity_flags_t(Identity::*)()) &Identity::flags, + (void(Identity::*)(identity_flags_t)) &Identity::flags, + "flags (p≡p internal)") + .add_property("rating", &Identity::rating, "rating of Identity") + .add_property("color", &Identity::color, "color of Identity as PEP_color") + .add_property("is_pEp_user", &Identity::is_pEp_user, "True if this is an identity of a pEp user") + .def("__deepcopy__", &Identity::deepcopy) + .def("update", &Identity::update, "update Identity") + .def("__copy__", &Identity::copy); + + identity_class.attr("PEP_OWN_USERID") = "pEp_own_userId"; + + auto blob_class = class_("Blob", + "Blob(data, mime_type='', filename='')\n" + "\n" + "Binary large object\n" + "\n" + " data bytes-like object\n" + " mime_type MIME type for the data\n" + " filename filename to store the data\n", + boost::python::init< object, char const*, char const* >(args("data", "mime_type", "filename"))) + .def(boost::python::init()) + .def(boost::python::init()) + .def("__repr__", &Message::Blob::_repr) + .def("__len__", &Message::Blob::size, "size of Blob data in bytes") + .def("decode", (string(Message::Blob::*)()) &Message::Blob::decode) + .def("decode", (string(Message::Blob::*)(string)) &Message::Blob::decode, + "text = blob.decode(encoding='')\n" + "\n" + "decode Blob data into string depending on MIME type if encoding=''\n" + "\n" + " mime_type='application/pEp.sync' decode as 'pEp.sync'\n" + " mime_type='application/pEp.keyreset' decode as 'pEp.keyreset'\n" + " other mime_type decode as 'ascii' by default\n" + ) + .add_property("mime_type", (string(Message::Blob::*)()) &Message::Blob::mime_type, + (void(Message::Blob::*)(string)) &Message::Blob::mime_type, + "MIME type of object in Blob") + .add_property("filename", (string(Message::Blob::*)()) &Message::Blob::filename, + (void(Message::Blob::*)(string)) &Message::Blob::filename, + "filename of object in Blob"); + + ((PyTypeObject *)(void *)blob_class.ptr())->tp_as_buffer = &Message::Blob::bp; + + auto message_class = class_("Message", + "Message(dir=1, from=None)\n" + "\n" + "new p≡p message\n" + "\n" + " dir 1 for outgoing, 2 for incoming\n" + " from Identity() of sender\n" + "\n" + "Message(mime_text)\n" + "\n" + "new incoming p≡p message\n" + "\n" + " mime_text text in Multipurpose Internet Mail Extensions format\n" + ) + .def(boost::python::init()) + .def(boost::python::init()) + .def(boost::python::init()) + .def("__str__", &Message::_str, + "the string representation of a Message is it's MIME text" + ) + .def("__repr__", &Message::_repr) + .add_property("dir", (int(Message::*)()) + (PEP_msg_direction(Message::*)()) &Message::dir, + (void(Message::*)(int)) + (void(Message::*)(PEP_msg_direction)) &Message::dir, + "0: incoming, 1: outgoing message") + .add_property("id", (string(Message::*)()) &Message::id, + (void(Message::*)(string)) &Message::id, + "message ID") + .add_property("shortmsg", (string(Message::*)()) &Message::shortmsg, + (void(Message::*)(string)) &Message::shortmsg, + "subject or short message") + .add_property("longmsg", (string(Message::*)()) &Message::longmsg, + (void(Message::*)(string)) &Message::longmsg, + "body or long version of message") + .add_property("longmsg_formatted", (string(Message::*)()) &Message::longmsg_formatted, + (void(Message::*)(string)) &Message::longmsg_formatted, + "HTML body or fromatted long version of message") + .add_property("attachments", (boost::python::tuple(Message::*)()) &Message::attachments, + (void(Message::*)(boost::python::list)) &Message::attachments, + "tuple of Blobs with attachments; setting moves Blobs to attachment tuple") + .add_property("sent", (time_t(Message::*)()) &Message::sent, + (void(Message::*)(time_t)) &Message::sent, + "time when message was sent in UTC seconds since epoch") + .add_property("recv", (time_t(Message::*)()) &Message::recv, + (void(Message::*)(time_t)) &Message::recv, + "time when message was received in UTC seconds since epoch") + .add_property("from_", (Identity(Message::*)()) &Message::from, + (void(Message::*)(object)) &Message::from, + "identity where message is from") + .add_property("to", (boost::python::list(Message::*)()) &Message::to, + (void(Message::*)(boost::python::list)) &Message::to, + "list of identities message is going to") + .add_property("recv_by", (Identity(Message::*)()) &Message::recv_by, + (void(Message::*)(object)) &Message::recv_by, + "identity where message was received by") + .add_property("cc", (boost::python::list(Message::*)()) &Message::cc, + (void(Message::*)(boost::python::list)) &Message::cc, + "list of identities message is going cc") + .add_property("bcc", (boost::python::list(Message::*)()) &Message::bcc, + (void(Message::*)(boost::python::list)) &Message::bcc, + "list of identities message is going bcc") + .add_property("reply_to", (boost::python::list(Message::*)()) &Message::reply_to, + (void(Message::*)(boost::python::list)) &Message::reply_to, + "list of identities where message will be replied to") + .add_property("in_reply_to", (boost::python::list(Message::*)()) &Message::in_reply_to, + (void(Message::*)(boost::python::list)) &Message::in_reply_to, + "in_reply_to list") + .add_property("references", (boost::python::list(Message::*)()) &Message::references, + (void(Message::*)(boost::python::list)) &Message::references, + "message IDs of messages this one is referring to") + .add_property("keywords", (boost::python::list(Message::*)()) &Message::keywords, + (void(Message::*)(boost::python::list)) &Message::keywords, + "keywords this message should be stored under") + .add_property("comments", (string(Message::*)()) &Message::comments, + (void(Message::*)(string)) &Message::comments, + "comments added to message") + .add_property("opt_fields", (dict(Message::*)()) &Message::opt_fields, + (void(Message::*)(dict)) &Message::opt_fields, + "opt_fields of message") + .add_property("enc_format", (int(Message::*)()) + (PEP_enc_format(Message::*)()) &Message::enc_format, + (void(Message::*)(int)) + (void(Message::*)(PEP_enc_format)) &Message::enc_format, + "0: unencrypted, 1: inline PGP, 2: S/MIME, 3: PGP/MIME, 4: p≡p format") + .def("encrypt", (Message(Message::*)())&Message::encrypt) + .def("encrypt", (Message(Message::*)(boost::python::list))&Message::_encrypt) + .def("encrypt", (Message(Message::*)(boost::python::list, int))&Message::_encrypt) + .def("encrypt", (Message(Message::*)(boost::python::list, int, int))&Message::_encrypt, + "msg2 = msg1.encrypt(extra_keys=[], enc_format='pEp', flags=0)\n" + "\n" + "encrypts a p≡p message and returns the encrypted message\n" + "\n" + " extra_keys list of strings with fingerprints for extra keys to use\n" + " for encryption\n" + " enc_format 0 for none, 1 for partitioned, 2 for S/MIME,\n" + " 3 for PGP/MIME, 4 for pEp\n" + " flags 1 is force encryption\n" + ) + .def("decrypt", &Message::decrypt, boost::python::arg("flags")=0, + "msg2, keys, rating, flags = msg1.decrypt()\n" + "\n" + "decrypts a p≡p message and returns a tuple with data\n" + "\n" + " msg the decrypted p≡p message\n" + " keys a list of keys being used\n" + " rating the rating of the message as integer\n" + " flags flags set while decryption\n" + ) + .add_property("outgoing_rating", &Message::outgoing_rating, "rating outgoing message will have") + .add_property("outgoing_color", &Message::outgoing_color, "color outgoing message will have as PEP_color") + .def("__deepcopy__", &Message::deepcopy) + .def("__copy__", &Message::copy); + + // basic API and key management API + + def("update_identity", &update_identity, + "update_identity(ident)\n" + "\n" + "update identity information\n" + "call this to complete identity information when you at least have an address\n" + ); + def("myself", &myself, + "myself(ident)\n" + "\n" + "ensures that the own identity is being complete\n" + "supply ident.address and ident.username\n" + ); + def("trust_personal_key", &trust_personal_key, + "trust_personal_key(ident)\n" + "\n" + "mark a key as trusted with a person\n" + ); + + enum_("identity_flags") + .value("PEP_idf_not_for_sync", PEP_idf_not_for_sync) + .value("PEP_idf_list", PEP_idf_list) + .value("PEP_idf_devicegroup", PEP_idf_devicegroup); + + def("set_identity_flags", &set_identity_flags, + "set_identity_flags(ident, flags)\n" + "\n" + "set identity flags\n" + ); + + def("unset_identity_flags", &unset_identity_flags, + "unset_identity_flags(ident, flags)\n" + "\n" + "unset identity flags\n" + ); + + def("key_reset_trust", &key_reset_trust, + "key_reset_trust(ident)\n" + "\n" + "reset trust bit or explicitly mistrusted status for an identity and " + "its accompanying key/user_id pair\n" + ); + + def("import_key", &import_key, + "private_key_list = import_key(key_data)\n" + "\n" + "import key(s) from key_data\n" + ); + + def("export_key", &export_key, + "key_data = export_key(identity)\n" + "\n" + "export key(s) of identity\n" + ); + + def("export_secret_key", &export_secret_key, + "key_data = export_seret_key(identity)\n" + "\n" + "export secret key(s) of identity\n" + ); + + def("set_own_key", &set_own_key, + "set_own_key(me, fpr)\n" + "\n" + "mark a key as an own key, and make it the default key\n" + "\n" + "me Own identity for which to add the existing key\n" + "fpr The fingerprint of the key to be added\n" + "\n" + "me->address, me->user_id and me->username must be set to valid data\n" + "myself() is called by set_own_key() without key generation\n" + "me->flags are ignored\n" + "me->address must not be an alias\n" + "me->fpr will be ignored and replaced by fpr\n" + ); + + // message API + + enum_("rating") + .value("_undefined", PEP_rating_undefined) + .value("cannot_decrypt", PEP_rating_cannot_decrypt) + .value("have_no_key", PEP_rating_have_no_key) + .value("unencrypted", PEP_rating_unencrypted) + .value("unreliable", PEP_rating_unreliable) + .value("reliable", PEP_rating_reliable) + .value("trusted", PEP_rating_trusted) + .value("trusted_and_anonymized", PEP_rating_trusted_and_anonymized) + .value("fully_anonymous", PEP_rating_fully_anonymous) + .value("mistrust", PEP_rating_mistrust) + .value("b0rken", PEP_rating_b0rken) + .value("under_attack", PEP_rating_under_attack); + + enum_("colorvalue") + .value("no_color", PEP_color_no_color) + .value("yellow", PEP_color_yellow) + .value("green", PEP_color_green) + .value("red", PEP_color_red); + + + def("incoming_message", &incoming_message, + "msg = incoming_message(mime_text)\n" + "\n" + "create an incoming message from a MIME text" + ); + def("outgoing_message", &outgoing_message, + "msg = outgoing_message(ident)\n" + "\n" + "create an outgoing message using an own identity" + ); + def("color", &_color, + "c = color(rating)\n" + "\n" + "calculate color value out of rating. Returns PEP_color" + ); + def("trustwords", &_trustwords, + "text = trustwords(ident_own, ident_partner)\n" + "\n" + "calculate trustwords for two Identities"); + + // Sync API + + enum_("sync_handshake_signal") + .value("SYNC_NOTIFY_UNDEFINED", SYNC_NOTIFY_UNDEFINED) + .value("SYNC_NOTIFY_INIT_ADD_OUR_DEVICE", SYNC_NOTIFY_INIT_ADD_OUR_DEVICE) + .value("SYNC_NOTIFY_INIT_ADD_OTHER_DEVICE", SYNC_NOTIFY_INIT_ADD_OTHER_DEVICE) + .value("SYNC_NOTIFY_INIT_FORM_GROUP", SYNC_NOTIFY_INIT_FORM_GROUP) + .value("SYNC_NOTIFY_TIMEOUT", SYNC_NOTIFY_TIMEOUT) + .value("SYNC_NOTIFY_ACCEPTED_DEVICE_ADDED", SYNC_NOTIFY_ACCEPTED_DEVICE_ADDED) + .value("SYNC_NOTIFY_ACCEPTED_GROUP_CREATED", SYNC_NOTIFY_ACCEPTED_GROUP_CREATED) + .value("SYNC_NOTIFY_ACCEPTED_DEVICE_ACCEPTED", SYNC_NOTIFY_ACCEPTED_DEVICE_ACCEPTED) + .value("SYNC_NOTIFY_SOLE", SYNC_NOTIFY_SOLE) + .value("SYNC_NOTIFY_IN_GROUP", SYNC_NOTIFY_IN_GROUP); + +// auto user_interface_class = class_( +// "UserInterface", +// "class MyUserInterface(UserInterface):\n" +// " def notifyHandshake(self, me, partner):\n" +// " ...\n" +// "\n" +// "p≡p User Interface class\n" +// "To be used as a mixin\n" +// ) +// .def("notifyHandshake", &UserInterface::notifyHandshake, +// "notifyHandshake(self, me, partner)\n" +// "\n" +// " me own identity\n" +// " partner identity of communication partner\n" +// "\n" +// "overwrite this method with an implementation of a handshake dialog") +// .def("deliverHandshakeResult", &UserInterface::deliverHandshakeResult, +// boost::python::arg("identities")=object(), +// "deliverHandshakeResult(self, result, identities=None)\n" +// "\n" +// " result -1: cancel, 0: accepted, 1: rejected\n" +// " identities list of identities to share or None for all\n" +// "\n" +// "call to deliver the handshake result of the handshake dialog" +// ); + + def("deliver_handshake_result", &deliverHandshakeResult, boost::python::arg("identities")=object(), + "deliverHandshakeResult(self, result, identities=None)\n" + "\n" + " result -1: cancel, 0: accepted, 1: rejected\n" + " identities list of identities to share or None for all\n" + "\n" + "call to deliver the handshake result of the handshake dialog" + ); + + def("start_sync", &start_sync, + "start_sync()\n" + "\n" + "starts the sync thread" + ); + + def("shutdown_sync", &shutdown_sync, + "shutdown_sync()\n" + "\n" + "call this from another thread to shut down the sync thread\n" + ); + + def("debug_color", &debug_color, + "for debug builds set ANSI color value"); + + def("leave_device_group", &leave_device_group, + "leave_device_group()\n" + "\n" + "call this for a grouped device, which should leave\n" + ); + + def("is_sync_active", &is_sync_active, + "is_sync_active()\n" + "\n" + "True if sync is active, False otherwise\n" + ); + + + // codecs + call< object >(((object)(import("codecs").attr("register"))).ptr(), make_function(sync_search)); + call< object >(((object)(import("codecs").attr("register"))).ptr(), make_function(distribution_search)); + } + + } // namespace PythonAdapter +} // namespace pEp diff --git a/src/pEp/_pEp/pEpmodule.hh b/src/pEp/_pEp/pEpmodule.hh new file mode 100644 index 0000000..8ae3c25 --- /dev/null +++ b/src/pEp/_pEp/pEpmodule.hh @@ -0,0 +1,35 @@ +// This file is under GNU Affero General Public License 3.0 +// see LICENSE.txt + +#ifndef PEPMODULE_HH +#define PEPMODULE_HH + +// Engine +#include + +// local +#include "message.hh" + +namespace pEp { + namespace PythonAdapter { + + extern string device_name; + + void config_passive_mode(bool enable); + + void config_unencrypted_subject(bool enable); + + void key_reset_user(string user_id, string fpr); + + void key_reset_all_own_keys(); + + void _throw_status(PEP_STATUS status); + + PEP_STATUS _messageToSend(::message *msg); + + PEP_STATUS notifyHandshake(pEp_identity *me, pEp_identity *partner, sync_handshake_signal signal); + + } /* namespace PythonAdapter */ +} /* namespace pEp */ + +#endif /* PEPMODULE_HH */ \ No newline at end of file diff --git a/src/pEp/_pEp/str_attr.cc b/src/pEp/_pEp/str_attr.cc new file mode 100644 index 0000000..74f44ee --- /dev/null +++ b/src/pEp/_pEp/str_attr.cc @@ -0,0 +1,172 @@ +// This file is under GNU Affero General Public License 3.0 +// see LICENSE.txt + +// System +#include +#include +#include + +// local +#include "str_attr.hh" + +namespace pEp { + namespace PythonAdapter { + using namespace std; + using namespace boost::python; + using namespace boost::locale; + + object repr(object s) { + return s.attr("__repr__")(); + } + + string repr(string s) { + str _s = s.c_str(); + object _r = _s.attr("__repr__")(); + string r = extract(_r); + return r; + } + + string str_attr(char *&str) { + if (!str) + return string(""); + return string(str); + } + + void str_attr(char *&str, string value) { + string normalized = normalize(value, norm_nfc); + free(str); + str = strdup(normalized.c_str()); + if (!str) + throw bad_alloc(); + } + + time_t timestamp_attr(timestamp *&ts) { + if (!ts) + return 0; + + return timegm(ts); + } + + void timestamp_attr(timestamp *&ts, time_t value) { + free_timestamp(ts); + ts = new_timestamp(value); + } + + boost::python::list strlist_attr(stringlist_t *&sl) { + boost::python::list result; + + for (stringlist_t *_sl = sl; _sl && _sl->value; _sl = _sl->next) { + string s(_sl->value); + result.append(object(s)); + } + + return result; + } + + void strlist_attr(stringlist_t *&sl, boost::python::list value) { + stringlist_t *_sl = new_stringlist(NULL); + if (!_sl) + throw bad_alloc(); + + stringlist_t *_s = _sl; + for (int i = 0; i < len(value); i++) { + extract extract_string(value[i]); + if (!extract_string.check()) { + free_stringlist(_sl); + } + string s = extract_string(); + s = normalize(s, norm_nfc); + _s = stringlist_add(_s, s.c_str()); + if (!_s) { + free_stringlist(_sl); + throw bad_alloc(); + } + } + + free_stringlist(sl); + sl = _sl; + } + + dict strdict_attr(stringpair_list_t *&spl) { + dict result; + + for (stringpair_list_t *_spl = spl; _spl && _spl->value; _spl = + _spl->next) { + stringpair_t *p = _spl->value; + if (p->key && p->value) { + string key(p->key); + string value(p->value); + + result[key] = value; + } + } + + return result; + } + + void strdict_attr(stringpair_list_t *&spl, dict value) { + stringpair_list_t *_spl = new_stringpair_list(NULL); + if (!_spl) + throw bad_alloc(); + + stringpair_list_t *_s = _spl; + for (int i = 0; i < len(value); i++) { + extract extract_key(value.keys()[i]); + extract extract_value(value.values()[i]); + + if (!(extract_key.check() && extract_value.check())) + free_stringpair_list(_spl); + + string key = extract_key(); + key = normalize(key, norm_nfc); + string _value = extract_value(); + _value = normalize(_value, norm_nfc); + stringpair_t *pair = new_stringpair(key.c_str(), _value.c_str()); + if (!pair) { + free_stringpair_list(_spl); + throw bad_alloc(); + } + _s = stringpair_list_add(_s, pair); + if (!_s) { + free_stringpair_list(_spl); + throw bad_alloc(); + } + } + + free_stringpair_list(spl); + spl = _spl; + } + + stringlist_t *to_stringlist(boost::python::list l) { + stringlist_t *result = new_stringlist(NULL); + if (!result) + throw bad_alloc(); + + stringlist_t *_s = result; + for (int i = 0; i < len(l); i++) { + extract extract_string(l[i]); + if (!extract_string.check()) + free_stringlist(result); + string s = extract_string(); + _s = stringlist_add(_s, s.c_str()); + if (!_s) { + free_stringlist(result); + throw bad_alloc(); + } + } + + return result; + } + + boost::python::list from_stringlist(const stringlist_t *sl) { + boost::python::list result; + for (const stringlist_t *_sl = sl; _sl && _sl->value; _sl = _sl->next) { + string s = _sl->value; + result.append(s); + } + return result; + } + + } // namespace PythonAdapter +} // namespace pEp { + diff --git a/src/pEp/native_pEp/str_attr.hh b/src/pEp/_pEp/str_attr.hh similarity index 100% rename from src/pEp/native_pEp/str_attr.hh rename to src/pEp/_pEp/str_attr.hh diff --git a/src/pEp/_pEp/user_interface.cc b/src/pEp/_pEp/user_interface.cc new file mode 100644 index 0000000..f31ad82 --- /dev/null +++ b/src/pEp/_pEp/user_interface.cc @@ -0,0 +1,123 @@ +// This file is under GNU Affero General Public License 3.0 +// see LICENSE.txt + +// System +#include + +// local +#include "user_interface.hh" + +namespace pEp { + namespace PythonAdapter { + using namespace std; + using namespace boost::python; + + UserInterface *UserInterface::_ui = nullptr; + + UserInterface::UserInterface() { + if (_ui) + throw runtime_error("only one UserInterface thread allowed"); + _ui = this; + } + + UserInterface::~UserInterface() { + _ui = nullptr; + } + + UserInterface_callback::UserInterface_callback(PyObject *self) : + UserInterface(), _self(self) { +// adapter.ui_object(self); +// PEP_STATUS status = ::register_sync_callbacks(Adapter::session(), +// (void *) this, _notifyHandshake, retrieve_next_sync_event); +// assert(status == PEP_STATUS_OK); +// if (status) +// _throw_status(status); + } + + UserInterface_callback::~UserInterface_callback() { +// ::unregister_sync_callbacks(Adapter::session()); + } + + PEP_STATUS UserInterface::_notifyHandshake( + pEp_identity *me, pEp_identity *partner, + sync_handshake_signal signal + ) { + if (!(me && partner)) + return PEP_ILLEGAL_VALUE; + + auto that = dynamic_cast< UserInterface_callback * >(_ui); + that->notifyHandshake(Identity(me), Identity(partner), signal); + + return PEP_STATUS_OK; + } + + void UserInterface::deliverHandshakeResult(int result, object identities) { + identity_list *shared_identities = nullptr; + if (identities != boost::python::api::object() && boost::python::len(identities)) { + shared_identities = new_identity_list(nullptr); + if (!shared_identities) + throw bad_alloc(); + + try { + identity_list *si = shared_identities; + for (int i = 0; i < boost::python::len(identities); ++i) { + Identity ident = extract(identities[i]); + si = identity_list_add(si, ident); + if (!si) + throw bad_alloc(); + } + } + catch (exception &ex) { + free_identity_list(shared_identities); + throw ex; + } + } + + PEP_STATUS status = ::deliverHandshakeResult(Adapter::session(), + (sync_handshake_result) result, shared_identities); + free_identity_list(shared_identities); + _throw_status(status); + } + +//PEP_rating UserInterface::get_key_rating_for_user(string user_id, string fpr) +//{ +// PEP_rating result; +// PEP_STATUS status = +// ::get_key_rating_for_user(Adapter::session(), +// user_id.c_str(), fpr.c_str(), &result); +// _throw_status(status); +// return result; +//} + +//SYNC_EVENT UserInterface::retrieve_next_sync_event(void *management, unsigned threshold) +//{ +// time_t started = time(nullptr); +// bool timeout = false; +// +// while (adapter.queue().empty()) { +// int i = 0; +// ++i; +// if (i > 10) { +// if (time(nullptr) > started + threshold) { +// timeout = true; +// break; +// } +// i = 0; +// } +// nanosleep((const struct timespec[]){{0, 100000000L}}, NULL); +// } +// +// if (timeout) +// return new_sync_timeout_event(); +// +// return adapter.queue().pop_front(); +//} + + void UserInterface_callback::notifyHandshake( + Identity me, Identity partner, sync_handshake_signal signal) { + call_method(_self, "notifyHandshake", me, partner, signal); + } + + } // namespace PythonAdapter +} // namespace pEp { + diff --git a/src/pEp/_pEp/user_interface.hh b/src/pEp/_pEp/user_interface.hh new file mode 100644 index 0000000..59e17fe --- /dev/null +++ b/src/pEp/_pEp/user_interface.hh @@ -0,0 +1,61 @@ +// This file is under GNU Affero General Public License 3.0 +// see LICENSE.txt + +#ifndef USER_INTERFACE_HH +#define USER_INTERFACE_HH + +// System +#include + +// Engine +#include +#include + +// local +#include "pEpmodule.hh" + + +namespace pEp { + namespace PythonAdapter { + + class UserInterface { + static UserInterface *_ui; + public: + UserInterface(); + + virtual ~UserInterface(); + + virtual void notifyHandshake( + Identity me, + Identity partner, + sync_handshake_signal signal) { + throw runtime_error("override this method"); + } + + virtual void deliverHandshakeResult(int result, object identities); + +// PEP_rating get_key_rating_for_user(string user_id, string fpr); + + protected: + static PEP_STATUS _notifyHandshake(pEp_identity *me, pEp_identity *partner, sync_handshake_signal signal); + }; + + class UserInterface_callback : public UserInterface { + PyObject *_self; + public: + UserInterface_callback(PyObject *self); + + ~UserInterface_callback(); + + void notifyHandshake( + Identity me, + Identity partner, + sync_handshake_signal signal + ); + }; + + } /* namespace PythonAdapter */ +} /* namespace pEp */ + +#endif /* USER_INTERFACE_HH */ + diff --git a/src/pEp/native_pEp/basic_api.cc b/src/pEp/native_pEp/basic_api.cc deleted file mode 100644 index c3e1097..0000000 --- a/src/pEp/native_pEp/basic_api.cc +++ /dev/null @@ -1,172 +0,0 @@ -// This file is under GNU Affero General Public License 3.0 -// see LICENSE.txt - -// System -#include - -// Engine -#include -#include -#include - -// local -#include "basic_api.hh" - -namespace pEp { -namespace PythonAdapter { -using namespace std; - -void update_identity(Identity& ident) -{ - if (ident.address() == "") - throw invalid_argument("address needed"); - if (ident.user_id() == PEP_OWN_USERID) - throw runtime_error("update_identity: '" PEP_OWN_USERID - "' may only be used for own identities"); - - PEP_STATUS status = update_identity(Adapter::session(), ident); - _throw_status(status); -} - -void myself(Identity& ident) -{ - if (ident.address() == "") - throw invalid_argument("address needed"); - if (ident.username() == "") - throw invalid_argument("username needed"); - - if (ident.user_id() == "") - ident.user_id(ident.address()); - - PEP_STATUS status = myself(Adapter::session(), ident); - _throw_status(status); -} - -string _trustwords(Identity me, Identity partner, string lang, bool full) -{ - if (me.fpr() == "" || partner.fpr() == "") - throw invalid_argument("fingerprint needed in Identities"); - - if (lang == "" && me.lang() == partner.lang()) - lang = me.lang(); - - char *words = NULL; - size_t size = 0; - PEP_STATUS status = get_trustwords(Adapter::session(), me, partner, - lang.c_str(),&words, &size, full); - _throw_status(status); - return words; -} - -void trust_personal_key(Identity ident) -{ - if (ident.fpr() == "") - throw invalid_argument("fingerprint needed in Identities"); - if (ident.user_id() == "") - throw invalid_argument("user_id must be provided"); - - PEP_STATUS status = trust_personal_key(Adapter::session(), ident); - _throw_status(status); -} - -void set_identity_flags(Identity ident, identity_flags_t flags) -{ - if (ident.address() == "") - throw invalid_argument("address needed"); - if (ident.user_id() == "") - throw invalid_argument("user_id needed"); - - PEP_STATUS status = set_identity_flags(Adapter::session(), ident, flags); - _throw_status(status); -} - -void unset_identity_flags(Identity ident, identity_flags_t flags) -{ - if (ident.address() == "") - throw invalid_argument("address needed"); - if (ident.user_id() == "") - throw invalid_argument("user_id needed"); - - PEP_STATUS status = unset_identity_flags(Adapter::session(), ident, flags); - _throw_status(status); -} - -void key_reset_trust(Identity ident) -{ - if (ident.fpr() == "") - throw invalid_argument("fpr needed"); - if (ident.address() == "") - throw invalid_argument("address needed"); - if (ident.user_id() == "") - throw invalid_argument("user_id needed"); - - PEP_STATUS status = key_reset_trust(Adapter::session(), ident); - _throw_status(status); -} - - - -boost::python::list import_key(string key_data) -{ - ::identity_list *private_keys = NULL; - PEP_STATUS status = ::import_key(Adapter::session(), key_data.c_str(), key_data.size(), &private_keys); - if (status && status != PEP_KEY_IMPORTED) - _throw_status(status); - - auto result = boost::python::list(); - for (::identity_list *il = private_keys; il && il->ident; il=il->next) { - ::pEp_identity *ident = ::identity_dup(il->ident); - if (!ident) { - free_identity_list(private_keys); - throw bad_alloc(); - } - result.append(Identity(ident)); - } - - free_identity_list(private_keys); - return result; -} - -string export_key(Identity ident) -{ - PEP_STATUS status = PEP_STATUS_OK; - char* key_data = NULL; - size_t size; - status = ::export_key(Adapter::session(), ident.fpr().c_str(), &key_data, &size); - - _throw_status(status); - return key_data; -} - -string export_secret_key(Identity ident) -{ - PEP_STATUS status = PEP_STATUS_OK; - char* key_data = NULL; - size_t size; - status = ::export_secret_key(Adapter::session(), ident.fpr().c_str(), &key_data, &size); - - _throw_status(status); - return key_data; -} - -void set_own_key(Identity& ident, string fpr) -{ - if (ident.address() == "") - throw invalid_argument("address needed"); - if (ident.username() == "") - throw invalid_argument("username needed"); - if (ident.user_id() == "") - throw invalid_argument("user_id needed"); - if (fpr == "") - throw invalid_argument("fpr needed"); - - - const char* fpr_c = fpr.c_str(); - PEP_STATUS status = set_own_key(Adapter::session(), ident, fpr_c); - _throw_status(status); -} - -} // namespace PythonAdapter -} // namespace pEp { - - diff --git a/src/pEp/native_pEp/basic_api.hh b/src/pEp/native_pEp/basic_api.hh deleted file mode 100644 index f4714a8..0000000 --- a/src/pEp/native_pEp/basic_api.hh +++ /dev/null @@ -1,30 +0,0 @@ -// This file is under GNU Affero General Public License 3.0 -// see LICENSE.txt - -#ifndef BASIC_API_HH -#define BASIC_API_HH - -#include "pEpmodule.hh" - -namespace pEp { -namespace PythonAdapter { - -void update_identity(Identity& ident); -void myself(Identity& ident); -string _trustwords(Identity me, Identity partner, string lang, bool full); -void trust_personal_key(Identity ident); - -void set_identity_flags(Identity ident, identity_flags_t flags); -void unset_identity_flags(Identity ident, identity_flags_t flags); - -void key_reset_trust(Identity ident); - -boost::python::list import_key(string key_data); -string export_key(Identity ident); -string export_secret_key(Identity ident); -void set_own_key(Identity& ident, string fpr); - -} /* namespace PythonAdapter */ -} /* namespace pEp */ - -#endif /* BASIC_API_HH */ \ No newline at end of file diff --git a/src/pEp/native_pEp/identity.cc b/src/pEp/native_pEp/identity.cc deleted file mode 100644 index 6849741..0000000 --- a/src/pEp/native_pEp/identity.cc +++ /dev/null @@ -1,285 +0,0 @@ -// This file is under GNU Affero General Public License 3.0 -// see LICENSE.txt - -// System -#include -#include - -// Engine -#include -#include -#include - -// local -#include "identity.hh" -#include "pEpmodule.hh" -#include "basic_api.hh" -#include "message_api.hh" - -namespace pEp { -namespace PythonAdapter { -using namespace std; -using namespace boost::python; - -Identity::Identity(string address, string username, string user_id, - string fpr, int comm_type, string lang, identity_flags_t flags) - : _ident(new_identity(address.c_str(), fpr.c_str(), user_id.c_str(), - username.c_str()), &::free_identity) -{ - if (!_ident) - throw bad_alloc(); - _ident->comm_type = (PEP_comm_type) comm_type; - _ident->flags = (identity_flags_t) flags; - this->lang(lang); -} - -Identity::Identity(const Identity& second) - : _ident(second._ident) -{ - -} - -Identity::Identity(pEp_identity *ident) - : _ident(ident, &::free_identity) -{ - -} - -Identity::~Identity() -{ - -} - -Identity::operator pEp_identity *() -{ - return _ident.get(); -} - -Identity::operator const pEp_identity *() const -{ - return _ident.get(); -} - -string Identity::_repr() -{ - stringstream build; - build << "Identity("; - string address; - if (_ident->address) - address = string(_ident->address); - build << repr(address) << ", "; - string username; - if (_ident->username) - username = string(_ident->username); - build << repr(username) << ", "; - string user_id; - if (_ident->user_id) - user_id = string(_ident->user_id); - build << repr(user_id) << ", "; - string fpr; - if (_ident->fpr) - fpr = string(_ident->fpr); - build << repr(fpr) << ", "; - build << (int) _ident->comm_type << ", "; - string lang = _ident->lang; - build << repr(lang) << ")"; - return build.str(); -} - -string Identity::_str() -{ - if (!(_ident->address && _ident->address[0])) - return ""; - if (!(_ident->username && _ident->username[0])) - return _ident->address; - return string(_ident->username) + " <" + _ident->address + ">"; -} - -void Identity::username(string value) -{ - if (value.length() && value.length() < 5) - throw length_error("username must be at least 5 characters"); - - str_attr(_ident->username, value); -} - -void Identity::lang(string value) -{ - if (value == "") - memset(_ident->lang, 0, 3); - else if (value.length() != 2) - throw length_error("length of lang must be 2"); - else - memcpy(_ident->lang, value.c_str(), 3); -} - -string Identity::lang() -{ - return _ident->lang; -} - -int Identity::rating() -{ - if (!(_ident->address)) - throw invalid_argument("address must be given"); - - PEP_rating rating = PEP_rating_undefined; - PEP_STATUS status = ::identity_rating(Adapter::session(), _ident.get(), &rating); - _throw_status(status); - - return (int) rating; -} - -PEP_color Identity::color() -{ - return _color(rating()); -} - -Identity Identity::copy() -{ - pEp_identity *dup = ::identity_dup(*this); - if (!dup) - throw bad_alloc(); - - return Identity(dup); -} - -Identity Identity::deepcopy(dict&) -{ - return copy(); -} - -void Identity::update() -{ - update_identity(*this); -} - -void Identity::key_reset(string fpr) -{ - PEP_STATUS status = ::key_reset_identity(Adapter::session(), *this, - fpr != "" ? fpr.c_str() : nullptr); - _throw_status(status); -} - -void Identity::key_mistrusted() -{ - PEP_STATUS status = ::key_mistrusted(Adapter::session(), *this); - _throw_status(status); -} - -bool Identity::is_pEp_user() -{ - bool result; - PEP_STATUS status = ::is_pEp_user(Adapter::session(), *this, &result); - _throw_status(status); - return result; -} - -void Identity::enable_for_sync() -{ - PEP_STATUS status = ::enable_identity_for_sync(Adapter::session(), *this); - _throw_status(status); -} - -void Identity::disable_for_sync() -{ - PEP_STATUS status = ::disable_identity_for_sync(Adapter::session(), *this); - _throw_status(status); -} - -Myself::Myself(string address, string username, string user_id, string lang) - : Identity(address, username, user_id, "", 0, lang) - -{ - if (!(address.length() && username.length())) - throw invalid_argument("address and username must be set"); - if (lang.length() && lang.length() != 2) - throw length_error("lang must be an ISO 639-1 language code or empty"); - - // FIXME: should set .me - // _ident->me = true; - if (user_id.length()) - throw runtime_error("user_id feature not yet implemented for Myself"); -} - -void Myself::update() -{ - pEp::PythonAdapter::myself(*this); -} - -Identity identity_attr(pEp_identity *&ident) -{ - if (!ident) - throw out_of_range("no identity assigned"); - - pEp_identity *_dup = identity_dup(ident); - if (!_dup) - throw bad_alloc(); - - Identity _ident(_dup); - return _ident; -} - -void identity_attr(pEp_identity *&ident, object value) -{ - Identity& _ident = extract< Identity& >(value); - pEp_identity *_dup = ::identity_dup(_ident); - if (!_dup) - throw bad_alloc(); - PEP_STATUS status = update_identity(Adapter::session(), _dup); - _throw_status(status); - free_identity(ident); - ident = _dup; -} - -boost::python::list identitylist_attr(identity_list *&il) -{ - boost::python::list result; - - for (identity_list *_il = il; _il && _il->ident; _il = _il->next) { - pEp_identity *ident = ::identity_dup(_il->ident); - if (!ident) - throw bad_alloc(); - result.append(object(Identity(ident))); - } - - return result; -} - -void identitylist_attr(identity_list *&il, boost::python::list value) -{ - identity_list *_il = new_identity_list(NULL); - if (!_il) - throw bad_alloc(); - - identity_list *_i = _il; - for (int i=0; i extract_identity(value[i]); - if (!extract_identity.check()) { - free_identity_list(_il); - } - pEp_identity *_ident = extract_identity(); - pEp_identity *_dup = ::identity_dup(_ident); - if (!_dup) { - free_identity_list(_il); - throw bad_alloc(); - } - PEP_STATUS status = update_identity(Adapter::session(), _dup); - if (status != PEP_STATUS_OK) { - free_identity_list(_il); - _throw_status(status); - } - _i = identity_list_add(_i, _dup); - if (!_i) { - free_identity_list(_il); - throw bad_alloc(); - } - } - - free_identity_list(il); - il = _il; -} - -} // namespace PythonAdapter -} // namespace pEp { - diff --git a/src/pEp/native_pEp/identity.hh b/src/pEp/native_pEp/identity.hh deleted file mode 100644 index d70133f..0000000 --- a/src/pEp/native_pEp/identity.hh +++ /dev/null @@ -1,102 +0,0 @@ -// This file is under GNU Affero General Public License 3.0 -// see LICENSE.txt - -#ifndef IDENTITY_HH -#define IDENTITY_HH - -// System -#include -#include -#include -#include - -// Engine -#include -#include - -//libpEpAdapter -#include "pEp/Adapter.hh" - -// local -#include "str_attr.hh" - -namespace pEp { -namespace PythonAdapter { - -using std::string; -using std::shared_ptr; - -// Identity is owning a pEp_identity - -class Identity { -protected: - shared_ptr< pEp_identity > _ident; - -public: - Identity(string address = "", string username = "", - string user_id = "", string fpr = "", int comm_type = 0, - string lang = "", identity_flags_t flags = 0); - - Identity(const Identity& second); - Identity(pEp_identity *ident); - virtual ~Identity(); - operator pEp_identity *(); - operator const pEp_identity *() const; - - string _repr(); - string _str(); - - string address() { return str_attr(_ident->address); } - void address(string value) { str_attr(_ident->address, value); } - - string fpr() { return str_attr(_ident->fpr); } - void fpr(string value) { str_attr(_ident->fpr, value); } - - string user_id() { return str_attr(_ident->user_id); } - void user_id(string value) { str_attr(_ident->user_id, value); } - - string username() { return str_attr(_ident->username); } - void username(string value); - - PEP_comm_type comm_type() { return _ident->comm_type; } - void comm_type(PEP_comm_type value) { _ident->comm_type = value; }; - - std::string lang(); - void lang(std::string value); - - identity_flags_t flags() { return _ident->flags; } - void flags(identity_flags_t flags) { _ident->flags = flags; } - - int rating(); - PEP_color color(); - - Identity copy(); - Identity deepcopy(dict& memo); - - virtual void update(); - - void key_reset(string fpr=""); - void key_mistrusted(); - - bool is_pEp_user(); - - void enable_for_sync(); - void disable_for_sync(); -}; - -class Myself : public Identity { -public: - Myself(string address, string username, string user_id="", string lang=""); - virtual void update(); -}; - -Identity identity_attr(pEp_identity *&ident); -void identity_attr(pEp_identity *&ident, object value); - -boost::python::list identitylist_attr(identity_list *&il); -void identitylist_attr(identity_list *&il, boost::python::list value); - -} /* namespace PythonAdapter */ -} /* namespace pEp */ - -#endif /* IDENTITY_HH */ \ No newline at end of file diff --git a/src/pEp/native_pEp/message.cc b/src/pEp/native_pEp/message.cc deleted file mode 100644 index 3d0a279..0000000 --- a/src/pEp/native_pEp/message.cc +++ /dev/null @@ -1,413 +0,0 @@ -// This file is under GNU Affero General Public License 3.0 -// see LICENSE.txt - -// System -#include -#include -#include -#include -#include -#include - -// Engine -#include -#include -#include - -// local -#include "message.hh" -#include "message_api.hh" - -namespace pEp { -namespace PythonAdapter { -using namespace std; -using namespace boost::python; - -Message::Blob::Blob(bloblist_t *bl, bool chained) : - _bl(bl), part_of_chain(chained) -{ - if (!_bl) - throw bad_alloc(); -} - -Message::Blob::Blob(object data, string mime_type, string filename) : - _bl(new_bloblist(NULL, 0, NULL, NULL)), part_of_chain(false) -{ - if (!_bl) - throw bad_alloc(); - - Py_buffer src; - int result = PyObject_GetBuffer(data.ptr(), &src, PyBUF_CONTIG_RO); - if (result) - throw invalid_argument("need a contiguous buffer to read"); - - char *mem = (char *)malloc(src.len); - if (!mem) { - PyBuffer_Release(&src); - throw bad_alloc(); - } - - memcpy(mem, src.buf, src.len); - free(_bl->value); - _bl->size = src.len; - _bl->value = mem; - - PyBuffer_Release(&src); - - this->mime_type(mime_type); - this->filename(filename); -} - -Message::Blob::Blob(const Message::Blob& second) : - _bl(second._bl), part_of_chain(true) -{ - -} - -Message::Blob::~Blob() -{ - if (!part_of_chain) { - free(_bl->value); - free(_bl); - } -} - -string Message::Blob::_repr() -{ - stringstream build; - build << "Blob("; - if (!_bl) { - build << "b'', '', ''"; - } - else { - build << "bytes(" << _bl->size << "), "; - string mime_type; - if (_bl->mime_type) - mime_type = string(_bl->mime_type); - string filename; - if (_bl->filename) - filename = string(_bl->filename); - build << repr(mime_type) << ", "; - build << repr(filename); - } - build << ")"; - return build.str(); -} - -int Message::Blob::getbuffer(PyObject *self, Py_buffer *view, int flags) { - bloblist_t *bl = NULL; - - try { - Message::Blob& blob = extract< Message::Blob& >(self); - bl = blob._bl; - } - catch (exception& e) { - PyErr_SetString(PyExc_RuntimeError, "extract not possible"); - view->obj = NULL; - return -1; - } - - if (!(bl && bl->value)) { - PyErr_SetString(PyExc_RuntimeError, "no data available"); - view->obj = NULL; - return -1; - } - - return PyBuffer_FillInfo(view, self, bl->value, bl->size, 0, flags); -} - -string Message::Blob::decode(string encoding) -{ - if (encoding == "") { - string _mime_type = _bl->mime_type ? _bl->mime_type : ""; - encoding = "ascii"; - - if (_mime_type == "application/pEp.sync") - encoding = "pep.sync"; - - if (_mime_type == "application/pEp.keyreset") - encoding = "pep.distribution"; - - } - object codecs = import("codecs"); - object _decode = codecs.attr("decode"); - return call< string >(_decode.ptr(), this, encoding); -} - -PyBufferProcs Message::Blob::bp = { getbuffer, NULL }; - -Message::Message(int dir, Identity *from) - : _msg(new_message((PEP_msg_direction) dir), &free_message) -{ - if (!_msg) - throw bad_alloc(); - - if (from) { - _msg->from = ::identity_dup(*from); - if (!_msg->from) - throw bad_alloc(); - _msg->dir = (PEP_msg_direction) dir; - } -} - -Message::Message(string mimetext) - : _msg(NULL, &free_message) -{ - message *_cpy; - PEP_STATUS status = mime_decode_message(mimetext.c_str(), - mimetext.size(), &_cpy, NULL); - switch (status) { - case PEP_STATUS_OK: - if (_cpy) - _cpy->dir = PEP_dir_outgoing; - else - _cpy = new_message(PEP_dir_outgoing); - - if (!_cpy) - throw bad_alloc(); - - _msg = shared_ptr< message >(_cpy); - break; - - case PEP_BUFFER_TOO_SMALL: - throw runtime_error("mime_decode_message: buffer too small"); - - case PEP_CANNOT_CREATE_TEMP_FILE: - throw runtime_error("mime_decode_message: cannot create temp file"); - - case PEP_OUT_OF_MEMORY: - throw bad_alloc(); - - default: - stringstream build; - build << "mime_decode_message: unknown error (" << (int) status << ")"; - throw runtime_error(build.str()); - } -} - -Message::Message(const Message& second) - : _msg(second._msg) -{ - if (!_msg.get()) - throw bad_alloc(); -} - -Message::Message(message *msg) - : _msg(::message_dup(msg), &free_message) -{ - -} - -Message::~Message() -{ - -} - -Message::operator message *() -{ - return _msg.get(); -} - -Message::operator const message *() const -{ - return _msg.get(); -} - -string Message::_str() -{ - if (!(_msg->from && _msg->from->address && _msg->from->address[0])) - throw out_of_range(".from_.address missing"); - - char *mimetext; - string result; - - PEP_STATUS status = mime_encode_message(*this, false, &mimetext, false); - switch (status) { - case PEP_STATUS_OK: - result = mimetext; - free(mimetext); - break; - - case PEP_BUFFER_TOO_SMALL: - throw runtime_error("mime_encode_message: buffer too small"); - - case PEP_CANNOT_CREATE_TEMP_FILE: - throw runtime_error("mime_encode_message: cannot create temp file"); - - case PEP_OUT_OF_MEMORY: - throw bad_alloc(); - - default: - stringstream build; - build << "mime_encode_message: unknown error (" << (int) status << ")"; - throw runtime_error(build.str()); - } - - return result; -} - -string Message::_repr() -{ - stringstream build; - build << "Message(" << repr(_str()) << ")"; - return build.str(); -} - -boost::python::tuple Message::attachments() -{ - boost::python::list l; - - for (bloblist_t *bl = _msg->attachments; bl && bl->value; bl = - bl->next) { - l.append(Blob(bl, true)); - } - - return boost::python::tuple(l); -} - -void Message::attachments(boost::python::list value) -{ - bloblist_t *bl = new_bloblist(NULL, 0, NULL, NULL); - if (!bl) - throw bad_alloc(); - - bloblist_t *_l = bl; - for (int i=0; i(value[i]); - _l = bloblist_add(_l, blob._bl->value, blob._bl->size, - blob._bl->mime_type, blob._bl->filename); - if (!_l) { - for (_l = bl; _l && _l->value; ) { - free(_l->mime_type); - free(_l->filename); - bloblist_t *_ll = _l; - _l = _l->next; - free(_ll); - } - throw bad_alloc(); - } - } - - for (int i=0; i(value[i]); - blob._bl->value = NULL; - blob._bl->size = 0; - free(blob._bl->mime_type); - blob._bl->mime_type = NULL; - free(blob._bl->filename); - blob._bl->filename = NULL; - } - - free_bloblist(_msg->attachments); - _msg->attachments = bl; -} - -Message Message::encrypt() -{ - boost::python::list extra; - return encrypt_message(*this, extra, PEP_enc_PGP_MIME, 0); -} - -Message Message::_encrypt(boost::python::list extra, int enc_format, int flags) -{ - if (!enc_format) - enc_format = PEP_enc_PGP_MIME; - return encrypt_message(*this, extra, enc_format, flags); -} - -boost::python::tuple Message::decrypt(int flags) { - return pEp::PythonAdapter::decrypt_message(*this, flags); -} - -PEP_rating Message::outgoing_rating() -{ - if (_msg->dir != PEP_dir_outgoing) - throw invalid_argument("Message.dir must be outgoing"); - - if (from().address() == "") - throw invalid_argument("from.address needed"); - if (from().username() == "") - throw invalid_argument("from.username needed"); - - if (len(to()) + len(cc()) == 0) - throw invalid_argument("either to or cc needed"); - - PEP_STATUS status = myself(Adapter::session(), _msg->from); - _throw_status(status); - - PEP_rating rating = PEP_rating_undefined; - status = outgoing_message_rating(Adapter::session(), *this, &rating); - _throw_status(status); - - return rating; -} - -PEP_color Message::outgoing_color() -{ - return _color(outgoing_rating()); -} - -Message Message::copy() -{ - message *dup = message_dup(*this); - if (!dup) - throw bad_alloc(); - return Message(dup); -} - -Message Message::deepcopy(dict&) -{ - return copy(); -} - -Message outgoing_message(Identity me) -{ - if (me.address().empty() || me.user_id().empty()) - throw runtime_error("at least address and user_id of own user needed"); - - ::myself(Adapter::session(), me); - auto m = Message(PEP_dir_outgoing, &me); - return m; -} - -static object update(Identity ident) -{ - if (ident.address().empty()) - throw runtime_error("at least address needed"); - update_identity(Adapter::session(), ident); - return object(ident); -} - -static boost::python::list update(boost::python::list il) -{ - for (int i=0; i(il[i])); - } - - return il; -} - -Message incoming_message(string mime_text) -{ - auto m = Message(mime_text); - m.dir(PEP_dir_incoming); - - try { - m.from(update(m.from())); - } - catch (out_of_range&) { } - - try { - m.recv_by(update(m.recv_by())); - } - catch (out_of_range&) { } - - m.to(update(m.to())); - m.cc(update(m.cc())); - m.reply_to(update(m.reply_to())); - - return m; -} - -} // namespace PythonAdapter -} // namespace pEp { diff --git a/src/pEp/native_pEp/message.hh b/src/pEp/native_pEp/message.hh deleted file mode 100644 index dac9a73..0000000 --- a/src/pEp/native_pEp/message.hh +++ /dev/null @@ -1,154 +0,0 @@ -// This file is under GNU Affero General Public License 3.0 -// see LICENSE.txt - -#ifndef MESSAGE_HH -#define MESSAGE_HH - -// System -#include -#include -#include - -// Engine -#include -#include - -// local -#include "str_attr.hh" -#include "identity.hh" - -namespace pEp { -namespace PythonAdapter { -using std::string; -using std::runtime_error; -using std::invalid_argument; -using boost::lexical_cast; - -// Message is owning a message struct - -class Message { - shared_ptr< ::message > _msg; - -public: - // Blob is owning a bloblist_t struct - or not and just managing - // one depending on part_of_chain - - class Blob { - bloblist_t *_bl; - bool part_of_chain; - - public: - Blob(bloblist_t *bl = new_bloblist(NULL, 0, NULL, NULL), - bool chained = false); - Blob(object data, string mime_type = "", string filename = ""); - Blob(const Blob& second); - ~Blob(); - - string _repr(); - - string mime_type() { return _bl ? str_attr(_bl->mime_type) : ""; } - void mime_type(string value) { str_attr(_bl->mime_type, value); } - - string filename() { return str_attr(_bl->filename); } - void filename(string value) { str_attr(_bl->filename, value); } - - size_t size() { return _bl->size; } - string decode(string encoding); - string decode() { return decode(""); } - - static PyBufferProcs bp; - - friend class Message; - - protected: - static int getbuffer(PyObject *self, Py_buffer *view, int flags); - }; - - Message(int dir = PEP_dir_outgoing, Identity *from = NULL); - Message(string mimetext); - Message(const Message& second); - Message(message *msg); - ~Message(); - operator message *(); - operator const message *() const; - - string _str(); - string _repr(); - - PEP_msg_direction dir() { return _msg->dir; } - void dir(PEP_msg_direction value) { _msg->dir = value; } - - string id() { return str_attr(_msg->id); } - void id(string value) { str_attr(_msg->id, value); } - - string shortmsg() { return str_attr(_msg->shortmsg); } - void shortmsg(string value) { str_attr(_msg->shortmsg, value); } - - string longmsg() { return str_attr(_msg->longmsg); } - void longmsg(string value) { str_attr(_msg->longmsg, value); } - - string longmsg_formatted() { return str_attr(_msg->longmsg_formatted); } - void longmsg_formatted(string value) { str_attr(_msg->longmsg_formatted, value); } - - boost::python::tuple attachments(); - void attachments(boost::python::list value); - - time_t sent() { return timestamp_attr(_msg->sent); } - void sent(time_t value) { timestamp_attr(_msg->sent, value); } - - time_t recv() { return timestamp_attr(_msg->recv); } - void recv(time_t value) { timestamp_attr(_msg->recv, value); } - - Identity from() { return identity_attr(_msg->from); } - void from(object value) { identity_attr(_msg->from, value); } - - boost::python::list to() { return identitylist_attr(_msg->to); } - void to(boost::python::list value) { identitylist_attr(_msg->to, value); } - - Identity recv_by() { return identity_attr(_msg->recv_by); } - void recv_by(object value) { identity_attr(_msg->recv_by, value); } - - boost::python::list cc() { return identitylist_attr(_msg->cc); } - void cc(boost::python::list value) { identitylist_attr(_msg->cc, value); } - - boost::python::list bcc() { return identitylist_attr(_msg->bcc); } - void bcc(boost::python::list value) { identitylist_attr(_msg->bcc, value); } - - boost::python::list reply_to() { return identitylist_attr(_msg->reply_to); } - void reply_to(boost::python::list value) { identitylist_attr(_msg->reply_to, value); } - - boost::python::list in_reply_to() { return strlist_attr(_msg->in_reply_to); } - void in_reply_to(boost::python::list value) { strlist_attr(_msg->in_reply_to, value); } - - boost::python::list references() { return strlist_attr(_msg->references); } - void references(boost::python::list value) { strlist_attr(_msg->references, value); } - - boost::python::list keywords() { return strlist_attr(_msg->keywords); } - void keywords(boost::python::list value) { strlist_attr(_msg->keywords, value); } - - string comments() { return str_attr(_msg->comments); } - void comments(string value) { str_attr(_msg->comments, value); } - - dict opt_fields() { return strdict_attr(_msg->opt_fields); } - void opt_fields(dict value) { return strdict_attr(_msg->opt_fields, value); } - - PEP_enc_format enc_format() { return _msg->enc_format; } - void enc_format(PEP_enc_format value) { _msg->enc_format = value; } - - Message encrypt(); - Message _encrypt(boost::python::list extra, int enc_format=4, int flags=0); - - boost::python::tuple decrypt(int flags=0); - PEP_rating outgoing_rating(); - PEP_color outgoing_color(); - Message deepcopy(dict& memo); - Message copy(); -}; - -Message outgoing_message(Identity me); -Message incoming_message(string mime_text); - -} /* namespace PythonAdapter */ -} /* namespace pEp */ - -#endif /* MESSAGE_HH */ \ No newline at end of file diff --git a/src/pEp/native_pEp/message_api.cc b/src/pEp/native_pEp/message_api.cc deleted file mode 100644 index 6a4b76d..0000000 --- a/src/pEp/native_pEp/message_api.cc +++ /dev/null @@ -1,172 +0,0 @@ -// This file is under GNU Affero General Public License 3.0 -// see LICENSE.txt - -// Engine -#include -#include -#include -#include -#include - -// local -#include "message_api.hh" -#include "basic_api.hh" - -namespace pEp { -namespace PythonAdapter { -using namespace std; -using namespace boost::python; - -Message encrypt_message(Message src, boost::python::list extra, int enc_format, int flags) -{ - Identity _from = src.from(); - if (_from.address() == "") - throw invalid_argument("encrypt_message: src.from_.address empty"); - if (_from.username() == "") - throw invalid_argument("encrypt_message: src.from_.username empty"); - - if (_from.user_id() == "") - src.from().user_id(_from.address()); - - stringlist_t *_extra = to_stringlist(extra); - PEP_enc_format _enc_format = (PEP_enc_format) enc_format; - PEP_encrypt_flags_t _flags = (PEP_encrypt_flags_t) flags; - message *_dst = NULL; - - message *_src = src; - PEP_STATUS status = encrypt_message(Adapter::session(), _src, _extra, &_dst, - _enc_format, _flags); - free_stringlist(_extra); - _throw_status(status); - - if (!_dst || _dst == _src) - return Message(_src); - - return Message(_dst); -} - -boost::python::tuple decrypt_message(Message src, int flags) -{ - message *_dst = NULL; - stringlist_t *_keylist = NULL; - PEP_rating _rating = PEP_rating_undefined; - PEP_decrypt_flags_t _flags = (PEP_decrypt_flags_t) flags; - message *_src = src; - - PEP_STATUS status = ::decrypt_message(Adapter::session(), _src, &_dst, &_keylist, - &_rating, &_flags); - _throw_status(status); - - boost::python::list keylist; - if (_keylist) { - keylist = from_stringlist(_keylist); - free_stringlist(_keylist); - } - - Message dst = _dst ? Message(_dst) : Message(src); - return boost::python::make_tuple(dst, keylist, _rating, _flags); -} - -PEP_color _color(int rating) -{ - return ::color_from_rating((PEP_rating) rating); -} - -boost::python::tuple sync_decode(object buffer) -{ - Py_buffer src; - int result = PyObject_GetBuffer(buffer.ptr(), &src, PyBUF_CONTIG_RO); - if (result) - throw invalid_argument("need a contiguous buffer to read"); - - char *dst = NULL; - PEP_STATUS status = PER_to_XER_Sync_msg((char *) src.buf, src.len, &dst); - PyBuffer_Release(&src); - _throw_status(status); - - string _dst(dst); - free(dst); - return boost::python::make_tuple(_dst, 0); -} - -static boost::python::tuple sync_encode(string text) -{ - char *data = NULL; - size_t size = 0; - PEP_STATUS status = XER_to_PER_Sync_msg(text.c_str(), &data, &size); - _throw_status(status); - - PyObject *ba = PyBytes_FromStringAndSize(data, size); - free(data); - if (!ba) - throw bad_alloc(); - - return boost::python::make_tuple(object(handle<>(ba)), 0); -} - -boost::python::tuple Distribution_decode(object buffer) -{ - Py_buffer src; - int result = PyObject_GetBuffer(buffer.ptr(), &src, PyBUF_CONTIG_RO); - if (result) - throw invalid_argument("need a contiguous buffer to read"); - - char *dst = NULL; - PEP_STATUS status = PER_to_XER_Distribution_msg((char *) src.buf, src.len, &dst); - PyBuffer_Release(&src); - _throw_status(status); - - string _dst(dst); - free(dst); - return boost::python::make_tuple(_dst, 0); -} - -static boost::python::tuple Distribution_encode(string text) -{ - char *data = NULL; - size_t size = 0; - PEP_STATUS status = XER_to_PER_Distribution_msg(text.c_str(), &data, &size); - _throw_status(status); - - PyObject *ba = PyBytes_FromStringAndSize(data, size); - free(data); - if (!ba) - throw bad_alloc(); - - return boost::python::make_tuple(object(handle<>(ba)), 0); -} - -object sync_search(string name) -{ - if (name != "pep.sync") { - return object(); - } - else { - object codecs = import("codecs"); - object CodecInfo = codecs.attr("CodecInfo"); - - object _sync_decode = make_function(sync_decode); - object _sync_encode = make_function(sync_encode); - - return call< object >(CodecInfo.ptr(), _sync_encode, _sync_decode); - } -} - -object distribution_search(string name) -{ - if (name != "pep.distribution") { - return object(); - } - else { - object codecs = import("codecs"); - object CodecInfo = codecs.attr("CodecInfo"); - - object _distribution_decode = make_function(Distribution_decode); - object _distribution_encode = make_function(Distribution_encode); - - return call< object >(CodecInfo.ptr(), _distribution_encode, _distribution_decode); - } -} - -} // namespace PythonAdapter -} // namespace pEp { diff --git a/src/pEp/native_pEp/message_api.hh b/src/pEp/native_pEp/message_api.hh deleted file mode 100644 index a793d32..0000000 --- a/src/pEp/native_pEp/message_api.hh +++ /dev/null @@ -1,27 +0,0 @@ -// This file is under GNU Affero General Public License 3.0 -// see LICENSE.txt - -#ifndef MESSAGE_API_HH -#define MESSAGE_API_HH - -#include "pEpmodule.hh" - -namespace pEp { -namespace PythonAdapter { - -Message encrypt_message( - Message src, - boost::python::list extra = boost::python::list(), - int enc_format = 4, - int flags = 0 -); - -boost::python::tuple decrypt_message(Message src, int flags=0); -PEP_color _color(int rating); -object sync_search(string name); -object distribution_search(string name); - -} /* namespace PythonAdapter */ -} /* namespace pEp */ - -#endif /* MESSAGE_API_HH */ \ No newline at end of file diff --git a/src/pEp/native_pEp/pEpmodule.cc b/src/pEp/native_pEp/pEpmodule.cc deleted file mode 100644 index 6148c95..0000000 --- a/src/pEp/native_pEp/pEpmodule.cc +++ /dev/null @@ -1,673 +0,0 @@ -// This file is under GNU Affero General Public License 3.0 -// see LICENSE.txt - -// System -#include -#include -#include -#include -#include -#include - -// Engine -#include -#include -#include -#include - -// libpEpAdapter -#include -#include -#include - -// local -#include "pEpmodule.hh" -#include "basic_api.hh" -#include "message_api.hh" -//#include "user_interface.hh" - -namespace pEp { -namespace PythonAdapter { -using namespace std; -using namespace boost::python; - -static const char *version_string = "p≡p Python adapter version 0.3"; - -void init_before_main_module() { - pEpLog("called"); -} - -// hidden init function, wrapped by hello_world.init() -void _init_after_main_module() { - pEpLog("called"); - callback_dispatcher.add(_messageToSend, notifyHandshake, nullptr, nullptr); - Adapter::_messageToSend = CallbackDispatcher::messageToSend; -} - - -void config_passive_mode(bool enable) -{ - ::config_passive_mode(Adapter::session(), enable); -} - -void config_unencrypted_subject(bool enable) -{ - ::config_unencrypted_subject(Adapter::session(), enable); -} - -void key_reset_user(string user_id, string fpr) -{ - if (user_id == "") - throw invalid_argument("user_id required"); - - PEP_STATUS status = ::key_reset_user(Adapter::session(), - user_id.c_str(), fpr != "" ? fpr.c_str() : nullptr); - _throw_status(status); -} - -void key_reset_user2(string user_id) -{ - key_reset_user(user_id, ""); -} - -void key_reset_all_own_keys() -{ - PEP_STATUS status = ::key_reset_all_own_keys(Adapter::session()); - _throw_status(status); -} - -static string about() -{ - string version = string(version_string) + "\np≡p version " - + PEP_VERSION + "\n"; - return version; -} - -void _throw_status(PEP_STATUS status) -{ - if (status == PEP_STATUS_OK) - return; - if (status >= 0x400 && status <= 0x4ff) - return; - if (status == PEP_OUT_OF_MEMORY) - throw bad_alloc(); - if (status == PEP_ILLEGAL_VALUE) - throw invalid_argument("illegal value"); - - if (string(pEp_status_to_string(status)) == "unknown status code") { - stringstream build; - build << setfill('0') << "p≡p 0x" << setw(4) << hex << status; - throw runtime_error(build.str()); - } - else { - throw runtime_error(pEp_status_to_string(status)); - } -} - -PEP_STATUS _messageToSend(::message *msg) -{ - pEpLog("called"); - try { - PyGILState_STATE gil = PyGILState_Ensure(); - pEpLog("GIL Aquired"); - object modref = import("pEp"); - object funcref = modref.attr("message_to_send"); - call(funcref.ptr(), Message()); - PyGILState_Release(gil); - pEpLog("GIL released"); - } catch (exception& e) { } - - return PEP_STATUS_OK; -} - -PEP_STATUS notifyHandshake(pEp_identity *me, pEp_identity *partner, sync_handshake_signal signal) -{ - pEpLog("called"); - try { - PyGILState_STATE gil = PyGILState_Ensure(); - pEpLog("GIL Aquired"); - object modref = import("pEp"); - object funcref = modref.attr("notify_handshake"); - call(funcref.ptr(), me, partner, signal); - PyGILState_Release(gil); - pEpLog("GIL released"); - } catch (exception& e) { } - - return PEP_STATUS_OK; -} - - -void start_sync() -{ - CallbackDispatcher::start_sync(); -} - -void shutdown_sync() -{ - CallbackDispatcher::stop_sync(); -} - -void debug_color(int ansi_color) -{ - ::set_debug_color(Adapter::session(), ansi_color); -} - -void leave_device_group() -{ - ::leave_device_group(Adapter::session()); -} - -bool is_sync_active() -{ - return Adapter::is_sync_running(); -} - -void testfunc() { - _messageToSend(NULL); -} - -void deliverHandshakeResult(int result, object identities) -{ - identity_list *shared_identities = nullptr; - if (identities != boost::python::api::object() && boost::python::len(identities)) { - shared_identities = new_identity_list(nullptr); - if (!shared_identities) - throw bad_alloc(); - - try { - identity_list *si = shared_identities; - for (int i=0; i < boost::python::len(identities); ++i) { - Identity ident = extract< Identity >(identities[i]); - si = identity_list_add(si, ident); - if (!si) - throw bad_alloc(); - } - } - catch (exception& ex) { - free_identity_list(shared_identities); - throw ex; - } - } - - PEP_STATUS status = ::deliverHandshakeResult(Adapter::session(), (sync_handshake_result) result, shared_identities); - free_identity_list(shared_identities); - _throw_status(status); -} - -BOOST_PYTHON_MODULE(native_pEp) -{ - init_before_main_module(); - - // Module init function called by pEp.init() - def("_init_after_main_module", _init_after_main_module); - def("testfunc", &testfunc); - - docstring_options doc_options(true, false, false); - boost::locale::generator gen; - std::locale::global(gen("")); - -// _scope = new scope(); - scope().attr("about") = about(); - scope().attr("per_user_directory") = per_user_directory(); - scope().attr("per_machine_directory") = per_machine_directory(); - scope().attr("engine_version") = get_engine_version(); - scope().attr("protocol_version") = get_protocol_version(); - - def("passive_mode", config_passive_mode, - "do not attach pub keys to all messages"); - - def("unencrypted_subject", config_unencrypted_subject, - "do not encrypt the subject of messages"); - - def("key_reset", key_reset_user, - "reset the default database status for the user / keypair provided\n" - "This will effectively perform key_reset on each identity\n" - "associated with the key and user_id, if a key is provided, and for\n" - "each key (and all of their identities) if an fpr is not."); - - def("key_reset", key_reset_user2, - "reset the default database status for the user / keypair provided\n" - "This will effectively perform key_reset on each identity\n" - "associated with the key and user_id, if a key is provided, and for\n" - "each key (and all of their identities) if an fpr is not."); - - def("key_reset_all_own_keys", key_reset_all_own_keys, - "revoke and mistrust all own keys, generate new keys for all\n" - "own identities, and opportunistically communicate key reset\n" - "information to people we have recently contacted."); - - auto identity_class = class_("Identity", - "Identity(address, username, user_id='', fpr='', comm_type=0, lang='en')\n" - "\n" - "represents a p≡p identity\n" - "\n" - "an identity is a network address, under which a user is represented in\n" - "the network\n" - "\n" - " address network address, either an SMTP address or a URI\n" - " username real name or nickname for user\n" - " user_id ID this user is handled by the application\n" - " fpr full fingerprint of the key being used as key ID,\n" - " hex encoded\n" - " comm_type first rating level of this communication channel\n" - " lang ISO 639-1 language code for language being preferred\n" - " on this communication channel\n" - ) - .def(boost::python::init()) - .def(boost::python::init()) - .def(boost::python::init()) - .def(boost::python::init()) - .def(boost::python::init()) - .def(boost::python::init()) - .def("__repr__", &Identity::_repr) - .def("__str__", &Identity::_str, - "string representation of this identity\n" - "following the pattern 'username < address >'\n" - ) - .def("key_reset", &Identity::key_reset, - boost::python::arg("fpr")=object(""), - "reset the default database status for the identity / keypair provided. If this\n" - "corresponds to the own user and a private key, also revoke the key, generate a\n" - "new one, and communicate the reset to recently contacted pEp partners for this\n" - "identity. If it does not, remove the key from the keyring; the key's status is\n" - "completely fresh on next contact from the partner.") - - .def("key_mistrusted", &Identity::key_mistrusted, - boost::python::arg("fpr")=object(""), - "If you want updated trust on the identity, you ll have" - "to call update_identity or myself respectively after this." - "N.B. If you are calling this on a key that is the identity or user default," - "it will be removed as the default key for ANY identity and user for which" - "it is the default. Please keep in mind that the undo in undo_last_mistrust" - "will only undo the current identity's / it's user's default, not any" - "other identities which may be impacted (this will not affect most use cases)") - - .def("enable_for_sync", &Identity::enable_for_sync, - "Enable own identity for p≡p sync.\n\n" - "Only use this on own identities, which are used as accounts.\n") - .def("disable_for_sync", &Identity::disable_for_sync, - "Disable own identity for p≡p sync.\n\n" - "Only use this on own identities, which are used as accounts.\n") - - .add_property("address", (string(Identity::*)()) &Identity::address, - (void(Identity::*)(string)) &Identity::address, - "email address or URI") - .add_property("fpr", (string(Identity::*)()) &Identity::fpr, - (void(Identity::*)(string)) &Identity::fpr, - "key ID (full fingerprint, hex encoded)") - .add_property("user_id", (string(Identity::*)()) &Identity::user_id, - (void(Identity::*)(string)) &Identity::user_id, - "ID of person associated or 'pEp_own_userId' if own identity") - .add_property("username", (string(Identity::*)()) &Identity::username, - (void(Identity::*)(string)) &Identity::username, - "name in full of person associated") - .add_property("comm_type", (int(Identity::*)()) - (PEP_comm_type(Identity::*)()) &Identity::comm_type, - (void(Identity::*)(int)) - (void(Identity::*)(PEP_comm_type)) &Identity::comm_type, - "communication type, first rating level (p≡p internal)") - .add_property("lang", (string(Identity::*)()) &Identity::lang, - (void(Identity::*)(string)) &Identity::lang, - "ISO 639-1 language code") - .add_property("flags", (identity_flags_t(Identity::*)()) &Identity::flags, - (void(Identity::*)(identity_flags_t)) &Identity::flags, - "flags (p≡p internal)") - .add_property("rating", &Identity::rating, "rating of Identity") - .add_property("color", &Identity::color, "color of Identity as PEP_color") - .add_property("is_pEp_user", &Identity::is_pEp_user, "True if this is an identity of a pEp user") - .def("__deepcopy__", &Identity::deepcopy) - .def("update", &Identity::update, "update Identity") - .def("__copy__", &Identity::copy); - - identity_class.attr("PEP_OWN_USERID") = "pEp_own_userId"; - - auto blob_class = class_("Blob", - "Blob(data, mime_type='', filename='')\n" - "\n" - "Binary large object\n" - "\n" - " data bytes-like object\n" - " mime_type MIME type for the data\n" - " filename filename to store the data\n" , - boost::python::init< object, char const*, char const* >(args("data", "mime_type", "filename"))) - .def(boost::python::init()) - .def(boost::python::init()) - .def("__repr__", &Message::Blob::_repr) - .def("__len__", &Message::Blob::size, "size of Blob data in bytes") - .def("decode", (string(Message::Blob::*)()) &Message::Blob::decode) - .def("decode", (string(Message::Blob::*)(string)) &Message::Blob::decode, - "text = blob.decode(encoding='')\n" - "\n" - "decode Blob data into string depending on MIME type if encoding=''\n" - "\n" - " mime_type='application/pEp.sync' decode as 'pEp.sync'\n" - " mime_type='application/pEp.keyreset' decode as 'pEp.keyreset'\n" - " other mime_type decode as 'ascii' by default\n" - ) - .add_property("mime_type", (string(Message::Blob::*)()) &Message::Blob::mime_type, - (void(Message::Blob::*)(string)) &Message::Blob::mime_type, - "MIME type of object in Blob") - .add_property("filename", (string(Message::Blob::*)()) &Message::Blob::filename, - (void(Message::Blob::*)(string)) &Message::Blob::filename, - "filename of object in Blob"); - - ((PyTypeObject *)(void *)blob_class.ptr())->tp_as_buffer = &Message::Blob::bp; - - auto message_class = class_("Message", - "Message(dir=1, from=None)\n" - "\n" - "new p≡p message\n" - "\n" - " dir 1 for outgoing, 2 for incoming\n" - " from Identity() of sender\n" - "\n" - "Message(mime_text)\n" - "\n" - "new incoming p≡p message\n" - "\n" - " mime_text text in Multipurpose Internet Mail Extensions format\n" - ) - .def(boost::python::init()) - .def(boost::python::init()) - .def(boost::python::init()) - .def("__str__", &Message::_str, - "the string representation of a Message is it's MIME text" - ) - .def("__repr__", &Message::_repr) - .add_property("dir", (int(Message::*)()) - (PEP_msg_direction(Message::*)()) &Message::dir, - (void(Message::*)(int)) - (void(Message::*)(PEP_msg_direction)) &Message::dir, - "0: incoming, 1: outgoing message") - .add_property("id", (string(Message::*)()) &Message::id, - (void(Message::*)(string)) &Message::id, - "message ID") - .add_property("shortmsg", (string(Message::*)()) &Message::shortmsg, - (void(Message::*)(string)) &Message::shortmsg, - "subject or short message") - .add_property("longmsg", (string(Message::*)()) &Message::longmsg, - (void(Message::*)(string)) &Message::longmsg, - "body or long version of message") - .add_property("longmsg_formatted", (string(Message::*)()) &Message::longmsg_formatted, - (void(Message::*)(string)) &Message::longmsg_formatted, - "HTML body or fromatted long version of message") - .add_property("attachments", (boost::python::tuple(Message::*)()) &Message::attachments, - (void(Message::*)(boost::python::list)) &Message::attachments, - "tuple of Blobs with attachments; setting moves Blobs to attachment tuple") - .add_property("sent", (time_t(Message::*)()) &Message::sent, - (void(Message::*)(time_t)) &Message::sent, - "time when message was sent in UTC seconds since epoch") - .add_property("recv", (time_t(Message::*)()) &Message::recv, - (void(Message::*)(time_t)) &Message::recv, - "time when message was received in UTC seconds since epoch") - .add_property("from_", (Identity(Message::*)()) &Message::from, - (void(Message::*)(object)) &Message::from, - "identity where message is from") - .add_property("to", (boost::python::list(Message::*)()) &Message::to, - (void(Message::*)(boost::python::list)) &Message::to, - "list of identities message is going to") - .add_property("recv_by", (Identity(Message::*)()) &Message::recv_by, - (void(Message::*)(object)) &Message::recv_by, - "identity where message was received by") - .add_property("cc", (boost::python::list(Message::*)()) &Message::cc, - (void(Message::*)(boost::python::list)) &Message::cc, - "list of identities message is going cc") - .add_property("bcc", (boost::python::list(Message::*)()) &Message::bcc, - (void(Message::*)(boost::python::list)) &Message::bcc, - "list of identities message is going bcc") - .add_property("reply_to", (boost::python::list(Message::*)()) &Message::reply_to, - (void(Message::*)(boost::python::list)) &Message::reply_to, - "list of identities where message will be replied to") - .add_property("in_reply_to", (boost::python::list(Message::*)()) &Message::in_reply_to, - (void(Message::*)(boost::python::list)) &Message::in_reply_to, - "in_reply_to list") - .add_property("references", (boost::python::list(Message::*)()) &Message::references, - (void(Message::*)(boost::python::list)) &Message::references, - "message IDs of messages this one is referring to") - .add_property("keywords", (boost::python::list(Message::*)()) &Message::keywords, - (void(Message::*)(boost::python::list)) &Message::keywords, - "keywords this message should be stored under") - .add_property("comments", (string(Message::*)()) &Message::comments, - (void(Message::*)(string)) &Message::comments, - "comments added to message") - .add_property("opt_fields", (dict(Message::*)()) &Message::opt_fields, - (void(Message::*)(dict)) &Message::opt_fields, - "opt_fields of message") - .add_property("enc_format", (int(Message::*)()) - (PEP_enc_format(Message::*)()) &Message::enc_format, - (void(Message::*)(int)) - (void(Message::*)(PEP_enc_format)) &Message::enc_format, - "0: unencrypted, 1: inline PGP, 2: S/MIME, 3: PGP/MIME, 4: p≡p format") - .def("encrypt", (Message(Message::*)())&Message::encrypt) - .def("encrypt", (Message(Message::*)(boost::python::list))&Message::_encrypt) - .def("encrypt", (Message(Message::*)(boost::python::list,int))&Message::_encrypt) - .def("encrypt", (Message(Message::*)(boost::python::list,int,int))&Message::_encrypt, - "msg2 = msg1.encrypt(extra_keys=[], enc_format='pEp', flags=0)\n" - "\n" - "encrypts a p≡p message and returns the encrypted message\n" - "\n" - " extra_keys list of strings with fingerprints for extra keys to use\n" - " for encryption\n" - " enc_format 0 for none, 1 for partitioned, 2 for S/MIME,\n" - " 3 for PGP/MIME, 4 for pEp\n" - " flags 1 is force encryption\n" - ) - .def("decrypt", &Message::decrypt, boost::python::arg("flags")=0, - "msg2, keys, rating, flags = msg1.decrypt()\n" - "\n" - "decrypts a p≡p message and returns a tuple with data\n" - "\n" - " msg the decrypted p≡p message\n" - " keys a list of keys being used\n" - " rating the rating of the message as integer\n" - " flags flags set while decryption\n" - ) - .add_property("outgoing_rating", &Message::outgoing_rating, "rating outgoing message will have") - .add_property("outgoing_color", &Message::outgoing_color, "color outgoing message will have as PEP_color") - .def("__deepcopy__", &Message::deepcopy) - .def("__copy__", &Message::copy); - - // basic API and key management API - - def("update_identity", &update_identity, - "update_identity(ident)\n" - "\n" - "update identity information\n" - "call this to complete identity information when you at least have an address\n" - ); - def("myself", &myself, - "myself(ident)\n" - "\n" - "ensures that the own identity is being complete\n" - "supply ident.address and ident.username\n" - ); - def("trust_personal_key", &trust_personal_key, - "trust_personal_key(ident)\n" - "\n" - "mark a key as trusted with a person\n" - ); - - enum_("identity_flags") - .value("PEP_idf_not_for_sync", PEP_idf_not_for_sync) - .value("PEP_idf_list", PEP_idf_list) - .value("PEP_idf_devicegroup", PEP_idf_devicegroup); - - def("set_identity_flags", &set_identity_flags, - "set_identity_flags(ident, flags)\n" - "\n" - "set identity flags\n" - ); - - def("unset_identity_flags", &unset_identity_flags, - "unset_identity_flags(ident, flags)\n" - "\n" - "unset identity flags\n" - ); - - def("key_reset_trust", &key_reset_trust, - "key_reset_trust(ident)\n" - "\n" - "reset trust bit or explicitly mistrusted status for an identity and " - "its accompanying key/user_id pair\n" - ); - - def("import_key", &import_key, - "private_key_list = import_key(key_data)\n" - "\n" - "import key(s) from key_data\n" - ); - - def("export_key", &export_key, - "key_data = export_key(identity)\n" - "\n" - "export key(s) of identity\n" - ); - - def("export_secret_key", &export_secret_key, - "key_data = export_seret_key(identity)\n" - "\n" - "export secret key(s) of identity\n" - ); - - def("set_own_key", &set_own_key, - "set_own_key(me, fpr)\n" - "\n" - "mark a key as an own key, and make it the default key\n" - "\n" - "me Own identity for which to add the existing key\n" - "fpr The fingerprint of the key to be added\n" - "\n" - "me->address, me->user_id and me->username must be set to valid data\n" - "myself() is called by set_own_key() without key generation\n" - "me->flags are ignored\n" - "me->address must not be an alias\n" - "me->fpr will be ignored and replaced by fpr\n" - ); - - // message API - - enum_("rating") - .value("_undefined", PEP_rating_undefined) - .value("cannot_decrypt", PEP_rating_cannot_decrypt) - .value("have_no_key", PEP_rating_have_no_key) - .value("unencrypted", PEP_rating_unencrypted) - .value("unreliable", PEP_rating_unreliable) - .value("reliable", PEP_rating_reliable) - .value("trusted", PEP_rating_trusted) - .value("trusted_and_anonymized", PEP_rating_trusted_and_anonymized) - .value("fully_anonymous", PEP_rating_fully_anonymous) - .value("mistrust", PEP_rating_mistrust) - .value("b0rken", PEP_rating_b0rken) - .value("under_attack", PEP_rating_under_attack); - - enum_("colorvalue") - .value("no_color", PEP_color_no_color) - .value("yellow", PEP_color_yellow) - .value("green", PEP_color_green) - .value("red", PEP_color_red); - - - def("incoming_message", &incoming_message, - "msg = incoming_message(mime_text)\n" - "\n" - "create an incoming message from a MIME text" - ); - def("outgoing_message", &outgoing_message, - "msg = outgoing_message(ident)\n" - "\n" - "create an outgoing message using an own identity" - ); - def("color", &_color, - "c = color(rating)\n" - "\n" - "calculate color value out of rating. Returns PEP_color" - ); - def("trustwords", &_trustwords, - "text = trustwords(ident_own, ident_partner)\n" - "\n" - "calculate trustwords for two Identities"); - - // Sync API - - enum_("sync_handshake_signal") - .value("SYNC_NOTIFY_UNDEFINED" , SYNC_NOTIFY_UNDEFINED) - .value("SYNC_NOTIFY_INIT_ADD_OUR_DEVICE" , SYNC_NOTIFY_INIT_ADD_OUR_DEVICE) - .value("SYNC_NOTIFY_INIT_ADD_OTHER_DEVICE" , SYNC_NOTIFY_INIT_ADD_OTHER_DEVICE) - .value("SYNC_NOTIFY_INIT_FORM_GROUP" , SYNC_NOTIFY_INIT_FORM_GROUP) - .value("SYNC_NOTIFY_TIMEOUT" , SYNC_NOTIFY_TIMEOUT) - .value("SYNC_NOTIFY_ACCEPTED_DEVICE_ADDED" , SYNC_NOTIFY_ACCEPTED_DEVICE_ADDED) - .value("SYNC_NOTIFY_ACCEPTED_GROUP_CREATED", SYNC_NOTIFY_ACCEPTED_GROUP_CREATED) - .value("SYNC_NOTIFY_ACCEPTED_DEVICE_ACCEPTED", SYNC_NOTIFY_ACCEPTED_DEVICE_ACCEPTED) - .value("SYNC_NOTIFY_SOLE" , SYNC_NOTIFY_SOLE) - .value("SYNC_NOTIFY_IN_GROUP" , SYNC_NOTIFY_IN_GROUP); - -// auto user_interface_class = class_( -// "UserInterface", -// "class MyUserInterface(UserInterface):\n" -// " def notifyHandshake(self, me, partner):\n" -// " ...\n" -// "\n" -// "p≡p User Interface class\n" -// "To be used as a mixin\n" -// ) -// .def("notifyHandshake", &UserInterface::notifyHandshake, -// "notifyHandshake(self, me, partner)\n" -// "\n" -// " me own identity\n" -// " partner identity of communication partner\n" -// "\n" -// "overwrite this method with an implementation of a handshake dialog") -// .def("deliverHandshakeResult", &UserInterface::deliverHandshakeResult, -// boost::python::arg("identities")=object(), -// "deliverHandshakeResult(self, result, identities=None)\n" -// "\n" -// " result -1: cancel, 0: accepted, 1: rejected\n" -// " identities list of identities to share or None for all\n" -// "\n" -// "call to deliver the handshake result of the handshake dialog" -// ); - - def("deliver_handshake_result", &deliverHandshakeResult, boost::python::arg("identities")=object(), - "deliverHandshakeResult(self, result, identities=None)\n" - "\n" - " result -1: cancel, 0: accepted, 1: rejected\n" - " identities list of identities to share or None for all\n" - "\n" - "call to deliver the handshake result of the handshake dialog" - ); - - def("start_sync", &start_sync, - "start_sync()\n" - "\n" - "starts the sync thread" - ); - - def("shutdown_sync", &shutdown_sync, - "shutdown_sync()\n" - "\n" - "call this from another thread to shut down the sync thread\n" - ); - - def("debug_color", &debug_color, - "for debug builds set ANSI color value"); - - def("leave_device_group", &leave_device_group, - "leave_device_group()\n" - "\n" - "call this for a grouped device, which should leave\n" - ); - - def("is_sync_active", &is_sync_active, - "is_sync_active()\n" - "\n" - "True if sync is active, False otherwise\n" - ); - - - // codecs - call< object >(((object)(import("codecs").attr("register"))).ptr(), make_function(sync_search)); - call< object >(((object)(import("codecs").attr("register"))).ptr(), make_function(distribution_search)); -} - -} // namespace PythonAdapter -} // namespace pEp diff --git a/src/pEp/native_pEp/pEpmodule.hh b/src/pEp/native_pEp/pEpmodule.hh deleted file mode 100644 index 0b72c96..0000000 --- a/src/pEp/native_pEp/pEpmodule.hh +++ /dev/null @@ -1,28 +0,0 @@ -// This file is under GNU Affero General Public License 3.0 -// see LICENSE.txt - -#ifndef PEPMODULE_HH -#define PEPMODULE_HH - -// Engine -#include - -// local -#include "message.hh" - -namespace pEp { -namespace PythonAdapter { - -extern string device_name; -void config_passive_mode(bool enable); -void config_unencrypted_subject(bool enable); -void key_reset_user(string user_id, string fpr); -void key_reset_all_own_keys(); -void _throw_status(PEP_STATUS status); -PEP_STATUS _messageToSend(::message *msg); -PEP_STATUS notifyHandshake(pEp_identity *me, pEp_identity *partner, sync_handshake_signal signal); - -} /* namespace PythonAdapter */ -} /* namespace pEp */ - -#endif /* PEPMODULE_HH */ \ No newline at end of file diff --git a/src/pEp/native_pEp/str_attr.cc b/src/pEp/native_pEp/str_attr.cc deleted file mode 100644 index 96810ad..0000000 --- a/src/pEp/native_pEp/str_attr.cc +++ /dev/null @@ -1,184 +0,0 @@ -// This file is under GNU Affero General Public License 3.0 -// see LICENSE.txt - -// System -#include -#include -#include - -// local -#include "str_attr.hh" - -namespace pEp { -namespace PythonAdapter { -using namespace std; -using namespace boost::python; -using namespace boost::locale; - -object repr(object s) -{ - return s.attr("__repr__")(); -} - -string repr(string s) -{ - str _s = s.c_str(); - object _r = _s.attr("__repr__")(); - string r = extract< string >(_r); - return r; -} - -string str_attr(char *&str) -{ - if (!str) - return string(""); - return string(str); -} - -void str_attr(char *&str, string value) -{ - string normalized = normalize(value, norm_nfc); - free(str); - str = strdup(normalized.c_str()); - if (!str) - throw bad_alloc(); -} - -time_t timestamp_attr(timestamp *&ts) -{ - if (!ts) - return 0; - - return timegm(ts); -} - -void timestamp_attr(timestamp *&ts, time_t value) -{ - free_timestamp(ts); - ts = new_timestamp(value); -} - -boost::python::list strlist_attr(stringlist_t *&sl) -{ - boost::python::list result; - - for (stringlist_t *_sl = sl; _sl && _sl->value; _sl = _sl->next) { - string s(_sl->value); - result.append(object(s)); - } - - return result; -} - -void strlist_attr(stringlist_t *&sl, boost::python::list value) -{ - stringlist_t *_sl = new_stringlist(NULL); - if (!_sl) - throw bad_alloc(); - - stringlist_t *_s = _sl; - for (int i=0; i extract_string(value[i]); - if (!extract_string.check()) { - free_stringlist(_sl); - } - string s = extract_string(); - s = normalize(s, norm_nfc); - _s = stringlist_add(_s, s.c_str()); - if (!_s) { - free_stringlist(_sl); - throw bad_alloc(); - } - } - - free_stringlist(sl); - sl = _sl; -} - -dict strdict_attr(stringpair_list_t *&spl) -{ - dict result; - - for (stringpair_list_t *_spl = spl; _spl && _spl->value; _spl = - _spl->next) { - stringpair_t *p = _spl->value; - if (p->key && p->value) { - string key(p->key); - string value(p->value); - - result[key] = value; - } - } - - return result; -} - -void strdict_attr(stringpair_list_t *&spl, dict value) -{ - stringpair_list_t *_spl = new_stringpair_list(NULL); - if (!_spl) - throw bad_alloc(); - - stringpair_list_t *_s = _spl; - for (int i=0; i extract_key(value.keys()[i]); - extract< string > extract_value(value.values()[i]); - - if (!(extract_key.check() && extract_value.check())) - free_stringpair_list(_spl); - - string key = extract_key(); - key = normalize(key, norm_nfc); - string _value = extract_value(); - _value = normalize(_value, norm_nfc); - stringpair_t *pair = new_stringpair(key.c_str(), _value.c_str()); - if (!pair) { - free_stringpair_list(_spl); - throw bad_alloc(); - } - _s = stringpair_list_add(_s, pair); - if (!_s) { - free_stringpair_list(_spl); - throw bad_alloc(); - } - } - - free_stringpair_list(spl); - spl = _spl; -} - -stringlist_t *to_stringlist(boost::python::list l) -{ - stringlist_t *result = new_stringlist(NULL); - if (!result) - throw bad_alloc(); - - stringlist_t *_s = result; - for (int i=0; i extract_string(l[i]); - if (!extract_string.check()) - free_stringlist(result); - string s = extract_string(); - _s = stringlist_add(_s, s.c_str()); - if (!_s) { - free_stringlist(result); - throw bad_alloc(); - } - } - - return result; -} - -boost::python::list from_stringlist(const stringlist_t *sl) -{ - boost::python::list result; - for (const stringlist_t *_sl = sl; _sl && _sl->value; _sl = _sl->next) { - string s = _sl->value; - result.append(s); - } - return result; -} - -} // namespace PythonAdapter -} // namespace pEp { - diff --git a/src/pEp/native_pEp/user_interface.cc b/src/pEp/native_pEp/user_interface.cc deleted file mode 100644 index 22be5db..0000000 --- a/src/pEp/native_pEp/user_interface.cc +++ /dev/null @@ -1,130 +0,0 @@ -// This file is under GNU Affero General Public License 3.0 -// see LICENSE.txt - -// System -#include - -// local -#include "user_interface.hh" - -namespace pEp { -namespace PythonAdapter { -using namespace std; -using namespace boost::python; - -UserInterface *UserInterface::_ui = nullptr; - -UserInterface::UserInterface() -{ - if (_ui) - throw runtime_error("only one UserInterface thread allowed"); - _ui = this; -} - -UserInterface::~UserInterface() -{ - _ui = nullptr; -} - -UserInterface_callback::UserInterface_callback(PyObject *self) : - UserInterface(), _self(self) -{ -// adapter.ui_object(self); -// PEP_STATUS status = ::register_sync_callbacks(Adapter::session(), -// (void *) this, _notifyHandshake, retrieve_next_sync_event); -// assert(status == PEP_STATUS_OK); -// if (status) -// _throw_status(status); -} - -UserInterface_callback::~UserInterface_callback() -{ -// ::unregister_sync_callbacks(Adapter::session()); -} - -PEP_STATUS UserInterface::_notifyHandshake( - pEp_identity *me, pEp_identity *partner, - sync_handshake_signal signal - ) -{ - if (!(me && partner)) - return PEP_ILLEGAL_VALUE; - - auto that = dynamic_cast< UserInterface_callback * >(_ui); - that->notifyHandshake(Identity(me), Identity(partner), signal); - - return PEP_STATUS_OK; -} - -void UserInterface::deliverHandshakeResult(int result, object identities) -{ - identity_list *shared_identities = nullptr; - if (identities != boost::python::api::object() && boost::python::len(identities)) { - shared_identities = new_identity_list(nullptr); - if (!shared_identities) - throw bad_alloc(); - - try { - identity_list *si = shared_identities; - for (int i=0; i < boost::python::len(identities); ++i) { - Identity ident = extract< Identity >(identities[i]); - si = identity_list_add(si, ident); - if (!si) - throw bad_alloc(); - } - } - catch (exception& ex) { - free_identity_list(shared_identities); - throw ex; - } - } - - PEP_STATUS status = ::deliverHandshakeResult(Adapter::session(), - (sync_handshake_result) result, shared_identities); - free_identity_list(shared_identities); - _throw_status(status); -} - -//PEP_rating UserInterface::get_key_rating_for_user(string user_id, string fpr) -//{ -// PEP_rating result; -// PEP_STATUS status = -// ::get_key_rating_for_user(Adapter::session(), -// user_id.c_str(), fpr.c_str(), &result); -// _throw_status(status); -// return result; -//} - -//SYNC_EVENT UserInterface::retrieve_next_sync_event(void *management, unsigned threshold) -//{ -// time_t started = time(nullptr); -// bool timeout = false; -// -// while (adapter.queue().empty()) { -// int i = 0; -// ++i; -// if (i > 10) { -// if (time(nullptr) > started + threshold) { -// timeout = true; -// break; -// } -// i = 0; -// } -// nanosleep((const struct timespec[]){{0, 100000000L}}, NULL); -// } -// -// if (timeout) -// return new_sync_timeout_event(); -// -// return adapter.queue().pop_front(); -//} - -void UserInterface_callback::notifyHandshake( - Identity me, Identity partner, sync_handshake_signal signal) -{ - call_method< void >(_self, "notifyHandshake", me, partner, signal); -} - -} // namespace PythonAdapter -} // namespace pEp { - diff --git a/src/pEp/native_pEp/user_interface.hh b/src/pEp/native_pEp/user_interface.hh deleted file mode 100644 index f609a41..0000000 --- a/src/pEp/native_pEp/user_interface.hh +++ /dev/null @@ -1,60 +0,0 @@ -// This file is under GNU Affero General Public License 3.0 -// see LICENSE.txt - -#ifndef USER_INTERFACE_HH -#define USER_INTERFACE_HH - -// System -#include - -// Engine -#include -#include - -// local -#include "pEpmodule.hh" - - -namespace pEp { -namespace PythonAdapter { - -class UserInterface { - static UserInterface *_ui; -public: - UserInterface(); - virtual ~UserInterface(); - - virtual void notifyHandshake( - Identity me, - Identity partner, - sync_handshake_signal signal) - { - throw runtime_error("override this method"); - } - - virtual void deliverHandshakeResult(int result, object identities); - -// PEP_rating get_key_rating_for_user(string user_id, string fpr); - -protected: - static PEP_STATUS _notifyHandshake(pEp_identity *me, pEp_identity *partner, sync_handshake_signal signal); -}; - -class UserInterface_callback : public UserInterface { - PyObject *_self; -public: - UserInterface_callback(PyObject *self); - ~UserInterface_callback(); - - void notifyHandshake( - Identity me, - Identity partner, - sync_handshake_signal signal - ); -}; - -} /* namespace PythonAdapter */ -} /* namespace pEp */ - -#endif /* USER_INTERFACE_HH */ - diff --git a/test/basic_doctest.py b/test/basic_doctest.py deleted file mode 100755 index 2207bed..0000000 --- a/test/basic_doctest.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -""" ->>> import pEp ->>> me = pEp.Identity("alice.smith@peptest.ch", "Alice Smith", "23") ->>> me.username -'Alice Smith' ->>> print(me) -Alice Smith ->>> you = pEp.Identity("bob.bourne@peptest.ch", "Bob Bourne", "42") ->>> print(you) -Bob Bourne ->>> m = pEp.outgoing_message(me) ->>> m.to = [you] ->>> m.shortmsg = "let's meet next week" ->>> m.longmsg = "Please call me back" ->>> m2 = m.encrypt() ->>> print(m2) ->>> m3, keys, rating, flags = m2.decrypt() ->>> rating -pEp.rating.reliable -""" - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..7934bb2 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# This file is under GNU Affero General Public License 3.0 +# see LICENSE.txt diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..71bd834 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# This file is under GNU Affero General Public License 3.0 +# see LICENSE.txt + +"""pytest configuration for the unit tests.""" + +from .model import * + +# Init +@pytest.fixture() +def env_init(tmpdir_factory, request): + """Create a tmp dir for the tests""" + base = str(abs(hash(request.node.nodeid)))[:3] + bn = tmpdir_factory.mktemp(base) + print(bn) + import os + os.environ["PEP_HOME"] = str(bn) + os.environ["HOME"] = str(bn) + + +@pytest.fixture() +def pEp(env_init): + import pEp + return pEp + + +@pytest.fixture() +def alice_myself(pEp, model): + alice = pEp.Identity( + model.alice.addr, + model.alice.name, + model.alice.user_id + ) + pEp.myself(alice) + return alice + + +@pytest.fixture() +def alice_imported(pEp, model): + pEp.import_key(model.alice.key_sec) + alice = pEp.Identity( + model.alice.addr, + model.alice.name, + model.alice.user_id + ) + return alice + + +@pytest.fixture() +def import_ident_alice_as_own_ident(pEp, model, alice_imported): + alice = alice_imported + pEp.set_own_key(alice, model.alice.fpr) + return alice + + +@pytest.fixture() +def import_ident_bob(pEp, model): + pEp.import_key(model.bob.key_pub) + bob = pEp.Identity( + model.bob.addr, + model.bob.name, + ) + bob.update() + return bob diff --git a/tests/constants.py b/tests/constants.py new file mode 100644 index 0000000..a141ea6 --- /dev/null +++ b/tests/constants.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# This file is under GNU Affero General Public License 3.0 +# see LICENSE.txt + +"""Constants for unit tests.""" +DATADIR = "data" + +OUTGOING_MSG = 1 +INCOMING_MSG = 2 + +SUBJECT = "This is a subject" +BODY = "Hi world!\n" diff --git a/tests/data/1A97F263D8319D6885F638C5AA81E1B5457A2B40.pub.asc b/tests/data/1A97F263D8319D6885F638C5AA81E1B5457A2B40.pub.asc new file mode 100644 index 0000000..945be9a --- /dev/null +++ b/tests/data/1A97F263D8319D6885F638C5AA81E1B5457A2B40.pub.asc @@ -0,0 +1,24 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: 1A97 F263 D831 9D68 85F6 38C5 AA81 E1B5 457A 2B40 +Comment: bob_work@peptest.org + +xjMEXyp4RRYJKwYBBAHaRw8BAQdAdA0qiUaazZJrnWAtqQ+V/XevsZkNHyspsD8M +jWv69i3ChAQfFgoAFQWCXyp4RQWJBaSPvQIVCgKbAQIeAQAhCRCqgeG1RXorQBYh +BBqX8mPYMZ1ohfY4xaqB4bVFeitA1j0BAONtcJp4KI4CTjVJps7udMFPRqAPDXU6 +0YJzHWXK0mspAQCA2kZ2mJKFvMiKl3mY79N08eF2QE4zOVHYQYR+GZrtAc0UYm9i +X3dvcmtAcGVwdGVzdC5vcmfChAQTFgoAFQWCXyp4RQWJBaSPvQIVCgKbAQIeAQAh +CRCqgeG1RXorQBYhBBqX8mPYMZ1ohfY4xaqB4bVFeitA0NsA/i+jZZ8jVJVjTN9d +OPlCSg2rgMoW4CMudbvCeJ05HBGgAQDfpHv0mceDpw0Xb7JvSCZGpcnx7JvnrijO +o7s0km1fB84zBF8qeEUWCSsGAQQB2kcPAQEHQCqxykiuw5mJk7ulNeeS9ZRmNMh+ +A5Za3/Mqwr8neCElwsA7BBgWCgAVBYJfKnhFBYkFpI+9AhUKApsCAh4BAJgJEKqB +4bVFeitAdqAEGRYKAAYFgl8qeEUAIQkQZIW7NIu2E2oWIQQjjCvzjFveMgb5MvZk +hbs0i7YTav2zAQCDuIDQu3vO31HUiGvSc2/ZYRKUAHJsVfnCovZSFiBaHwD+MNgr +QYydV0jkx2hyaQV7GGml132sKCVmHW6t+8+B8AYWIQQal/Jj2DGdaIX2OMWqgeG1 +RXorQI6IAP0RuRRGJV8zg9ax3HSpBYsqQCTjq/Kswiskrzlda77RrwD9EnohAaPF +0yd5MrZ0H01+RJseLhF20mznjYX4ucKckQrOOARfKnhFEgorBgEEAZdVAQUBAQdA +/QOYQ28riHwdqlzL1SS89+sK52RUSGJ+z2jMEGZXrg4DAQgJwoQEGBYKABUFgl8q +eEUFiQWkj70CCwkCmwwCHgEAIQkQqoHhtUV6K0AWIQQal/Jj2DGdaIX2OMWqgeG1 +RXorQAI/AQCVdZqaCPSHI8KQ75SmjCHOrizFJ5OvRQIxLcgFYnbf4QD/RK8/na7X +t3H5zFD9yjCeNtTKuBubz8wnlV48V6F8NQk= +=71Mh +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/data/1A97F263D8319D6885F638C5AA81E1B5457A2B40.sec.asc b/tests/data/1A97F263D8319D6885F638C5AA81E1B5457A2B40.sec.asc new file mode 100644 index 0000000..5fea9ad --- /dev/null +++ b/tests/data/1A97F263D8319D6885F638C5AA81E1B5457A2B40.sec.asc @@ -0,0 +1,26 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: 1A97 F263 D831 9D68 85F6 38C5 AA81 E1B5 457A 2B40 +Comment: bob_work@peptest.org + +xVgEXyp4RRYJKwYBBAHaRw8BAQdAdA0qiUaazZJrnWAtqQ+V/XevsZkNHyspsD8M +jWv69i0AAP9vlqqd0FjnC219hFfUH3NWDyNkE+qLncgL8iauLJJW2xApwoQEHxYK +ABUFgl8qeEUFiQWkj70CFQoCmwECHgEAIQkQqoHhtUV6K0AWIQQal/Jj2DGdaIX2 +OMWqgeG1RXorQNY9AQDjbXCaeCiOAk41SabO7nTBT0agDw11OtGCcx1lytJrKQEA +gNpGdpiShbzIipd5mO/TdPHhdkBOMzlR2EGEfhma7QHNFGJvYl93b3JrQHBlcHRl +c3Qub3JnwoQEExYKABUFgl8qeEUFiQWkj70CFQoCmwECHgEAIQkQqoHhtUV6K0AW +IQQal/Jj2DGdaIX2OMWqgeG1RXorQNDbAP4vo2WfI1SVY0zfXTj5QkoNq4DKFuAj +LnW7wnidORwRoAEA36R79JnHg6cNF2+yb0gmRqXJ8eyb564ozqO7NJJtXwfHWARf +KnhFFgkrBgEEAdpHDwEBB0AqscpIrsOZiZO7pTXnkvWUZjTIfgOWWt/zKsK/J3gh +JQABAJW6BesKSs2CgM+fx+SKkv+di6b1aTLirOmVdfbse0R9E5TCwDsEGBYKABUF +gl8qeEUFiQWkj70CFQoCmwICHgEAmAkQqoHhtUV6K0B2oAQZFgoABgWCXyp4RQAh +CRBkhbs0i7YTahYhBCOMK/OMW94yBvky9mSFuzSLthNq/bMBAIO4gNC7e87fUdSI +a9Jzb9lhEpQAcmxV+cKi9lIWIFofAP4w2CtBjJ1XSOTHaHJpBXsYaaXXfawoJWYd +bq37z4HwBhYhBBqX8mPYMZ1ohfY4xaqB4bVFeitAjogA/RG5FEYlXzOD1rHcdKkF +iypAJOOr8qzCKySvOV1rvtGvAP0SeiEBo8XTJ3kytnQfTX5Emx4uEXbSbOeNhfi5 +wpyRCsddBF8qeEUSCisGAQQBl1UBBQEBB0D9A5hDbyuIfB2qXMvVJLz36wrnZFRI +Yn7PaMwQZleuDgMBCAkAAP99a9Hrl/wNGP8ohfOaTkMYE9zuBEo8FHsCpLT+/Yz9 +8BIBwoQEGBYKABUFgl8qeEUFiQWkj70CCwkCmwwCHgEAIQkQqoHhtUV6K0AWIQQa +l/Jj2DGdaIX2OMWqgeG1RXorQAI/AQCVdZqaCPSHI8KQ75SmjCHOrizFJ5OvRQIx +LcgFYnbf4QD/RK8/na7Xt3H5zFD9yjCeNtTKuBubz8wnlV48V6F8NQk= +=Yzdu +-----END PGP PRIVATE KEY BLOCK----- diff --git a/tests/data/2D35731B9C754564CBAD15D2D18F7444594F2283.pub.asc b/tests/data/2D35731B9C754564CBAD15D2D18F7444594F2283.pub.asc new file mode 100644 index 0000000..229ab9b --- /dev/null +++ b/tests/data/2D35731B9C754564CBAD15D2D18F7444594F2283.pub.asc @@ -0,0 +1,24 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: 2D35 731B 9C75 4564 CBAD 15D2 D18F 7444 594F 2283 +Comment: alice@peptest.org + +xjMEXxFT2hYJKwYBBAHaRw8BAQdA/q3RICIqQ4FHd6RfqP6QY1gd2trvJn0DBg/1 +jIfdpKfChAQfFgoAFQWCXxFT2gWJBaSPvQIVCgKbAQIeAQAhCRDRj3REWU8igxYh +BC01cxucdUVky60V0tGPdERZTyKDUnYBAMT6z+rJ6HvoAhE531BWhsSU9X0QKLC7 +UYLQ3/ZGBVAbAQC14aJX1sODCKCPK6eivyhfd8l6W69vCBpPa55TgQ3GD80RYWxp +Y2VAcGVwdGVzdC5vcmfChAQTFgoAFQWCXxFT2gWJBaSPvQIVCgKbAQIeAQAhCRDR +j3REWU8igxYhBC01cxucdUVky60V0tGPdERZTyKDQlMBAPhingJLnqAjx+DnEWdu +nPRwnhxWSgY31GRAD8LG36zlAP94BN2pRvv6sPSCSla+yUvVcYQt927MvI1B3mOI +2jnlAM4zBF8RU9oWCSsGAQQB2kcPAQEHQCxlXfe9mHyk7Z9Cd+/12i5kYm6ljyKV +Jsst1oxMj/CnwsA7BBgWCgAVBYJfEVPaBYkFpI+9AhUKApsCAh4BAJgJENGPdERZ +TyKDdqAEGRYKAAYFgl8RU9oAIQkQYvLo9HBcHrAWIQTnCIjW7qTg/Ju3h6Ni8uj0 +cFwesAy3AP4uHp91sH/DWv5Ac7jppfEXpg+CW/RoKdJ32N5S6BnWZQEAn5jtnve2 +cBx2jgUQEKkGZ6AprrrdCnPgH8AOxNOWbAYWIQQtNXMbnHVFZMutFdLRj3REWU8i +g9JRAQCHR0PJVrsyy91jLA0oAYaEyf743XlrJY8yhM3H4HKKzQEAishD4vi9cpCj +tx4EXIwoqxy2wfLbwfkGulENJS38DAbOOARfEVPaEgorBgEEAZdVAQUBAQdAo7WR +Up2ZLQbkUhTB9Aqw3Z6uKGNUUiOp7GsVu3fpAScDAQgJwoQEGBYKABUFgl8RU9oF +iQWkj70CCwkCmwwCHgEAIQkQ0Y90RFlPIoMWIQQtNXMbnHVFZMutFdLRj3REWU8i +g1QJAP9l9Xyw8j22fFUa6iKzD68SP7zLb4LbGpHdKXcBd86kigD+O0zwJp7S/Ogq +LpOoJOD6xX50+0JnlPnIxZWwhgYPrAs= +=S7O4 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/data/2D35731B9C754564CBAD15D2D18F7444594F2283.sec.asc b/tests/data/2D35731B9C754564CBAD15D2D18F7444594F2283.sec.asc new file mode 100644 index 0000000..db4c9e1 --- /dev/null +++ b/tests/data/2D35731B9C754564CBAD15D2D18F7444594F2283.sec.asc @@ -0,0 +1,26 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: 2D35 731B 9C75 4564 CBAD 15D2 D18F 7444 594F 2283 +Comment: alice@peptest.org + +xVgEXxFT2hYJKwYBBAHaRw8BAQdA/q3RICIqQ4FHd6RfqP6QY1gd2trvJn0DBg/1 +jIfdpKcAAP0W8bWDwzy2kpwqa5gx9T/DWt4kDCoa7tGzMU+dv0FBnBCMwoQEHxYK +ABUFgl8RU9oFiQWkj70CFQoCmwECHgEAIQkQ0Y90RFlPIoMWIQQtNXMbnHVFZMut +FdLRj3REWU8ig1J2AQDE+s/qyeh76AIROd9QVobElPV9ECiwu1GC0N/2RgVQGwEA +teGiV9bDgwigjyunor8oX3fJeluvbwgaT2ueU4ENxg/NEWFsaWNlQHBlcHRlc3Qu +b3JnwoQEExYKABUFgl8RU9oFiQWkj70CFQoCmwECHgEAIQkQ0Y90RFlPIoMWIQQt +NXMbnHVFZMutFdLRj3REWU8ig0JTAQD4Yp4CS56gI8fg5xFnbpz0cJ4cVkoGN9Rk +QA/Cxt+s5QD/eATdqUb7+rD0gkpWvslL1XGELfduzLyNQd5jiNo55QDHWARfEVPa +FgkrBgEEAdpHDwEBB0AsZV33vZh8pO2fQnfv9douZGJupY8ilSbLLdaMTI/wpwAA +/RU5lcZzjrlWdEh95e12IjqpCKt1VpwQhT617pSZyYpqELbCwDsEGBYKABUFgl8R +U9oFiQWkj70CFQoCmwICHgEAmAkQ0Y90RFlPIoN2oAQZFgoABgWCXxFT2gAhCRBi +8uj0cFwesBYhBOcIiNbupOD8m7eHo2Ly6PRwXB6wDLcA/i4en3Wwf8Na/kBzuOml +8RemD4Jb9Ggp0nfY3lLoGdZlAQCfmO2e97ZwHHaOBRAQqQZnoCmuut0Kc+AfwA7E +05ZsBhYhBC01cxucdUVky60V0tGPdERZTyKD0lEBAIdHQ8lWuzLL3WMsDSgBhoTJ +/vjdeWsljzKEzcfgcorNAQCKyEPi+L1ykKO3HgRcjCirHLbB8tvB+Qa6UQ0lLfwM +BsddBF8RU9oSCisGAQQBl1UBBQEBB0CjtZFSnZktBuRSFMH0CrDdnq4oY1RSI6ns +axW7d+kBJwMBCAkAAP9kY8sKTIAbQiR7PVRDPfI49ccZmoDSjNG6nPBeT7JwEA/h +woQEGBYKABUFgl8RU9oFiQWkj70CCwkCmwwCHgEAIQkQ0Y90RFlPIoMWIQQtNXMb +nHVFZMutFdLRj3REWU8ig1QJAP9l9Xyw8j22fFUa6iKzD68SP7zLb4LbGpHdKXcB +d86kigD+O0zwJp7S/OgqLpOoJOD6xX50+0JnlPnIxZWwhgYPrAs= +=uIGP +-----END PGP PRIVATE KEY BLOCK----- diff --git a/tests/data/3E45175EE953EBBEB948F11A6A03DB2A17FB9D15.pub.asc b/tests/data/3E45175EE953EBBEB948F11A6A03DB2A17FB9D15.pub.asc new file mode 100644 index 0000000..ea98c2a --- /dev/null +++ b/tests/data/3E45175EE953EBBEB948F11A6A03DB2A17FB9D15.pub.asc @@ -0,0 +1,24 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: 3E45 175E E953 EBBE B948 F11A 6A03 DB2A 17FB 9D15 +Comment: alice_work@peptest.org + +xjMEXxFT7BYJKwYBBAHaRw8BAQdAToI/CRX5M3LjPpSCfmJtOngHTos502W02e4e +I1CyNE/ChAQfFgoAFQWCXxFT7AWJBaSPvQIVCgKbAQIeAQAhCRBqA9sqF/udFRYh +BD5FF17pU+u+uUjxGmoD2yoX+50VV9cA/1HmBlEd6SVkhAK7dP46yGQtEJLix5x+ +Z14COnk/EKETAP9Roz7sJakpqC/VejESHekpt5DterDWAXc4hjf1ADutDc0WYWxp +Y2Vfd29ya0BwZXB0ZXN0Lm9yZ8KEBBMWCgAVBYJfEVPsBYkFpI+9AhUKApsBAh4B +ACEJEGoD2yoX+50VFiEEPkUXXulT6765SPEaagPbKhf7nRVc2wD/Q7ISeG3cDbji +W+17gSnlYEYtG5EjIzGavYMr2tPhAlcA/RpuaaYkB+9FGYapXpKEZfOa9gRfg4sY +l2jIK5QNWYcDzjMEXxFT7BYJKwYBBAHaRw8BAQdAnljLgkOQD0jx8eoIbV0rUN1o +m5mux0+pLHZ6sIt+yD7CwDsEGBYKABUFgl8RU+wFiQWkj70CFQoCmwICHgEAmAkQ +agPbKhf7nRV2oAQZFgoABgWCXxFT7AAhCRBLlptQp2yWJxYhBMqF7w4SDl18JRD6 +akuWm1CnbJYnUBEBAM55LvWxQ7Mi65AJEGhRlqGzCexUDheuXgVZYbqboc2wAQDc +SUxqP5Jbq66pAWSpHBTgT1rrZW9RbsA+yOA9B6DpBxYhBD5FF17pU+u+uUjxGmoD +2yoX+50VAjgBANay4I3jqeDBCkUlxPQZr0wlcxhiZW4m6NSamTqQvsLCAPwPivrv +YXaMc4zGSAesvk0Mwo5+2eX984EiHzsW+1mUAM44BF8RU+wSCisGAQQBl1UBBQEB +B0C0Ix+Z8q+NmQ7W64bAZwf1lYbDIykmtmh4pOCu9/XGdQMBCAnChAQYFgoAFQWC +XxFT7AWJBaSPvQILCQKbDAIeAQAhCRBqA9sqF/udFRYhBD5FF17pU+u+uUjxGmoD +2yoX+50VvKMBAOHyhqD60oJJpA1G116lL7LjKjnFuJG/6tG4xfvFi2fPAQCxPM+N +Pm5OZkyuDqsmY1RkrsbPCMOer3nrCNaKWYBcCg== +=FGLu +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/data/3E45175EE953EBBEB948F11A6A03DB2A17FB9D15.sec.asc b/tests/data/3E45175EE953EBBEB948F11A6A03DB2A17FB9D15.sec.asc new file mode 100644 index 0000000..505ce3b --- /dev/null +++ b/tests/data/3E45175EE953EBBEB948F11A6A03DB2A17FB9D15.sec.asc @@ -0,0 +1,26 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: 3E45 175E E953 EBBE B948 F11A 6A03 DB2A 17FB 9D15 +Comment: alice_work@peptest.org + +xVgEXxFT7BYJKwYBBAHaRw8BAQdAToI/CRX5M3LjPpSCfmJtOngHTos502W02e4e +I1CyNE8AAP9nsNGRiCaR0Rd9A0zQ8olYfdApaEGAtlI7Ke5AjnRGvRBMwoQEHxYK +ABUFgl8RU+wFiQWkj70CFQoCmwECHgEAIQkQagPbKhf7nRUWIQQ+RRde6VPrvrlI +8RpqA9sqF/udFVfXAP9R5gZRHeklZIQCu3T+OshkLRCS4secfmdeAjp5PxChEwD/ +UaM+7CWpKagv1XoxEh3pKbeQ7Xqw1gF3OIY39QA7rQ3NFmFsaWNlX3dvcmtAcGVw +dGVzdC5vcmfChAQTFgoAFQWCXxFT7AWJBaSPvQIVCgKbAQIeAQAhCRBqA9sqF/ud +FRYhBD5FF17pU+u+uUjxGmoD2yoX+50VXNsA/0OyEnht3A244lvte4Ep5WBGLRuR +IyMxmr2DK9rT4QJXAP0abmmmJAfvRRmGqV6ShGXzmvYEX4OLGJdoyCuUDVmHA8dY +BF8RU+wWCSsGAQQB2kcPAQEHQJ5Yy4JDkA9I8fHqCG1dK1DdaJuZrsdPqSx2erCL +fsg+AAEA9dzB32gcqp1IQ3GXFZ7gu6ay46pdfrlUNNvnyzUxW8wSOcLAOwQYFgoA +FQWCXxFT7AWJBaSPvQIVCgKbAgIeAQCYCRBqA9sqF/udFXagBBkWCgAGBYJfEVPs +ACEJEEuWm1CnbJYnFiEEyoXvDhIOXXwlEPpqS5abUKdslidQEQEAznku9bFDsyLr +kAkQaFGWobMJ7FQOF65eBVlhupuhzbABANxJTGo/klurrqkBZKkcFOBPWutlb1Fu +wD7I4D0HoOkHFiEEPkUXXulT6765SPEaagPbKhf7nRUCOAEA1rLgjeOp4MEKRSXE +9BmvTCVzGGJlbibo1JqZOpC+wsIA/A+K+u9hdoxzjMZIB6y+TQzCjn7Z5f3zgSIf +Oxb7WZQAx10EXxFT7BIKKwYBBAGXVQEFAQEHQLQjH5nyr42ZDtbrhsBnB/WVhsMj +KSa2aHik4K739cZ1AwEICQAA/2iRtG/26b2MEAaxszNxbwdaeE39GU5HvdLmnuVQ +kTwgENbChAQYFgoAFQWCXxFT7AWJBaSPvQILCQKbDAIeAQAhCRBqA9sqF/udFRYh +BD5FF17pU+u+uUjxGmoD2yoX+50VvKMBAOHyhqD60oJJpA1G116lL7LjKjnFuJG/ +6tG4xfvFi2fPAQCxPM+NPm5OZkyuDqsmY1RkrsbPCMOer3nrCNaKWYBcCg== +=Vrqy +-----END PGP PRIVATE KEY BLOCK----- diff --git a/tests/data/6A9835699EF1215F1558A496D9C1D4B0984094E5.pub.asc b/tests/data/6A9835699EF1215F1558A496D9C1D4B0984094E5.pub.asc new file mode 100644 index 0000000..8219779 --- /dev/null +++ b/tests/data/6A9835699EF1215F1558A496D9C1D4B0984094E5.pub.asc @@ -0,0 +1,24 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: 6A98 3569 9EF1 215F 1558 A496 D9C1 D4B0 9840 94E5 +Comment: bob@peptest.org + +xjMEXxFT+xYJKwYBBAHaRw8BAQdAAYt8QUG+i4GQyfwaUQL+cmGKUcWHkcWX87IC +qmNpm//ChAQfFgoAFQWCXxFT+wWJBaSPvQIVCgKbAQIeAQAhCRDZwdSwmECU5RYh +BGqYNWme8SFfFVikltnB1LCYQJTlle4A/3Xl+WA2cHvE5pYsuhxZGqwABxaOmIx8 +AMxj0TBpuObXAP9y/flFpkK15Et/wl/T7iwAn2HAbQWRaPMoX694iQGjB80PYm9i +QHBlcHRlc3Qub3JnwoQEExYKABUFgl8RU/sFiQWkj70CFQoCmwECHgEAIQkQ2cHU +sJhAlOUWIQRqmDVpnvEhXxVYpJbZwdSwmECU5cmFAQDNarwFonE0e3rmjt1eI6CK +e8VjWgMpOYRa05xPdONVsQD/cejxjE+vsMlw3xVprNbKeJeXN+ZPJjNI4LIcw1B/ +igrOMwRfEVP7FgkrBgEEAdpHDwEBB0DdURvGONC+G8dZa0Q1vFGp5WVnrtahfJ0T +PRx5LPdvC8LAOwQYFgoAFQWCXxFT+wWJBaSPvQIVCgKbAgIeAQCYCRDZwdSwmECU +5XagBBkWCgAGBYJfEVP7ACEJENwMujbE/mf1FiEEvRHaDz+iRkPLimpN3Ay6NsT+ +Z/VX8QD/eKyJ/O2yF4jaNDDjzNrEzOZg7pvqiKZolV2WMlA4msUBANdN8PkKCH5G +HPo/T3mWl+xYkOVqZuw90Ay6npS6++wJFiEEapg1aZ7xIV8VWKSW2cHUsJhAlOUQ +UwEAhWLV69mEyxA1FrixumNan7vulofahcc7ox5cfZAANbEA/jBNmTPtROEaSeuU +IPyQ0lO+DS+Gmh9FYIfa35uAaQYKzjgEXxFT+xIKKwYBBAGXVQEFAQEHQL3nDAFV +lgBfUmw5C9OEv0txcP98NUgtADUmr5p9MdswAwEICcKEBBgWCgAVBYJfEVP7BYkF +pI+9AgsJApsMAh4BACEJENnB1LCYQJTlFiEEapg1aZ7xIV8VWKSW2cHUsJhAlOWl +CQD+O3xgfil6wkuenJEEWOWRHm0ft2u50DeP3/mJiRRdefoBAOwurevk+Ky1ZGqo +gpTmsS/1vAf/fz2DLWWN61hIyOEM +=/DS+ +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/data/6A9835699EF1215F1558A496D9C1D4B0984094E5.sec.asc b/tests/data/6A9835699EF1215F1558A496D9C1D4B0984094E5.sec.asc new file mode 100644 index 0000000..916d123 --- /dev/null +++ b/tests/data/6A9835699EF1215F1558A496D9C1D4B0984094E5.sec.asc @@ -0,0 +1,26 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: 6A98 3569 9EF1 215F 1558 A496 D9C1 D4B0 9840 94E5 +Comment: bob@peptest.org + +xVgEXxFT+xYJKwYBBAHaRw8BAQdAAYt8QUG+i4GQyfwaUQL+cmGKUcWHkcWX87IC +qmNpm/8AAQCrGsb0A0+Vfxn13bwcs9cotQzf4f8gP4AN6oX3wiMgQxB1woQEHxYK +ABUFgl8RU/sFiQWkj70CFQoCmwECHgEAIQkQ2cHUsJhAlOUWIQRqmDVpnvEhXxVY +pJbZwdSwmECU5ZXuAP915flgNnB7xOaWLLocWRqsAAcWjpiMfADMY9Ewabjm1wD/ +cv35RaZCteRLf8Jf0+4sAJ9hwG0FkWjzKF+veIkBowfND2JvYkBwZXB0ZXN0Lm9y +Z8KEBBMWCgAVBYJfEVP7BYkFpI+9AhUKApsBAh4BACEJENnB1LCYQJTlFiEEapg1 +aZ7xIV8VWKSW2cHUsJhAlOXJhQEAzWq8BaJxNHt65o7dXiOginvFY1oDKTmEWtOc +T3TjVbEA/3Ho8YxPr7DJcN8VaazWyniXlzfmTyYzSOCyHMNQf4oKx1gEXxFT+xYJ +KwYBBAHaRw8BAQdA3VEbxjjQvhvHWWtENbxRqeVlZ67WoXydEz0ceSz3bwsAAPsF +YmCVyg8ega1rLrOGVRhphlUGpGwiAvUd8C56L/ZGlg3kwsA7BBgWCgAVBYJfEVP7 +BYkFpI+9AhUKApsCAh4BAJgJENnB1LCYQJTldqAEGRYKAAYFgl8RU/sAIQkQ3Ay6 +NsT+Z/UWIQS9EdoPP6JGQ8uKak3cDLo2xP5n9VfxAP94rIn87bIXiNo0MOPM2sTM +5mDum+qIpmiVXZYyUDiaxQEA103w+QoIfkYc+j9PeZaX7FiQ5Wpm7D3QDLqelLr7 +7AkWIQRqmDVpnvEhXxVYpJbZwdSwmECU5RBTAQCFYtXr2YTLEDUWuLG6Y1qfu+6W +h9qFxzujHlx9kAA1sQD+ME2ZM+1E4RpJ65Qg/JDSU74NL4aaH0Vgh9rfm4BpBgrH +XQRfEVP7EgorBgEEAZdVAQUBAQdAvecMAVWWAF9SbDkL04S/S3Fw/3w1SC0ANSav +mn0x2zADAQgJAAD/cLLc4Gss5eq6v0PO/sCLyyA69hsz5FAyDFX7F3nMBygRzMKE +BBgWCgAVBYJfEVP7BYkFpI+9AgsJApsMAh4BACEJENnB1LCYQJTlFiEEapg1aZ7x +IV8VWKSW2cHUsJhAlOWlCQD+O3xgfil6wkuenJEEWOWRHm0ft2u50DeP3/mJiRRd +efoBAOwurevk+Ky1ZGqogpTmsS/1vAf/fz2DLWWN61hIyOEM +=4nOw +-----END PGP PRIVATE KEY BLOCK----- diff --git a/tests/model.py b/tests/model.py new file mode 100644 index 0000000..5724119 --- /dev/null +++ b/tests/model.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +# This file is under GNU Affero General Public License 3.0 +# see LICENSE.txt + +from . import utils + +import pytest + +@pytest.fixture() +def model(): + "Returns the whole data model" + return Model() + +identities = \ + { + "alice": { + "name": "alice", + "user_id": "23", + "accounts": + { + "work": { + "addr": "alice_work@peptest.org", + "fpr": "3E45175EE953EBBEB948F11A6A03DB2A17FB9D15" + }, + "home": { + "addr": "alice@peptest.org", + "fpr": "2D35731B9C754564CBAD15D2D18F7444594F2283" + } + } + }, + "bob": { + "name": "bob", + "user_id": "uuid:1-2-3-4", + "accounts": \ + { + "work": { + "addr": "bob_work@peptest.org", + "fpr": "1A97F263D8319D6885F638C5AA81E1B5457A2B40" + }, + "home": { + "addr": "bob@peptest.org", + "fpr": "6A9835699EF1215F1558A496D9C1D4B0984094E5" + } + } + } + } + +class Identity: + """ + An Identity class that is: + - can represent pEp.Identity + - is read-only (const) + """ + + def __init__(self, name="", user_id="", addr="", fpr="", key_sec="", key_pub=""): + self.__name = name + self.__user_id = user_id + self.__addr = addr + self.__fpr = fpr + self.__key_sec = key_sec + self.__key_pub = key_pub + + @property + def name(self): + return self.__name + + @property + def user_id(self): + return self.__user_id + + @property + def addr(self): + return self.__addr + + @property + def fpr(self): + return self.__fpr + + @property + def key_sec(self): + return self.__key_sec + + @property + def key_pub(self): + return self.__key_pub + + def debug(self) -> str: + ret = "name:" + self.__name + ret +="user_id:" + self.__user_id + ret +="addr:" + self.__addr + ret +="fpr:" + self.__fpr + ret +="key_sec:" + self.__key_sec[0:255] + ret +="key_pub:" + self.__key_pub[0:255] + return ret + + def __str__(self): + return "{} {}".format(self.name, "<{}>".format(self.addr)) + + + +# The Data Model +class Model: + alice = None + alice_work = None + bob = None + bob_work = None + + def getIdentity(self, name, account) -> Identity: + # fetch keys for ident from data folder + key_sec = utils.data_file_contents(identities[name]['accounts'][account]['fpr'] + ".sec.asc") + key_pub = utils.data_file_contents(identities[name]['accounts'][account]['fpr'] + ".pub.asc") + ident = Identity(name=identities[name]['name'], + user_id=identities[name]['user_id'], + addr=identities[name]['accounts'][account]['addr'], + fpr=identities[name]['accounts'][account]['fpr'], + key_pub=key_pub, + key_sec=key_sec + ) + return ident + + def __init__(self): + self.alice = self.getIdentity("alice", "home") + self.alice_work = self.getIdentity("alice", "work") + + self.bob = self.getIdentity("bob", "home") + self.bob_work = self.getIdentity("bob", "work") diff --git a/tests/test_basic.py b/tests/test_basic.py new file mode 100755 index 0000000..06dc60b --- /dev/null +++ b/tests/test_basic.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from . import constants +import pytest + +# """ +# >>> import pEp +# >>> me = pEp.Identity("alice.smith@peptest.ch", "Alice Smith", "23") +# >>> me.username +# 'Alice Smith' +# >>> print(me) +# Alice Smith +# >>> you = pEp.Identity("bob.bourne@peptest.ch", "Bob Bourne", "42") +# >>> print(you) +# Bob Bourne +# >>> m = pEp.outgoing_message(me) +# >>> m.to = [you] +# >>> m.shortmsg = "let's meet next week" +# >>> m.longmsg = "Please call me back" +# >>> m2 = m.encrypt() +# >>> print(m2) +# >>> m3, keys, rating, flags = m2.decrypt() +# >>> rating +# pEp.rating.reliable +# """ +# +# if __name__ == "__main__": +# import doctest +# doctest.testmod() + +def test_basic(pEp, model): + me = pEp.Identity( + model.alice.addr, + model.alice.name, + model.alice.user_id + ) + assert me.username == model.alice.name + assert str(me) == str(model.alice) + you = pEp.Identity( + model.bob.addr, + model.bob.name, + model.bob.user_id + ) + assert str(you) == str(model.bob) + #TODO: pEp.outgoing_message() needs to return type pEp.Message not None + m = pEp.outgoing_message(me) + m.to = [you] + m.shortmsg = constants.SUBJECT + m.longmsg = constants.BODY + #TODO: encrypt needs to return message type + m2 = m.encrypt() + m3, keys, rating, flags = m2.decrypt() + #TODO: fix pEp.rating + # assert rating == pEp. diff --git a/tests/test_identity.py b/tests/test_identity.py new file mode 100644 index 0000000..62125b6 --- /dev/null +++ b/tests/test_identity.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +# This file is under GNU Affero General Public License 3.0 +# see LICENSE.txt + +"""Identity unit tests.""" + +import pytest + +from . import constants +from . import model + + +# TODO: test_get_identity_by_{name,addr,etc..} +# def test_create_ident_myself(pEp, alice_myself): +# def test_create_ident_import(pEp, alice_myself): +# def test_create_ident_nokey(pEp, alice_myself): + + +def test_identity_constructor(pEp, model): + alice = pEp.Identity( + model.alice.addr, + model.alice.name, + model.alice.user_id + ) + + assert alice.address == model.alice.addr + assert alice.username == model.alice.name + assert alice.user_id == model.alice.user_id + assert str(alice) == str(model.alice) + + +# TODO: +# These here are actually plenty of individual tests +# Identity.update +# key_import +# set_own_key + +@pytest.mark.skip(reason="needs to be decomposed and migrated to new data model") +def test_two_identities_succeed(pEp, model): + alice = pEp.Identity( + constants.ALICE_ADDRESS, constants.ALICE_USERNAME, '', + constants.ALICE_FPR, 0, '' + ) + assert alice.address == constants.ALICE_ADDRESS + assert alice.username == constants.ALICE_USERNAME + assert alice.fpr == constants.ALICE_FPR + assert alice.user_id == "" + assert alice.comm_type == 0 + assert alice.flags == 0 + + pEp.import_key(model.bob.key_pub) + + bob = pEp.Identity() + bob.address = constants.BOB_ADDRESS + bob.username = constants.BOB_USERNAME + bob.fpr = constants.BOB_FPR + expected_bob = pEp.Identity( + constants.BOB_ADDRESS, constants.BOB_USERNAME, '', + constants.BOB_FPR, 56, '' + ) + + assert str(bob) == constants.BOB_NAME_ADDR + assert bob.address == expected_bob.address + assert bob.username == expected_bob.username + assert bob.fpr == expected_bob.fpr + assert bob.user_id == "" + assert bob.comm_type == 0 + assert bob.flags == 0 + + # Test that data after updating. + bob.update() + assert str(bob) == constants.BOB_NAME_ADDR + assert bob.address == expected_bob.address + assert bob.username == expected_bob.username + assert bob.fpr == expected_bob.fpr + assert bob.user_id == "TOFU_bob@openpgp.example" + assert bob.comm_type == 56 + assert bob.flags == 0 + + +@pytest.mark.skip(reason="needs to be decomposed and migrated to new data model") +def test_set_own_key(pEp, alice_key_sec): + pEp.import_key(alice_key_sec) + alice = pEp.Identity() + alice.address = constants.ALICE_ADDRESS + alice.username = constants.ALICE_USERNAME + alice.fpr = constants.ALICE_FPR + alice.user_id = constants.ALICE_NAME_ADDR + + expected_alice = pEp.Identity( + constants.ALICE_ADDRESS, constants.ALICE_USERNAME, '', + constants.ALICE_FPR, 0, '' + ) + + pEp.set_own_key(alice, alice.fpr) + # assert str(alice) == constants.ALICE_NAME_ADDR + assert str(alice) == str(expected_alice) + assert alice.address == expected_alice.address + assert alice.username == expected_alice.username + # assert alice.user_id == constants.ALICE_NAME_ADDR + assert alice.user_id == str(expected_alice) + assert alice.fpr == expected_alice.fpr + assert alice.comm_type == 255 + assert alice.flags == 0 + + # After setting own key this would give ValueError: illegal value + # alice.update() diff --git a/tests/test_message.py b/tests/test_message.py new file mode 100644 index 0000000..58b99f2 --- /dev/null +++ b/tests/test_message.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +# This file is under GNU Affero General Public License 3.0 +# see LICENSE.txt + +"""Message unit tests.""" + +from . import constants + + +def test_msg_enc_dec_roundtrip(pEp, model, import_ident_alice_as_own_ident, import_ident_bob): + alice = import_ident_alice_as_own_ident + bob = import_ident_bob + + msg = pEp.Message(constants.OUTGOING_MSG, alice) + msg.to = [bob] + msg.shortmsg = constants.SUBJECT + msg.longmsg = constants.BODY + assert msg.enc_format == 0 + # XXX: No way to check MIME so far. + + # Test that creating the `Message` with `outgoing_message` is the same. + msg2 = pEp.outgoing_message(alice) + msg2.to = [bob] + msg2.shortmsg = constants.SUBJECT + msg2.longmsg = constants.BODY + assert str(msg2) == str(msg) + + # Encrypt Message + enc_msg = msg.encrypt() + + assert enc_msg.enc_format == 3 + assert str(enc_msg.from_) == str(model.alice) + assert str(enc_msg.to[0]) == str(model.bob) + assert enc_msg.shortmsg == "p≡p" + assert enc_msg.longmsg == "this message was encrypted with p≡p https://pEp-project.org" + + # Decrypt message. + dec_msg, key_list, rating, r = enc_msg.decrypt() + assert r == 0 + # pEp version 2.2 throws this error: + # AttributeError: module 'pEp' has no attribute 'PEP_rating' + # assert rating == pEp.PEP_rating.PEP_rating_reliable + # It seems to have changed to the following. + assert rating == pEp._pEp.rating.reliable + + # The first 2 keys are Alice's ones, the last is Bob's one. + assert key_list[0] == key_list[1] == model.alice.fpr + assert key_list[-1] == model.bob.fpr + assert dec_msg.shortmsg == constants.SUBJECT + assert dec_msg.longmsg.replace("\r", "") == msg.longmsg + dec_lines = str(dec_msg).replace("\r", "").split("\n") + # pEp version 2.2 seems to have fixed some of the replaced characters. + # and changed also: + # Content-Type: doesn't pring `; charset="utf-8"` anymore. + # Content-Transfer-Encoding: doesn't print `quoted-printable` anymore. + # Content-Disposition: is not present anymore. + # `!` is not replaced by `=21` anymore. + expected_dec_lines = \ +"""From: alice +To: bob +Subject: This is a subject +X-pEp-Version: 2.1 +X-EncStatus: reliable +X-KeyList: + X,X,6A9835699EF1215F1558A496D9C1D4B0984094E5 +MIME-Version: 1.0 +Content-Type: text/plain +Content-Transfer-Encoding: 7bit + +Hi world! +""".split("\n") + assert dec_lines[:5] == expected_dec_lines[:5] + assert dec_lines[7:] == expected_dec_lines[7:] + + +def test_msg_len_changes(pEp, import_ident_alice_as_own_ident, import_ident_bob): + """Test that the original message is modified after encryption. + + Headers are added and therefore the modified unencrypted message length + is different to the original. + XXX: The original message should be left unchanged. + There could be another method previous to `encrypt` that adds the + extra headers and modify the subject returning a new message. + + """ + alice = import_ident_alice_as_own_ident + bob = import_ident_bob + + msg = pEp.outgoing_message(alice) + msg.to = [bob] + msg.shortmsg = constants.SUBJECT + msg.longmsg = constants.BODY + msg_len = len(str(msg)) + # Encrypt Message + msg.encrypt() + + # After encryption, the original message is modified!! + # It contains one more header and the alice's public key, if it's the first + # msg to bob. + # XXX: if/when this is fixed, change the following `!=` to `==` + msg_after_encrypt_len = len(str(msg)) + assert msg.shortmsg != constants.SUBJECT + assert msg.longmsg == constants.BODY + assert msg_after_encrypt_len != msg_len + + +def test_dec_msg_len(pEp, import_ident_alice_as_own_ident, import_ident_bob): + """ + Test that the decrypted message length is different from the original. + + Because it adds extra headers. + + """ + alice = import_ident_alice_as_own_ident + bob = import_ident_bob + + msg = pEp.outgoing_message(alice) + msg.to = [bob] + msg.shortmsg = constants.SUBJECT + msg.longmsg = constants.BODY + msg_len = len(str(msg)) + # Encrypt Message + enc_msg = msg.encrypt() + + # Decrypt message. + dec_msg, _key_list, _rating, _r = enc_msg.decrypt() + dec_msg_len = len(str(dec_msg)) + + assert dec_msg.longmsg.replace("\r", "") == constants.BODY # msg.longmsg + expected_dec_msg = \ +"""From: alice \r +To: bob \r +Subject: This is a subject\r +X-pEp-Version: 2.1\r +X-EncStatus: reliable\r +X-KeyList: \r + 2D35731B9C754564CBAD15D2D18F7444594F2283,2D35731B9C754564CBAD15D2D18F7444594F2283,6A9835699EF1215F1558A496D9C1D4B0984094E5\r +MIME-Version: 1.0\r +Content-Type: text/plain\r +Content-Transfer-Encoding: 7bit\r +\r +Hi world!\r +""" + assert expected_dec_msg == str(dec_msg) + # The decrypted message length should then be equal to the original message + # minus the extra headers added. + dec_lines = str(dec_msg).split("\n") + extra_headers_lines = dec_lines[3:7] + extra_headers = "\n".join(extra_headers_lines) + "\n" + len_extra_headers = len(extra_headers) + print("len_extra_headers", len_extra_headers) + assert dec_msg_len - len_extra_headers == msg_len + +#@pytest.mark.skip(reason="PYADAPT-91") +def test_null_char_rmed(pEp, import_ident_alice_as_own_ident, import_ident_bob): + """Test that null characters and anything after them are not removed.""" + alice = import_ident_alice_as_own_ident + bob = import_ident_bob + + msg = pEp.outgoing_message(alice) + msg.to = [bob] + msg.shortmsg = constants.SUBJECT + + # Message with null chars, potentially for padding. + body = "Hi Bob,\n" + "\0" * 255 + "\nBye,\nAlice." + msg.longmsg = body + # PYADAPT-91: The null characters and anything after them is removed. + # If/when this is fixed, change the following assertion. + assert msg.longmsg != body diff --git a/tests/test_pep.py b/tests/test_pep.py new file mode 100755 index 0000000..75a5222 --- /dev/null +++ b/tests/test_pep.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# This file is under GNU Affero General Public License 3.0 +# see LICENSE.txt + +"""Unit test for pEp package, not for subpackages or modules.""" + + + +def test_pep_version(): + """ Test that __version__ is not None or empty and is not 0.0.0.""" + from pEp import __version__ + # We could also test that match the regex, but that is already a test in + # setuptools_scm itself. + assert __version__ + assert __version__ != "0.0.0" diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..d65fc2a --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# This file is under GNU Affero General Public License 3.0 +# see LICENSE.txt + +from . import constants + +import pathlib + +def data_file_contents(name) -> str: + """grab the contents of a file in the data folder""" + path = pathlib.Path(__file__).parent / constants.DATADIR / name + with path.open(mode='r') as fid: + return fid.read() diff --git a/utils/pEp b/utils/pEp index 086eff0..1710163 100755 --- a/utils/pEp +++ b/utils/pEp @@ -1,5 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +# This file is under GNU Affero General Public License 3.0 +# see LICENSE.txt """