├── .gitignore
├── .travis.yml
├── ChangeLog
├── README.md
├── doc
├── gen_startup_troubleshooting.sh
├── startup_troubleshooting.dot
└── startup_troubleshooting.png
├── docker
├── Dockerfile
├── README
├── build
└── run
├── pylint.rc
├── requirements.txt
├── run_tests.sh
├── signatures
└── index.html
├── test_until_fail.sh
├── watch_tests.py
├── weechat_otr.py
└── weechat_otr_test
├── __init__.py
├── is_encrypted_account.py
├── is_encrypted_context.py
├── mock_account.py
├── mock_context.py
├── mock_user.py
├── mock_weechat.py
├── mock_window.py
├── raising_account.py
├── raising_context.py
├── recording_account.py
├── recording_context.py
├── session_test_case.py
├── test_arg_parsing.py
├── test_assembler.py
├── test_buffer_closing.py
├── test_command_finish.py
├── test_command_start.py
├── test_context.py
├── test_context_get_logger_option_name.py
├── test_context_message_in_cb.py
├── test_context_msg_convert_in.py
├── test_context_msg_convert_out.py
├── test_debug_buffer.py
├── test_excepthook.py
├── test_fingerprint.py
├── test_git_info.py
├── test_html_escape_policy.py
├── test_irc_context.py
├── test_irc_html_parser.py
├── test_irc_otr_account_load_trusts.py
├── test_irc_user.py
├── test_is_a_channel.py
├── test_key_generation.py
├── test_log_level.py
├── test_message_out_cb.py
├── test_otr_session.py
├── test_otr_statusbar_cb.py
├── test_parse_irc_privmsg.py
├── test_policy.py
├── test_policy_account.py
├── test_policy_server.py
├── test_shutdown.py
├── test_smp.py
├── test_table_formatter.py
├── test_weechat_otr.py
├── test_weechat_version_ok.py
└── weechat_otr_test_case.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.swp
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | sudo: false
3 | python:
4 | - "2.7"
5 | - "3.3"
6 | - "3.4"
7 | - "3.5"
8 | install:
9 | - "pip install pylint"
10 | - "pip install -r requirements.txt"
11 | script: ./run_tests.sh
12 | matrix:
13 | fast_finish: true
14 |
--------------------------------------------------------------------------------
/ChangeLog:
--------------------------------------------------------------------------------
1 | 1.9.2
2 | 2018-03-26
3 |
4 | Fix weechat crash on /reload.
5 |
6 | Preparation for python 2.6 support.
7 |
8 | 1.9.1
9 | 2017-07-30
10 |
11 | Fix potr ErrorReceived printing bug.
12 |
13 | Argument parsing errors now raise exceptions.
14 |
15 | Fix require_encryption policy hint.
16 |
17 | Fix traceback caused by non-ASCII nick
18 | (https://github.com/mmb/weechat-otr/issues/133).
19 |
20 | 1.9.0
21 | 2017-04-19
22 |
23 | Fix completion for log command.
24 |
25 | Fix error in log command help (thanks @w2ak).
26 |
27 | Set buffer local vars with OTR conversation status. These can be used by
28 | remote interfaces like Glowing Bear.
29 |
30 | 1.8.0
31 | 2015-12-21
32 |
33 | IRC nicks are case insensitive. Treat all cases of a nick as
34 | equivalent.
35 |
36 | 1.7.0
37 | 2015-08-02
38 |
39 | Allow setting policies on a per server and per account basis. Peer policies
40 | override account policies which override server policies.
41 |
42 | Convert data loaded from trust files to unicode.
43 |
44 | Do not OTR whitespace tag nicks starting with *.
45 |
46 | Nicks to skip OTR tagging for are now a user-configurable regex. Set the
47 | general.no_send_tag_regex option.
48 |
49 | Add otr.general.default.key option to share a key across accounts. Set
50 | this to nick@server of the account with the key you want to use as the
51 | default key for new accounts.
52 |
53 | Do not OTR whitespace tag CTCP messages.
54 |
55 | 1.6.0
56 | 2015-02-06
57 |
58 | Fix bug with messaging yourself
59 |
60 | Print dependency versions with exception tracebacks to diagnose
61 | reported bugs more easily
62 |
63 | Fix a number of bugs that caused tracebacks when incorrect
64 | arguments were passed to commands
65 |
66 | Addition of screenshot and other improvements to the README
67 |
68 | Make the meanings of allow_v2 and require_encryption policies
69 | clearer to users
70 |
71 | Fix bug with allow_v2/require_encryption policies where a
72 | message was sent but the user was told the message was not sent
73 |
74 | Faster Travis builds due to using a container on Travis
75 |
76 | Use IRC isupport (005) messages to get channel and statusmsg
77 | prefixes instead of hardcoding them
78 |
79 | Run tests on Python 3.2 and 3.4
80 |
81 | Fix bug with treating statusmsgs as private messages instead
82 | of channel messages
83 |
84 | Check WeeChat version during startup
85 |
86 | /otr refresh is an alias for /otr start
87 |
88 | /otr end is an alias for /otr finish
89 |
90 | 1.5.0
91 | 2014-09-30
92 |
93 | Handle nicks containing unicode characters
94 |
95 | Add /otr fingerprint command for showing fingerprints
96 |
97 | Disable send_tag for ChanServ, MemoServ and NickServer
98 |
99 | End OTR sessions when their buffer is closed
100 |
101 | Colorize a lot of output that was previously not colored
102 |
103 | User configurable prefix for messages from script
104 |
105 | Handle incoming IRC ACTION in unencrypted messages
106 |
107 | Only modify actions if the context is encrypted
108 |
109 | Restore default log level after OTR conversation
110 |
111 | Make PRIVMSG parsing case insensitive
112 |
113 | Raise an exception when a PRIVMSG can't be parsed
114 |
115 | Fix parsing PRIVMSGs where the message doesn't start with :
116 |
117 | 1.4.0
118 | 2014-04-11
119 |
120 | Non-ascii character bug fixes
121 |
122 | Use WeeChat IRC message parser
123 |
124 | Make messages from the script look different from messages from a user to
125 | prevent a user impersonating the script
126 |
127 | Fix character encoding bug when writing trusts file
128 |
129 | More experimental Python 3 support
130 |
131 | Add ability to remove HTML from incoming messages with per-peer setting
132 |
133 | Script version now includes git describe output if the script is in a git
134 | tree
135 |
136 | Add outbound HTML escaping with with per-peer setting
137 |
138 | Better multiline message handling
139 |
140 | Bug fix for SMP command parsing with nick, server and secret
141 |
142 | 1.3.0
143 | 2013-12-13
144 |
145 | Character encoding bug fixes
146 |
147 | Update to latest version of python-potr
148 |
149 | Add tests
150 |
151 | Fix for channels that start with &
152 |
153 | Fix for incoming messages with newlines in them
154 |
155 | Preference for turning off hints
156 |
157 | Match OTR query string anywhere in the message
158 |
159 | Better sanitization when building PRIVMSG commands
160 |
161 | Prefix messages printed to server buffer with nick
162 |
163 | Warn when a message is not sent due to require_encryption policy
164 |
165 | Support ACTIONs (/me) in OTR messages
166 |
167 | SMP questions can now contain non-ASCII characters
168 |
169 | 1.2.0
170 | 2013-08-13
171 |
172 | Fix UTF8 encoding errors
173 |
174 | Add the possibility to revoke a contact's trust permanently
175 |
176 | Add logging control and settings during an OTR session. Logs are disabled
177 | by default during the OTR session and restored to their previous value
178 | after the OTR session has ended
179 |
180 | Add the possibility to abort a SMP process
181 |
182 | Better policy management
183 |
184 | Clearer messages and warnings to avoid user mistakes and confusion
185 |
186 | Send debug messages to a dedicated buffer for easier debugging
187 |
188 | Add a command "/otr status" to see fingerprints and the OTR status
189 | of a specific conversation
190 |
191 | Lots of bugfixes and little improvements
192 |
193 |
194 |
195 | 1.1.0
196 | 2012-08-15
197 |
198 | Remove references to OTR protocol version 1 because pure-python-otr
199 | doesn't support it.
200 |
201 | Stricter argument parsing to prevent tracebacks.
202 |
203 | Fix status bar item when it occurs in a root bar.
204 |
205 | 1.0.0
206 | 2012-05-26
207 |
208 | Initial release.
209 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WeeChat script for Off-the-Record (OTR) Messaging
2 |
3 | 
4 |
5 | > **Please note**: This script makes every effort to securely provide OTR
6 | > Messaging in WeeChat but offers no guarantee. Please report any security
7 | > problems you find.
8 |
9 | Testing and security auditing are appreciated.
10 |
11 | [](https://travis-ci.org/mmb/weechat-otr)
12 |
13 | ## Installation
14 |
15 | This script requires Weechat 0.4.2 or later and the
16 | [Pure Python OTR](https://github.com/afflux/pure-python-otr)
17 | package to be installed with one of the following methods:
18 |
19 | Python package:
20 | ```bash
21 | pip install --upgrade --user python-potr
22 | ```
23 |
24 | If this fails, read
25 | [Requirements for building Pure Python OTR](#requirements-for-building-pure-python-otr)
26 | below.
27 |
28 | Arch:
29 | ```bash
30 | yaourt -S python2-potr
31 | ```
32 |
33 | Debian based systems:
34 | ```bash
35 | sudo apt-get install python-potr
36 | ```
37 |
38 | The latest release version of WeeChat OTR can be found in the
39 | [WeeChat scripts repository](https://www.weechat.org/scripts/source/otr.py.html/).
40 | To install from within WeeChat:
41 |
42 | /script install otr.py
43 |
44 | To install manually, download `weechat_otr.py` from GitHub and save it in
45 | `~/.weechat/python`. Then either symlink it into
46 | `~/.weechat/python/autoload` or `/python load weechat_otr.py`
47 | in WeeChat.
48 |
49 | [Latest unstable version from GitHub](https://raw.githubusercontent.com/mmb/weechat-otr/master/weechat_otr.py)
50 |
51 | If you are using an official release of the script, it is a good idea to
52 | [verify the signature](https://s3.amazonaws.com/weechat-otr-signatures/index.html).
53 |
54 | ### Requirements for building Pure Python OTR
55 |
56 | If python-potr fails to install, you are probably missing some packages.
57 | To install all the requirements on a Debian/Ubuntu system, run
58 |
59 | sudo apt-get install python-pip python-wheel build-essential python-dev
60 |
61 | Or on Arch run
62 |
63 | sudo pacman --needed -S python2-pip python2-wheel python2-keyring base-devel
64 |
65 | ## Buffer Local Variables
66 |
67 | The script will set the following buffer local variables:
68 |
69 | - `localvar_set_otr_encrypted` - whether the buffer is OTR encrypted (true or false)
70 | - `localvar_set_otr_authenticated` - whether the buffer is OTR authenticated (true or false)
71 | - `localvar_set_otr_logged` - whether the buffer is logged (true or false)
72 |
73 | These match what is shown in the status bar and can be used by remote interfaces
74 | via the WeeChat relay protocol or by other scripts.
75 |
76 | ## Support
77 |
78 | IRC channel: `#weechat-otr` on Freenode
79 |
80 | Create GitHub issues/pull requests for questions, comments and patches or
81 | email matthewm@boedicker.org or koolfy@koolfy.be.
82 |
83 | ## Thanks
84 |
85 | Thanks to Kjell Braden for the Pure Python OTR library and the Gajim
86 | Python plugin which was used as a reference.
87 |
--------------------------------------------------------------------------------
/doc/gen_startup_troubleshooting.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | dot \
4 | -Tpng \
5 | startup_troubleshooting.dot \
6 | -o startup_troubleshooting.png
7 |
--------------------------------------------------------------------------------
/doc/startup_troubleshooting.dot:
--------------------------------------------------------------------------------
1 | digraph {
2 | start [shape=box, label="Start"];
3 | start -> python;
4 |
5 | python [shape=box, label="Is WeeChat compiled with Python support?\n/python"];
6 | python -> python_version [label="Yes"];
7 | python -> recompile [label="No"];
8 |
9 | python_version [shape=box, label="Which version of Python is WeeChat using?\n/debug libs"];
10 | python_version -> potr_installed;
11 |
12 | recompile [shape=box, label="Recompile WeeChat with Python support"];
13 | recompile -> start;
14 |
15 | potr_installed [shape=box, label="Is potr installed in that version of Python?\n$ python --version\n$ python -c 'import potr'"];
16 | potr_installed -> script [label="Yes"];
17 | potr_installed -> pip_python [label="No"];
18 |
19 | script [shape=box, label="Install weechat-otr\n/script install otr.py"];
20 | script -> help;
21 |
22 | pip_python [shape="box", label="Is pip using that version of Python?\n$ pip --version"];
23 | pip_python -> install_potr [label="Yes"];
24 | pip_python -> pip_version [label="No"];
25 |
26 | install_potr [shape=box, label="$ pip install --upgrade --user python-potr"];
27 | install_potr -> potr_installed;
28 |
29 | pip_version [shape=box, label="$ python2.7 $(which pip) install --upgrade --user python-potr"];
30 | pip_version -> potr_installed;
31 |
32 | help [shape=box, label="/help otr"];
33 | }
34 |
--------------------------------------------------------------------------------
/doc/startup_troubleshooting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmb/weechat-otr/330d7b0632d5e05dbdd5891fd3622c7358b2b6f6/doc/startup_troubleshooting.png
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu
2 |
3 | RUN apt-get update
4 | RUN apt-get --assume-yes install build-essential
5 | RUN apt-get --assume-yes install pylint
6 | RUN apt-get --assume-yes install python-dev
7 | RUN apt-get --assume-yes install python-setuptools
8 |
9 | RUN easy_install pip
10 | RUN pip install python-potr
11 |
--------------------------------------------------------------------------------
/docker/README:
--------------------------------------------------------------------------------
1 | Dockerfile and scripts for a reproducible weechat-otr development environment.
2 |
--------------------------------------------------------------------------------
/docker/build:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | docker build \
4 | --tag=weechat-otr \
5 | .
6 |
--------------------------------------------------------------------------------
/docker/run:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | docker run \
4 | --interactive=true \
5 | --tty=true \
6 | --volume=$(readlink -f ..):/weechat-otr \
7 | --workdir=/weechat-otr \
8 | --rm=true \
9 | weechat-otr \
10 | /bin/bash
11 |
--------------------------------------------------------------------------------
/pylint.rc:
--------------------------------------------------------------------------------
1 | [REPORTS]
2 | max-line-length=80
3 | output-format=colorized
4 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | python-potr
--------------------------------------------------------------------------------
/run_tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | pylint --rcfile=pylint.rc weechat_otr.py
4 | pylint --rcfile=pylint.rc weechat_otr_test
5 |
6 | python -m unittest discover
7 |
--------------------------------------------------------------------------------
/signatures/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | weechat-otr Release Signatures
5 |
6 |
7 |
8 |
9 | weechat-otr Release Signatures
10 |
11 | Import the public key
12 |
13 |
14 | curl https://s3.amazonaws.com/matthewm.boedicker.org/matthewm@boedicker.org_pgp_public_key.txt | gpg --import
15 |
16 |
17 | Verify the key fingerprint
18 |
19 |
20 | gpg --fingerprint matthewm@boedicker.org
21 | dig +short txt matthewm._pka.boedicker.org
22 |
23 |
24 | Download 1.9.2.asc
25 |
26 |
27 | gpg --verify 1.9.2.asc ~/.weechat/python/otr.py
28 |
29 |
30 | Download 1.9.1.asc
31 |
32 |
33 | gpg --verify 1.9.1.asc ~/.weechat/python/otr.py
34 |
35 |
36 | Download 1.9.0.asc
37 |
38 |
39 | gpg --verify 1.9.0.asc ~/.weechat/python/otr.py
40 |
41 |
42 | Download 1.8.0.asc
43 |
44 |
45 | gpg --verify 1.8.0.asc ~/.weechat/python/otr.py
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/test_until_fail.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | COUNT=0
4 |
5 | while python -m unittest discover; do
6 | echo $((COUNT+=1))
7 | done
8 |
--------------------------------------------------------------------------------
/watch_tests.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import time
3 |
4 | import watchdog.observers
5 | import watchdog.events
6 |
7 | class CodeModifiedEventHandler(watchdog.events.PatternMatchingEventHandler):
8 |
9 | def __init__(self, command):
10 | super(CodeModifiedEventHandler, self).__init__(['*.py'])
11 | self.command = command
12 |
13 | def on_any_event(self, event):
14 | try:
15 | print(subprocess.check_output(self.command))
16 | except subprocess.CalledProcessError as err:
17 | print(err.output)
18 |
19 | print('-' * 80)
20 |
21 | class TestWatcher(object):
22 |
23 | def __init__(self, path, command):
24 | self.observer = watchdog.observers.Observer()
25 | handler = CodeModifiedEventHandler(command)
26 | self.observer.schedule(handler, path, recursive=True)
27 |
28 | def start(self):
29 | self.observer.start()
30 | try:
31 | while True:
32 | time.sleep(1)
33 | except KeyboardInterrupt:
34 | self.observer.stop()
35 | self.observer.join()
36 |
37 | if __name__ == '__main__':
38 | TestWatcher('.', ['python', '-m', 'unittest', 'discover']).start()
39 |
--------------------------------------------------------------------------------
/weechat_otr.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """otr - WeeChat script for Off-the-Record IRC messaging
3 |
4 | DISCLAIMER: To the best of my knowledge this script securely provides OTR
5 | messaging in WeeChat, but I offer no guarantee. Please report any security
6 | holes you find.
7 |
8 | Copyright (c) 2012-2015 Matthew M. Boedicker
9 | Nils Görs
10 | Daniel "koolfy" Faucon
11 | Felix Eckhofer
12 |
13 | Report issues at https://github.com/mmb/weechat-otr
14 |
15 | This program is free software: you can redistribute it and/or modify
16 | it under the terms of the GNU General Public License as published by
17 | the Free Software Foundation, either version 3 of the License, or
18 | (at your option) any later version.
19 |
20 | This program is distributed in the hope that it will be useful,
21 | but WITHOUT ANY WARRANTY; without even the implied warranty of
22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 | GNU General Public License for more details.
24 |
25 | You should have received a copy of the GNU General Public License
26 | along with this program. If not, see .
27 | """
28 |
29 | # pylint: disable=too-many-lines
30 |
31 | from __future__ import unicode_literals
32 |
33 | import collections
34 | import glob
35 | import io
36 | import os
37 | import platform
38 | import re
39 | import traceback
40 | import shlex
41 | import shutil
42 | import sys
43 |
44 | import potr
45 | import weechat
46 |
47 | class PythonVersion2(object):
48 | """Python 2 version of code that must differ between Python 2 and 3."""
49 |
50 | def __init__(self):
51 | import cgi
52 | self.cgi = cgi
53 |
54 | import HTMLParser
55 | self.html_parser = HTMLParser
56 | self.html_parser_init_kwargs = {}
57 |
58 | import htmlentitydefs
59 | self.html_entities = htmlentitydefs
60 |
61 | def html_escape(self, strng):
62 | """Escape HTML characters in a string."""
63 | return self.cgi.escape(strng)
64 |
65 | def unicode(self, *args, **kwargs):
66 | """Return the Unicode version of a string."""
67 | return unicode(*args, **kwargs)
68 |
69 | def unichr(self, *args, **kwargs):
70 | """Return the one character string of a Unicode character number."""
71 | return unichr(*args, **kwargs)
72 |
73 | def to_unicode(self, strng):
74 | """Convert a utf-8 encoded string to a Unicode."""
75 | if isinstance(strng, unicode):
76 | return strng
77 | return strng.decode('utf-8', 'replace')
78 |
79 | def to_str(self, strng):
80 | """Convert a Unicode to a utf-8 encoded string."""
81 | return strng.encode('utf-8', 'replace')
82 |
83 | class PythonVersion3(object):
84 | """Python 3 version of code that must differ between Python 2 and 3."""
85 |
86 | def __init__(self, minor):
87 | self.minor = minor
88 |
89 | import html
90 | self.html = html
91 |
92 | import html.parser
93 | self.html_parser = html.parser
94 | if self.minor >= 4:
95 | self.html_parser_init_kwargs = {'convert_charrefs' : True}
96 | else:
97 | self.html_parser_init_kwargs = {}
98 |
99 | import html.entities
100 | self.html_entities = html.entities
101 |
102 | def html_escape(self, strng):
103 | """Escape HTML characters in a string."""
104 | return self.html.escape(strng, quote=False)
105 |
106 | def unicode(self, *args, **kwargs):
107 | """Return the Unicode version of a string."""
108 | return str(*args, **kwargs)
109 |
110 | def unichr(self, *args, **kwargs):
111 | """Return the one character string of a Unicode character number."""
112 | return chr(*args, **kwargs)
113 |
114 | def to_unicode(self, strng):
115 | """Convert a utf-8 encoded string to unicode."""
116 | if isinstance(strng, bytes):
117 | return strng.decode('utf-8', 'replace')
118 | return strng
119 |
120 | def to_str(self, strng):
121 | """Convert a Unicode to a utf-8 encoded string."""
122 | return strng
123 |
124 | # We cannot use version_info.major as this is only supported on python >= 2.7
125 | if sys.version_info[0] >= 3:
126 | PYVER = PythonVersion3(sys.version_info.minor)
127 | else:
128 | PYVER = PythonVersion2()
129 |
130 | SCRIPT_NAME = 'otr'
131 | SCRIPT_DESC = 'Off-the-Record messaging for IRC'
132 | SCRIPT_HELP = """{description}
133 |
134 | Quick start:
135 |
136 | Add an OTR item to the status bar by adding '[otr]' to the config setting
137 | weechat.bar.status.items. This will show you whether your current conversation
138 | is encrypted, authenticated and logged. /set otr.* for OTR status bar
139 | customization options.
140 |
141 | Start a private conversation with a friend who has OTR: /query yourpeer hi
142 |
143 | In the private chat buffer: /otr start
144 |
145 | If you have not authenticated your peer yet, follow the instructions for
146 | authentication.
147 |
148 | You can, at any time, see the current OTR session status and fingerprints with:
149 | /otr status
150 |
151 | View OTR policies for your peer: /otr policy
152 |
153 | View default OTR policies: /otr policy default
154 |
155 | Start/Stop log recording for the current OTR session: /otr log [start|stop]
156 | This will be reverted back to the previous log setting at the end of the session.
157 |
158 | To refresh the OTR session: /otr refresh
159 |
160 | To end your private conversation: /otr finish
161 |
162 | This script supports only OTR protocol version 2.
163 | """.format(description=SCRIPT_DESC)
164 |
165 | SCRIPT_AUTHOR = 'Matthew M. Boedicker'
166 | SCRIPT_LICENCE = 'GPL3'
167 | SCRIPT_VERSION = '1.9.2'
168 |
169 | OTR_DIR_NAME = 'otr'
170 |
171 | OTR_QUERY_RE = re.compile(r'\?OTR(\?|\??v[a-z\d]*\?)')
172 |
173 | POLICIES = {
174 | 'allow_v2' : 'allow OTR protocol version 2, effectively enable OTR '
175 | 'since v2 is the only supported version',
176 | 'require_encryption' : 'refuse to send unencrypted messages when OTR is '
177 | 'enabled',
178 | 'log' : 'enable logging of OTR conversations',
179 | 'send_tag' : 'advertise your OTR capability using the whitespace tag',
180 | 'html_escape' : 'escape HTML special characters in outbound messages',
181 | 'html_filter' : 'filter HTML in incoming messages',
182 | }
183 |
184 | READ_ONLY_POLICIES = {
185 | 'allow_v1' : False,
186 | }
187 |
188 | ACTION_PREFIX = '/me '
189 | IRC_ACTION_RE = re.compile('^\x01ACTION (?P.*)\x01$')
190 | PLAIN_ACTION_RE = re.compile('^'+ACTION_PREFIX+'(?P.*)$')
191 |
192 | IRC_SANITIZE_TABLE = dict((ord(char), None) for char in '\n\r\x00')
193 |
194 | # Patch potr.proto.TaggedPlaintext to not end plaintext tags in a space.
195 | #
196 | # When POTR adds OTR tags to plaintext it puts them at the end of the message.
197 | # The tags end in a space which gets stripped off by WeeChat because it
198 | # strips trailing spaces from commands. This causes OTR initiation to fail so
199 | # the following code adds an extra tab at the end of the plaintext tags if
200 | # they end in a space.
201 | #
202 | # The patched version also skips OTR tagging for CTCP messages because it
203 | # breaks the CTCP format.
204 | def patched__bytes__(self):
205 | """Patched potr.proto.TaggedPlainText.__bytes__."""
206 | # Do not tag CTCP messages.
207 | if self.msg.startswith(b'\x01') and \
208 | self.msg.endswith(b'\x01'):
209 | return self.msg
210 |
211 | data = self.msg + potr.proto.MESSAGE_TAG_BASE
212 | for v in self.versions:
213 | data += potr.proto.MESSAGE_TAGS[v]
214 | if data.endswith(b' '):
215 | data += b'\t'
216 | return data
217 |
218 | potr.proto.TaggedPlaintext.__bytes__ = patched__bytes__
219 |
220 | def command(buf, command_str):
221 | """Wrap weechat.command() with utf-8 encode."""
222 | debug(command_str)
223 | weechat.command(buf, PYVER.to_str(command_str))
224 |
225 | def privmsg(server, nick, message):
226 | """Send a private message to a nick."""
227 | for line in message.splitlines():
228 | command('', '/quote -server {server} PRIVMSG {nick} :{line}'.format(
229 | server=irc_sanitize(server),
230 | nick=irc_sanitize(nick),
231 | line=irc_sanitize(line)))
232 |
233 | def build_privmsg_in(fromm, target, msg):
234 | """Build inbound IRC PRIVMSG command."""
235 | return ':{user} PRIVMSG {target} :{msg}'.format(
236 | user=irc_sanitize(fromm),
237 | target=irc_sanitize(target),
238 | msg=irc_sanitize(msg))
239 |
240 | def build_privmsgs_in(fromm, target, msg, prefix=''):
241 | """Build an inbound IRC PRIVMSG command for each line in msg.
242 | If prefix is supplied, prefix each line of msg with it."""
243 | cmd = []
244 | for line in msg.splitlines():
245 | cmd.append(build_privmsg_in(fromm, target, prefix+line))
246 | return '\r\n'.join(cmd)
247 |
248 | def build_privmsg_out(target, msg):
249 | """Build outbound IRC PRIVMSG command(s)."""
250 | cmd = []
251 | for line in msg.splitlines():
252 | cmd.append('PRIVMSG {target} :{line}'.format(
253 | target=irc_sanitize(target),
254 | line=irc_sanitize(line)))
255 | return '\r\n'.join(cmd)
256 |
257 | def irc_sanitize(msg):
258 | """Remove NUL, CR and LF characters from msg.
259 | The (utf-8 encoded version of a) string returned from this function
260 | should be safe to use as an argument in an irc command."""
261 | return PYVER.unicode(msg).translate(IRC_SANITIZE_TABLE)
262 |
263 | def prnt(buf, message):
264 | """Wrap weechat.prnt() with utf-8 encode."""
265 | weechat.prnt(buf, PYVER.to_str(message))
266 |
267 | def print_buffer(buf, message, level='info'):
268 | """Print message to buf with prefix,
269 | using color according to level."""
270 | prnt(buf, '{prefix}\t{msg}'.format(
271 | prefix=get_prefix(),
272 | msg=colorize(message, 'buffer.{0}'.format(level))))
273 |
274 | def get_prefix():
275 | """Returns configured message prefix."""
276 | return weechat.string_eval_expression(
277 | config_string('look.prefix'),
278 | {}, {}, {})
279 |
280 | def debug(msg):
281 | """Send a debug message to the OTR debug buffer."""
282 | debug_option = config_get_prefixed('general.debug')
283 |
284 | if not weechat.config_boolean(debug_option):
285 | return
286 |
287 | debug_buffer = weechat.buffer_search('python', 'OTR Debug')
288 | if not debug_buffer:
289 | debug_buffer = weechat.buffer_new('OTR Debug', '', '', '', '')
290 | weechat.buffer_set(debug_buffer, 'title', 'OTR Debug')
291 | weechat.buffer_set(debug_buffer, 'localvar_set_no_log', '1')
292 |
293 | prnt(debug_buffer, ('{script} debug\t{text}'.format(
294 | script=SCRIPT_NAME, text=PYVER.unicode(msg))))
295 |
296 | def current_user(server_name):
297 | """Get the nick and server of the current user on a server."""
298 | return irc_user(info_get('irc_nick', server_name), server_name)
299 |
300 | def irc_user(nick, server):
301 | """Build an IRC user string from a nick and server."""
302 | return '{nick}@{server}'.format(
303 | nick=nick.lower(),
304 | server=server)
305 |
306 | def isupport_value(server, feature):
307 | """Get the value of an IRC server feature."""
308 | args = '{server},{feature}'.format(server=server, feature=feature)
309 | return info_get('irc_server_isupport_value', args)
310 |
311 | def is_a_channel(channel, server):
312 | """Return true if a string has an IRC channel prefix."""
313 | prefixes = \
314 | tuple(isupport_value(server, 'CHANTYPES')) + \
315 | tuple(isupport_value(server, 'STATUSMSG'))
316 |
317 | # If the server returns nothing for CHANTYPES and STATUSMSG use
318 | # default prefixes.
319 | if not prefixes:
320 | prefixes = ('#', '&', '+', '!', '@')
321 |
322 | return channel.startswith(prefixes)
323 |
324 | class PrivmsgParseException(Exception):
325 | """Exception class for PRIVMSG parsing exceptions."""
326 | pass
327 |
328 | def parse_irc_privmsg(message, server):
329 | """Parse an IRC PRIVMSG command and return a dictionary.
330 |
331 | Either the to_channel key or the to_nick key will be set depending on
332 | whether the message is to a nick or a channel. The other will be None.
333 |
334 | Example input:
335 |
336 | :nick!user@host PRIVMSG #weechat :message here
337 |
338 | Output:
339 |
340 | {'from': 'nick!user@host',
341 | 'from_nick': 'nick',
342 | 'to': '#weechat',
343 | 'to_channel': '#weechat',
344 | 'to_nick': None,
345 | 'text': 'message here'}
346 | """
347 |
348 | weechat_result = weechat.info_get_hashtable(
349 | 'irc_message_parse', dict(message=message))
350 |
351 | if weechat_result['command'].upper() == 'PRIVMSG':
352 | target, text = PYVER.to_unicode(
353 | weechat_result['arguments']).split(' ', 1)
354 | if text.startswith(':'):
355 | text = text[1:]
356 |
357 | result = {
358 | 'from': PYVER.to_unicode(weechat_result['host']),
359 | 'to' : target,
360 | 'text': text,
361 | }
362 |
363 | if weechat_result['host']:
364 | result['from_nick'] = PYVER.to_unicode(weechat_result['nick'])
365 | else:
366 | result['from_nick'] = ''
367 |
368 | if is_a_channel(target, server):
369 | result['to_channel'] = target
370 | result['to_nick'] = None
371 | else:
372 | result['to_channel'] = None
373 | result['to_nick'] = target
374 |
375 | return result
376 | else:
377 | raise PrivmsgParseException(message)
378 |
379 | def has_otr_end(msg):
380 | """Return True if the message is the end of an OTR message."""
381 | return msg.endswith('.') or msg.endswith(',')
382 |
383 | def first_instance(objs, klass):
384 | """Return the first object in the list that is an instance of a class."""
385 | for obj in objs:
386 | if isinstance(obj, klass):
387 | return obj
388 |
389 | def config_prefix(option):
390 | """Add the config prefix to an option and return the full option name."""
391 | return '{script}.{option}'.format(
392 | script=SCRIPT_NAME,
393 | option=option)
394 |
395 | def config_color(option):
396 | """Get the color of a color config option."""
397 | return weechat.color(weechat.config_color(config_get_prefixed(
398 | 'color.{0}'.format(option))))
399 |
400 | def config_string(option):
401 | """Get the string value of a config option with utf-8 decode."""
402 | return PYVER.to_unicode(weechat.config_string(
403 | config_get_prefixed(option)))
404 |
405 | def config_get(option):
406 | """Get the value of a WeeChat config option."""
407 | return weechat.config_get(PYVER.to_str(option))
408 |
409 | def config_get_prefixed(option):
410 | """Get the value of a script prefixed WeeChat config option."""
411 | return config_get(config_prefix(option))
412 |
413 | def buffer_get_string(buf, prop):
414 | """Wrap weechat.buffer_get_string() with utf-8 encode/decode."""
415 | if buf is not None:
416 | encoded_buf = PYVER.to_str(buf)
417 | else:
418 | encoded_buf = None
419 |
420 | return PYVER.to_unicode(weechat.buffer_get_string(
421 | encoded_buf, PYVER.to_str(prop)))
422 |
423 | def buffer_is_private(buf):
424 | """Return True if a buffer is private."""
425 | return buffer_get_string(buf, 'localvar_type') == 'private'
426 |
427 | def info_get(info_name, arguments):
428 | """Wrap weechat.info_get() with utf-8 encode/decode."""
429 | return PYVER.to_unicode(weechat.info_get(
430 | PYVER.to_str(info_name), PYVER.to_str(arguments)))
431 |
432 | def msg_irc_from_plain(msg):
433 | """Transform a plain-text message to irc format.
434 | This will replace lines that start with /me with the respective
435 | irc command."""
436 | return PLAIN_ACTION_RE.sub('\x01ACTION \g\x01', msg)
437 |
438 | def msg_plain_from_irc(msg):
439 | """Transform an irc message to plain-text.
440 | Any ACTION found will be rewritten as /me ."""
441 | return IRC_ACTION_RE.sub(ACTION_PREFIX + r'\g', msg)
442 |
443 | def default_peer_args(args, buf):
444 | """Get the nick and server of a remote peer from command arguments or
445 | a buffer.
446 |
447 | args is the [nick, server] slice of arguments from a command.
448 | If these are present, return them. If args is empty and the buffer buf
449 | is private, return the remote nick and server of buf."""
450 | result = None, None
451 |
452 | if len(args) == 2:
453 | result = tuple(args)
454 | else:
455 | if buffer_is_private(buf):
456 | result = (
457 | buffer_get_string(buf, 'localvar_channel'),
458 | buffer_get_string(buf, 'localvar_server'))
459 |
460 | return result
461 |
462 | def format_default_policies():
463 | """Return current default policies formatted as a string for the user."""
464 | buf = io.StringIO()
465 |
466 | buf.write('Current default OTR policies:\n')
467 |
468 | for policy, desc in sorted(POLICIES.items()):
469 | buf.write(' {policy} ({desc}) : {value}\n'.format(
470 | policy=policy,
471 | desc=desc,
472 | value=config_string('policy.default.{0}'.format(policy))))
473 |
474 | buf.write('Change default policies with: /otr policy default NAME on|off')
475 |
476 | return buf.getvalue()
477 |
478 | def to_bytes(strng):
479 | """Convert a python str or unicode to bytes."""
480 | return strng.encode('utf-8', 'replace')
481 |
482 | def colorize(msg, color):
483 | """Colorize each line of msg using color."""
484 | result = []
485 | colorstr = config_color(color)
486 |
487 | for line in msg.splitlines():
488 | result.append('{color}{msg}'.format(
489 | color=colorstr,
490 | msg=line))
491 |
492 | return '\r\n'.join(result)
493 |
494 | def accounts():
495 | """Return a list of all IrcOtrAccounts sorted by name."""
496 | result = []
497 | for key_path in glob.iglob(os.path.join(OTR_DIR, '*.key3')):
498 | key_name, _ = os.path.splitext(os.path.basename(key_path))
499 | result.append(ACCOUNTS[key_name])
500 |
501 | return sorted(result, key=lambda account: account.name)
502 |
503 | def show_account_fingerprints():
504 | """Print all account names and their fingerprints to the core buffer."""
505 | table_formatter = TableFormatter()
506 | for account in accounts():
507 | table_formatter.add_row([
508 | account.name,
509 | str(account.getPrivkey())])
510 | print_buffer('', table_formatter.format())
511 |
512 | def show_peer_fingerprints(grep=None):
513 | """Print peer names and their fingerprints to the core buffer.
514 |
515 | If grep is passed in, show all peer names containing that substring."""
516 | trust_descs = {
517 | '' : 'unverified',
518 | 'smp' : 'SMP verified',
519 | 'verified' : 'verified',
520 | }
521 |
522 | table_formatter = TableFormatter()
523 | for account in accounts():
524 | for peer, peer_data in sorted(account.trusts.items()):
525 | for fingerprint, trust in sorted(peer_data.items()):
526 | if grep is None or grep in peer:
527 | table_formatter.add_row([
528 | peer,
529 | account.name,
530 | potr.human_hash(fingerprint),
531 | trust_descs[trust],
532 | ])
533 | print_buffer('', table_formatter.format())
534 |
535 | def private_key_file_path(account_name):
536 | """Return the private key file path for an account."""
537 | return os.path.join(OTR_DIR, '{0}.key3'.format(account_name))
538 |
539 | def read_private_key(key_file_path):
540 | """Return the private key in a private key file."""
541 | debug(('read private key', key_file_path))
542 |
543 | with open(key_file_path, 'rb') as key_file:
544 | return potr.crypt.PK.parsePrivateKey(key_file.read())[0]
545 |
546 | def get_context(account_name, context_name):
547 | """Return a context from an account."""
548 | return ACCOUNTS[account_name].getContext(context_name)
549 |
550 | def get_server_context(server, peer_nick):
551 | """Return the context for the current server user and peer."""
552 | return get_context(current_user(server), irc_user(peer_nick, server))
553 |
554 | class AccountDict(collections.defaultdict):
555 | """Dictionary that adds missing keys as IrcOtrAccount instances."""
556 |
557 | def __missing__(self, key):
558 | debug(('add account', key))
559 | self[key] = IrcOtrAccount(key)
560 |
561 | return self[key]
562 |
563 | class Assembler(object):
564 | """Reassemble fragmented OTR messages.
565 |
566 | This does not deal with OTR fragmentation, which is handled by potr, but
567 | fragmentation of received OTR messages that are too large for IRC.
568 | """
569 | def __init__(self):
570 | self.clear()
571 |
572 | def add(self, data):
573 | """Add data to the buffer."""
574 | self.value += data
575 |
576 | def clear(self):
577 | """Empty the buffer."""
578 | self.value = ''
579 |
580 | def is_done(self):
581 | """Return True if the buffer is a complete message."""
582 | return self.is_query() or \
583 | not to_bytes(self.value).startswith(potr.proto.OTRTAG) or \
584 | has_otr_end(self.value)
585 |
586 | def get(self):
587 | """Return the current value of the buffer and empty it."""
588 | result = self.value
589 | self.clear()
590 |
591 | return result
592 |
593 | def is_query(self):
594 | """Return true if the buffer is an OTR query."""
595 | return OTR_QUERY_RE.search(self.value)
596 |
597 | class IrcContext(potr.context.Context):
598 | """Context class for OTR over IRC."""
599 |
600 | def __init__(self, account, peername):
601 | super(IrcContext, self).__init__(account, peername)
602 |
603 | self.peer_nick, self.peer_server = peername.split('@', 1)
604 | self.in_assembler = Assembler()
605 | self.in_otr_message = False
606 | self.in_smp = False
607 | self.smp_question = False
608 |
609 | def policy_config_option(self, policy):
610 | """Get the option name of a policy option for this context."""
611 | return config_prefix('.'.join([
612 | 'policy', self.peer_server, self.user.nick, self.peer_nick,
613 | policy.lower()]))
614 |
615 | def getPolicy(self, key):
616 | """Get the value of a policy option for this context."""
617 | key_lower = key.lower()
618 |
619 | if key_lower in READ_ONLY_POLICIES:
620 | result = READ_ONLY_POLICIES[key_lower]
621 | elif key_lower == 'send_tag' and self.no_send_tag():
622 | result = False
623 | else:
624 | option = config_get(self.policy_config_option(key))
625 |
626 | if option == '':
627 | option = config_get(self.user.policy_config_option(key))
628 |
629 | if option == '':
630 | option = config_get_prefixed('.'.join(
631 | ['policy', self.peer_server, key_lower]))
632 |
633 | if option == '':
634 | option = config_get_prefixed(
635 | 'policy.default.{0}'.format(key_lower))
636 |
637 | result = bool(weechat.config_boolean(option))
638 |
639 | debug(('getPolicy', key, result))
640 |
641 | return result
642 |
643 | def inject(self, msg, appdata=None):
644 | """Send a message to the remote peer."""
645 | if isinstance(msg, potr.proto.OTRMessage):
646 | msg = PYVER.unicode(msg)
647 | else:
648 | msg = PYVER.to_unicode(msg)
649 |
650 | debug(('inject', msg, 'len {0}'.format(len(msg)), appdata))
651 |
652 | privmsg(self.peer_server, self.peer_nick, msg)
653 |
654 | def setState(self, newstate):
655 | """Handle state transition."""
656 | debug(('state', self.state, newstate))
657 |
658 | if self.is_encrypted():
659 | if newstate == potr.context.STATE_ENCRYPTED:
660 | self.print_buffer(
661 | 'Private conversation has been refreshed.', 'success')
662 | elif newstate == potr.context.STATE_FINISHED:
663 | self.print_buffer(
664 | '{peer} has ended the private conversation. You should do '
665 | 'the same:\n/otr finish'.format(peer=self.peer_nick))
666 | elif newstate == potr.context.STATE_ENCRYPTED:
667 | # unencrypted => encrypted
668 | trust = self.getCurrentTrust()
669 |
670 | # Disable logging before any proof of OTR activity is generated.
671 | # This is necessary when the session is started automatically, and
672 | # not by /otr start.
673 | if not self.getPolicy('log'):
674 | self.previous_log_level = self.disable_logging()
675 | else:
676 | self.previous_log_level = self.get_log_level()
677 | if self.is_logged():
678 | self.hint(
679 | 'You have enabled the recording to disk of OTR '
680 | 'conversations. By doing this you are potentially '
681 | 'putting yourself and your correspondent in danger. '
682 | 'Please consider disabling this policy with '
683 | '"/otr policy default log off". To disable logging '
684 | 'for this OTR session, use "/otr log stop"')
685 |
686 | if trust is None:
687 | fpr = str(self.getCurrentKey())
688 | self.print_buffer('New fingerprint: {0}'.format(fpr), 'warning')
689 | self.setCurrentTrust('')
690 |
691 | if bool(trust):
692 | self.print_buffer(
693 | 'Authenticated secured OTR conversation started.',
694 | 'success')
695 | else:
696 | self.print_buffer(
697 | 'Unauthenticated secured OTR conversation started.',
698 | 'warning')
699 | self.hint(self.verify_instructions())
700 |
701 | if self.state != potr.context.STATE_PLAINTEXT and \
702 | newstate == potr.context.STATE_PLAINTEXT:
703 | self.print_buffer('Private conversation ended.')
704 |
705 | # If we altered the logging value, restore it.
706 | if self.previous_log_level is not None:
707 | self.restore_logging(self.previous_log_level)
708 |
709 | super(IrcContext, self).setState(newstate)
710 |
711 | def maxMessageSize(self, appdata=None):
712 | """Return the max message size for this context."""
713 | # remove 'PRIVMSG :' from max message size
714 | result = self.user.maxMessageSize - 10 - len(self.peer_nick)
715 | debug('max message size {0}'.format(result))
716 |
717 | return result
718 |
719 | def buffer(self):
720 | """Get the buffer for this context."""
721 | return info_get(
722 | 'irc_buffer', '{server},{nick}'.format(
723 | server=self.peer_server,
724 | nick=self.peer_nick
725 | ))
726 |
727 | def print_buffer(self, msg, level='info'):
728 | """Print a message to the buffer for this context.
729 | level is used to colorize the message."""
730 | buf = self.buffer()
731 |
732 | # add [nick] prefix if we have only a server buffer for the query
733 | if self.peer_nick and not buffer_is_private(buf):
734 | msg = '[{nick}] {msg}'.format(
735 | nick=self.peer_nick,
736 | msg=msg)
737 |
738 | print_buffer(buf, msg, level)
739 |
740 | def hint(self, msg):
741 | """Print a message to the buffer but only when hints are enabled."""
742 | hints_option = config_get_prefixed('general.hints')
743 |
744 | if weechat.config_boolean(hints_option):
745 | self.print_buffer(msg, 'hint')
746 |
747 | def smp_finish(self, message=False, level='info'):
748 | """Reset SMP state and send a message to the user."""
749 | self.in_smp = False
750 | self.smp_question = False
751 |
752 | self.user.saveTrusts()
753 | if message:
754 | self.print_buffer(message, level)
755 |
756 | def handle_tlvs(self, tlvs):
757 | """Handle SMP states."""
758 | if tlvs:
759 | smp1q = first_instance(tlvs, potr.proto.SMP1QTLV)
760 | smp3 = first_instance(tlvs, potr.proto.SMP3TLV)
761 | smp4 = first_instance(tlvs, potr.proto.SMP4TLV)
762 |
763 | if first_instance(tlvs, potr.proto.SMPABORTTLV):
764 | debug('SMP aborted by peer')
765 | self.smp_finish('SMP aborted by peer.', 'warning')
766 | elif self.in_smp and not self.smpIsValid():
767 | debug('SMP aborted')
768 | self.smp_finish('SMP aborted.', 'error')
769 | elif first_instance(tlvs, potr.proto.SMP1TLV):
770 | debug('SMP1')
771 | self.in_smp = True
772 |
773 | self.print_buffer(
774 | """Peer has requested SMP verification.
775 | Respond with: /otr smp respond """)
776 | elif smp1q:
777 | debug(('SMP1Q', smp1q.msg))
778 | self.in_smp = True
779 | self.smp_question = True
780 |
781 | self.print_buffer(
782 | """Peer has requested SMP verification: {msg}
783 | Respond with: /otr smp respond """.format(
784 | msg=PYVER.to_unicode(smp1q.msg)))
785 | elif first_instance(tlvs, potr.proto.SMP2TLV):
786 | if not self.in_smp:
787 | debug('Received unexpected SMP2')
788 | self.smp_finish()
789 | else:
790 | debug('SMP2')
791 | self.print_buffer('SMP progressing.')
792 | elif smp3 or smp4:
793 | if smp3:
794 | debug('SMP3')
795 | elif smp4:
796 | debug('SMP4')
797 |
798 | if self.smpIsSuccess():
799 |
800 | if self.smp_question:
801 | self.smp_finish(
802 | 'SMP verification succeeded.', 'success')
803 | if not self.is_verified:
804 | self.print_buffer(
805 | 'You may want to authenticate your peer by '
806 | 'asking your own question:\n'
807 | "/otr smp ask <'question'> 'secret'")
808 | else:
809 | self.smp_finish(
810 | 'SMP verification succeeded.', 'success')
811 |
812 | else:
813 | self.smp_finish('SMP verification failed.', 'error')
814 |
815 | def verify_instructions(self):
816 | """Generate verification instructions for user."""
817 | return """You can verify that this contact is who they claim to be in
818 | one of the following ways:
819 |
820 | 1) Verify each other's fingerprints using a secure channel:
821 | Your fingerprint : {your_fp}
822 | {peer}'s fingerprint : {peer_fp}
823 | then use the command: /otr trust {peer_nick} {peer_server}
824 |
825 | 2) SMP pre-shared secret that you both know:
826 | /otr smp ask {peer_nick} {peer_server} 'secret'
827 |
828 | 3) SMP pre-shared secret that you both know with a question:
829 | /otr smp ask {peer_nick} {peer_server} <'question'> 'secret'
830 |
831 | Note: You can safely omit specifying the peer and server when
832 | executing these commands from the appropriate conversation
833 | buffer
834 | """.format(
835 | your_fp=self.user.getPrivkey(),
836 | peer=self.peer,
837 | peer_nick=self.peer_nick,
838 | peer_server=self.peer_server,
839 | peer_fp=potr.human_hash(self.crypto.theirPubkey.cfingerprint()))
840 |
841 | def is_encrypted(self):
842 | """Return True if the conversation with this context's peer is
843 | currently encrypted."""
844 | return self.state == potr.context.STATE_ENCRYPTED
845 |
846 | def is_verified(self):
847 | """Return True if this context's peer is verified."""
848 | return bool(self.getCurrentTrust())
849 |
850 | def format_policies(self):
851 | """Return current policies for this context formatted as a string for
852 | the user."""
853 | buf = io.StringIO()
854 |
855 | buf.write('Current OTR policies for {peer}:\n'.format(
856 | peer=self.peer))
857 |
858 | for policy, desc in sorted(POLICIES.items()):
859 | buf.write(' {policy} ({desc}) : {value}\n'.format(
860 | policy=policy,
861 | desc=desc,
862 | value='on' if self.getPolicy(policy) else 'off'))
863 |
864 | buf.write('Change policies with: /otr policy NAME on|off')
865 |
866 | return buf.getvalue()
867 |
868 | def is_logged(self):
869 | """Return True if conversations with this context's peer are currently
870 | being logged to disk."""
871 | infolist = weechat.infolist_get('logger_buffer', '', '')
872 |
873 | buf = self.buffer()
874 |
875 | result = False
876 |
877 | while weechat.infolist_next(infolist):
878 | if weechat.infolist_pointer(infolist, 'buffer') == buf:
879 | result = bool(weechat.infolist_integer(infolist, 'log_enabled'))
880 | break
881 |
882 | weechat.infolist_free(infolist)
883 |
884 | return result
885 |
886 | def get_log_level(self):
887 | """Return the current logging level for this context's peer
888 | or -1 if the buffer uses the default log level of weechat."""
889 | infolist = weechat.infolist_get('logger_buffer', '', '')
890 |
891 | buf = self.buffer()
892 |
893 | if not config_get(self.get_logger_option_name()):
894 | result = -1
895 | else:
896 | result = 0
897 |
898 | while weechat.infolist_next(infolist):
899 | if weechat.infolist_pointer(infolist, 'buffer') == buf:
900 | result = weechat.infolist_integer(infolist, 'log_level')
901 | break
902 |
903 | weechat.infolist_free(infolist)
904 |
905 | return result
906 |
907 | def get_logger_option_name(self):
908 | """Returns the logger config option for this context's buffer."""
909 | buf = self.buffer()
910 | name = buffer_get_string(buf, 'name')
911 | plugin = buffer_get_string(buf, 'plugin')
912 |
913 | return 'logger.level.{plugin}.{name}'.format(
914 | plugin=plugin, name=name)
915 |
916 | def disable_logging(self):
917 | """Return the previous logger level and set the buffer logger level
918 | to 0. If it was already 0, return None."""
919 | # If previous_log_level has not been previously set, return the level
920 | # we detect now.
921 | if not hasattr(self, 'previous_log_level'):
922 | previous_log_level = self.get_log_level()
923 |
924 | if self.is_logged():
925 | weechat.command(self.buffer(), '/mute logger disable')
926 | self.print_buffer(
927 | 'Logs have been temporarily disabled for the session. '
928 | 'They will be restored upon finishing the OTR session.')
929 |
930 | return previous_log_level
931 |
932 | # If previous_log_level was already set, it means we already altered it
933 | # and that we just detected an already modified logging level.
934 | # Return the pre-existing value so it doesn't get lost, and we can
935 | # restore it later.
936 | else:
937 | return self.previous_log_level
938 |
939 | def restore_logging(self, previous_log_level):
940 | """Restore the log level of the buffer."""
941 | buf = self.buffer()
942 |
943 | if (previous_log_level >= 0) and (previous_log_level < 10):
944 | self.print_buffer(
945 | 'Restoring buffer logging value to: {0}'.format(
946 | previous_log_level), 'warning')
947 | weechat.command(buf, '/mute logger set {0}'.format(
948 | previous_log_level))
949 |
950 | if previous_log_level == -1:
951 | logger_option_name = self.get_logger_option_name()
952 | self.print_buffer(
953 | 'Restoring buffer logging value to default', 'warning')
954 | weechat.command(buf, '/mute unset {0}'.format(
955 | logger_option_name))
956 |
957 | del self.previous_log_level
958 |
959 | def msg_convert_in(self, msg):
960 | """Transform incoming OTR message to IRC format.
961 | This includes stripping html, converting plain-text ACTIONs
962 | and character encoding conversion.
963 | Only character encoding is changed if context is unencrypted."""
964 | msg = PYVER.to_unicode(msg)
965 |
966 | if not self.is_encrypted():
967 | return msg
968 |
969 | if self.getPolicy('html_filter'):
970 | try:
971 | msg = IrcHTMLParser.parse(msg)
972 | except PYVER.html_parser.HTMLParseError:
973 | pass
974 |
975 | return msg_irc_from_plain(msg)
976 |
977 | def msg_convert_out(self, msg):
978 | """Convert an outgoing IRC message to be sent over OTR.
979 | This includes escaping html, converting ACTIONs to plain-text
980 | and character encoding conversion
981 | Only character encoding is changed if context is unencrypted."""
982 | if self.is_encrypted():
983 | msg = msg_plain_from_irc(msg)
984 |
985 | if self.getPolicy('html_escape'):
986 | msg = PYVER.html_escape(msg)
987 |
988 | # potr expects bytes to be returned
989 | return to_bytes(msg)
990 |
991 | def no_send_tag(self):
992 | """Skip OTR whitespace tagging to bots and services.
993 |
994 | Any nicks matching the otr.general.no_send_tag_regex config setting
995 | will not be tagged.
996 | """
997 | no_send_tag_regex = config_string('general.no_send_tag_regex')
998 | debug(('no_send_tag', no_send_tag_regex, self.peer_nick))
999 | if no_send_tag_regex:
1000 | return re.match(no_send_tag_regex, self.peer_nick, re.IGNORECASE)
1001 |
1002 | def __repr__(self):
1003 | return PYVER.to_str((
1004 | '<{0} {1:x} peer_nick={c.peer_nick} peer_server={c.peer_server}>'
1005 | ).format(self.__class__.__name__, id(self), c=self))
1006 |
1007 | class IrcOtrAccount(potr.context.Account):
1008 | """Account class for OTR over IRC."""
1009 |
1010 | contextclass = IrcContext
1011 |
1012 | PROTOCOL = 'irc'
1013 | MAX_MSG_SIZE = 415
1014 |
1015 | def __init__(self, name):
1016 | super(IrcOtrAccount, self).__init__(
1017 | name, IrcOtrAccount.PROTOCOL, IrcOtrAccount.MAX_MSG_SIZE)
1018 |
1019 | self.nick, self.server = self.name.split('@', 1)
1020 |
1021 | # IRC messages cannot have newlines, OTR query and "no plugin" text
1022 | # need to be one message
1023 | self.defaultQuery = self.defaultQuery.replace("\n", ' ')
1024 |
1025 | self.key_file_path = private_key_file_path(name)
1026 | self.fpr_file_path = os.path.join(OTR_DIR, '{0}.fpr'.format(name))
1027 |
1028 | self.load_trusts()
1029 |
1030 | def load_trusts(self):
1031 | """Load trust data from the fingerprint file."""
1032 | if os.path.exists(self.fpr_file_path):
1033 | with open(self.fpr_file_path) as fpr_file:
1034 | for line in fpr_file:
1035 | debug(('load trust check', line))
1036 |
1037 | context, account, protocol, fpr, trust = \
1038 | PYVER.to_unicode(line[:-1]).split('\t')
1039 |
1040 | if account == self.name and \
1041 | protocol == IrcOtrAccount.PROTOCOL:
1042 | debug(('set trust', context, fpr, trust))
1043 | self.setTrust(context, fpr, trust)
1044 |
1045 | def loadPrivkey(self):
1046 | """Load key file.
1047 |
1048 | If no key file exists, load the default key. If there is no default
1049 | key, a new key will be generated automatically by potr."""
1050 | debug(('load private key', self.key_file_path))
1051 |
1052 | if os.path.exists(self.key_file_path):
1053 | return read_private_key(self.key_file_path)
1054 | else:
1055 | default_key = config_string('general.defaultkey')
1056 | if default_key:
1057 | default_key_path = private_key_file_path(default_key)
1058 |
1059 | if os.path.exists(default_key_path):
1060 | shutil.copyfile(default_key_path, self.key_file_path)
1061 | return read_private_key(self.key_file_path)
1062 |
1063 | def savePrivkey(self):
1064 | """Save key file."""
1065 | debug(('save private key', self.key_file_path))
1066 |
1067 | with open(self.key_file_path, 'wb') as key_file:
1068 | key_file.write(self.getPrivkey().serializePrivateKey())
1069 |
1070 | def saveTrusts(self):
1071 | """Save trusts."""
1072 | with open(self.fpr_file_path, 'w') as fpr_file:
1073 | for uid, trusts in self.trusts.items():
1074 | for fpr, trust in trusts.items():
1075 | debug(('trust write', uid, self.name,
1076 | IrcOtrAccount.PROTOCOL, fpr, trust))
1077 | fpr_file.write(PYVER.to_str('\t'.join(
1078 | (uid, self.name, IrcOtrAccount.PROTOCOL, fpr, trust))))
1079 | fpr_file.write('\n')
1080 |
1081 | def end_all_private(self):
1082 | """End all currently encrypted conversations."""
1083 | for context in self.ctxs.values():
1084 | if context.is_encrypted():
1085 | context.disconnect()
1086 |
1087 | def policy_config_option(self, policy):
1088 | """Get the option name of a policy option for this account."""
1089 | return config_prefix('.'.join([
1090 | 'policy', self.server, self.nick, policy.lower()]))
1091 |
1092 | class IrcHTMLParser(PYVER.html_parser.HTMLParser):
1093 | """A simple HTML parser that throws away anything but newlines and links"""
1094 |
1095 | @staticmethod
1096 | def parse(data):
1097 | """Create a temporary IrcHTMLParser and parse a single string"""
1098 | parser = IrcHTMLParser(**PYVER.html_parser_init_kwargs)
1099 | parser.feed(data)
1100 | parser.close()
1101 | return parser.result
1102 |
1103 | def reset(self):
1104 | """Forget all state, called from __init__"""
1105 | PYVER.html_parser.HTMLParser.reset(self)
1106 | self.result = ''
1107 | self.linktarget = ''
1108 | self.linkstart = 0
1109 |
1110 | def handle_starttag(self, tag, attrs):
1111 | """Called when a start tag is encountered"""
1112 | if tag == 'br':
1113 | self.result += '\n'
1114 | elif tag == 'a':
1115 | attrs = dict(attrs)
1116 | if 'href' in attrs:
1117 | self.result += '['
1118 | self.linktarget = attrs['href']
1119 | self.linkstart = len(self.result)
1120 |
1121 | def handle_endtag(self, tag):
1122 | """Called when an end tag is encountered"""
1123 | if tag == 'a':
1124 | if self.linktarget:
1125 | if self.result[self.linkstart:] == self.linktarget:
1126 | self.result += ']'
1127 | else:
1128 | self.result += ']({0})'.format(self.linktarget)
1129 | self.linktarget = ''
1130 |
1131 | def handle_data(self, data):
1132 | """Called for character data (i.e. text)"""
1133 | self.result += data
1134 |
1135 | def handle_entityref(self, name):
1136 | """Called for entity references, such as &"""
1137 | try:
1138 | self.result += PYVER.unichr(
1139 | PYVER.html_entities.name2codepoint[name])
1140 | except KeyError:
1141 | self.result += '&{0};'.format(name)
1142 |
1143 | def handle_charref(self, name):
1144 | """Called for character references, such as '"""
1145 | try:
1146 | if name.startswith('x'):
1147 | self.result += PYVER.unichr(int(name[1:], 16))
1148 | else:
1149 | self.result += PYVER.unichr(int(name))
1150 | except ValueError:
1151 | self.result += '{0};'.format(name)
1152 |
1153 | class TableFormatter(object):
1154 | """Format lists of string into aligned tables."""
1155 |
1156 | def __init__(self):
1157 | self.rows = []
1158 | self.max_widths = None
1159 |
1160 | def add_row(self, row):
1161 | """Add a row to the table."""
1162 | self.rows.append(row)
1163 | row_widths = [len(s) for s in row]
1164 | if self.max_widths is None:
1165 | self.max_widths = row_widths
1166 | else:
1167 | self.max_widths = list(map(max, self.max_widths, row_widths))
1168 |
1169 | def format(self):
1170 | """Return the formatted table as a string."""
1171 | return '\n'.join([self.format_row(row) for row in self.rows])
1172 |
1173 | def format_row(self, row):
1174 | """Format a single row as a string."""
1175 | return ' |'.join(
1176 | [s.ljust(self.max_widths[i]) for i, s in enumerate(row)])
1177 |
1178 | def message_in_cb(data, modifier, modifier_data, string):
1179 | """Incoming message callback"""
1180 | debug(('message_in_cb', data, modifier, modifier_data, string))
1181 |
1182 | parsed = parse_irc_privmsg(
1183 | PYVER.to_unicode(string), PYVER.to_unicode(modifier_data))
1184 | debug(('parsed message', parsed))
1185 |
1186 | # skip processing messages to public channels
1187 | if parsed['to_channel']:
1188 | return string
1189 |
1190 | server = PYVER.to_unicode(modifier_data)
1191 |
1192 | context = get_server_context(server, parsed['from_nick'])
1193 |
1194 | context.in_assembler.add(parsed['text'])
1195 |
1196 | result = ''
1197 |
1198 | if context.in_assembler.is_done():
1199 | try:
1200 | msg, tlvs = context.receiveMessage(
1201 | # potr expects bytes
1202 | to_bytes(context.in_assembler.get()))
1203 |
1204 | debug(('receive', msg, tlvs))
1205 |
1206 | if msg:
1207 | result = PYVER.to_str(build_privmsgs_in(
1208 | parsed['from'], parsed['to'],
1209 | context.msg_convert_in(msg)))
1210 |
1211 | context.handle_tlvs(tlvs)
1212 | except potr.context.ErrorReceived as err:
1213 | context.print_buffer('Received OTR error: {0}'.format(
1214 | PYVER.to_unicode(err.args[0])), 'error')
1215 | except potr.context.NotEncryptedError:
1216 | context.print_buffer(
1217 | 'Received encrypted data but no private session established.',
1218 | 'warning')
1219 | except potr.context.NotOTRMessage:
1220 | result = string
1221 | except potr.context.UnencryptedMessage as err:
1222 | result = PYVER.to_str(build_privmsgs_in(
1223 | parsed['from'], parsed['to'], PYVER.to_unicode(
1224 | msg_plain_from_irc(err.args[0])),
1225 | 'Unencrypted message received: '))
1226 |
1227 | weechat.bar_item_update(SCRIPT_NAME)
1228 |
1229 | return result
1230 |
1231 | def message_out_cb(data, modifier, modifier_data, string):
1232 | """Outgoing message callback."""
1233 | result = ''
1234 |
1235 | # If any exception is raised in this function, WeeChat will not send the
1236 | # outgoing message, which could be something that the user intended to be
1237 | # encrypted. This paranoid exception handling ensures that the system
1238 | # fails closed and not open.
1239 | try:
1240 | debug(('message_out_cb', data, modifier, modifier_data, string))
1241 |
1242 | parsed = parse_irc_privmsg(
1243 | PYVER.to_unicode(string), PYVER.to_unicode(modifier_data))
1244 | debug(('parsed message', parsed))
1245 |
1246 | # skip processing messages to public channels
1247 | if parsed['to_channel']:
1248 | return string
1249 |
1250 | server = PYVER.to_unicode(modifier_data)
1251 |
1252 | context = get_server_context(server, parsed['to_nick'])
1253 | is_query = OTR_QUERY_RE.search(parsed['text'])
1254 |
1255 | parsed_text_bytes = to_bytes(parsed['text'])
1256 |
1257 | is_otr_message = \
1258 | parsed_text_bytes[:len(potr.proto.OTRTAG)] == potr.proto.OTRTAG
1259 |
1260 | if is_otr_message and not is_query:
1261 | if not has_otr_end(parsed['text']):
1262 | debug('in OTR message')
1263 | context.in_otr_message = True
1264 | else:
1265 | debug('complete OTR message')
1266 | result = string
1267 | elif context.in_otr_message:
1268 | if has_otr_end(parsed['text']):
1269 | context.in_otr_message = False
1270 | debug('in OTR message end')
1271 | result = string
1272 | else:
1273 | debug(('context send message', parsed['text'], parsed['to_nick'],
1274 | server))
1275 |
1276 | if context.policyOtrEnabled() and \
1277 | not context.is_encrypted() and \
1278 | not is_query and \
1279 | context.getPolicy('require_encryption'):
1280 | context.print_buffer(
1281 | 'Your message will not be sent, because policy requires an '
1282 | 'encrypted connection.', 'error')
1283 | context.hint(
1284 | 'Wait for the OTR connection or change the policy to allow '
1285 | 'clear-text messages:\n'
1286 | '/otr policy require_encryption off')
1287 |
1288 | try:
1289 | ret = context.sendMessage(
1290 | potr.context.FRAGMENT_SEND_ALL,
1291 | context.msg_convert_out(parsed['text']))
1292 |
1293 | if ret:
1294 | debug(('sendMessage returned', ret))
1295 | result = PYVER.to_str(
1296 | build_privmsg_out(
1297 | parsed['to_nick'], PYVER.to_unicode(ret)
1298 | ))
1299 |
1300 | except potr.context.NotEncryptedError as err:
1301 | if err.args[0] == potr.context.EXC_FINISHED:
1302 | context.print_buffer(
1303 | 'Your message was not sent. End your private '
1304 | 'conversation:\n/otr finish',
1305 | 'error')
1306 | else:
1307 | raise
1308 |
1309 | weechat.bar_item_update(SCRIPT_NAME)
1310 | # pylint: disable=bare-except
1311 | except:
1312 | try:
1313 | print_buffer('', traceback.format_exc(), 'error')
1314 | print_buffer('', 'Versions: {versions}'.format(
1315 | versions=dependency_versions()), 'error')
1316 | context.print_buffer(
1317 | 'Failed to send message. See core buffer for traceback.',
1318 | 'error')
1319 | # pylint: disable=bare-except
1320 | except:
1321 | pass
1322 |
1323 | return result
1324 |
1325 | def shutdown():
1326 | """Script unload callback."""
1327 | debug('shutdown')
1328 |
1329 | weechat.config_write(CONFIG_FILE)
1330 |
1331 | for account in ACCOUNTS.values():
1332 | account.end_all_private()
1333 |
1334 | free_all_config()
1335 |
1336 | weechat.bar_item_remove(OTR_STATUSBAR)
1337 |
1338 | return weechat.WEECHAT_RC_OK
1339 |
1340 | def command_cb(data, buf, args):
1341 | """Parse and dispatch WeeChat OTR commands."""
1342 | result = weechat.WEECHAT_RC_ERROR
1343 |
1344 | arg_parts = [PYVER.to_unicode(arg) for arg in shlex.split(args)]
1345 |
1346 | if len(arg_parts) in (1, 3) and arg_parts[0] in ('start', 'refresh'):
1347 | nick, server = default_peer_args(arg_parts[1:3], buf)
1348 |
1349 | if nick is not None and server is not None:
1350 | context = get_server_context(server, nick)
1351 | # We need to wall disable_logging() here so that no OTR-related
1352 | # buffer messages get logged at any point. disable_logging() will
1353 | # be called again when effectively switching to encrypted, but
1354 | # the previous_log_level we set here will be preserved for later
1355 | # restoring.
1356 | if not context.getPolicy('log'):
1357 | context.previous_log_level = context.disable_logging()
1358 | else:
1359 | context.previous_log_level = context.get_log_level()
1360 |
1361 | context.hint(
1362 | 'Sending OTR query... Please await confirmation of the OTR '
1363 | 'session being started before sending a message.')
1364 | if not context.getPolicy('send_tag'):
1365 | context.hint(
1366 | 'To try OTR on all conversations with {peer}: /otr '
1367 | 'policy send_tag on'.format(peer=context.peer))
1368 |
1369 | privmsg(server, nick, '?OTR?')
1370 |
1371 | result = weechat.WEECHAT_RC_OK
1372 | elif len(arg_parts) in (1, 3) and arg_parts[0] in ('finish', 'end'):
1373 | nick, server = default_peer_args(arg_parts[1:3], buf)
1374 |
1375 | if nick is not None and server is not None:
1376 | context = get_server_context(server, nick)
1377 | context.disconnect()
1378 |
1379 | result = weechat.WEECHAT_RC_OK
1380 |
1381 | elif len(arg_parts) in (1, 3) and arg_parts[0] == 'status':
1382 | nick, server = default_peer_args(arg_parts[1:3], buf)
1383 |
1384 | if nick is not None and server is not None:
1385 | context = get_server_context(server, nick)
1386 | if context.is_encrypted():
1387 | context.print_buffer(
1388 | 'This conversation is encrypted.', 'success')
1389 | context.print_buffer("Your fingerprint is: {0}".format(
1390 | context.user.getPrivkey()))
1391 | context.print_buffer("Your peer's fingerprint is: {0}".format(
1392 | potr.human_hash(context.crypto.theirPubkey.cfingerprint())))
1393 | if context.is_verified():
1394 | context.print_buffer(
1395 | "The peer's identity has been verified.",
1396 | 'success')
1397 | else:
1398 | context.print_buffer(
1399 | "You have not verified the peer's identity yet.",
1400 | 'warning')
1401 | else:
1402 | context.print_buffer(
1403 | "This current conversation is not encrypted.",
1404 | 'warning')
1405 |
1406 | result = weechat.WEECHAT_RC_OK
1407 |
1408 | elif len(arg_parts) in range(2, 7) and arg_parts[0] == 'smp':
1409 | action = arg_parts[1]
1410 |
1411 | if action == 'respond':
1412 | # Check if nickname and server are specified
1413 | if len(arg_parts) == 3:
1414 | nick, server = default_peer_args([], buf)
1415 | secret = arg_parts[2]
1416 | elif len(arg_parts) == 5:
1417 | nick, server = default_peer_args(arg_parts[2:4], buf)
1418 | secret = arg_parts[4]
1419 | else:
1420 | return weechat.WEECHAT_RC_ERROR
1421 |
1422 | if secret:
1423 | secret = PYVER.to_str(secret)
1424 |
1425 | context = get_server_context(server, nick)
1426 | context.smpGotSecret(secret)
1427 |
1428 | result = weechat.WEECHAT_RC_OK
1429 |
1430 | elif action == 'ask':
1431 | question = None
1432 | secret = None
1433 |
1434 | # Nickname and server are not specified
1435 | # Check whether it's a simple challenge or a question/answer request
1436 | if len(arg_parts) == 3:
1437 | nick, server = default_peer_args([], buf)
1438 | secret = arg_parts[2]
1439 | elif len(arg_parts) == 4:
1440 | nick, server = default_peer_args([], buf)
1441 | secret = arg_parts[3]
1442 | question = arg_parts[2]
1443 |
1444 | # Nickname and server are specified
1445 | # Check whether it's a simple challenge or a question/answer request
1446 | elif len(arg_parts) == 5:
1447 | nick, server = default_peer_args(arg_parts[2:4], buf)
1448 | secret = arg_parts[4]
1449 | elif len(arg_parts) == 6:
1450 | nick, server = default_peer_args(arg_parts[2:4], buf)
1451 | secret = arg_parts[5]
1452 | question = arg_parts[4]
1453 | else:
1454 | return weechat.WEECHAT_RC_ERROR
1455 |
1456 | context = get_server_context(server, nick)
1457 |
1458 | if secret:
1459 | secret = PYVER.to_str(secret)
1460 | if question:
1461 | question = PYVER.to_str(question)
1462 |
1463 | try:
1464 | context.smpInit(secret, question)
1465 | except potr.context.NotEncryptedError:
1466 | context.print_buffer(
1467 | 'There is currently no encrypted session with {0}.'.format(
1468 | context.peer), 'error')
1469 | else:
1470 | if question:
1471 | context.print_buffer('SMP challenge sent...')
1472 | else:
1473 | context.print_buffer('SMP question sent...')
1474 | context.in_smp = True
1475 | result = weechat.WEECHAT_RC_OK
1476 |
1477 | elif action == 'abort':
1478 | # Nickname and server are not specified
1479 | if len(arg_parts) == 2:
1480 | nick, server = default_peer_args([], buf)
1481 | # Nickname and server are specified
1482 | elif len(arg_parts) == 4:
1483 | nick, server = default_peer_args(arg_parts[2:4], buf)
1484 | else:
1485 | return weechat.WEECHAT_RC_ERROR
1486 |
1487 | context = get_server_context(server, nick)
1488 |
1489 | if context.in_smp:
1490 | try:
1491 | context.smpAbort()
1492 | except potr.context.NotEncryptedError:
1493 | context.print_buffer(
1494 | 'There is currently no encrypted session with {0}.'
1495 | .format(context.peer), 'error')
1496 | else:
1497 | debug('SMP aborted')
1498 | context.smp_finish('SMP aborted.')
1499 | result = weechat.WEECHAT_RC_OK
1500 |
1501 | elif len(arg_parts) in (1, 3) and arg_parts[0] == 'trust':
1502 | nick, server = default_peer_args(arg_parts[1:3], buf)
1503 |
1504 | if nick is not None and server is not None:
1505 | context = get_server_context(server, nick)
1506 |
1507 | if context.crypto.theirPubkey is not None:
1508 | context.setCurrentTrust('verified')
1509 | context.print_buffer('{peer} is now authenticated.'.format(
1510 | peer=context.peer))
1511 |
1512 | weechat.bar_item_update(SCRIPT_NAME)
1513 | else:
1514 | context.print_buffer(
1515 | 'No fingerprint for {peer}. Start an OTR conversation '
1516 | 'first: /otr start'.format(peer=context.peer),
1517 | 'error')
1518 |
1519 | result = weechat.WEECHAT_RC_OK
1520 | elif len(arg_parts) in (1, 3) and arg_parts[0] == 'distrust':
1521 | nick, server = default_peer_args(arg_parts[1:3], buf)
1522 |
1523 | if nick is not None and server is not None:
1524 | context = get_server_context(server, nick)
1525 |
1526 | if context.crypto.theirPubkey is not None:
1527 | context.setCurrentTrust('')
1528 | context.print_buffer(
1529 | '{peer} is now de-authenticated.'.format(
1530 | peer=context.peer))
1531 |
1532 | weechat.bar_item_update(SCRIPT_NAME)
1533 | else:
1534 | context.print_buffer(
1535 | 'No fingerprint for {peer}. Start an OTR conversation '
1536 | 'first: /otr start'.format(peer=context.peer), 'error')
1537 |
1538 | result = weechat.WEECHAT_RC_OK
1539 |
1540 | elif len(arg_parts) in (1, 2) and arg_parts[0] == 'log':
1541 | nick, server = default_peer_args([], buf)
1542 | if len(arg_parts) == 1:
1543 | if nick is not None and server is not None:
1544 | context = get_server_context(server, nick)
1545 |
1546 | if context.is_encrypted():
1547 | if context.is_logged():
1548 | context.print_buffer(
1549 | 'This conversation is currently being logged.',
1550 | 'warning')
1551 | result = weechat.WEECHAT_RC_OK
1552 |
1553 | else:
1554 | context.print_buffer(
1555 | 'This conversation is currently NOT being logged.')
1556 | result = weechat.WEECHAT_RC_OK
1557 | else:
1558 | context.print_buffer(
1559 | 'OTR LOG: Not in an OTR session', 'error')
1560 | result = weechat.WEECHAT_RC_OK
1561 |
1562 | else:
1563 | print_buffer('', 'OTR LOG: Not in an OTR session', 'error')
1564 | result = weechat.WEECHAT_RC_OK
1565 |
1566 | if len(arg_parts) == 2:
1567 | if nick is not None and server is not None:
1568 | context = get_server_context(server, nick)
1569 |
1570 | if arg_parts[1] == 'start' and \
1571 | not context.is_logged() and \
1572 | context.is_encrypted():
1573 | if context.previous_log_level is None:
1574 | context.previous_log_level = context.get_log_level()
1575 | context.print_buffer(
1576 | 'From this point on, this conversation will be '
1577 | 'logged. Please keep in mind that by doing so you '
1578 | 'are potentially putting yourself and your '
1579 | 'interlocutor at risk. You can disable this by doing '
1580 | '/otr log stop',
1581 | 'warning')
1582 | weechat.command(buf, '/mute logger set 9')
1583 | result = weechat.WEECHAT_RC_OK
1584 |
1585 | elif arg_parts[1] == 'stop' and \
1586 | context.is_logged() and \
1587 | context.is_encrypted():
1588 | if context.previous_log_level is None:
1589 | context.previous_log_level = context.get_log_level()
1590 | weechat.command(buf, '/mute logger set 0')
1591 | context.print_buffer(
1592 | 'From this point on, this conversation will NOT be '
1593 | 'logged ANYMORE.')
1594 | result = weechat.WEECHAT_RC_OK
1595 |
1596 | elif not context.is_encrypted():
1597 | context.print_buffer(
1598 | 'OTR LOG: Not in an OTR session', 'error')
1599 | result = weechat.WEECHAT_RC_OK
1600 |
1601 | else:
1602 | # Don't need to do anything.
1603 | result = weechat.WEECHAT_RC_OK
1604 |
1605 | else:
1606 | print_buffer('', 'OTR LOG: Not in an OTR session', 'error')
1607 |
1608 | elif len(arg_parts) in (1, 2, 3, 4) and arg_parts[0] == 'policy':
1609 | if len(arg_parts) == 1:
1610 | nick, server = default_peer_args([], buf)
1611 |
1612 | if nick is not None and server is not None:
1613 | context = get_server_context(server, nick)
1614 |
1615 | context.print_buffer(context.format_policies())
1616 | else:
1617 | prnt('', format_default_policies())
1618 |
1619 | result = weechat.WEECHAT_RC_OK
1620 |
1621 | elif len(arg_parts) == 2 and arg_parts[1].lower() == 'default':
1622 | nick, server = default_peer_args([], buf)
1623 |
1624 | if nick is not None and server is not None:
1625 | context = get_server_context(server, nick)
1626 |
1627 | context.print_buffer(format_default_policies())
1628 | else:
1629 | prnt('', format_default_policies())
1630 |
1631 | result = weechat.WEECHAT_RC_OK
1632 |
1633 | elif len(arg_parts) == 3 and arg_parts[1].lower() in POLICIES:
1634 | nick, server = default_peer_args([], buf)
1635 |
1636 | if nick is not None and server is not None:
1637 | context = get_server_context(server, nick)
1638 |
1639 | policy_var = context.policy_config_option(arg_parts[1].lower())
1640 |
1641 | command('', '/set {policy} {value}'.format(
1642 | policy=policy_var,
1643 | value=arg_parts[2]))
1644 |
1645 | context.print_buffer(context.format_policies())
1646 |
1647 | result = weechat.WEECHAT_RC_OK
1648 |
1649 | elif len(arg_parts) == 4 and \
1650 | arg_parts[1].lower() == 'default' and \
1651 | arg_parts[2].lower() in POLICIES:
1652 | nick, server = default_peer_args([], buf)
1653 |
1654 | policy_var = "otr.policy.default." + arg_parts[2].lower()
1655 |
1656 | command('', '/set {policy} {value}'.format(
1657 | policy=policy_var,
1658 | value=arg_parts[3]))
1659 |
1660 | if nick is not None and server is not None:
1661 | context = get_server_context(server, nick)
1662 |
1663 | context.print_buffer(format_default_policies())
1664 | else:
1665 | prnt('', format_default_policies())
1666 |
1667 | result = weechat.WEECHAT_RC_OK
1668 | elif len(arg_parts) in (1, 2) and arg_parts[0] == 'fingerprint':
1669 | if len(arg_parts) == 1:
1670 | show_account_fingerprints()
1671 | result = weechat.WEECHAT_RC_OK
1672 | elif len(arg_parts) == 2:
1673 | if arg_parts[1] == 'all':
1674 | show_peer_fingerprints()
1675 | else:
1676 | show_peer_fingerprints(grep=arg_parts[1])
1677 | result = weechat.WEECHAT_RC_OK
1678 |
1679 | return result
1680 |
1681 | def otr_statusbar_cb(data, item, window):
1682 | """Update the statusbar."""
1683 | if window:
1684 | buf = weechat.window_get_pointer(window, 'buffer')
1685 | else:
1686 | # If the bar item is in a root bar that is not in a window, window
1687 | # will be empty.
1688 | buf = weechat.current_buffer()
1689 |
1690 | if not buffer_is_private(buf):
1691 | return ''
1692 |
1693 | local_user = irc_user(
1694 | buffer_get_string(buf, 'localvar_nick'),
1695 | buffer_get_string(buf, 'localvar_server'))
1696 |
1697 | remote_user = irc_user(
1698 | buffer_get_string(buf, 'localvar_channel'),
1699 | buffer_get_string(buf, 'localvar_server'))
1700 |
1701 | context = get_context(local_user, remote_user)
1702 |
1703 | encrypted_str = config_string('look.bar.state.encrypted')
1704 | unencrypted_str = config_string('look.bar.state.unencrypted')
1705 | authenticated_str = config_string('look.bar.state.authenticated')
1706 | unauthenticated_str = config_string('look.bar.state.unauthenticated')
1707 | logged_str = config_string('look.bar.state.logged')
1708 | notlogged_str = config_string('look.bar.state.notlogged')
1709 |
1710 | bar_parts = []
1711 |
1712 | if context.is_encrypted():
1713 | if encrypted_str:
1714 | bar_parts.append(''.join([
1715 | config_color('status.encrypted'),
1716 | encrypted_str,
1717 | config_color('status.default')]))
1718 |
1719 | if context.is_verified():
1720 | if authenticated_str:
1721 | bar_parts.append(''.join([
1722 | config_color('status.authenticated'),
1723 | authenticated_str,
1724 | config_color('status.default')]))
1725 | elif unauthenticated_str:
1726 | bar_parts.append(''.join([
1727 | config_color('status.unauthenticated'),
1728 | unauthenticated_str,
1729 | config_color('status.default')]))
1730 |
1731 | if context.is_logged():
1732 | if logged_str:
1733 | bar_parts.append(''.join([
1734 | config_color('status.logged'),
1735 | logged_str,
1736 | config_color('status.default')]))
1737 | elif notlogged_str:
1738 | bar_parts.append(''.join([
1739 | config_color('status.notlogged'),
1740 | notlogged_str,
1741 | config_color('status.default')]))
1742 |
1743 | elif unencrypted_str:
1744 | bar_parts.append(''.join([
1745 | config_color('status.unencrypted'),
1746 | unencrypted_str,
1747 | config_color('status.default')]))
1748 |
1749 | result = config_string('look.bar.state.separator').join(bar_parts)
1750 |
1751 | if result:
1752 | result = '{color}{prefix}{result}'.format(
1753 | color=config_color('status.default'),
1754 | prefix=config_string('look.bar.prefix'),
1755 | result=result)
1756 |
1757 | if context.is_encrypted():
1758 | weechat.buffer_set(buf, 'localvar_set_otr_encrypted', 'true')
1759 | else:
1760 | weechat.buffer_set(buf, 'localvar_set_otr_encrypted', 'false')
1761 |
1762 | if context.is_verified():
1763 | weechat.buffer_set(buf, 'localvar_set_otr_authenticated', 'true')
1764 | else:
1765 | weechat.buffer_set(buf, 'localvar_set_otr_authenticated', 'false')
1766 |
1767 | if context.is_logged():
1768 | weechat.buffer_set(buf, 'localvar_set_otr_logged', 'true')
1769 | else:
1770 | weechat.buffer_set(buf, 'localvar_set_otr_logged', 'false')
1771 |
1772 | return result
1773 |
1774 | def bar_config_update_cb(data, option):
1775 | """Callback for updating the status bar when its config changes."""
1776 | weechat.bar_item_update(SCRIPT_NAME)
1777 |
1778 | return weechat.WEECHAT_RC_OK
1779 |
1780 | def policy_completion_cb(data, completion_item, buf, completion):
1781 | """Callback for policy tab completion."""
1782 | for policy in POLICIES:
1783 | weechat.hook_completion_list_add(
1784 | completion, policy, 0, weechat.WEECHAT_LIST_POS_SORT)
1785 |
1786 | return weechat.WEECHAT_RC_OK
1787 |
1788 | def policy_create_option_cb(data, config_file, section, name, value):
1789 | """Callback for creating a new policy option when the user sets one
1790 | that doesn't exist."""
1791 | weechat.config_new_option(
1792 | config_file, section, name, 'boolean', '', '', 0, 0, value, value, 0,
1793 | '', '', '', '', '', '')
1794 |
1795 | return weechat.WEECHAT_CONFIG_OPTION_SET_OK_CHANGED
1796 |
1797 | def logger_level_update_cb(data, option, value):
1798 | """Callback called when any logger level changes."""
1799 | weechat.bar_item_update(SCRIPT_NAME)
1800 |
1801 | return weechat.WEECHAT_RC_OK
1802 |
1803 | def buffer_switch_cb(data, signal, signal_data):
1804 | """Callback for buffer switched.
1805 |
1806 | Used for updating the status bar item when it is in a root bar.
1807 | """
1808 | weechat.bar_item_update(SCRIPT_NAME)
1809 |
1810 | return weechat.WEECHAT_RC_OK
1811 |
1812 | def buffer_closing_cb(data, signal, signal_data):
1813 | """Callback for buffer closed.
1814 |
1815 | It closes the OTR session when the buffer is about to be closed.
1816 | """
1817 | result = weechat.WEECHAT_RC_ERROR
1818 | nick, server = default_peer_args([], signal_data)
1819 |
1820 | if nick is not None and server is not None:
1821 | context = get_server_context(server, nick)
1822 | context.disconnect()
1823 |
1824 | result = weechat.WEECHAT_RC_OK
1825 | return result
1826 |
1827 | def init_config():
1828 | """Set up configuration options and load config file."""
1829 | global CONFIG_FILE
1830 | CONFIG_FILE = weechat.config_new(SCRIPT_NAME, '', '')
1831 |
1832 | global CONFIG_SECTIONS
1833 | CONFIG_SECTIONS = {}
1834 |
1835 | CONFIG_SECTIONS['general'] = weechat.config_new_section(
1836 | CONFIG_FILE, 'general', 0, 0, '', '', '', '', '', '', '', '', '', '')
1837 |
1838 | for option, typ, desc, default in [
1839 | ('debug',
1840 | 'boolean',
1841 | 'OTR script debugging',
1842 | 'off'),
1843 | ('hints',
1844 | 'boolean',
1845 | 'Give helpful hints how to use this script and how to stay '
1846 | 'secure while using OTR (recommended)',
1847 | 'on'),
1848 | ('defaultkey',
1849 | 'string',
1850 | 'default private key to use for new accounts (nick@server)',
1851 | ''),
1852 | ('no_send_tag_regex',
1853 | 'string',
1854 | 'do not OTR whitespace tag messages to nicks matching this regex '
1855 | '(case insensitive)',
1856 | '^(alis|chanfix|global|.+serv|\*.+)$'),
1857 | ]:
1858 | weechat.config_new_option(
1859 | CONFIG_FILE, CONFIG_SECTIONS['general'], option, typ, desc, '', 0,
1860 | 0, default, default, 0, '', '', '', '', '', '')
1861 |
1862 | CONFIG_SECTIONS['color'] = weechat.config_new_section(
1863 | CONFIG_FILE, 'color', 0, 0, '', '', '', '', '', '', '', '', '', '')
1864 |
1865 | for option, desc, default, update_cb in [
1866 | ('status.default',
1867 | 'status bar default color',
1868 | 'default',
1869 | 'bar_config_update_cb'),
1870 | ('status.encrypted',
1871 | 'status bar encrypted indicator color',
1872 | 'green',
1873 | 'bar_config_update_cb'),
1874 | ('status.unencrypted',
1875 | 'status bar unencrypted indicator color',
1876 | 'lightred',
1877 | 'bar_config_update_cb'),
1878 | ('status.authenticated',
1879 | 'status bar authenticated indicator color',
1880 | 'green',
1881 | 'bar_config_update_cb'),
1882 | ('status.unauthenticated',
1883 | 'status bar unauthenticated indicator color',
1884 | 'lightred',
1885 | 'bar_config_update_cb'),
1886 | ('status.logged',
1887 | 'status bar logged indicator color',
1888 | 'lightred',
1889 | 'bar_config_update_cb'),
1890 | ('status.notlogged',
1891 | 'status bar not logged indicator color',
1892 | 'green',
1893 | 'bar_config_update_cb'),
1894 | ('buffer.hint',
1895 | 'text color for hints',
1896 | 'lightblue',
1897 | ''),
1898 | ('buffer.info',
1899 | 'text color for informational messages',
1900 | 'default',
1901 | ''),
1902 | ('buffer.success',
1903 | 'text color for success messages',
1904 | 'lightgreen',
1905 | ''),
1906 | ('buffer.warning',
1907 | 'text color for warnings',
1908 | 'yellow',
1909 | ''),
1910 | ('buffer.error',
1911 | 'text color for errors',
1912 | 'lightred',
1913 | ''),
1914 | ]:
1915 | weechat.config_new_option(
1916 | CONFIG_FILE, CONFIG_SECTIONS['color'], option, 'color', desc, '', 0,
1917 | 0, default, default, 0, '', '', update_cb, '', '', '')
1918 |
1919 | CONFIG_SECTIONS['look'] = weechat.config_new_section(
1920 | CONFIG_FILE, 'look', 0, 0, '', '', '', '', '', '', '', '', '', '')
1921 |
1922 | for option, desc, default, update_cb in [
1923 | ('bar.prefix',
1924 | 'prefix for OTR status bar item',
1925 | 'OTR:',
1926 | 'bar_config_update_cb'),
1927 | ('bar.state.encrypted',
1928 | 'shown in status bar when conversation is encrypted',
1929 | 'SEC',
1930 | 'bar_config_update_cb'),
1931 | ('bar.state.unencrypted',
1932 | 'shown in status bar when conversation is not encrypted',
1933 | '!SEC',
1934 | 'bar_config_update_cb'),
1935 | ('bar.state.authenticated',
1936 | 'shown in status bar when peer is authenticated',
1937 | 'AUTH',
1938 | 'bar_config_update_cb'),
1939 | ('bar.state.unauthenticated',
1940 | 'shown in status bar when peer is not authenticated',
1941 | '!AUTH',
1942 | 'bar_config_update_cb'),
1943 | ('bar.state.logged',
1944 | 'shown in status bar when peer conversation is being logged to '
1945 | 'disk',
1946 | 'LOG',
1947 | 'bar_config_update_cb'),
1948 | ('bar.state.notlogged',
1949 | 'shown in status bar when peer conversation is not being logged '
1950 | 'to disk',
1951 | '!LOG',
1952 | 'bar_config_update_cb'),
1953 | ('bar.state.separator',
1954 | 'separator for states in the status bar',
1955 | ',',
1956 | 'bar_config_update_cb'),
1957 | ('prefix',
1958 | 'prefix used for messages from otr (note: content is evaluated, '
1959 | 'see /help eval)',
1960 | '${color:default}:! ${color:brown}otr${color:default} !:',
1961 | ''),
1962 | ]:
1963 | weechat.config_new_option(
1964 | CONFIG_FILE, CONFIG_SECTIONS['look'], option, 'string', desc, '',
1965 | 0, 0, default, default, 0, '', '', update_cb, '', '', '')
1966 |
1967 | CONFIG_SECTIONS['policy'] = weechat.config_new_section(
1968 | CONFIG_FILE, 'policy', 1, 1, '', '', '', '', '', '',
1969 | 'policy_create_option_cb', '', '', '')
1970 |
1971 | for option, desc, default in [
1972 | ('default.allow_v2',
1973 | 'default allow OTR v2 policy',
1974 | 'on'),
1975 | ('default.require_encryption',
1976 | 'default require encryption policy',
1977 | 'off'),
1978 | ('default.log',
1979 | 'default enable logging to disk',
1980 | 'off'),
1981 | ('default.send_tag',
1982 | 'default send tag policy',
1983 | 'off'),
1984 | ('default.html_escape',
1985 | 'default HTML escape policy',
1986 | 'off'),
1987 | ('default.html_filter',
1988 | 'default HTML filter policy',
1989 | 'on'),
1990 | ]:
1991 | weechat.config_new_option(
1992 | CONFIG_FILE, CONFIG_SECTIONS['policy'], option, 'boolean', desc, '',
1993 | 0, 0, default, default, 0, '', '', '', '', '', '')
1994 |
1995 | weechat.config_read(CONFIG_FILE)
1996 |
1997 | def free_all_config():
1998 | """Free all config options, sections and config file."""
1999 | for section in CONFIG_SECTIONS.values():
2000 | weechat.config_section_free_options(section)
2001 | weechat.config_section_free(section)
2002 |
2003 | weechat.config_free(CONFIG_FILE)
2004 |
2005 | def create_dir():
2006 | """Create the OTR subdirectory in the WeeChat config directory if it does
2007 | not exist."""
2008 | if not os.path.exists(OTR_DIR):
2009 | weechat.mkdir_home(OTR_DIR_NAME, 0o700)
2010 |
2011 | def git_info():
2012 | """If this script is part of a git repository return the repo state."""
2013 | result = None
2014 | script_dir = os.path.dirname(os.path.realpath(__file__))
2015 | git_dir = os.path.join(script_dir, '.git')
2016 | if os.path.isdir(git_dir):
2017 | import subprocess
2018 | try:
2019 | # We can't use check_output here without breaking compatibility
2020 | # for Python 2.6, but we ignore the return value anyway, so Popen
2021 | # is only slightly more complicated:
2022 | process = subprocess.Popen([
2023 | 'git',
2024 | '--git-dir', git_dir,
2025 | '--work-tree', script_dir,
2026 | 'describe', '--dirty', '--always',
2027 | ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2028 | output = process.communicate()[0]
2029 | if output:
2030 | result = PYVER.to_unicode(output).lstrip('v').rstrip()
2031 | except OSError:
2032 | pass
2033 |
2034 | return result
2035 |
2036 | def weechat_version_ok():
2037 | """Check if the WeeChat version is compatible with this script.
2038 |
2039 | If WeeChat version < 0.4.2 log an error to the core buffer and return
2040 | False. Otherwise return True.
2041 | """
2042 | weechat_version = weechat.info_get('version_number', '') or 0
2043 | if int(weechat_version) < 0x00040200:
2044 | error_message = (
2045 | '{script_name} requires WeeChat version >= 0.4.2. The current '
2046 | 'version is {current_version}.').format(
2047 | script_name=SCRIPT_NAME,
2048 | current_version=weechat.info_get('version', ''))
2049 | prnt('', error_message)
2050 | return False
2051 | return True
2052 |
2053 | SCRIPT_VERSION = git_info() or SCRIPT_VERSION
2054 |
2055 | def dependency_versions():
2056 | """Return a string containing the versions of all dependencies."""
2057 | return ('weechat-otr {script_version}, '
2058 | 'potr {potr_major}.{potr_minor}.{potr_patch}-{potr_sub}, '
2059 | 'Python {python_version}, '
2060 | 'WeeChat {weechat_version}'
2061 | ).format(
2062 | script_version=SCRIPT_VERSION,
2063 | potr_major=potr.VERSION[0],
2064 | potr_minor=potr.VERSION[1],
2065 | potr_patch=potr.VERSION[2],
2066 | potr_sub=potr.VERSION[3],
2067 | python_version=platform.python_version(),
2068 | weechat_version=weechat.info_get('version', ''))
2069 |
2070 | def excepthook(typ, value, tracebak):
2071 | """Add dependency versions to tracebacks."""
2072 | sys.stderr.write('Versions: ')
2073 | sys.stderr.write(dependency_versions())
2074 | sys.stderr.write('\n')
2075 |
2076 | sys.__excepthook__(typ, value, tracebak)
2077 |
2078 | sys.excepthook = excepthook
2079 |
2080 | if weechat.register(
2081 | SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENCE, SCRIPT_DESC,
2082 | 'shutdown', ''):
2083 | if weechat_version_ok():
2084 | init_config()
2085 |
2086 | OTR_DIR = os.path.join(info_get('weechat_dir', ''), OTR_DIR_NAME)
2087 | create_dir()
2088 |
2089 | ACCOUNTS = AccountDict()
2090 |
2091 | weechat.hook_modifier('irc_in_privmsg', 'message_in_cb', '')
2092 | weechat.hook_modifier('irc_out_privmsg', 'message_out_cb', '')
2093 |
2094 | weechat.hook_command(
2095 | SCRIPT_NAME, SCRIPT_HELP,
2096 | 'start [NICK SERVER] || '
2097 | 'refresh [NICK SERVER] || '
2098 | 'finish [NICK SERVER] || '
2099 | 'end [NICK SERVER] || '
2100 | 'status [NICK SERVER] || '
2101 | 'smp ask [NICK SERVER] [QUESTION] SECRET || '
2102 | 'smp respond [NICK SERVER] SECRET || '
2103 | 'smp abort [NICK SERVER] || '
2104 | 'trust [NICK SERVER] || '
2105 | 'distrust [NICK SERVER] || '
2106 | 'log [start|stop] || '
2107 | 'policy [POLICY on|off] || '
2108 | 'fingerprint [SEARCH|all]',
2109 | '',
2110 | 'start %(nick) %(irc_servers) %-||'
2111 | 'refresh %(nick) %(irc_servers) %-||'
2112 | 'finish %(nick) %(irc_servers) %-||'
2113 | 'end %(nick) %(irc_servers) %-||'
2114 | 'status %(nick) %(irc_servers) %-||'
2115 | 'smp ask|respond %(nick) %(irc_servers) %-||'
2116 | 'smp abort %(nick) %(irc_servers) %-||'
2117 | 'trust %(nick) %(irc_servers) %-||'
2118 | 'distrust %(nick) %(irc_servers) %-||'
2119 | 'log start|stop %-||'
2120 | 'policy %(otr_policy) on|off %-||'
2121 | 'fingerprint all %-||',
2122 | 'command_cb',
2123 | '')
2124 |
2125 | weechat.hook_completion(
2126 | 'otr_policy', 'OTR policies', 'policy_completion_cb', '')
2127 |
2128 | weechat.hook_config('logger.level.irc.*', 'logger_level_update_cb', '')
2129 |
2130 | weechat.hook_signal('buffer_switch', 'buffer_switch_cb', '')
2131 | weechat.hook_signal('buffer_closing', 'buffer_closing_cb', '')
2132 |
2133 | OTR_STATUSBAR = weechat.bar_item_new(
2134 | SCRIPT_NAME, 'otr_statusbar_cb', '')
2135 | weechat.bar_item_update(SCRIPT_NAME)
2136 |
--------------------------------------------------------------------------------
/weechat_otr_test/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """unittest tests for weechat-otr."""
3 |
--------------------------------------------------------------------------------
/weechat_otr_test/is_encrypted_account.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 |
5 | import weechat_otr_test.is_encrypted_context
6 |
7 | import weechat_otr
8 |
9 | class IsEncryptedAccount(weechat_otr.IrcOtrAccount):
10 | contextclass = weechat_otr_test.is_encrypted_context.IsEncryptedContext
11 |
--------------------------------------------------------------------------------
/weechat_otr_test/is_encrypted_context.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=abstract-method
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | import weechat_otr
7 |
8 | class IsEncryptedContext(weechat_otr.IrcContext):
9 |
10 | def __init__(self, account, peername):
11 | super(IsEncryptedContext, self).__init__(account, peername)
12 | self.is_encrypted_fake = False
13 |
14 | def is_encrypted(self):
15 | return self.is_encrypted_fake
16 |
--------------------------------------------------------------------------------
/weechat_otr_test/mock_account.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 |
5 | class MockAccount(object):
6 |
7 | def __init__(self):
8 | self.contexts = {}
9 | self.end_all_privates = 0
10 |
11 | def add_context(self, peer, context):
12 | self.contexts[peer] = context
13 |
14 | def getContext(self, peer):
15 | return self.contexts[peer]
16 |
17 | def end_all_private(self):
18 | self.end_all_privates += 1
19 |
--------------------------------------------------------------------------------
/weechat_otr_test/mock_context.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-instance-attributes
5 |
6 | class MockContext(object):
7 |
8 | def __init__(self):
9 | self.smp_init = None
10 | self.smp_got_secret = None
11 | self.in_smp = False
12 | self.smp_finishes = []
13 | self.disconnects = 0
14 | self.encrypted = False
15 | self.verified = False
16 | self.logged = False
17 |
18 | def smpAbort(self):
19 | pass
20 |
21 | def smpInit(self, *args):
22 | self.smp_init = args
23 |
24 | def smpGotSecret(self, *args):
25 | self.smp_got_secret = args
26 |
27 | def smp_finish(self, *args):
28 | self.smp_finishes.append(args)
29 |
30 | def print_buffer(self, *args):
31 | pass
32 |
33 | def disconnect(self):
34 | self.disconnects += 1
35 |
36 | def is_encrypted(self):
37 | return self.encrypted
38 |
39 | def is_verified(self):
40 | return self.verified
41 |
42 | def is_logged(self):
43 | return self.logged
44 |
--------------------------------------------------------------------------------
/weechat_otr_test/mock_user.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=missing-docstring
3 | # pylint: disable=too-few-public-methods
4 |
5 | class MockUser(object):
6 |
7 | def __init__(self, nick):
8 | self.nick = nick
9 |
10 | def policy_config_option(self, _):
11 | # pylint: disable=no-self-use
12 | return ''
13 |
--------------------------------------------------------------------------------
/weechat_otr_test/mock_weechat.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=missing-docstring
3 | # pylint: disable=no-method-argument
4 | # pylint: disable=no-self-use
5 | # pylint: disable=too-many-public-methods
6 | # pylint: disable=unused-argument
7 | # pylint: disable=too-many-instance-attributes
8 | # pylint: disable=invalid-name
9 | # pylint: disable=too-many-arguments
10 |
11 | from __future__ import unicode_literals
12 |
13 | import collections
14 | import copy
15 | import io
16 | import os
17 | import shutil
18 | import tarfile
19 | import types
20 |
21 | class MockWeechat(types.ModuleType):
22 |
23 | WEECHAT_RC_ERROR = None
24 | WEECHAT_RC_OK = None
25 |
26 | def __init__(self, weechat_dir):
27 | self.weechat_dir = weechat_dir
28 |
29 | self.config_options = {}
30 | self.script_name = None
31 | self.printed = {}
32 | self.saved_state = None
33 | self.weechat_dir_tar = None
34 | self.infos = {
35 | ('',) : {
36 | 'weechat_dir': self.weechat_dir,
37 | 'version_number': 0x01000100,
38 | },
39 | ('server',) : {
40 | 'irc_nick': 'nick',
41 | },
42 | ('server,nick',) : {
43 | 'irc_buffer' : 'server_nick_buffer',
44 | },
45 | ('server,nick2',) : {
46 | 'irc_buffer' : 'server_nick2_buffer',
47 | },
48 | ('server,no_window_nick',) : {
49 | 'irc_buffer' : 'non_private_buffer',
50 | },
51 | ('server,STATUSMSG',) : {
52 | 'irc_server_isupport_value' : '@+',
53 | },
54 | ('server,CHANTYPES',) : {
55 | 'irc_server_isupport_value' : '#',
56 | },
57 | }
58 | self.buffers = {
59 | None : {
60 | 'localvar_type' : 'private',
61 | 'localvar_channel' : 'nick',
62 | 'localvar_server' : 'server',
63 | 'localvar_nick' : 'me',
64 | },
65 | 'server_nick_buffer' : {
66 | 'localvar_type' : 'private',
67 | 'localvar_channel' : 'nick',
68 | 'localvar_server' : 'server',
69 | 'name' : 'server_nick_buffer_name',
70 | 'plugin' : 'irc',
71 | },
72 | 'server_nick2_buffer': {
73 | 'localvar_type' : 'private',
74 | 'localvar_channel' : 'nick2',
75 | 'localvar_server' : 'server',
76 | 'name' : 'server_nick2_buffer_name',
77 | 'plugin' : 'irc',
78 | },
79 | 'non_private_buffer' : {
80 | 'localvar_type' : 'non_private',
81 | 'name' : 'non_private_buffer_name',
82 | 'plugin' : 'irc',
83 | }
84 | }
85 | self.config_written = []
86 | self.bar_items_removed = []
87 | self.config_section_free_options_calls = []
88 | self.config_section_free_calls = []
89 | self.config_free_calls = []
90 | self.buffer_new_calls = []
91 | self.buffer_sets = {}
92 | self.buffer_search_calls = []
93 | self.buffer_search_returns = []
94 | self.info_hashtables = {}
95 | self.info_get_hashtable_raise = None
96 | self.infolists = {}
97 | self.config_integer_defaults = {}
98 | self.commands = []
99 | self.hook_signals = []
100 | self.info_gets = []
101 | self.window_get_pointers = {}
102 |
103 | def save(self):
104 | self.snapshot_weechat_dir()
105 | self.saved_state = copy.deepcopy(self.__dict__)
106 |
107 | def restore(self):
108 | prev_state = copy.deepcopy(self.saved_state)
109 | self.__dict__.clear()
110 | self.__dict__.update(prev_state)
111 |
112 | self.restore_weechat_dir()
113 |
114 | def snapshot_weechat_dir(self):
115 | tar_io = io.BytesIO()
116 | # Note that we cannot use "with tarfile.open" due to lack of support
117 | # for context manager in Python 2.6
118 | tar = tarfile.open(fileobj=tar_io, mode='w')
119 | tar.add(self.weechat_dir, '.')
120 | tar.close()
121 | self.weechat_dir_tar = tar_io.getvalue()
122 |
123 | def restore_weechat_dir(self):
124 | shutil.rmtree(self.weechat_dir)
125 | tar_io = io.BytesIO(self.weechat_dir_tar)
126 | # Note that we cannot use "with tarfile.open" due to lack of support
127 | # for context manager in Python 2.6
128 | tar = tarfile.open(fileobj=tar_io)
129 | tar.extractall(self.weechat_dir)
130 | tar.close()
131 |
132 | def bar_item_new(*args):
133 | return 'bar item'
134 |
135 | def bar_item_update(*args):
136 | pass
137 |
138 | def buffer_get_string(self, buf, string):
139 | return self.buffers[buf].get(string)
140 |
141 | def buffer_search(self, plugin, name):
142 | self.buffer_search_calls.append((plugin, name))
143 | return self.buffer_search_returns.pop(0)
144 |
145 | def command(self, *args):
146 | self.commands.append(args)
147 |
148 | def config_boolean(self, val):
149 | if val == 'on':
150 | return 1
151 | return 0
152 |
153 | def config_get(self, key):
154 | return self.config_options.get(key, '')
155 |
156 | def config_new(*args):
157 | return 'config file'
158 |
159 | def config_new_option(self, config_file, section, name, *args):
160 | parts = [self.script_name]
161 | if section is not None:
162 | parts.append(section)
163 | parts.append(name)
164 | default = args[5]
165 | full_option_name = '.'.join(parts)
166 |
167 | self.config_options[full_option_name] = default
168 |
169 | def config_new_section(self, config_file, name, *args):
170 | return name
171 |
172 | def config_read(*args):
173 | pass
174 |
175 | def config_string(self, key):
176 | return key
177 |
178 | def current_buffer(*args):
179 | pass
180 |
181 | def hook_command(*args):
182 | pass
183 |
184 | def hook_completion(*args):
185 | pass
186 |
187 | def hook_config(*args):
188 | pass
189 |
190 | def hook_modifier(*args):
191 | pass
192 |
193 | def hook_signal(self, *args):
194 | self.hook_signals.append(args)
195 |
196 | def info_get(self, name, *args):
197 | self.info_gets.append((name, args))
198 |
199 | return self.infos[args].get(name)
200 |
201 | def info_get_hashtable(self, name, args):
202 | # pylint: disable=too-many-branches
203 | # pylint: disable=too-many-statements
204 | # pylint: disable=too-many-nested-blocks
205 | if self.info_get_hashtable_raise:
206 | # pylint: disable=raising-bad-type
207 | raise self.info_get_hashtable_raise
208 | if name in self.info_hashtables:
209 | return self.info_hashtables[name].pop()
210 | if name == 'irc_message_parse':
211 | # Python translation of WeeChat's message parsing code.
212 | #
213 | # https://github.com/weechat/weechat/blob/bd06f0f60f8c3f5ab883df9c
214 | # b876fe29715055b3/src/plugins/irc/irc-message.c#L43-210
215 | result = {
216 | 'arguments' : '',
217 | 'channel' : '',
218 | 'host' : '',
219 | 'nick' : ''
220 | }
221 |
222 | nick_set = False
223 |
224 | ptr_message = args['message']
225 |
226 | if ptr_message[0] == ':':
227 | pos3 = ptr_message.find('@')
228 | pos2 = ptr_message.find('!')
229 | pos = ptr_message.find(' ')
230 | if pos2 == -1 or (pos != -1 and pos2 > pos):
231 | pos2 = pos3
232 | if pos2 != -1 and (pos == -1 or pos > pos2):
233 | result['nick'] = ptr_message[1:pos2]
234 | nick_set = True
235 | elif pos != -1:
236 | result['nick'] = ptr_message[1:pos]
237 | nick_set = True
238 | if pos != -1:
239 | result['host'] = ptr_message[1:pos]
240 | ptr_message = ptr_message[pos:].lstrip()
241 | else:
242 | result['host'] = ptr_message[1:]
243 | ptr_message = ''
244 |
245 | if ptr_message:
246 | pos = ptr_message.find(' ')
247 | if pos != -1:
248 | result['command'] = ptr_message[:pos]
249 | pos += 1
250 | while ptr_message[pos] == ' ':
251 | pos += 1
252 | result['arguments'] = ptr_message[pos:]
253 | if ptr_message[pos] != ':':
254 | if ptr_message[pos] in ('#', '&', '+', '!'):
255 | pos2 = ptr_message[pos:].find(' ')
256 | if pos2 != -1:
257 | result['channel'] = ptr_message[pos:][:pos2]
258 | else:
259 | result['channel'] = ptr_message[pos:]
260 | else:
261 | pos2 = ptr_message[pos:].find(' ')
262 | if not nick_set:
263 | if pos2 != -1:
264 | result['nick'] = ptr_message[pos:][:pos2]
265 | else:
266 | result['nick'] = ptr_message[pos:]
267 | if pos2 != -1:
268 | pos3 = pos2
269 | pos2 += 1
270 | while ptr_message[pos2] == ' ':
271 | pos2 += 1
272 | if ptr_message[pos2] in ('#', '&', '+', '!'):
273 | pos4 = ptr_message[pos2:].find(' ')
274 | if pos4 != -1:
275 | result['channel'] = \
276 | ptr_message[pos2:][:pos4]
277 | else:
278 | result['channel'] = ptr_message[pos2:]
279 | elif not result['channel']:
280 | result['channel'] = ptr_message[pos:][:pos3]
281 | else:
282 | result['command'] = ptr_message
283 |
284 | return result
285 |
286 | def infolist_free(*args):
287 | pass
288 |
289 | def infolist_get(self, *args):
290 | return collections.deque(self.infolists.get(args, []) + [False])
291 |
292 | def infolist_next(self, infolist):
293 | # The current item in the infolist is the last item.
294 | infolist.rotate(-1)
295 | return infolist[-1]
296 |
297 | def infolist_integer(self, infolist, key):
298 | return infolist[-1]['integer'][key]
299 |
300 | def infolist_pointer(self, infolist, key):
301 | return infolist[-1]['pointer'][key]
302 |
303 | def mkdir_home(self, name, mode):
304 | os.mkdir(os.path.join(self.weechat_dir, name), mode)
305 |
306 | def string_eval_expression(self, expr, pointers, extra_vars, options):
307 | return 'eval({0})'.format(expr)
308 |
309 | def config_color(self, key):
310 | return key
311 |
312 | def color(self, name):
313 | return '(color {0})'.format(name)
314 |
315 | def prnt(self, buf, message):
316 | self.printed.setdefault(buf, []).append(message)
317 |
318 | def register(self, script_name, *args):
319 | self.script_name = script_name
320 |
321 | return True
322 |
323 | def set_server_current_nick(self, server, nick):
324 | self.infos[(server, )]['irc_nick'] = nick
325 |
326 | def config_integer_default(self, key):
327 | return self.config_integer_defaults.get(key)
328 |
329 | def config_write(self, *args):
330 | self.config_written.append(args)
331 |
332 | def config_section_free_options(self, *args):
333 | self.config_section_free_options_calls.append(args)
334 |
335 | def config_section_free(self, *args):
336 | self.config_section_free_calls.append(args)
337 |
338 | def config_free(self, *args):
339 | self.config_free_calls.append(args)
340 |
341 | def bar_item_remove(self, *args):
342 | self.bar_items_removed.append(args)
343 |
344 | def buffer_new(self, *args):
345 | self.buffer_new_calls.append(args)
346 |
347 | return args[0]
348 |
349 | def buffer_set(self, name, key, value):
350 | self.buffer_sets.setdefault(name, {}).update({key: value})
351 |
352 | def window_get_pointer(self, *args):
353 | return self.window_get_pointers[args]
354 |
355 | def add_channel_prefix(self, prefix):
356 | self.infos[('server,CHANTYPES',)]['irc_server_isupport_value'] += \
357 | prefix
358 |
359 | def server_no_chantypes(self):
360 | self.infos[('server,CHANTYPES',)]['irc_server_isupport_value'] = ''
361 | self.infos[('server,STATUSMSG',)]['irc_server_isupport_value'] = ''
362 |
--------------------------------------------------------------------------------
/weechat_otr_test/mock_window.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=missing-docstring
3 | # pylint: disable=too-few-public-methods
4 |
5 | class MockWindow(object):
6 |
7 | pass
8 |
--------------------------------------------------------------------------------
/weechat_otr_test/raising_account.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 |
5 | import weechat_otr_test.raising_context
6 |
7 | import weechat_otr
8 |
9 | class RaisingAccount(weechat_otr.IrcOtrAccount):
10 |
11 | contextclass = weechat_otr_test.raising_context.RaisingContext
12 |
--------------------------------------------------------------------------------
/weechat_otr_test/raising_context.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=abstract-method
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | import potr
7 |
8 | import weechat_otr
9 |
10 | class RaisingContext(weechat_otr.IrcContext):
11 |
12 | def __init__(self, account, peername):
13 | super(RaisingContext, self).__init__(account, peername)
14 | self.unencrypted = []
15 |
16 | def receiveMessage(self, messageData, appdata=None):
17 | if self.unencrypted:
18 | raise potr.context.UnencryptedMessage(*self.unencrypted)
19 | super(RaisingContext, self).receiveMessage(messageData, appdata)
20 |
--------------------------------------------------------------------------------
/weechat_otr_test/recording_account.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=abstract-method
3 | # pylint: disable=invalid-name
4 | # pylint: disable=missing-docstring
5 |
6 | import weechat_otr_test.recording_context
7 |
8 | import weechat_otr
9 |
10 | class RecordingAccount(weechat_otr.IrcOtrAccount):
11 | contextclass = weechat_otr_test.recording_context.RecordingContext
12 |
--------------------------------------------------------------------------------
/weechat_otr_test/recording_context.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=abstract-method
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | import weechat_otr
7 |
8 | class RecordingContext(weechat_otr.IrcContext):
9 |
10 | def __init__(self, account, peername):
11 | super(RecordingContext, self).__init__(account, peername)
12 |
13 | self.injected = []
14 |
15 | def inject(self, msg, appdata=None):
16 | self.injected.insert(0, msg)
17 |
18 | super(RecordingContext, self).inject(msg, appdata)
19 |
--------------------------------------------------------------------------------
/weechat_otr_test/session_test_case.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=missing-docstring
3 | # pylint: disable=too-many-public-methods
4 |
5 | from __future__ import unicode_literals
6 |
7 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
8 |
9 | import weechat_otr
10 |
11 | class SessionTestCase(WeechatOtrTestCase):
12 |
13 | def send_all(self, from_nick, to_nick, messages):
14 | # pylint: disable=no-self-use
15 | while messages:
16 | weechat_otr.message_in_cb(
17 | None, None, 'server',
18 | ':{from_nick}!user@host PRIVMSG {to_nick} :{message}'.format(
19 | from_nick=from_nick,
20 | to_nick=to_nick,
21 | message=messages.pop().decode('utf-8', 'replace')))
22 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_arg_parsing.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-few-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | import sys
9 |
10 | import potr
11 |
12 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
13 |
14 | import weechat_otr
15 |
16 | def create_arg_test_method(command, num_args):
17 | def f(_):
18 | sys.modules['weechat'].infos.update({
19 | ('arg2',) : {'irc_nick' : ''},
20 | ('arg2,arg1',) : {'irc_buffer' : 'arg2_arg1_buffer'},
21 | })
22 | sys.modules['weechat'].buffers.update({
23 | 'arg2_arg1_buffer' : {
24 | 'localvar_type' : 'private',
25 | 'name' : 'arg2_arg1_buffer_name',
26 | 'plugin' : 'irc',
27 | },
28 | })
29 |
30 | args = ["arg{i}".format(i=i) for i in range(1, num_args + 1)]
31 | arg_str = ' '.join(args)
32 | try:
33 | weechat_otr.command_cb(
34 | None, None,
35 | "{command} {args}".format(command=command, args=arg_str))
36 | except potr.context.NotEncryptedError:
37 | pass
38 |
39 | return f
40 |
41 | def add_arg_test_methods():
42 | commands = [
43 | 'distrust',
44 | 'end',
45 | 'fingerprint',
46 | 'fingerprinti all',
47 | 'finish',
48 | 'log start',
49 | 'log stop',
50 | 'log',
51 | 'policy allow_v2',
52 | 'policy default allow_v2',
53 | 'policy default',
54 | 'policy',
55 | 'refresh',
56 | 'smp abort',
57 | 'smp ask',
58 | 'smp respond',
59 | 'smp',
60 | 'start',
61 | 'status',
62 | 'trust',
63 | ]
64 |
65 | for command, num_args in [(c, i) for c in commands for i in range(8)]:
66 | method = create_arg_test_method(command, num_args)
67 | command_method = command.replace(' ', '_')
68 | setattr(ArgParsingTestCase, 'test_{command}_{num_args}_args'.format(
69 | command=command_method, num_args=num_args), method)
70 |
71 | class ArgParsingTestCase(WeechatOtrTestCase):
72 | """Fuzz test all commands with 0 - 7 arguments.
73 |
74 | Find inputs that don't parse correctly and cause tracebacks.
75 | """
76 | pass
77 |
78 | add_arg_test_methods()
79 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_assembler.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=missing-docstring
3 | # pylint: disable=too-many-public-methods
4 |
5 | from __future__ import unicode_literals
6 |
7 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
8 |
9 | import weechat_otr
10 |
11 | class AssemblerTestCase(WeechatOtrTestCase):
12 |
13 | def after_setup(self):
14 | # pylint: disable=attribute-defined-outside-init
15 | self.assembler = weechat_otr.Assembler()
16 |
17 | def test_is_query_start(self):
18 | self.assembler.add('?OTRv2? encryption?')
19 |
20 | self.assertTrue(self.assembler.is_query())
21 |
22 | def test_is_query_start_non_ascii(self):
23 | self.assembler.add('?OTRv2? verschlüsselung?')
24 |
25 | self.assertTrue(self.assembler.is_query())
26 |
27 | def test_is_query_middle(self):
28 | self.assembler.add('ATT: ?OTRv2?someone requested encryption!')
29 |
30 | self.assertTrue(self.assembler.is_query())
31 |
32 | def test_is_query_middle_non_ascii(self):
33 | self.assembler.add('ATT: ?OTRv2?someone requested verschlüsselung!')
34 |
35 | self.assertTrue(self.assembler.is_query())
36 |
37 | def test_is_query_end(self):
38 | self.assembler.add('encryption? ?OTRv2?')
39 |
40 | self.assertTrue(self.assembler.is_query())
41 |
42 | def test_is_query_end_non_ascii(self):
43 | self.assembler.add('verschlüsselung? ?OTRv2?')
44 |
45 | self.assertTrue(self.assembler.is_query())
46 |
47 | def test_add_get(self):
48 | self.assembler.add('part 1')
49 | self.assembler.add('part 2')
50 | self.assertEqual(self.assembler.get(), 'part 1part 2')
51 | self.assertEqual(self.assembler.get(), '')
52 |
53 | def test_add_get_non_ascii(self):
54 | self.assembler.add('stück 1')
55 | self.assembler.add('stück 2')
56 | self.assertEqual(self.assembler.get(), 'stück 1stück 2')
57 | self.assertEqual(self.assembler.get(), '')
58 |
59 | def test_is_done_non_otr(self):
60 | self.assembler.add('part 1')
61 | self.assertTrue(self.assembler.is_done())
62 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_buffer_closing.py:
--------------------------------------------------------------------------------
1 |
2 | # -*- coding: utf-8 -*-
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 | # pylint: disable=invalid-name
6 |
7 | from __future__ import unicode_literals
8 |
9 | import sys
10 |
11 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
12 | import weechat_otr_test.mock_account
13 | import weechat_otr_test.mock_context
14 |
15 | import weechat_otr
16 |
17 | class BufferClosedTestCase(WeechatOtrTestCase):
18 |
19 | def test_callback_registered(self):
20 | self.assertIn(
21 | ('buffer_closing', 'buffer_closing_cb', ''),
22 | sys.modules['weechat'].hook_signals)
23 |
24 | def test_session_ended(self):
25 | context = weechat_otr_test.mock_context.MockContext()
26 | account = weechat_otr_test.mock_account.MockAccount()
27 | account.add_context('nick2@server', context)
28 | weechat_otr.ACCOUNTS['nick@server'] = account
29 |
30 | weechat_otr.buffer_closing_cb(None, None, 'server_nick2_buffer')
31 |
32 | self.assertEqual(context.disconnects, 1)
33 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_command_finish.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
9 | import weechat_otr_test.mock_account
10 | import weechat_otr_test.mock_context
11 |
12 | import weechat_otr
13 |
14 | class CommandFinishTestCase(WeechatOtrTestCase):
15 |
16 | def after_setup(self):
17 | self.context = weechat_otr_test.mock_context.MockContext()
18 | account = weechat_otr_test.mock_account.MockAccount()
19 |
20 | account.add_context('nick2@server', self.context)
21 | weechat_otr.ACCOUNTS['nick@server'] = account
22 |
23 | def test_finish_buffer(self):
24 | weechat_otr.command_cb(None, 'server_nick2_buffer', 'finish')
25 |
26 | self.assertEqual(self.context.disconnects, 1)
27 |
28 | def test_finish_args(self):
29 | weechat_otr.command_cb(None, None, 'finish nick2 server')
30 |
31 | self.assertEqual(self.context.disconnects, 1)
32 |
33 | # /otr end is an alias for /otr finish
34 |
35 | def test_end_buffer(self):
36 | weechat_otr.command_cb(None, 'server_nick2_buffer', 'end')
37 |
38 | self.assertEqual(self.context.disconnects, 1)
39 |
40 | def test_end_args(self):
41 | weechat_otr.command_cb(None, None, 'end nick2 server')
42 |
43 | self.assertEqual(self.context.disconnects, 1)
44 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_command_start.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | import sys
9 |
10 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
11 |
12 | import weechat_otr
13 |
14 | class CommandStartTestCase(WeechatOtrTestCase):
15 |
16 | def test_start_buffer(self):
17 | weechat_otr.command_cb(None, 'server_nick2_buffer', 'start')
18 |
19 | self.assertIn(
20 | (u'', '/quote -server server PRIVMSG nick2 :?OTR?'),
21 | sys.modules['weechat'].commands)
22 |
23 | def test_start_args(self):
24 | weechat_otr.command_cb(None, None, 'start nick2 server')
25 |
26 | self.assertIn(
27 | (u'', '/quote -server server PRIVMSG nick2 :?OTR?'),
28 | sys.modules['weechat'].commands)
29 |
30 | # /otr refresh is an alias for /otr start
31 |
32 | def test_refresh_buffer(self):
33 | weechat_otr.command_cb(None, 'server_nick2_buffer', 'refresh')
34 |
35 | self.assertIn(
36 | (u'', '/quote -server server PRIVMSG nick2 :?OTR?'),
37 | sys.modules['weechat'].commands)
38 |
39 | def test_refresh_args(self):
40 | weechat_otr.command_cb(None, None, 'refresh nick2 server')
41 |
42 | self.assertIn(
43 | (u'', '/quote -server server PRIVMSG nick2 :?OTR?'),
44 | sys.modules['weechat'].commands)
45 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_context.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=missing-docstring
3 | # pylint: disable=too-many-public-methods
4 |
5 | from __future__ import unicode_literals
6 |
7 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
8 |
9 | import weechat_otr_test.mock_account
10 |
11 | import weechat_otr
12 |
13 | class ContextTestCase(WeechatOtrTestCase):
14 |
15 | def test_print_buffer(self):
16 | account = weechat_otr_test.mock_account.MockAccount()
17 | context = weechat_otr.IrcContext(account, 'nick@server')
18 | context.print_buffer('a message from the script')
19 |
20 | self.assertPrinted(
21 | 'server_nick_buffer',
22 | 'eval(${color:default}:! ${color:brown}otr${color:default} !:)\t'
23 | '(color default)a message from the script')
24 |
25 | def test_print_buffer_non_ascii(self):
26 | account = weechat_otr_test.mock_account.MockAccount()
27 | context = weechat_otr.IrcContext(account, 'nick@server')
28 | context.print_buffer('gefährte')
29 |
30 | self.assertPrinted(
31 | 'server_nick_buffer',
32 | weechat_otr.PYVER.to_str(
33 | 'eval(${color:default}:! ${color:brown}otr${color:default} '
34 | '!:)\t(color default)gefährte'))
35 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_context_get_logger_option_name.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=missing-docstring
3 | # pylint: disable=too-many-public-methods
4 |
5 | from __future__ import unicode_literals
6 |
7 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
8 |
9 | import weechat_otr_test.mock_account
10 |
11 | import weechat_otr
12 |
13 | class ContextGetLoggerOptionNameTestCase(WeechatOtrTestCase):
14 |
15 | def test_get_log_level(self):
16 | account = weechat_otr_test.mock_account.MockAccount()
17 | context = weechat_otr.IrcContext(account, 'nick@server')
18 |
19 | self.assertEqual(
20 | context.get_logger_option_name(),
21 | 'logger.level.irc.server_nick_buffer_name')
22 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_context_message_in_cb.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
9 | import weechat_otr_test.raising_account
10 |
11 | import weechat_otr
12 |
13 | class ContextMessageInCbTestCase(WeechatOtrTestCase):
14 |
15 | def test_action_encrypted(self):
16 | account = \
17 | weechat_otr_test.raising_account.RaisingAccount('nick@server')
18 | weechat_otr.ACCOUNTS['nick@server'] = account
19 | context = account.getContext('friend@server')
20 | context.unencrypted = ['\x01ACTION lols\x01']
21 |
22 | result = weechat_otr.message_in_cb(
23 | None, None, 'server', ':friend!user@host PRIVMSG nick :test')
24 |
25 | self.assertEqual(
26 | result,
27 | ':friend!user@host PRIVMSG nick :Unencrypted message received: '
28 | '/me lols')
29 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_context_msg_convert_in.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | import sys
9 |
10 | import potr
11 |
12 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
13 |
14 | from weechat_otr_test import mock_user
15 |
16 | import weechat_otr
17 |
18 | class ContextMessageConvertInTestCase(WeechatOtrTestCase):
19 |
20 | def after_setup(self):
21 | self.context = weechat_otr.IrcContext('account', 'nick@server')
22 | self.context.user = mock_user.MockUser('user')
23 |
24 | def test_unicode(self):
25 | result = self.context.msg_convert_in(weechat_otr.PYVER.to_str('hi'))
26 | self.assertEqual(result, 'hi')
27 |
28 | def test_html_filter_on_encrypted(self):
29 | sys.modules['weechat'].config_options[
30 | 'otr.policy.server.user.nick.html_filter'] = 'on'
31 | self.context.state = potr.context.STATE_ENCRYPTED
32 |
33 | result = self.context.msg_convert_in(
34 | weechat_otr.PYVER.to_str('lol
<>'))
35 | self.assertEqual(result, 'lol\n<>')
36 |
37 | def test_html_filter_on_unencrypted(self):
38 | sys.modules['weechat'].config_options[
39 | 'otr.policy.server.user.nick.html_filter'] = 'on'
40 | self.context.state = potr.context.STATE_PLAINTEXT
41 |
42 | result = self.context.msg_convert_in(
43 | weechat_otr.PYVER.to_str('lol
<>'))
44 | self.assertEqual(result, 'lol
<>')
45 |
46 | def test_html_filter_off_encrypted(self):
47 | sys.modules['weechat'].config_options[
48 | 'otr.policy.server.user.nick.html_filter'] = 'off'
49 | self.context.state = potr.context.STATE_ENCRYPTED
50 |
51 | result = self.context.msg_convert_in(
52 | weechat_otr.PYVER.to_str('lol
<>'))
53 | self.assertEqual(result, 'lol
<>')
54 |
55 | def test_html_filter_off_unencrypted(self):
56 | sys.modules['weechat'].config_options[
57 | 'otr.policy.server.user.nick.html_filter'] = 'off'
58 | self.context.state = potr.context.STATE_PLAINTEXT
59 |
60 | result = self.context.msg_convert_in(
61 | weechat_otr.PYVER.to_str('lol
<>'))
62 | self.assertEqual(result, 'lol
<>')
63 |
64 | def test_action_encrypted(self):
65 | self.context.state = potr.context.STATE_ENCRYPTED
66 |
67 | result = self.context.msg_convert_in(
68 | weechat_otr.PYVER.to_str('/me lols'))
69 | self.assertEqual(result, '\x01ACTION lols\x01')
70 |
71 | def test_action_unencrypted(self):
72 | self.context.state = potr.context.STATE_PLAINTEXT
73 |
74 | result = self.context.msg_convert_in(
75 | weechat_otr.PYVER.to_str('/me lols'))
76 | self.assertEqual(result, '/me lols')
77 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_context_msg_convert_out.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | import sys
9 |
10 | import potr
11 |
12 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
13 |
14 | from weechat_otr_test import mock_user
15 |
16 | import weechat_otr
17 |
18 | class ContextMessageConvertOutTestCase(WeechatOtrTestCase):
19 |
20 | def after_setup(self):
21 | self.context = weechat_otr.IrcContext('account', 'nick@server')
22 | self.context.user = mock_user.MockUser('user')
23 |
24 | def test_action_encrypted(self):
25 | self.context.state = potr.context.STATE_ENCRYPTED
26 |
27 | result = self.context.msg_convert_out('\x01ACTION lols\x01')
28 | self.assertEqual(result, b'/me lols')
29 |
30 | def test_html_escape_on_encrypted(self):
31 | sys.modules['weechat'].config_options[
32 | 'otr.policy.server.user.nick.html_escape'] = 'on'
33 | self.context.state = potr.context.STATE_ENCRYPTED
34 |
35 | result = self.context.msg_convert_out(
36 | weechat_otr.PYVER.to_str('< > &'))
37 | self.assertEqual(result, b'< > &')
38 |
39 | def test_html_escape_on_unencrypted(self):
40 | sys.modules['weechat'].config_options[
41 | 'otr.policy.server.user.nick.html_escape'] = 'on'
42 | self.context.state = potr.context.STATE_PLAINTEXT
43 |
44 | result = self.context.msg_convert_out(
45 | weechat_otr.PYVER.to_str('< > &'))
46 | self.assertEqual(result, b'< > &')
47 |
48 | def test_html_escape_off_encrypted(self):
49 | sys.modules['weechat'].config_options[
50 | 'otr.policy.server.user.nick.html_escape'] = 'off'
51 | self.context.state = potr.context.STATE_ENCRYPTED
52 |
53 | result = self.context.msg_convert_out(
54 | weechat_otr.PYVER.to_str('< > &'))
55 | self.assertEqual(result, b'< > &')
56 |
57 | def test_html_escape_off_unencrypted(self):
58 | sys.modules['weechat'].config_options[
59 | 'otr.policy.server.user.nick.html_escape'] = 'off'
60 | self.context.state = potr.context.STATE_PLAINTEXT
61 |
62 | result = self.context.msg_convert_out(
63 | weechat_otr.PYVER.to_str('< > &'))
64 | self.assertEqual(result, b'< > &')
65 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_debug_buffer.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=missing-docstring
3 | # pylint: disable=too-many-public-methods
4 | # pylint: disable=invalid-name
5 |
6 | from __future__ import unicode_literals
7 |
8 | import sys
9 |
10 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
11 |
12 | import weechat_otr
13 |
14 | class ShutdownTestCase(WeechatOtrTestCase):
15 |
16 | def test_debug_option_off(self):
17 | sys.modules['weechat'].config_options['otr.general.debug'] = 'off'
18 | weechat_otr.debug('test')
19 |
20 | self.assertNotPrinted('OTR Debug', 'otr debug\ttest')
21 |
22 | def test_debug_buffer_on(self):
23 | sys.modules['weechat'].config_options['otr.general.debug'] = 'on'
24 | sys.modules['weechat'].buffer_search_returns = ['OTR Debug']
25 | weechat_otr.debug('test')
26 |
27 | self.assertPrinted('OTR Debug', 'otr debug\ttest')
28 |
29 | def test_debug_buffer_non_ascii(self):
30 | sys.modules['weechat'].config_options['otr.general.debug'] = 'on'
31 | sys.modules['weechat'].buffer_search_returns = ['OTR Debug']
32 | weechat_otr.debug('gefährte')
33 |
34 | self.assertPrinted(
35 | 'OTR Debug', weechat_otr.PYVER.to_str('otr debug\tgefährte'))
36 |
37 | def test_creates_buffer(self):
38 | sys.modules['weechat'].config_options['otr.general.debug'] = 'on'
39 | sys.modules['weechat'].buffer_search_returns = [None]
40 | weechat_otr.debug('test')
41 |
42 | self.assertEqual(sys.modules['weechat'].buffer_new_calls, [
43 | ('OTR Debug', '', '', '', '')])
44 | self.assertEqual(sys.modules['weechat'].buffer_sets, {
45 | 'OTR Debug':{
46 | 'title' : 'OTR Debug',
47 | 'localvar_set_no_log': '1',
48 | }})
49 |
50 | def test_caches_buffer(self):
51 | sys.modules['weechat'].config_options['otr.general.debug'] = 'on'
52 | sys.modules['weechat'].buffer_search_returns = [None, 'OTR Debug']
53 | weechat_otr.debug('test1')
54 | weechat_otr.debug('test2')
55 |
56 | self.assertEqual(1, len(sys.modules['weechat'].buffer_new_calls))
57 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_excepthook.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | import io
9 | import platform
10 | import sys
11 |
12 | import potr
13 |
14 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
15 |
16 | import weechat_otr
17 |
18 | class TestExcepthook(WeechatOtrTestCase):
19 |
20 | def test_excepthook(self):
21 | sys.modules['weechat'].infos[('',)]['version'] = '9.8.7'
22 |
23 | new_stderr = io.StringIO()
24 | orig_stderr = sys.stderr
25 | sys.stderr = new_stderr
26 | weechat_otr.excepthook(Exception, 'error', [])
27 | sys.stderr = orig_stderr
28 |
29 | version_str = (
30 | 'Versions: weechat-otr {script_version}, '
31 | 'potr {potr_major}.{potr_minor}.{potr_patch}-{potr_sub}, '
32 | 'Python {python_version}, '
33 | 'WeeChat 9.8.7\n'
34 | ).format(
35 | script_version=weechat_otr.SCRIPT_VERSION,
36 | potr_major=potr.VERSION[0],
37 | potr_minor=potr.VERSION[1],
38 | potr_patch=potr.VERSION[2],
39 | potr_sub=potr.VERSION[3],
40 | python_version=platform.python_version())
41 |
42 | self.assertIn(version_str, new_stderr.getvalue())
43 |
44 | def test_excepthook_hooked(self):
45 | self.assertEqual(sys.excepthook, weechat_otr.excepthook)
46 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_fingerprint.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | import os.path
9 | import sys
10 |
11 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
12 |
13 | import weechat_otr
14 |
15 | class FingerprintTestCase(WeechatOtrTestCase):
16 |
17 | def test_fingerprint(self):
18 | account1 = weechat_otr.ACCOUNTS['nick@server']
19 | account1.getPrivkey()
20 |
21 | account2 = weechat_otr.ACCOUNTS['nick2@server2']
22 | account2.getPrivkey()
23 |
24 | weechat_otr.command_cb(None, None, 'fingerprint')
25 |
26 | self.assertPrinted('', (
27 | 'eval(${{color:default}}:! ${{color:brown}}otr${{color:default}}'
28 | ' !:)\t'
29 | '(color default)nick2@server2 |{fp2}\r\n'
30 | '(color default)nick@server |{fp1}').format(
31 | fp1=account1.getPrivkey(),
32 | fp2=account2.getPrivkey()))
33 |
34 | def test_fingerprint_pattern(self):
35 | fpr_path1 = os.path.join(
36 | sys.modules['weechat'].weechat_dir,
37 | 'otr',
38 | 'nick@server.fpr')
39 | with open(fpr_path1, 'w') as f:
40 | for fields in [
41 | ['notamachxxx@server', 'nick@server', 'irc', 'fp123', ''],
42 | ['matchxxxxxx@server', 'nick@server', 'irc', 'fp123', ''],
43 | ['beforematch@server', 'nick@server', 'irc', 'fp123', ''],
44 | ['before@servermatch', 'nick@server', 'irc', 'fp123', ''],
45 | ]:
46 | f.write("\t".join(fields))
47 | f.write("\n")
48 |
49 | account1 = weechat_otr.ACCOUNTS['nick@server']
50 | account1.getPrivkey()
51 |
52 | weechat_otr.command_cb(None, None, 'fingerprint match')
53 |
54 | self.assertNoPrintedContains('', 'notamachxxx@server')
55 |
56 | self.assertPrinted('', (
57 | 'eval(${color:default}:! ${color:brown}otr${color:default}'
58 | ' !:)\t'
59 | '(color default)before@servermatch |nick@server |F P 1 2 3 |'
60 | 'unverified\r\n'
61 | '(color default)beforematch@server |nick@server |F P 1 2 3 |'
62 | 'unverified\r\n'
63 | '(color default)matchxxxxxx@server |nick@server |F P 1 2 3 |'
64 | 'unverified'))
65 |
66 | def test_fingerprint_all(self):
67 | fpr_path1 = os.path.join(
68 | sys.modules['weechat'].weechat_dir,
69 | 'otr',
70 | 'nick@server.fpr')
71 | with open(fpr_path1, 'w') as f:
72 | for fields in [
73 | ['peer1@server', 'nick@server', 'irc', 'fp111', ''],
74 | ['peer2@server', 'nick@server', 'irc', 'fp222', 'smp'],
75 | ['peer3@server', 'nick@server', 'irc', 'fp333', 'verified'],
76 | ]:
77 | f.write("\t".join(fields))
78 | f.write("\n")
79 |
80 | account1 = weechat_otr.ACCOUNTS['nick@server']
81 | account1.getPrivkey()
82 |
83 | fpr_path2 = os.path.join(
84 | sys.modules['weechat'].weechat_dir,
85 | 'otr',
86 | 'nick2@server2.fpr')
87 | with open(fpr_path2, 'w') as f:
88 | for fields in [
89 | ['peer4@server2', 'nick2@server2', 'irc', 'fp444',
90 | 'verified'],
91 | ]:
92 | f.write("\t".join(fields))
93 | f.write("\n")
94 |
95 | account2 = weechat_otr.ACCOUNTS['nick2@server2']
96 | account2.getPrivkey()
97 |
98 | weechat_otr.command_cb(None, None, 'fingerprint all')
99 |
100 | self.assertPrinted('', (
101 | 'eval(${color:default}:! ${color:brown}otr${color:default}'
102 | ' !:)\t'
103 | '(color default)peer4@server2 |nick2@server2 |F P 4 4 4 |'
104 | 'verified \r\n'
105 | '(color default)peer1@server |nick@server |F P 1 1 1 |'
106 | 'unverified \r\n'
107 | '(color default)peer2@server |nick@server |F P 2 2 2 |'
108 | 'SMP verified\r\n'
109 | '(color default)peer3@server |nick@server |F P 3 3 3 |'
110 | 'verified '))
111 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_git_info.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | import os
9 | import shutil
10 | import subprocess
11 | import tempfile
12 |
13 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
14 |
15 | import weechat_otr
16 |
17 | class TestGitInfo(WeechatOtrTestCase):
18 |
19 | def after_setup(self):
20 | self.temp_dir = tempfile.mkdtemp()
21 | self.test_file = os.path.join(self.temp_dir, 'test')
22 | open(self.test_file, 'w').close()
23 |
24 | self.prev_file = weechat_otr.__file__
25 | weechat_otr.__file__ = self.test_file
26 |
27 | def after_teardown(self):
28 | shutil.rmtree(self.temp_dir)
29 | weechat_otr.__file__ = self.prev_file
30 |
31 | def test_git_info(self):
32 | try:
33 | process = subprocess.Popen(['git', 'init', '--quiet', self.temp_dir])
34 | except OSError:
35 | self.skipTest('install git to run this test')
36 | process.communicate()
37 |
38 | git_dir = os.path.join(self.temp_dir, '.git')
39 | process = subprocess.Popen([
40 | 'git',
41 | '--git-dir', git_dir,
42 | '--work-tree', self.temp_dir,
43 | 'add', '.'])
44 | process.communicate()
45 |
46 | process = subprocess.Popen([
47 | 'git',
48 | '--git-dir', git_dir,
49 | '--work-tree', self.temp_dir,
50 | 'config',
51 | 'user.email', 'testy@test.com'])
52 | process.communicate()
53 |
54 | process = subprocess.Popen([
55 | 'git',
56 | '--git-dir', git_dir,
57 | '--work-tree', self.temp_dir,
58 | 'config',
59 | 'user.name', 'test'])
60 | process.communicate()
61 |
62 | process = subprocess.Popen([
63 | 'git',
64 | '--git-dir', git_dir,
65 | '--work-tree', self.temp_dir,
66 | 'commit',
67 | '--quiet',
68 | '-m',
69 | 'test'])
70 | process.communicate()
71 |
72 | self.assertRegex(weechat_otr.git_info(), '[0-9a-f]+')
73 |
74 | def test_git_info_failure(self):
75 | self.assertEqual(weechat_otr.git_info(), None)
76 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_html_escape_policy.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | import sys
9 |
10 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
11 |
12 | import weechat_otr_test.is_encrypted_account
13 | import weechat_otr
14 |
15 | class HtmlEscapePolicyTestCase(WeechatOtrTestCase):
16 |
17 | def after_setup(self):
18 | self.account = \
19 | weechat_otr_test.is_encrypted_account.IsEncryptedAccount(
20 | 'nick@server')
21 | weechat_otr.ACCOUNTS['nick@server'] = self.account
22 |
23 | def test_default_html_escape_policy(self):
24 | result = weechat_otr.message_out_cb(
25 | None, None, 'server', ':nick!user@host PRIVMSG friend :< > " \' &')
26 | self.assertEqual(result, 'PRIVMSG friend :< > " \' &')
27 |
28 | def test_html_escape_policy_on_encrypted(self):
29 | sys.modules['weechat'].config_options[
30 | 'otr.policy.server.nick.friend.html_escape'] = 'on'
31 | self.account.getContext('friend@server').is_encrypted_fake = True
32 |
33 | result = weechat_otr.message_out_cb(
34 | None, None, 'server', ':nick!user@host PRIVMSG friend :< > " \' &')
35 | self.assertEqual(result, 'PRIVMSG friend :< > " \' &')
36 |
37 | def test_html_escape_policy_on_unencrypted(self):
38 | sys.modules['weechat'].config_options[
39 | 'otr.policy.server.nick.friend.html_escape'] = 'on'
40 | self.account.getContext('friend@server').is_encrypted_fake = False
41 |
42 | result = weechat_otr.message_out_cb(
43 | None, None, 'server', ':nick!user@host PRIVMSG friend :< > " \' &')
44 | self.assertEqual(result, 'PRIVMSG friend :< > " \' &')
45 |
46 | def test_html_escape_policy_off_encrypted(self):
47 | sys.modules['weechat'].config_options[
48 | 'otr.policy.server.nick.friend.html_escape'] = 'off'
49 | self.account.getContext('friend@server').is_encrypted_fake = True
50 |
51 | result = weechat_otr.message_out_cb(
52 | None, None, 'server', ':nick!user@host PRIVMSG friend :< > " \' &')
53 | self.assertEqual(result, 'PRIVMSG friend :< > " \' &')
54 |
55 | def test_html_escape_policy_off_unencrypted(self):
56 | sys.modules['weechat'].config_options[
57 | 'otr.policy.server.nick.friend.html_escape'] = 'off'
58 | self.account.getContext('friend@server').is_encrypted_fake = False
59 |
60 | result = weechat_otr.message_out_cb(
61 | None, None, 'server', ':nick!user@host PRIVMSG friend :< > " \' &')
62 | self.assertEqual(result, 'PRIVMSG friend :< > " \' &')
63 |
64 | def test_html_escape_policy_non_ascii(self):
65 | sys.modules['weechat'].config_options[weechat_otr.PYVER.to_str(
66 | 'otr.policy.server.nick.gefährte.html_escape')] = 'on'
67 | self.account.getContext('gefährte@server').is_encrypted_fake = True
68 |
69 | result = weechat_otr.message_out_cb(
70 | None, None, 'server',
71 | ':nick!user@host PRIVMSG gefährte :< > " \' &')
72 | self.assertEqual(result, weechat_otr.PYVER.to_str(
73 | 'PRIVMSG gefährte :< > " \' &'))
74 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_irc_context.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=missing-docstring
3 | # pylint: disable=too-many-public-methods
4 |
5 | from __future__ import unicode_literals
6 |
7 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
8 |
9 | import weechat_otr
10 |
11 | class IrcContextTestCase(WeechatOtrTestCase):
12 |
13 | def test_repr(self):
14 | self.assertRegex(
15 | weechat_otr.IrcContext(None, 'nick@server').__repr__(),
16 | r'')
17 |
18 | def test_repr_non_ascii(self):
19 | self.assertRegex(
20 | weechat_otr.IrcContext(None, 'gefährte@gefährteserver').__repr__(),
21 | weechat_otr.PYVER.to_str(
22 | r''))
24 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_irc_html_parser.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=missing-docstring
3 | # pylint: disable=too-many-public-methods
4 |
5 | from __future__ import unicode_literals
6 |
7 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
8 |
9 | import weechat_otr
10 |
11 | class IrcHTMLParserTestCase(WeechatOtrTestCase):
12 |
13 | def test_tag_a(self):
14 | self.check_parse_result(
15 | 'this is a link',
16 | 'this is a [link](http://weechat.org)'
17 | )
18 |
19 | def test_tag_a_non_ascii(self):
20 | self.check_parse_result(
21 | 'this is a verknüpfung',
22 | 'this is a [verknüpfung](http://weechat.org)'
23 | )
24 |
25 | def test_tag_a_same(self):
26 | self.check_parse_result(
27 | 'http://weechat.org',
28 | '[http://weechat.org]'
29 | )
30 |
31 | def test_tag_br(self):
32 | self.check_parse_result(
33 | 'foo
bar
baz',
34 | 'foo\nbar\nbaz'
35 | )
36 |
37 | def test_tag_unknown(self):
38 | self.check_parse_result(
39 | ' ' \
41 | 'matters',
42 | 'none of this matters'
43 | )
44 |
45 | def test_entity_named(self):
46 | self.check_parse_result(
47 | 'tom & jerry',
48 | 'tom & jerry'
49 | )
50 |
51 | def test_entity_numeric(self):
52 | self.check_parse_result(
53 | '<html>',
54 | ''
55 | )
56 |
57 | def check_parse_result(self, html, result):
58 | self.assertEqual(weechat_otr.IrcHTMLParser.parse(html), result)
59 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_irc_otr_account_load_trusts.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | import os.path
9 | import sys
10 |
11 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
12 |
13 | import weechat_otr
14 |
15 | class IrcOtrAccountLoadTrustsTestCase(WeechatOtrTestCase):
16 |
17 | def test_trust_non_ascii(self):
18 | fpr_path = os.path.join(
19 | sys.modules['weechat'].weechat_dir,
20 | 'otr',
21 | 'nick@server.fpr')
22 | with open(fpr_path, 'w') as f:
23 | f.write(weechat_otr.PYVER.to_str(
24 | "gefährte@server\tnick@server\tirc\tfp123\tverified\n"))
25 |
26 | account1 = weechat_otr.ACCOUNTS['nick@server']
27 | self.assertEqual(
28 | 'verified', account1.getTrust('gefährte@server', 'fp123'))
29 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_irc_user.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=missing-docstring
3 |
4 | from __future__ import unicode_literals
5 |
6 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
7 |
8 | import weechat_otr
9 |
10 | class IrcUserTestCase(WeechatOtrTestCase):
11 |
12 | def test_irc_user(self):
13 | self.assertEqual(weechat_otr.irc_user('nick', 'server'), 'nick@server')
14 |
15 | def test_irc_user_case_insensitive(self):
16 | self.assertEqual(weechat_otr.irc_user('NiCk', 'server'), 'nick@server')
17 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_is_a_channel.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | import sys
9 |
10 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
11 |
12 | import weechat_otr
13 |
14 | class IsAChannelTestCase(WeechatOtrTestCase):
15 |
16 | def test_hash(self):
17 | self.assertTrue(weechat_otr.is_a_channel('#channel', 'server'))
18 |
19 | def test_ampersand(self):
20 | sys.modules['weechat'].add_channel_prefix('&')
21 | self.assertTrue(weechat_otr.is_a_channel('&channel', 'server'))
22 |
23 | def test_plus(self):
24 | sys.modules['weechat'].add_channel_prefix('+')
25 | self.assertTrue(weechat_otr.is_a_channel('+channel', 'server'))
26 |
27 | def test_bang(self):
28 | sys.modules['weechat'].add_channel_prefix('!')
29 | self.assertTrue(weechat_otr.is_a_channel('!channel', 'server'))
30 |
31 | def test_at(self):
32 | self.assertTrue(weechat_otr.is_a_channel('@#channel', 'server'))
33 |
34 | def test_not_a_channel(self):
35 | self.assertFalse(weechat_otr.is_a_channel('nick', 'server'))
36 |
37 | def test_hash_server_doesnt_isupport(self):
38 | sys.modules['weechat'].server_no_chantypes()
39 | self.assertTrue(weechat_otr.is_a_channel('#channel', 'server'))
40 |
41 | def test_ampersand_server_doesnt_isupport(self):
42 | sys.modules['weechat'].server_no_chantypes()
43 | self.assertTrue(weechat_otr.is_a_channel('&channel', 'server'))
44 |
45 | def test_plus_server_doesnt_isupport(self):
46 | sys.modules['weechat'].server_no_chantypes()
47 | self.assertTrue(weechat_otr.is_a_channel('+channel', 'server'))
48 |
49 | def test_bang_server_doesnt_isupport(self):
50 | sys.modules['weechat'].server_no_chantypes()
51 | self.assertTrue(weechat_otr.is_a_channel('!channel', 'server'))
52 |
53 | def test_at_server_doesnt_isupport(self):
54 | sys.modules['weechat'].server_no_chantypes()
55 | self.assertTrue(weechat_otr.is_a_channel('@#channel', 'server'))
56 |
57 | def test_not_a_channel_server_doesnt_isupport(self):
58 | sys.modules['weechat'].server_no_chantypes()
59 | self.assertFalse(weechat_otr.is_a_channel('nick', 'server'))
60 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_key_generation.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | import os
9 | import sys
10 |
11 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
12 |
13 | import weechat_otr
14 |
15 | class KeyGenerationTestCase(WeechatOtrTestCase):
16 |
17 | def test_creates_key_file(self):
18 | sys.modules['weechat'].set_server_current_nick('server', 'noob')
19 |
20 | weechat_otr.message_in_cb(
21 | None, None, 'server', ':nick!user@host PRIVMSG noob :?OTRv2?')
22 |
23 | key_path = os.path.join(
24 | sys.modules['weechat'].weechat_dir,
25 | 'otr',
26 | 'noob@server.key3')
27 |
28 | self.assertGreater(os.path.getsize(key_path), 0)
29 |
30 | def test_creates_key_file_non_ascii(self):
31 | sys.modules['weechat'].set_server_current_nick('server', 'gefährte')
32 |
33 | weechat_otr.message_in_cb(
34 | None, None, 'server', ':nick!user@host PRIVMSG gefährte :?OTRv2?')
35 |
36 | key_path = os.path.join(
37 | sys.modules['weechat'].weechat_dir,
38 | 'otr',
39 | 'gefährte@server.key3')
40 |
41 | self.assertGreater(os.path.getsize(key_path), 0)
42 |
43 | def test_creates_key_file_from_default_key(self):
44 | sys.modules['weechat'].set_server_current_nick('server', 'noob')
45 |
46 | weechat_otr.message_in_cb(
47 | None, None, 'server', ':nick!user@host PRIVMSG noob :?OTRv2?')
48 |
49 | sys.modules['weechat'].config_options[
50 | 'otr.general.defaultkey'] = 'noob@server'
51 |
52 | sys.modules['weechat'].set_server_current_nick('server', 'noob2')
53 |
54 | weechat_otr.message_in_cb(
55 | None, None, 'server', ':nick!user@host PRIVMSG noob2 :?OTRv2?')
56 |
57 | noob_key_path = os.path.join(
58 | sys.modules['weechat'].weechat_dir,
59 | 'otr',
60 | 'noob@server.key3')
61 |
62 | noob2_key_path = os.path.join(
63 | sys.modules['weechat'].weechat_dir,
64 | 'otr',
65 | 'noob2@server.key3')
66 |
67 | with open(noob_key_path, 'rb') as noob_key:
68 | with open(noob2_key_path, 'rb') as noob2_key:
69 | self.assertEqual(noob_key.read(), noob2_key.read())
70 |
71 | def test_preserves_existing_keys_if_default_key(self):
72 | sys.modules['weechat'].set_server_current_nick('server', 'noob2')
73 |
74 | weechat_otr.message_in_cb(
75 | None, None, 'server', ':nick!user@host PRIVMSG noob2 :?OTRv2?')
76 |
77 | account = weechat_otr.ACCOUNTS['noob2@server']
78 | orig_noob2_priv_key = account.getPrivkey()
79 |
80 | sys.modules['weechat'].set_server_current_nick('server', 'noob')
81 |
82 | weechat_otr.message_in_cb(
83 | None, None, 'server', ':nick!user@host PRIVMSG noob :?OTRv2?')
84 |
85 | sys.modules['weechat'].config_options[
86 | 'otr.general.defaultkey'] = 'noob@server'
87 |
88 | account.privkey = None
89 |
90 | sys.modules['weechat'].set_server_current_nick('server', 'noob2')
91 |
92 | weechat_otr.message_in_cb(
93 | None, None, 'server', ':nick!user@host PRIVMSG noob2 :?OTRv2?')
94 |
95 | self.assertEqual(account.privkey, orig_noob2_priv_key)
96 |
97 | def test_reads_key_file(self):
98 | sys.modules['weechat'].set_server_current_nick('server', 'noob')
99 |
100 | weechat_otr.message_in_cb(
101 | None, None, 'server', ':nick!user@host PRIVMSG noob :?OTRv2?')
102 |
103 | account = weechat_otr.ACCOUNTS['noob@server']
104 | priv_key = account.getPrivkey()
105 | account.privkey = None
106 |
107 | self.assertEqual(priv_key, account.getPrivkey())
108 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_log_level.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | import sys
9 |
10 | from weechat_otr_test.session_test_case import SessionTestCase
11 |
12 | import weechat_otr_test.recording_account
13 | import weechat_otr
14 |
15 | class OtrSessionTestCase(SessionTestCase):
16 |
17 | def test_log_level_return_to_default(self):
18 | sys.modules['weechat'].set_server_current_nick('server', 'nick')
19 |
20 | account1 = weechat_otr_test.recording_account.RecordingAccount(
21 | 'nick@server')
22 | weechat_otr.ACCOUNTS['nick@server'] = account1
23 |
24 | account2 = weechat_otr_test.recording_account.RecordingAccount(
25 | 'nick2@server')
26 | weechat_otr.ACCOUNTS['nick2@server'] = account2
27 |
28 | context1 = account2.getContext('nick@server')
29 | context2 = account1.getContext('nick2@server')
30 |
31 | sys.modules['weechat'].infolists = {
32 | ('logger_buffer', '', '') : [
33 | {
34 | 'pointer' : {'buffer' : context1.buffer()},
35 | 'integer' : {
36 | 'log_enabled' : 1,
37 | 'log_level' : 0,
38 | },
39 | }
40 | ],
41 | }
42 |
43 | weechat_otr.message_in_cb(
44 | None, None, 'server', ':nick2!user@host PRIVMSG nick :?OTRv2?')
45 |
46 | sys.modules['weechat'].set_server_current_nick('server', 'nick2')
47 | self.send_all('nick', 'nick2', context2.injected)
48 |
49 | sys.modules['weechat'].set_server_current_nick('server', 'nick')
50 | self.send_all('nick2', 'nick', context1.injected)
51 |
52 | sys.modules['weechat'].set_server_current_nick('server', 'nick2')
53 | self.send_all('nick', 'nick2', context2.injected)
54 |
55 | sys.modules['weechat'].set_server_current_nick('server', 'nick')
56 | self.send_all('nick2', 'nick', context1.injected)
57 |
58 | context1.disconnect()
59 |
60 | self.assertIn(
61 | ('server_nick_buffer',
62 | '/mute unset logger.level.irc.server_nick_buffer_name'),
63 | sys.modules['weechat'].commands)
64 |
65 | def test_log_level_return_to_previous(self):
66 | sys.modules['weechat'].config_options[
67 | 'logger.level.irc.server_nick_buffer_name'] = 2
68 |
69 | sys.modules['weechat'].set_server_current_nick('server', 'nick')
70 |
71 | account1 = weechat_otr_test.recording_account.RecordingAccount(
72 | 'nick@server')
73 | weechat_otr.ACCOUNTS['nick@server'] = account1
74 |
75 | account2 = weechat_otr_test.recording_account.RecordingAccount(
76 | 'nick2@server')
77 | weechat_otr.ACCOUNTS['nick2@server'] = account2
78 |
79 | context1 = account2.getContext('nick@server')
80 | context2 = account1.getContext('nick2@server')
81 |
82 | sys.modules['weechat'].infolists = {
83 | ('logger_buffer', '', '') : [
84 | {
85 | 'pointer' : {'buffer' : context1.buffer()},
86 | 'integer' : {
87 | 'log_enabled' : 1,
88 | 'log_level' : 2,
89 | },
90 | }
91 | ],
92 | }
93 |
94 | weechat_otr.message_in_cb(
95 | None, None, 'server', ':nick2!user@host PRIVMSG nick :?OTRv2?')
96 |
97 | sys.modules['weechat'].set_server_current_nick('server', 'nick2')
98 | self.send_all('nick', 'nick2', context2.injected)
99 |
100 | sys.modules['weechat'].set_server_current_nick('server', 'nick')
101 | self.send_all('nick2', 'nick', context1.injected)
102 |
103 | sys.modules['weechat'].set_server_current_nick('server', 'nick2')
104 | self.send_all('nick', 'nick2', context2.injected)
105 |
106 | sys.modules['weechat'].set_server_current_nick('server', 'nick')
107 | self.send_all('nick2', 'nick', context1.injected)
108 |
109 | context1.disconnect()
110 |
111 | self.assertIn(
112 | ('server_nick_buffer', '/mute logger set 2'),
113 | sys.modules['weechat'].commands)
114 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_message_out_cb.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | import platform
9 | import sys
10 |
11 | import potr
12 |
13 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
14 |
15 | import weechat_otr
16 |
17 | class MessageOutCbTestCase(WeechatOtrTestCase):
18 |
19 | def test_message_out_cb(self):
20 | result = weechat_otr.message_out_cb(
21 | None, None, 'server', ':nick!user@host PRIVMSG friend :hello')
22 | self.assertEqual(result, 'PRIVMSG friend :hello')
23 |
24 | def test_message_out_cb_send_tag_non_ascii(self):
25 | sys.modules['weechat'].config_options[
26 | 'otr.policy.server.nick.friend.send_tag'] = 'on'
27 |
28 | result = weechat_otr.message_out_cb(
29 | None, None, 'server',
30 | ":nick!user@host PRIVMSG friend :\xc3")
31 | self.assertEqual(
32 | weechat_otr.PYVER.to_unicode(result),
33 | "PRIVMSG friend :\xc3 \t \t\t\t\t \t \t \t \t\t \t \t")
34 |
35 | def test_message_out_cb_send_tag_alis(self):
36 | self.assertNickIsNotTagged('Alis')
37 |
38 | def test_message_out_cb_send_tag_botserv(self):
39 | self.assertNickIsNotTagged('BotServ')
40 |
41 | def test_message_out_cb_send_tag_chanfix(self):
42 | self.assertNickIsNotTagged('ChanFix')
43 |
44 | def test_message_out_cb_send_tag_chanserv(self):
45 | self.assertNickIsNotTagged('ChanServ')
46 |
47 | def test_message_out_cb_send_tag_gameserv(self):
48 | self.assertNickIsNotTagged('GameServ')
49 |
50 | def test_message_out_cb_send_tag_global(self):
51 | self.assertNickIsNotTagged('Global')
52 |
53 | def test_message_out_cb_send_tag_groupserv(self):
54 | self.assertNickIsNotTagged('GroupServ')
55 |
56 | def test_message_out_cb_send_tag_helpserv(self):
57 | self.assertNickIsNotTagged('HelpServ')
58 |
59 | def test_message_out_cb_send_tag_hostserv(self):
60 | self.assertNickIsNotTagged('HostServ')
61 |
62 | def test_message_out_cb_send_tag_infoserv(self):
63 | self.assertNickIsNotTagged('InfoServ')
64 |
65 | def test_message_out_cb_send_tag_memoserv(self):
66 | self.assertNickIsNotTagged('MemoServ')
67 |
68 | def test_message_out_cb_send_tag_nickserv(self):
69 | self.assertNickIsNotTagged('NickServ')
70 |
71 | def test_message_out_cb_send_tag_operserv(self):
72 | self.assertNickIsNotTagged('OperServ')
73 |
74 | def test_message_out_cb_send_tag_rpgserv(self):
75 | self.assertNickIsNotTagged('RpgServ')
76 |
77 | def test_message_out_cb_send_tag_statserv(self):
78 | self.assertNickIsNotTagged('StatServ')
79 |
80 | def test_message_out_cb_send_tag_saslserv(self):
81 | self.assertNickIsNotTagged('SaslServ')
82 |
83 | def test_message_out_cb_send_tag_star(self):
84 | self.assertNickIsNotTagged('*man')
85 |
86 | def test_message_out_cb_send_tag_ctcp(self):
87 | sys.modules['weechat'].config_options[
88 | 'otr.policy.default.send_tag'] = 'on'
89 |
90 | result = weechat_otr.message_out_cb(
91 | None, None, 'server',
92 | ':nick!user@host PRIVMSG friend :\x01CTCP VERSION\x01')
93 | self.assertEqual(
94 | weechat_otr.PYVER.to_unicode(result),
95 | 'PRIVMSG friend :\x01CTCP VERSION\x01')
96 |
97 | def test_message_out_cb_no_send_tag_regex_non_ascii(self):
98 | sys.modules['weechat'].config_options[
99 | 'otr.general.no_send_tag_regex'] = \
100 | weechat_otr.PYVER.to_str('^gefährte$')
101 |
102 | self.assertNickIsNotTagged('gefährte')
103 |
104 | def test_message_out_cb_empty_no_send_tag_regex(self):
105 | sys.modules['weechat'].config_options[
106 | 'otr.policy.default.send_tag'] = 'on'
107 | sys.modules['weechat'].config_options[
108 | 'otr.general.no_send_tag_regex'] = weechat_otr.PYVER.to_str('')
109 |
110 | result = weechat_otr.message_out_cb(
111 | None, None, 'server',
112 | weechat_otr.PYVER.to_str(":nick!user@host PRIVMSG friend :hi"))
113 | self.assertEqual(
114 | weechat_otr.PYVER.to_unicode(result),
115 | "PRIVMSG friend :hi \t \t\t\t\t \t \t \t \t\t \t \t")
116 |
117 | def test_message_out_cb_nick_with_at(self):
118 | result = weechat_otr.message_out_cb(
119 | None, None, 'server', ':nick!user@host PRIVMSG @#chan :hello')
120 | self.assertEqual(result, ':nick!user@host PRIVMSG @#chan :hello')
121 |
122 | def test_require_encryption(self):
123 | sys.modules['weechat'].config_options.update({
124 | 'otr.policy.default.require_encryption' : 'on'
125 | })
126 |
127 | weechat_otr.message_out_cb(
128 | None, None, 'server', ':nick!user@host PRIVMSG nick2 :hello')
129 |
130 | self.assertPrinted(
131 | 'server_nick2_buffer',
132 | 'eval(${color:default}:! ${color:brown}otr${color:default} !:)\t'
133 | '(color lightred)Your message will not be sent, because policy '
134 | 'requires an encrypted connection.')
135 | self.assertPrinted(
136 | 'server_nick2_buffer',
137 | 'eval(${color:default}:! ${color:brown}otr${color:default} !:)\t'
138 | '(color lightblue)Wait for the OTR connection or change the '
139 | 'policy to allow clear-text messages:\r\n(color '
140 | 'lightblue)/otr policy require_encryption off')
141 |
142 | def test_otr_disabled_require_encryption(self):
143 | sys.modules['weechat'].config_options.update({
144 | 'otr.policy.default.allow_v2' : 'off',
145 | 'otr.policy.default.require_encryption' : 'on'
146 | })
147 |
148 | weechat_otr.message_out_cb(
149 | None, None, 'server', ':nick!user@host PRIVMSG nick2 :hello')
150 |
151 | self.assertNotPrinted(
152 | 'server_nick2_buffer',
153 | 'eval(${color:default}:! ${color:brown}otr${color:default} !:)\t'
154 | '(color lightred)Your message will not be sent, because policy '
155 | 'requires an encrypted connection.')
156 | self.assertNotPrinted(
157 | 'server_nick2_buffer',
158 | 'eval(${color:default}:! ${color:brown}otr${color:default} !:)\t'
159 | '(color lightblue)Wait for the OTR connection or change the '
160 | 'policy to allow clear-text messages:\r\n(color '
161 | 'lightblue)/policy set require_encryption off')
162 |
163 | def test_exception_raised_returns_empty_string(self):
164 | sys.modules['weechat'].info_get_hashtable_raise = Exception('test')
165 |
166 | result = weechat_otr.message_out_cb(
167 | None, None, 'server', ':nick!user@host PRIVMSG nick2 :hello')
168 | self.assertEqual(result, '')
169 |
170 | def test_exception_raised_prints_traceback(self):
171 | sys.modules['weechat'].info_get_hashtable_raise = Exception('test')
172 |
173 | weechat_otr.message_out_cb(
174 | None, None, 'server', ':nick!user@host PRIVMSG nick2 :hello')
175 | self.assertPrintedContains('', 'Exception: test')
176 |
177 | def test_exception_raised_prints_versions(self):
178 | sys.modules['weechat'].info_get_hashtable_raise = Exception('test')
179 | sys.modules['weechat'].infos[('',)]['version'] = '9.8.7'
180 |
181 | version_str = (
182 | 'Versions: weechat-otr {script_version}, '
183 | 'potr {potr_major}.{potr_minor}.{potr_patch}-{potr_sub}, '
184 | 'Python {python_version}, '
185 | 'WeeChat 9.8.7'
186 | ).format(
187 | script_version=weechat_otr.SCRIPT_VERSION,
188 | potr_major=potr.VERSION[0],
189 | potr_minor=potr.VERSION[1],
190 | potr_patch=potr.VERSION[2],
191 | potr_sub=potr.VERSION[3],
192 | python_version=platform.python_version())
193 |
194 | weechat_otr.message_out_cb(
195 | None, None, 'server', ':nick!user@host PRIVMSG nick2 :hello')
196 | self.assertPrintedContains('', version_str)
197 |
198 | def assertNickIsNotTagged(self, nick):
199 | sys.modules['weechat'].config_options[
200 | 'otr.policy.default.send_tag'] = 'on'
201 |
202 | result = weechat_otr.message_out_cb(
203 | None, None, 'server', weechat_otr.PYVER.to_str(
204 | ':nick!user@host PRIVMSG {nick} :send friend hi'.format(
205 | nick=nick)))
206 | self.assertEqual(
207 | weechat_otr.PYVER.to_unicode(result),
208 | 'PRIVMSG {nick} :send friend hi'.format(nick=nick))
209 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_otr_session.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=missing-docstring
3 | # pylint: disable=too-many-public-methods
4 |
5 | from __future__ import unicode_literals
6 |
7 | import sys
8 |
9 | from weechat_otr_test.session_test_case import SessionTestCase
10 |
11 | import weechat_otr_test.recording_account
12 | import weechat_otr
13 |
14 | class OtrSessionTestCase(SessionTestCase):
15 |
16 | def test_otr_session(self):
17 | sys.modules['weechat'].set_server_current_nick('server', 'nick')
18 |
19 | account1 = weechat_otr_test.recording_account.RecordingAccount(
20 | 'nick@server')
21 | weechat_otr.ACCOUNTS['nick@server'] = account1
22 |
23 | account2 = weechat_otr_test.recording_account.RecordingAccount(
24 | 'nick2@server')
25 | weechat_otr.ACCOUNTS['nick2@server'] = account2
26 |
27 | context1 = account2.getContext('nick@server')
28 | context2 = account1.getContext('nick2@server')
29 |
30 | weechat_otr.message_in_cb(
31 | None, None, 'server', ':nick2!user@host PRIVMSG nick :?OTRv2?')
32 |
33 | sys.modules['weechat'].set_server_current_nick('server', 'nick2')
34 | self.send_all('nick', 'nick2', context2.injected)
35 |
36 | sys.modules['weechat'].set_server_current_nick('server', 'nick')
37 | self.send_all('nick2', 'nick', context1.injected)
38 |
39 | sys.modules['weechat'].set_server_current_nick('server', 'nick2')
40 | self.send_all('nick', 'nick2', context2.injected)
41 |
42 | sys.modules['weechat'].set_server_current_nick('server', 'nick')
43 | self.send_all('nick2', 'nick', context1.injected)
44 |
45 | self.assertTrue(context1.is_encrypted())
46 | self.assertTrue(context2.is_encrypted())
47 |
48 | weechat_otr.message_out_cb(
49 | None, None, 'server', ':nick!user@host PRIVMSG nick2 :hi')
50 |
51 | sys.modules['weechat'].set_server_current_nick('server', 'nick2')
52 |
53 | result = weechat_otr.message_in_cb(
54 | None, None, 'server',
55 | ':nick!user@host PRIVMSG nick2 :%s' % context2.injected.pop())
56 |
57 | self.assertEqual(result, ':nick!user@host PRIVMSG nick2 :hi')
58 |
59 | def test_otr_session_non_ascii(self):
60 | sys.modules['weechat'].set_server_current_nick('server', 'gefährte')
61 | sys.modules['weechat'].infos.update({
62 | (weechat_otr.PYVER.to_str('server,gefährte'),) :
63 | {'irc_buffer' : 'server_gefährte_buffer'},
64 | (weechat_otr.PYVER.to_str('server,gefährte2'),) :
65 | {'irc_buffer' : 'server_gefährte2_buffer'},
66 | })
67 | sys.modules['weechat'].buffers.update({
68 | weechat_otr.PYVER.to_str('server_gefährte_buffer') : {
69 | 'localvar_type' : 'private',
70 | 'localvar_channel' : 'gefährte',
71 | 'localvar_server' : 'server',
72 | 'name' : 'server_gefährte_buffer_name',
73 | 'plugin' : 'irc',
74 | },
75 | weechat_otr.PYVER.to_str('server_gefährte2_buffer') : {
76 | 'localvar_type' : 'private',
77 | 'localvar_channel' : 'gefährte2',
78 | 'localvar_server' : 'server',
79 | 'name' : 'server_gefährte2_buffer_name',
80 | 'plugin' : 'irc',
81 | },
82 | })
83 |
84 | account1 = weechat_otr_test.recording_account.RecordingAccount(
85 | 'gefährte@server')
86 | weechat_otr.ACCOUNTS['gefährte@server'] = account1
87 |
88 | account2 = weechat_otr_test.recording_account.RecordingAccount(
89 | 'gefährte2@server')
90 | weechat_otr.ACCOUNTS['gefährte2@server'] = account2
91 |
92 | context1 = account2.getContext('gefährte@server')
93 | context2 = account1.getContext('gefährte2@server')
94 |
95 | weechat_otr.message_in_cb(
96 | None, None, 'server',
97 | ':gefährte2!user@host PRIVMSG gefährte :?OTRv2?')
98 |
99 | sys.modules['weechat'].set_server_current_nick('server', 'gefährte2')
100 | self.send_all('gefährte', 'gefährte2', context2.injected)
101 |
102 | sys.modules['weechat'].set_server_current_nick('server', 'gefährte')
103 | self.send_all('gefährte2', 'gefährte', context1.injected)
104 |
105 | sys.modules['weechat'].set_server_current_nick('server', 'gefährte2')
106 | self.send_all('gefährte', 'gefährte2', context2.injected)
107 |
108 | sys.modules['weechat'].set_server_current_nick('server', 'gefährte')
109 | self.send_all('gefährte2', 'gefährte', context1.injected)
110 |
111 | self.assertTrue(context1.is_encrypted())
112 | self.assertTrue(context2.is_encrypted())
113 |
114 | weechat_otr.message_out_cb(
115 | None, None, 'server', ':gefährte!user@host PRIVMSG gefährte2 :hi')
116 |
117 | sys.modules['weechat'].set_server_current_nick('server', 'gefährte2')
118 |
119 | result = weechat_otr.message_in_cb(
120 | None, None, 'server',
121 | ':gefährte!user@host PRIVMSG gefährte2 :%s' % \
122 | context2.injected.pop())
123 |
124 | self.assertEqual(result, weechat_otr.PYVER.to_str(
125 | ':gefährte!user@host PRIVMSG gefährte2 :hi'))
126 |
127 | def test_otr_change_my_nick_case(self):
128 | sys.modules['weechat'].set_server_current_nick('server', 'nick')
129 |
130 | account1 = weechat_otr_test.recording_account.RecordingAccount(
131 | 'nick@server')
132 | weechat_otr.ACCOUNTS['nick@server'] = account1
133 |
134 | account2 = weechat_otr_test.recording_account.RecordingAccount(
135 | 'nick2@server')
136 | weechat_otr.ACCOUNTS['nick2@server'] = account2
137 |
138 | context1 = account2.getContext('nick@server')
139 | context2 = account1.getContext('nick2@server')
140 |
141 | weechat_otr.message_in_cb(
142 | None, None, 'server', ':nick2!user@host PRIVMSG nick :?OTRv2?')
143 |
144 | sys.modules['weechat'].set_server_current_nick('server', 'nick2')
145 | self.send_all('nick', 'nick2', context2.injected)
146 |
147 | sys.modules['weechat'].set_server_current_nick('server', 'nick')
148 | self.send_all('nick2', 'nick', context1.injected)
149 |
150 | sys.modules['weechat'].set_server_current_nick('server', 'nick2')
151 | self.send_all('nick', 'nick2', context2.injected)
152 |
153 | sys.modules['weechat'].set_server_current_nick('server', 'nick')
154 | self.send_all('nick2', 'nick', context1.injected)
155 |
156 | self.assertTrue(context1.is_encrypted())
157 | self.assertTrue(context2.is_encrypted())
158 |
159 | sys.modules['weechat'].set_server_current_nick('server', 'NiCk')
160 | weechat_otr.message_out_cb(
161 | None, None, 'server', ':NiCk!user@host PRIVMSG nick2 :hi')
162 |
163 | sys.modules['weechat'].set_server_current_nick('server', 'nick2')
164 |
165 | result = weechat_otr.message_in_cb(
166 | None, None, 'server',
167 | ':NiCk!user@host PRIVMSG nick2 :%s' % context2.injected.pop())
168 |
169 | self.assertEqual(result, ':NiCk!user@host PRIVMSG nick2 :hi')
170 |
171 | def test_otr_change_peer_nick_case(self):
172 | sys.modules['weechat'].set_server_current_nick('server', 'nick')
173 |
174 | account1 = weechat_otr_test.recording_account.RecordingAccount(
175 | 'nick@server')
176 | weechat_otr.ACCOUNTS['nick@server'] = account1
177 |
178 | account2 = weechat_otr_test.recording_account.RecordingAccount(
179 | 'nick2@server')
180 | weechat_otr.ACCOUNTS['nick2@server'] = account2
181 |
182 | context1 = account2.getContext('nick@server')
183 | context2 = account1.getContext('nick2@server')
184 |
185 | weechat_otr.message_in_cb(
186 | None, None, 'server', ':nick2!user@host PRIVMSG nick :?OTRv2?')
187 |
188 | sys.modules['weechat'].set_server_current_nick('server', 'nick2')
189 | self.send_all('nick', 'nick2', context2.injected)
190 |
191 | sys.modules['weechat'].set_server_current_nick('server', 'nick')
192 | self.send_all('nick2', 'nick', context1.injected)
193 |
194 | sys.modules['weechat'].set_server_current_nick('server', 'nick2')
195 | self.send_all('nick', 'nick2', context2.injected)
196 |
197 | sys.modules['weechat'].set_server_current_nick('server', 'nick')
198 | self.send_all('nick2', 'nick', context1.injected)
199 |
200 | self.assertTrue(context1.is_encrypted())
201 | self.assertTrue(context2.is_encrypted())
202 |
203 | weechat_otr.message_out_cb(
204 | None, None, 'server', ':nick!user@host PRIVMSG NiCk2 :hi')
205 |
206 | sys.modules['weechat'].set_server_current_nick('server', 'nick2')
207 |
208 | result = weechat_otr.message_in_cb(
209 | None, None, 'server',
210 | ':nick!user@host PRIVMSG nick2 :%s' % context2.injected.pop())
211 |
212 | self.assertEqual(result, ':nick!user@host PRIVMSG nick2 :hi')
213 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_otr_statusbar_cb.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | import sys
9 |
10 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
11 |
12 | import weechat_otr_test.mock_window
13 |
14 | import weechat_otr
15 |
16 | class OtrStatusbarCbTestCase(WeechatOtrTestCase):
17 |
18 | def test_no_window_unencrypted(self):
19 | self.assertEqual(
20 | weechat_otr.otr_statusbar_cb(None, None, None),
21 | '(color default)OTR:(color lightred)!SEC(color default)')
22 |
23 | def test_window_unencrypted(self):
24 | window = weechat_otr_test.mock_window.MockWindow()
25 | sys.modules['weechat'].window_get_pointers[(window, 'buffer')] = None
26 |
27 | self.assertEqual(
28 | weechat_otr.otr_statusbar_cb(None, None, window),
29 | '(color default)OTR:(color lightred)!SEC(color default)')
30 |
31 | def test_buffer_not_private(self):
32 | window = weechat_otr_test.mock_window.MockWindow()
33 | sys.modules['weechat'].window_get_pointers[(window, 'buffer')] = \
34 | 'non_private_buffer'
35 |
36 | self.assertEqual(weechat_otr.otr_statusbar_cb(None, None, window), '')
37 |
38 | def test_encrypted_authenticated_logged(self):
39 | context = self.setup_context('me@server', 'nick@server')
40 | context.encrypted = True
41 | context.verified = True
42 | context.logged = True
43 |
44 | self.assertEqual(
45 | weechat_otr.otr_statusbar_cb(None, None, None),
46 | '(color default)OTR:(color green)SEC(color default),(color green)'
47 | 'AUTH(color default),(color lightred)LOG(color default)')
48 |
49 | def test_encrypted_authenticated_not_logged(self):
50 | context = self.setup_context('me@server', 'nick@server')
51 | context.encrypted = True
52 | context.verified = True
53 | context.logged = False
54 |
55 | self.assertEqual(
56 | weechat_otr.otr_statusbar_cb(None, None, None),
57 | '(color default)OTR:(color green)SEC(color default),(color green)'
58 | 'AUTH(color default),(color green)!LOG(color default)')
59 |
60 | self.assertEqual(sys.modules['weechat'].buffer_sets, {
61 | None: {
62 | 'localvar_set_otr_encrypted': 'true',
63 | 'localvar_set_otr_authenticated': 'true',
64 | 'localvar_set_otr_logged': 'false',
65 | }})
66 |
67 | def test_encrypted_unauthenticated_logged(self):
68 | context = self.setup_context('me@server', 'nick@server')
69 | context.encrypted = True
70 | context.verified = False
71 | context.logged = True
72 |
73 | self.assertEqual(
74 | weechat_otr.otr_statusbar_cb(None, None, None),
75 | '(color default)OTR:(color green)SEC(color default),'
76 | '(color lightred)!AUTH(color default),(color lightred)LOG'
77 | '(color default)')
78 |
79 | self.assertEqual(sys.modules['weechat'].buffer_sets, {
80 | None: {
81 | 'localvar_set_otr_encrypted': 'true',
82 | 'localvar_set_otr_authenticated': 'false',
83 | 'localvar_set_otr_logged': 'true',
84 | }})
85 |
86 | def test_encrypted_unauthenticated_not_logged(self):
87 | context = self.setup_context('me@server', 'nick@server')
88 | context.encrypted = True
89 | context.verified = False
90 | context.logged = False
91 |
92 | self.assertEqual(
93 | weechat_otr.otr_statusbar_cb(None, None, None),
94 | '(color default)OTR:(color green)SEC(color default),'
95 | '(color lightred)!AUTH(color default),(color green)!LOG'
96 | '(color default)')
97 |
98 | self.assertEqual(sys.modules['weechat'].buffer_sets, {
99 | None: {
100 | 'localvar_set_otr_encrypted': 'true',
101 | 'localvar_set_otr_authenticated': 'false',
102 | 'localvar_set_otr_logged': 'false',
103 | }})
104 |
105 | def test_unencrypted_unauthenticated_logged(self):
106 | context = self.setup_context('me@server', 'nick@server')
107 | context.encrypted = False
108 | context.verified = False
109 | context.logged = True
110 |
111 | self.assertEqual(
112 | weechat_otr.otr_statusbar_cb(None, None, None),
113 | '(color default)OTR:(color lightred)!SEC(color default)')
114 |
115 | self.assertEqual(sys.modules['weechat'].buffer_sets, {
116 | None: {
117 | 'localvar_set_otr_encrypted': 'false',
118 | 'localvar_set_otr_authenticated': 'false',
119 | 'localvar_set_otr_logged': 'true',
120 | }})
121 |
122 | def test_unencrypted_unauthenticated_not_logged(self):
123 | context = self.setup_context('me@server', 'nick@server')
124 | context.encrypted = False
125 | context.verified = False
126 | context.logged = False
127 |
128 | self.assertEqual(
129 | weechat_otr.otr_statusbar_cb(None, None, None),
130 | '(color default)OTR:(color lightred)!SEC(color default)')
131 |
132 | self.assertEqual(sys.modules['weechat'].buffer_sets, {
133 | None: {
134 | 'localvar_set_otr_encrypted': 'false',
135 | 'localvar_set_otr_authenticated': 'false',
136 | 'localvar_set_otr_logged': 'false',
137 | }})
138 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_parse_irc_privmsg.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | import sys
9 |
10 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
11 |
12 | import weechat_otr
13 |
14 | class ParseIrcPrivmsgTestCase(WeechatOtrTestCase):
15 |
16 | def test_parse_irc_privmsg_nick(self):
17 | result = weechat_otr.parse_irc_privmsg(
18 | ':nick!user@host PRIVMSG nick2 :the message', 'server')
19 |
20 | self.assertEqual(result, {
21 | 'from': 'nick!user@host',
22 | 'from_nick': 'nick',
23 | 'to': 'nick2',
24 | 'to_channel': None,
25 | 'to_nick': 'nick2',
26 | 'text': 'the message'
27 | })
28 |
29 | def test_parse_irc_privmsg_channel_ampersand(self):
30 | sys.modules['weechat'].infos[
31 | ('server,CHANTYPES',)]['irc_server_isupport_value'] += '&'
32 |
33 | result = weechat_otr.parse_irc_privmsg(
34 | ':nick!user@host PRIVMSG &channel :test', 'server')
35 | self.assertEqual(result['to_channel'], '&channel')
36 |
37 | def test_parse_irc_privmsg_non_ascii(self):
38 | sys.modules['weechat'].info_hashtables = {
39 | 'irc_message_parse' : [{
40 | 'arguments': weechat_otr.PYVER.to_str('nick2\xc3 :\xc3'),
41 | 'channel': weechat_otr.PYVER.to_str('nick2\xc3'),
42 | 'command': weechat_otr.PYVER.to_str('PRIVMSG'),
43 | 'host': weechat_otr.PYVER.to_str('nick\xc3!user@host'),
44 | 'nick': weechat_otr.PYVER.to_str('nick\xc3'),
45 | }]
46 | }
47 |
48 | result = weechat_otr.parse_irc_privmsg(
49 | ':nick\xc3!user@host PRIVMSG nick2\xc3 :\xc3', 'server')
50 |
51 | self.assertEqual(result, {
52 | 'from': 'nick\xc3!user@host',
53 | 'from_nick': 'nick\xc3',
54 | 'to': 'nick2\xc3',
55 | 'to_channel': None,
56 | 'to_nick': 'nick2\xc3',
57 | 'text': '\xc3'
58 | })
59 |
60 | def test_parse_irc_privmsg_nick_no_colon(self):
61 | result = weechat_otr.parse_irc_privmsg(
62 | ':nick!user@host PRIVMSG nick2 test', 'server')
63 |
64 | self.assertEqual(result, {
65 | 'from': 'nick!user@host',
66 | 'from_nick': 'nick',
67 | 'to': 'nick2',
68 | 'to_channel': None,
69 | 'to_nick': 'nick2',
70 | 'text': 'test'
71 | })
72 |
73 | def test_parse_irc_privmsg_nick_no_colon_spaces(self):
74 | result = weechat_otr.parse_irc_privmsg(
75 | ':nick!user@host PRIVMSG nick2 the message', 'server')
76 |
77 | self.assertEqual(result, {
78 | 'from': 'nick!user@host',
79 | 'from_nick': 'nick',
80 | 'to': 'nick2',
81 | 'to_channel': None,
82 | 'to_nick': 'nick2',
83 | 'text': 'the message'
84 | })
85 |
86 | def test_parse_irc_privmsg_not_privmsg(self):
87 | with self.assertRaises(weechat_otr.PrivmsgParseException):
88 | weechat_otr.parse_irc_privmsg(
89 | ':nick!user@host OTHERCOMMAND nick2 the message', 'server')
90 |
91 | def test_parse_irc_privmsg_command_case_insensitive(self):
92 | result = weechat_otr.parse_irc_privmsg(
93 | ':nick!user@host privmsg nick2 :the message', 'server')
94 |
95 | self.assertEqual(result, {
96 | 'from': 'nick!user@host',
97 | 'from_nick': 'nick',
98 | 'to': 'nick2',
99 | 'to_channel': None,
100 | 'to_nick': 'nick2',
101 | 'text': 'the message'
102 | })
103 |
104 | def test_parse_irc_privmsg_no_from(self):
105 | result = weechat_otr.parse_irc_privmsg(
106 | 'PRIVMSG nickserv :identify secret', 'server')
107 |
108 | self.assertEqual(result, {
109 | 'from': '',
110 | 'from_nick': '',
111 | 'to': 'nickserv',
112 | 'to_channel': None,
113 | 'to_nick': 'nickserv',
114 | 'text': 'identify secret'
115 | })
116 |
117 | def test_parse_irc_privmsg_to_self(self):
118 | result = weechat_otr.parse_irc_privmsg(
119 | ':nick!user@host PRIVMSG nick :the message', 'server')
120 |
121 | self.assertEqual(result, {
122 | 'from': 'nick!user@host',
123 | 'from_nick': 'nick',
124 | 'to': 'nick',
125 | 'to_channel': None,
126 | 'to_nick': 'nick',
127 | 'text': 'the message'
128 | })
129 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_policy.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
9 |
10 | import weechat_otr
11 |
12 | class TestPolicy(WeechatOtrTestCase):
13 |
14 | def test_policy_default_server_nick_buffer(self):
15 | weechat_otr.command_cb(None, None, 'policy default')
16 |
17 | self.assertPrinted('server_nick_buffer', (
18 | 'eval(${color:default}:! ${color:brown}otr${color:default} !:)\t'
19 | '(color default)'
20 | 'Current default OTR policies:\r\n'
21 | '(color default)'
22 | ' allow_v2 (allow OTR protocol version 2, effectively enable '
23 | 'OTR since v2 is the only supported version) : on\r\n'
24 | '(color default)'
25 | ' html_escape (escape HTML special characters in outbound '
26 | 'messages) : off\r\n'
27 | '(color default)'
28 | ' html_filter (filter HTML in incoming messages) : on\r\n'
29 | '(color default)'
30 | ' log (enable logging of OTR conversations) : off\r\n'
31 | '(color default)'
32 | ' require_encryption (refuse to send unencrypted messages when '
33 | 'OTR is enabled) : off\r\n'
34 | '(color default)'
35 | ' send_tag (advertise your OTR capability using the whitespace '
36 | 'tag) : off\r\n'
37 | '(color default)'
38 | 'Change default policies with: /otr policy default NAME on|off'))
39 |
40 | def test_policy_default_no_server_nick_buffer(self):
41 | weechat_otr.command_cb(None, 'non_private_buffer', 'policy default')
42 |
43 | self.assertPrinted('', (
44 | 'Current default OTR policies:\n'
45 | ' allow_v2 (allow OTR protocol version 2, effectively enable '
46 | 'OTR since v2 is the only supported version) : on\n'
47 | ' html_escape (escape HTML special characters in outbound '
48 | 'messages) : off\n'
49 | ' html_filter (filter HTML in incoming messages) : on\n'
50 | ' log (enable logging of OTR conversations) : off\n'
51 | ' require_encryption (refuse to send unencrypted messages when '
52 | 'OTR is enabled) : off\n'
53 | ' send_tag (advertise your OTR capability using the whitespace '
54 | 'tag) : off\n'
55 | 'Change default policies with: /otr policy default NAME on|off'))
56 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_policy_account.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | import sys
9 |
10 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
11 |
12 | import weechat_otr
13 |
14 | class TestPolicyAccount(WeechatOtrTestCase):
15 |
16 | def test_policy_account(self):
17 | account = weechat_otr.ACCOUNTS['nick@server']
18 |
19 | self.assertEqual(
20 | account.getContext('friend@server').getPolicy('html_escape'),
21 | False)
22 | sys.modules['weechat'].config_options[
23 | 'otr.policy.server.nick.html_escape'] = 'on'
24 | self.assertEqual(
25 | account.getContext('friend@server').getPolicy('html_escape'),
26 | True)
27 |
28 | def test_policy_account_non_ascii(self):
29 | account = weechat_otr.ACCOUNTS['gefährtenick@server']
30 |
31 | self.assertEqual(
32 | account.getContext('gefährte@server').getPolicy('html_escape'),
33 | False)
34 | sys.modules['weechat'].config_options[weechat_otr.PYVER.to_str(
35 | 'otr.policy.server.gefährtenick.html_escape')] = 'on'
36 | self.assertEqual(
37 | account.getContext('gefährte@server').getPolicy('html_escape'),
38 | True)
39 |
40 | def test_policy_account_peer_override(self):
41 | account = weechat_otr.ACCOUNTS['nick@server']
42 |
43 | self.assertEqual(
44 | account.getContext('friend@server').getPolicy('html_escape'),
45 | False)
46 | sys.modules['weechat'].config_options[
47 | 'otr.policy.server.nick.html_escape'] = 'on'
48 | sys.modules['weechat'].config_options[
49 | 'otr.policy.server.nick.friend.html_escape'] = 'off'
50 | self.assertEqual(
51 | account.getContext('friend@server').getPolicy('html_escape'),
52 | False)
53 |
54 | def test_policy_account_peer_override_non_ascii(self):
55 | account = weechat_otr.ACCOUNTS['gefährtenick@server']
56 |
57 | self.assertEqual(
58 | account.getContext('gefährte@server').getPolicy('html_escape'),
59 | False)
60 | sys.modules['weechat'].config_options[weechat_otr.PYVER.to_str(
61 | 'otr.policy.server.gefährtenick.html_escape')] = 'on'
62 | sys.modules['weechat'].config_options[weechat_otr.PYVER.to_str(
63 | 'otr.policy.server.gefährtenick.gefährtenick.html_escape')] = 'off'
64 | self.assertEqual(
65 | account.getContext('gefährte@server').getPolicy('html_escape'),
66 | True)
67 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_policy_server.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | import sys
9 |
10 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
11 |
12 | import weechat_otr
13 |
14 | class TestPolicyServer(WeechatOtrTestCase):
15 |
16 | def test_policy_server(self):
17 | account = weechat_otr.ACCOUNTS['nick@server']
18 |
19 | self.assertEqual(
20 | account.getContext('friend@server').getPolicy('html_escape'),
21 | False)
22 | sys.modules['weechat'].config_options[
23 | 'otr.policy.server.html_escape'] = 'on'
24 | self.assertEqual(
25 | account.getContext('friend@server').getPolicy('html_escape'),
26 | True)
27 |
28 | def test_policy_server_account_override(self):
29 | account = weechat_otr.ACCOUNTS['nick@server']
30 |
31 | self.assertEqual(
32 | account.getContext('friend@server').getPolicy('html_escape'),
33 | False)
34 | sys.modules['weechat'].config_options[
35 | 'otr.policy.server.html_escape'] = 'on'
36 | sys.modules['weechat'].config_options[
37 | 'otr.policy.server.nick.html_escape'] = 'off'
38 | self.assertEqual(
39 | account.getContext('friend@server').getPolicy('html_escape'),
40 | False)
41 |
42 | def test_policy_server_peer_override(self):
43 | account = weechat_otr.ACCOUNTS['nick@server']
44 |
45 | self.assertEqual(
46 | account.getContext('friend@server').getPolicy('html_escape'),
47 | False)
48 | sys.modules['weechat'].config_options[
49 | 'otr.policy.server.html_escape'] = 'on'
50 | sys.modules['weechat'].config_options[
51 | 'otr.policy.server.nick.friend.html_escape'] = 'off'
52 | self.assertEqual(
53 | account.getContext('friend@server').getPolicy('html_escape'),
54 | False)
55 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_shutdown.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=missing-docstring
3 | # pylint: disable=too-many-public-methods
4 | # pylint: disable=invalid-name
5 |
6 | from __future__ import unicode_literals
7 |
8 | import sys
9 |
10 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
11 | import weechat_otr_test.mock_account
12 |
13 | import weechat_otr
14 |
15 | class ShutdownTestCase(WeechatOtrTestCase):
16 |
17 | def test_writes_config(self):
18 | weechat_otr.shutdown()
19 | self.assertEqual(
20 | [('config file', )], sys.modules['weechat'].config_written)
21 |
22 | def test_ends_all_private(self):
23 | account1 = weechat_otr_test.mock_account.MockAccount()
24 | account2 = weechat_otr_test.mock_account.MockAccount()
25 |
26 | weechat_otr.ACCOUNTS['account1'] = account1
27 | weechat_otr.ACCOUNTS['account2'] = account2
28 |
29 | weechat_otr.shutdown()
30 | self.assertEqual(account1.end_all_privates, 1)
31 | self.assertEqual(account2.end_all_privates, 1)
32 |
33 | def test_frees_config_section_options(self):
34 | weechat_otr.shutdown()
35 |
36 | sections = sorted([
37 | ('color', ),
38 | ('policy', ),
39 | ('look', ),
40 | ('general', )
41 | ])
42 |
43 | self.assertEqual(
44 | sections,
45 | sorted(sys.modules['weechat'].config_section_free_options_calls))
46 |
47 | def test_frees_config_sections(self):
48 | weechat_otr.shutdown()
49 |
50 | sections = sorted([
51 | ('color', ),
52 | ('policy', ),
53 | ('look', ),
54 | ('general', )
55 | ])
56 |
57 | self.assertEqual(
58 | sections, sorted(sys.modules['weechat'].config_section_free_calls))
59 |
60 | def test_frees_config_file(self):
61 | weechat_otr.shutdown()
62 |
63 | self.assertEqual(
64 | [('config file', )], sys.modules['weechat'].config_free_calls)
65 |
66 | def test_removes_bar_item(self):
67 | weechat_otr.shutdown()
68 | self.assertEqual(
69 | [('bar item', )], sys.modules['weechat'].bar_items_removed)
70 |
71 | def test_returns_ok(self):
72 | self.assertEqual(
73 | weechat_otr.shutdown(), sys.modules['weechat'].WEECHAT_RC_OK)
74 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_smp.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
9 |
10 | import weechat_otr
11 |
12 | class SmpTestCase(WeechatOtrTestCase):
13 |
14 | def test_smp_ask_nick_server_question_secret(self):
15 | context = self.setup_context('nick@server', 'nick2@server')
16 |
17 | weechat_otr.command_cb(
18 | None, None, 'smp ask nick2 server question secret')
19 |
20 | self.assertEqual(('secret', 'question'), context.smp_init)
21 |
22 | def test_smp_ask_nick_server_secret(self):
23 | context = self.setup_context('nick@server', 'nick2@server')
24 |
25 | weechat_otr.command_cb(
26 | None, None, 'smp ask nick2 server secret')
27 |
28 | self.assertEqual(('secret', None), context.smp_init)
29 |
30 | def test_smp_ask_nick_server_secret_non_ascii(self):
31 | context = self.setup_context('nick@server', 'nick2@server')
32 |
33 | weechat_otr.command_cb(
34 | None, None,
35 | weechat_otr.PYVER.to_str('smp ask nick2 server motörhead'))
36 |
37 | self.assertEqual(
38 | (weechat_otr.PYVER.to_str('motörhead'), None), context.smp_init)
39 |
40 | def test_smp_ask_question_secret(self):
41 | context = self.setup_context('nick@server', 'nick2@server')
42 |
43 | weechat_otr.command_cb(
44 | None, 'server_nick2_buffer', 'smp ask question secret')
45 |
46 | self.assertEqual(('secret', 'question'), context.smp_init)
47 |
48 | def test_smp_ask_secret(self):
49 | context = self.setup_context('nick@server', 'nick2@server')
50 |
51 | weechat_otr.command_cb(None, 'server_nick2_buffer', 'smp ask secret')
52 |
53 | self.assertEqual(('secret', None), context.smp_init)
54 |
55 | def test_smp_ask_nick_server_question_secret_multiple_words(self):
56 | context = self.setup_context('nick@server', 'nick2@server')
57 |
58 | weechat_otr.command_cb(
59 | None, None, "smp ask nick2 server 'what is the secret?' "
60 | "'eastmost penninsula is the secret'")
61 |
62 | self.assertEqual(
63 | ('eastmost penninsula is the secret', 'what is the secret?'),
64 | context.smp_init)
65 |
66 | def test_smp_respond_secret(self):
67 | context = self.setup_context('nick@server', 'nick2@server')
68 |
69 | weechat_otr.command_cb(
70 | None, 'server_nick2_buffer', 'smp respond secret')
71 |
72 | self.assertEqual(('secret', ), context.smp_got_secret)
73 |
74 | def test_smp_respond_nick_server_secret(self):
75 | context = self.setup_context('nick@server', 'nick2@server')
76 |
77 | weechat_otr.command_cb(
78 | None, 'server_nick2_buffer', 'smp respond nick2 server secret')
79 |
80 | self.assertEqual(('secret', ), context.smp_got_secret)
81 |
82 | def test_smp_respond_secret_non_ascii(self):
83 | context = self.setup_context('nick@server', 'nick2@server')
84 |
85 | weechat_otr.command_cb(
86 | None, 'server_nick2_buffer',
87 | weechat_otr.PYVER.to_str('smp respond deathtöngue'))
88 |
89 | self.assertEqual(
90 | (weechat_otr.PYVER.to_str('deathtöngue'), ),
91 | context.smp_got_secret)
92 |
93 | def test_smp_abort(self):
94 | context = self.setup_context('nick@server', 'nick2@server')
95 | context.in_smp = True
96 |
97 | weechat_otr.command_cb(None, 'server_nick2_buffer', 'smp abort')
98 |
99 | self.assertEqual([('SMP aborted.',)], context.smp_finishes)
100 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_table_formatter.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
9 |
10 | import weechat_otr
11 |
12 |
13 | class TableFormatterTestCase(WeechatOtrTestCase):
14 |
15 | def test_format(self):
16 | table_formatter = weechat_otr.TableFormatter()
17 | table_formatter.add_row(['a', 'b', 'c'])
18 | table_formatter.add_row(['a', 'bb', 'c'])
19 | table_formatter.add_row(['a', 'b', 'ccc'])
20 |
21 | self.assertEqual(
22 | table_formatter.format(),
23 | # pylint: disable=trailing-whitespace
24 | """a |b |c
25 | a |bb |c
26 | a |b |ccc""")
27 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_weechat_otr.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | import sys
9 |
10 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
11 |
12 | import weechat_otr
13 |
14 | class WeechatOtrGeneralTestCase(WeechatOtrTestCase):
15 |
16 | def test_build_privmsg_in_without_newline(self):
17 | result = weechat_otr.build_privmsg_in('f', 't', 'line1')
18 | self.assertEqual(result, ':f PRIVMSG t :line1')
19 |
20 | def test_build_privmsg_in_with_newline(self):
21 | result = weechat_otr.build_privmsg_in('f', 't', 'line1\nline2')
22 | self.assertEqual(result, ':f PRIVMSG t :line1line2')
23 |
24 | def test_build_privmsgs_in_without_newline(self):
25 | fromm = 'f'
26 | to = 't'
27 | line = 'line1'
28 | result = weechat_otr.build_privmsgs_in(fromm, to, line)
29 | self.assertEqual(
30 | result, weechat_otr.build_privmsg_in(fromm, to, line))
31 |
32 | def test_build_privmsgs_in_without_newline_prefix(self):
33 | fromm = 'f'
34 | to = 't'
35 | line = 'line1'
36 | prefix = 'Some prefix: '
37 | result = weechat_otr.build_privmsgs_in(fromm, to, line, prefix)
38 | self.assertEqual(
39 | result,
40 | weechat_otr.build_privmsg_in(
41 | fromm, to, '{prefix}{line}'.format(prefix=prefix, line=line)))
42 |
43 | def test_build_privmsgs_in_with_newline(self):
44 | fromm = 'f'
45 | to = 't'
46 | result = weechat_otr.build_privmsgs_in(fromm, to, 'line1\nline2')
47 | self.assertEqual(result, '{msg1}\r\n{msg2}'.format(
48 | msg1=weechat_otr.build_privmsg_in(fromm, to, 'line1'),
49 | msg2=weechat_otr.build_privmsg_in(fromm, to, 'line2')))
50 |
51 | def test_build_privmsgs_in_with_newline_prefix(self):
52 | fromm = 'f'
53 | to = 't'
54 | prefix = 'Some prefix: '
55 | result = weechat_otr.build_privmsgs_in(
56 | fromm, to, 'line1\nline2', prefix)
57 | self.assertEqual(result, '{msg1}\r\n{msg2}'.format(
58 | msg1=weechat_otr.build_privmsg_in(
59 | fromm, to, '{0}line1'.format(prefix)),
60 | msg2=weechat_otr.build_privmsg_in(
61 | fromm, to, '{0}line2'.format(prefix))))
62 |
63 | def test_build_privmsg_out_without_newline(self):
64 | result = weechat_otr.build_privmsg_out('t', 'line1')
65 | self.assertEqual(result, 'PRIVMSG t :line1')
66 |
67 | def test_build_privmsg_out_with_newline(self):
68 | result = weechat_otr.build_privmsg_out('t', 'line1\nline2')
69 | self.assertEqual(result, 'PRIVMSG t :line1\r\nPRIVMSG t :line2')
70 |
71 | def test_msg_irc_from_plain_action(self):
72 | result = weechat_otr.msg_irc_from_plain('/me does something')
73 | self.assertEqual(result, '\x01ACTION does something\x01')
74 |
75 | def test_msg_irc_from_plain_no_action(self):
76 | msg_no_action = 'just a message'
77 | self.assertEqual(
78 | weechat_otr.msg_irc_from_plain(msg_no_action),
79 | msg_no_action)
80 |
81 | def test_msg_irc_from_plain_action_invariant(self):
82 | msg_action = '\x01ACTION does something\x01'
83 | self.assertEqual(
84 | msg_action,
85 | weechat_otr.msg_irc_from_plain(
86 | weechat_otr.msg_plain_from_irc(msg_action)))
87 |
88 | def test_msg_plain_from_irc_action(self):
89 | result = weechat_otr.msg_plain_from_irc('\x01ACTION does something\x01')
90 | self.assertEqual(result, '/me does something')
91 |
92 | def test_msg_plain_from_irc_no_action(self):
93 | msg_no_action = 'just a message'
94 | self.assertEqual(
95 | weechat_otr.msg_plain_from_irc(msg_no_action),
96 | msg_no_action)
97 |
98 | def test_msg_plain_from_irc_action_invariant(self):
99 | msg_action = '/me does something'
100 | self.assertEqual(
101 | msg_action,
102 | weechat_otr.msg_plain_from_irc(
103 | weechat_otr.msg_irc_from_plain(msg_action)))
104 |
105 | def test_command_cb_start_send_tag_off(self):
106 | weechat_otr.command_cb(None, None, 'start')
107 |
108 | self.assertPrinted('server_nick_buffer', (
109 | 'eval(${color:default}:! ${color:brown}otr${color:default} !:)\t'
110 | '(color lightblue)'
111 | 'Sending OTR query... Please await confirmation of the OTR '
112 | 'session being started before sending a message.'))
113 |
114 | self.assertPrinted('server_nick_buffer', (
115 | 'eval(${color:default}:! ${color:brown}otr${color:default} !:)\t'
116 | '(color lightblue)'
117 | 'To try OTR on all conversations with nick@server: /otr '
118 | 'policy send_tag on'))
119 |
120 | def test_command_cb_start_send_tag_off_no_hints(self):
121 | sys.modules['weechat'].config_options[
122 | 'otr.general.hints'] = 'off'
123 | weechat_otr.command_cb(None, None, 'start')
124 |
125 | self.assertNotPrinted('server_nick_buffer', (
126 | 'eval(${color:default}:! ${color:brown}otr${color:default} !:)\t'
127 | '(color lightblue)'
128 | 'To try OTR on all conversations with nick@server: /otr '
129 | 'policy send_tag on'))
130 |
131 | def test_command_cb_start_send_tag_off_with_hints(self):
132 | sys.modules['weechat'].config_options['otr.general.hints'] = 'on'
133 | weechat_otr.command_cb(None, None, 'start')
134 |
135 | self.assertPrinted('server_nick_buffer', (
136 | 'eval(${color:default}:! ${color:brown}otr${color:default} !:)\t'
137 | '(color lightblue)'
138 | 'To try OTR on all conversations with nick@server: /otr '
139 | 'policy send_tag on'))
140 |
141 | def test_command_cb_start_send_tag_on(self):
142 | sys.modules['weechat'].config_options[
143 | 'otr.policy.server.nick.nick.send_tag'] = 'on'
144 | weechat_otr.command_cb(None, None, 'start')
145 |
146 | self.assertPrinted('server_nick_buffer', (
147 | 'eval(${color:default}:! ${color:brown}otr${color:default} !:)\t'
148 | '(color lightblue)'
149 | 'Sending OTR query... Please await confirmation of the OTR '
150 | 'session being started before sending a message.'))
151 |
152 | def test_irc_sanitize(self):
153 | result = weechat_otr.irc_sanitize(
154 | 'this\r\x00 is \r\n\rnot an i\n\x00rc command')
155 | self.assertEqual(result, 'this is not an irc command')
156 |
157 | def test_print_buffer_not_private(self):
158 | weechat_otr.command_cb(None, None, 'start no_window_nick server')
159 | self.assertPrinted('non_private_buffer', (
160 | 'eval(${color:default}:! ${color:brown}otr${color:default} !:)\t'
161 | '(color lightblue)'
162 | '[no_window_nick] Sending OTR query... Please await confirmation '
163 | 'of the OTR session being started before sending a message.'))
164 |
--------------------------------------------------------------------------------
/weechat_otr_test/test_weechat_version_ok.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=invalid-name
3 | # pylint: disable=missing-docstring
4 | # pylint: disable=too-many-public-methods
5 |
6 | from __future__ import unicode_literals
7 |
8 | import sys
9 |
10 | from weechat_otr_test.weechat_otr_test_case import WeechatOtrTestCase
11 |
12 | import weechat_otr
13 |
14 | class WeechatVersionOkTestCase(WeechatOtrTestCase):
15 |
16 | def test_empty_version(self):
17 | sys.modules['weechat'].infos[('',)]['version_number'] = ''
18 | sys.modules['weechat'].infos[('',)]['version'] = ''
19 | self.assertFalse(weechat_otr.weechat_version_ok())
20 | self.assertPrinted(
21 | '',
22 | 'otr requires WeeChat version >= 0.4.2. The current version is .')
23 |
24 | def test_too_low(self):
25 | sys.modules['weechat'].infos[('',)]['version_number'] = 0x00030800
26 | sys.modules['weechat'].infos[('',)]['version'] = '0.4.1'
27 | self.assertFalse(weechat_otr.weechat_version_ok())
28 | self.assertPrinted(
29 | '',
30 | 'otr requires WeeChat version >= 0.4.2. The current version is '
31 | '0.4.1.')
32 |
33 | def test_ok(self):
34 | sys.modules['weechat'].infos[('',)]['version_number'] = 0x01000100
35 | sys.modules['weechat'].infos[('',)]['version'] = '1.0.1'
36 | self.assertTrue(weechat_otr.weechat_version_ok())
37 | self.assertNotPrinted(
38 | '',
39 | 'otr requires WeeChat version >= 0.4.2. The current version is '
40 | '1.0.1.')
41 |
42 | def test_version_checked_during_setup(self):
43 | self.assertIn(
44 | ('version_number', ('',)), sys.modules['weechat'].info_gets)
45 |
--------------------------------------------------------------------------------
/weechat_otr_test/weechat_otr_test_case.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # pylint: disable=missing-docstring
3 | # pylint: disable=too-many-public-methods
4 | # pylint: disable=invalid-name
5 |
6 | from __future__ import unicode_literals
7 |
8 | import sys
9 | import tempfile
10 | import unittest
11 |
12 | import weechat_otr_test.mock_weechat
13 | sys.modules['weechat'] = weechat_otr_test.mock_weechat.MockWeechat(
14 | tempfile.mkdtemp())
15 |
16 | # pylint: disable=wrong-import-position
17 | import weechat_otr
18 |
19 | class WeechatOtrTestCase(unittest.TestCase):
20 |
21 | def setUp(self):
22 | sys.modules['weechat'].save()
23 | weechat_otr.ACCOUNTS.clear()
24 | weechat_otr.otr_debug_buffer = None
25 | self.after_setup()
26 |
27 | def tearDown(self):
28 | sys.modules['weechat'].restore()
29 | self.after_teardown()
30 |
31 | def after_setup(self):
32 | pass
33 |
34 | def after_teardown(self):
35 | pass
36 |
37 | def assertPrinted(self, buf, text):
38 | self.assertIn(text, sys.modules['weechat'].printed[buf])
39 |
40 | def assertPrintedContains(self, buf, regex):
41 | self.assertRegex(
42 | ''.join(sys.modules['weechat'].printed.get(buf, [])),
43 | regex)
44 |
45 | def assertNotPrinted(self, buf, text):
46 | self.assertNotIn(text, sys.modules['weechat'].printed.get(buf, []))
47 |
48 | def assertNoPrintedContains(self, buf, text):
49 | for line in sys.modules['weechat'].printed.get(buf, []):
50 | self.assertNotIn(text, line)
51 |
52 | def assertRegex(self, s, r):
53 | # To get rid of deprecation warning in python 3.
54 | try:
55 | # Python 3.
56 | super(WeechatOtrTestCase, self).assertRegex(s, r)
57 | except AttributeError:
58 | # Python 2.
59 | self.assertRegexpMatches(s, r)
60 |
61 | def setup_context(self, account_name, context_name):
62 | # pylint: disable=no-self-use
63 | context = weechat_otr_test.mock_context.MockContext()
64 | account = weechat_otr_test.mock_account.MockAccount()
65 | account.add_context(context_name, context)
66 | weechat_otr.ACCOUNTS[account_name] = account
67 |
68 | return context
69 |
--------------------------------------------------------------------------------