Welcome to mirror list, hosted at ThFree Co, Russian Federation.

dev.gajim.org/gajim/gajim-plugins.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilipp Hörist <forenjunkie@chello.at>2017-11-06 18:58:18 +0300
committerPhilipp Hörist <forenjunkie@chello.at>2017-11-06 18:58:18 +0300
commit348d87a1f974a8865da190a389bef142a6db40dc (patch)
treec134b21eb95067756f4e87cf48cc00bc14af4c25
parentfb59c70946ad2933b12df42164bc5c3bede73d63 (diff)
parent1abd096763c4c79d0f1aaf5728e6cd138c30defc (diff)
Merge branch 'gtk3' into 'gtk3'
Limit image preview to https and httpupload See merge request gajim/gajim-plugins!47
-rw-r--r--url_image_preview/manifest.ini2
-rw-r--r--url_image_preview/url_image_preview.py197
2 files changed, 107 insertions, 92 deletions
diff --git a/url_image_preview/manifest.ini b/url_image_preview/manifest.ini
index 16a352a..06d072c 100644
--- a/url_image_preview/manifest.ini
+++ b/url_image_preview/manifest.ini
@@ -1,7 +1,7 @@
[info]
name: Url image preview
short_name: url_image_preview
-version: 0.5.6
+version: 2.0.1
description: Displays a preview of links to images
authors = Denis Fomin <fominde@gmail.com>
Yann Leboulanger <asterix@lagaule.org>
diff --git a/url_image_preview/url_image_preview.py b/url_image_preview/url_image_preview.py
index 1d73898..206080a 100644
--- a/url_image_preview/url_image_preview.py
+++ b/url_image_preview/url_image_preview.py
@@ -15,30 +15,28 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
-from gi.repository import Gtk, Gdk, GLib, GObject, GdkPixbuf, Gio
import os
import hashlib
import binascii
+import logging
from urllib.parse import urlparse
from io import BytesIO
import shutil
from functools import partial
-import logging
-import nbxmpp
+from gi.repository import Gtk, Gdk, GLib, GdkPixbuf
+
from gajim.common import app
-from gajim.common import ged
from gajim.common import helpers
from gajim.common import configpaths
from gajim import dialogs
from gajim.plugins import GajimPlugin
from gajim.plugins.helpers import log_calls
-from gajim.plugins.gui import GajimPluginConfigDialog
from gajim.conversation_textview import TextViewImage
-from .http_functions import get_http_head, get_http_file
-from .config_dialog import UrlImagePreviewConfigDialog
+from url_image_preview.http_functions import get_http_head, get_http_file
+from url_image_preview.config_dialog import UrlImagePreviewConfigDialog
-log = logging.getLogger('gajim.plugin_system.url_image_preview')
+log = logging.getLogger('gajim.plugin_system.preview')
try:
from PIL import Image
@@ -54,10 +52,10 @@ try:
from cryptography.hazmat.primitives.ciphers import algorithms
from cryptography.hazmat.primitives.ciphers.modes import GCM
decryption_available = True
-except Exception as e:
+except Exception:
DEP_MSG = 'For preview of encrypted images, ' \
'please install python-cryptography!'
- log.debug('Cryptography Import Error: ' + str(e))
+ log.exception('Error')
log.info('Decryption/Encryption disabled due to errors')
decryption_available = False
@@ -71,32 +69,19 @@ class UrlImagePreviewPlugin(GajimPlugin):
if not decryption_available:
self.available_text = DEP_MSG
self.config_dialog = partial(UrlImagePreviewConfigDialog, self)
- self.events_handlers = {}
- self.events_handlers['message-received'] = (
- ged.PRECORE, self.handle_message_received)
self.gui_extension_points = {
'chat_control_base': (self.connect_with_chat_control,
self.disconnect_from_chat_control),
- 'print_special_text': (self.print_special_text, None), }
+ 'history_window':
+ (self.connect_with_history, self.disconnect_from_history),
+ 'print_real_text': (self.print_real_text, None), }
self.config_default_values = {
'PREVIEW_SIZE': (150, 'Preview size(10-512)'),
'MAX_FILE_SIZE': (524288, 'Max file size for image preview'),
- 'LEFTCLICK_ACTION': ('open_menuitem', 'Open')}
+ 'LEFTCLICK_ACTION': ('open_menuitem', 'Open'),
+ 'ANONYMOUS_MUC': False,}
self.controls = {}
-
- # remove oob tag if oob url == message text
- def handle_message_received(self, event):
- oob_node = event.stanza.getTag('x', namespace=nbxmpp.NS_X_OOB)
- oob_url = None
- oob_desc = None
- if oob_node:
- oob_url = oob_node.getTagData('url')
- oob_desc = oob_node.getTagData('desc')
- if oob_url and oob_url == event.msgtxt and \
- (not oob_desc or oob_desc == ""):
- log.debug("Detected oob tag containing same"
- "url as the message text, deleting oob tag...")
- event.stanza.delChild(oob_node)
+ self.history_window_control = None
@log_calls('UrlImagePreviewPlugin')
def connect_with_chat_control(self, chat_control):
@@ -104,32 +89,51 @@ class UrlImagePreviewPlugin(GajimPlugin):
jid = chat_control.contact.jid
if account not in self.controls:
self.controls[account] = {}
- self.controls[account][jid] = Base(self, chat_control)
+ self.controls[account][jid] = Base(self, chat_control.conv_textview,
+ chat_control.parent_win.window)
@log_calls('UrlImagePreviewPlugin')
def disconnect_from_chat_control(self, chat_control):
account = chat_control.contact.account.name
jid = chat_control.contact.jid
- self.controls[account][jid].deinit()
+ self.controls[account][jid].deinit_handlers()
del self.controls[account][jid]
- def print_special_text(self, tv, special_text, other_tags, graphics=True,
- additional_data=None, iter_=None):
+ @log_calls('UrlImagePreviewPlugin')
+ def connect_with_history(self, history_window):
+ if self.history_window_control:
+ log.error("connect_with_history: deinit handlers")
+ self.history_window_control.deinit_handlers()
+ log.error("connect_with_history: create base")
+ self.history_window_control = Base(
+ self, history_window.history_textview, history_window)
+
+ @log_calls('UrlImagePreviewPlugin')
+ def disconnect_from_history(self, history_window):
+ if self.history_window_control:
+ self.history_window_control.deinit_handlers()
+ self.history_window_control = None
+
+ def print_real_text(self, tv, real_text, text_tags, graphics,
+ iter_, additional_data):
+ if tv.used_in_history_window and self.history_window_control:
+ self.history_window_control.print_real_text(
+ real_text, text_tags, graphics, iter_, additional_data)
+
account = tv.account
for jid in self.controls[account]:
- if self.controls[account][jid].chat_control.conv_textview != tv:
+ if self.controls[account][jid].textview != tv:
continue
- self.controls[account][jid].print_special_text(
- special_text, other_tags, graphics=graphics,
- additional_data=additional_data, iter_=iter_)
+ self.controls[account][jid].print_real_text(
+ real_text, text_tags, graphics, iter_, additional_data)
return
class Base(object):
- def __init__(self, plugin, chat_control):
+ def __init__(self, plugin, textview, parent_win=None):
self.plugin = plugin
- self.chat_control = chat_control
- self.textview = self.chat_control.conv_textview
+ self.parent_win = parent_win
+ self.textview = textview
self.handlers = {}
self.directory = os.path.join(configpaths.gajimpaths['MY_DATA'],
@@ -140,11 +144,11 @@ class Base(object):
try:
self._create_path(self.directory)
self._create_path(self.thumbpath)
- except Exception as e:
+ except Exception:
log.error("Error creating download and/or thumbnail folder!")
raise
- def deinit(self):
+ def deinit_handlers(self):
# remove all register handlers on wigets, created by self.xml
# to prevent circular references among objects
for i in list(self.handlers.keys()):
@@ -152,20 +156,26 @@ class Base(object):
self.handlers[i].disconnect(i)
del self.handlers[i]
- def print_special_text(self, special_text, other_tags, graphics=True,
- additional_data=None, iter_=None):
- # remove qip bbcode
- special_text = special_text.rsplit('[/img]')[0]
-
- if special_text.startswith('www.'):
- special_text = 'http://' + special_text
- if special_text.startswith('ftp.'):
- special_text = 'ftp://' + special_text
+ def print_real_text(self, real_text, text_tags, graphics, iter_,
+ additional_data):
+ urlparts = urlparse(real_text)
+ if (urlparts.scheme not in ["https", "aesgcm"] or
+ not urlparts.netloc):
+ log.info("Not accepting URL scheme '%s' for image preview: %s",
+ urlparts.scheme, real_text)
+ return
- urlparts = urlparse(special_text)
- if urlparts.scheme not in ["https", "http", "ftp", "ftps", 'aesgcm'] or \
- not urlparts.netloc:
- log.info("Not accepting URL for image preview: %s" % special_text)
+ try:
+ oob_url = additional_data["gajim"]["oob_url"]
+ except (KeyError, AttributeError):
+ oob_url = None
+
+ # allow aesgcm uris without oob marker (aesgcm uris are always
+ # httpupload filetransfers)
+ if urlparts.scheme != "aesgcm" and real_text != oob_url:
+ log.info("Not accepting URL for image preview "
+ "(wrong or no oob data): %s", real_text)
+ log.debug("additional_data: %s", additional_data)
return
# Don't print the URL in the message window (in the calling function)
@@ -178,14 +188,14 @@ class Base(object):
# Show URL, until image is loaded (if ever)
ttt = buffer_.get_tag_table()
repl_start = buffer_.create_mark(None, iter_, True)
- buffer_.insert_with_tags(iter_, special_text,
+ buffer_.insert_with_tags(iter_, real_text,
*[(ttt.lookup(t) if isinstance(t, str) else t) for t in ["url"]])
repl_end = buffer_.create_mark(None, iter_, True)
filename = os.path.basename(urlparts.path)
ext = os.path.splitext(filename)[1]
name = os.path.splitext(filename)[0]
- namehash = hashlib.sha1(special_text.encode('utf-8')).hexdigest()
+ namehash = hashlib.sha1(real_text.encode('utf-8')).hexdigest()
newfilename = name + '_' + namehash + ext
thumbfilename = name + '_' + namehash + '_thumb_' \
+ str(self.plugin.config['PREVIEW_SIZE']) + ext
@@ -208,15 +218,14 @@ class Base(object):
iv = fragment[:12]
if len(key) == 32 and len(iv) == 12:
encrypted = True
-
+
# file exists but thumbnail got deleted
if os.path.exists(filepath) and not os.path.exists(thumbpath):
with open(filepath, 'rb') as f:
mem = f.read()
- f.closed
app.thread_interface(
self._save_thumbnail, [thumbpath, (mem, '')],
- self._update_img, [special_text, repl_start,
+ self._update_img, [real_text, repl_start,
repl_end, filepath, encrypted])
# display thumbnail if already downloadeded
@@ -224,7 +233,7 @@ class Base(object):
elif os.path.exists(filepath) and os.path.exists(thumbpath):
app.thread_interface(
self._load_thumbnail, [thumbpath],
- self._update_img, [special_text, repl_start,
+ self._update_img, [real_text, repl_start,
repl_end, filepath, encrypted])
# or download file, calculate thumbnail and finally display it
@@ -236,10 +245,10 @@ class Base(object):
# which does not fetch data, just headers
# then check the mime type and filesize
if urlparts.scheme == 'aesgcm':
- special_text = 'https://' + special_text[9:]
+ real_text = 'https://' + real_text[9:]
app.thread_interface(
- get_http_head, [self.textview.account, special_text],
- self._check_mime_size, [special_text, repl_start, repl_end,
+ get_http_head, [self.textview.account, real_text],
+ self._check_mime_size, [real_text, repl_start, repl_end,
filepaths, key, iv, encrypted])
def _save_thumbnail(self, thumbpath, tuple_arg):
@@ -272,7 +281,7 @@ class Base(object):
loader.close()
pixbuf = loader.get_pixbuf()
pixbuf, w, h = self._get_pixbuf_of_size(pixbuf, size)
-
+
ok, mem = pixbuf.save_to_bufferv("jpeg", ["quality"], ["100"])
except Exception as e:
log.info("Failed to load image using gdk pixbuf, "
@@ -289,7 +298,7 @@ class Base(object):
_('Exception raised while saving thumbnail '
'for image file (see error log for more '
'information)'),
- transient_for=self.chat_control.parent_win.window)
+ transient_for=self.parent_win)
log.error(str(e))
return (mem, alt)
@@ -300,13 +309,13 @@ class Base(object):
return (mem, '')
def _write_file(self, path, data):
- log.info("Writing '%s' of size %d..." % (path, len(data)))
+ log.info("Writing '%s' of size %d...", path, len(data))
try:
with open(path, "wb") as output_file:
output_file.write(data)
output_file.closed
except Exception as e:
- log.error("Failed to write file '%s'!" % path)
+ log.error("Failed to write file '%s'!", path)
raise
def _update_img(self, tuple_arg, url, repl_start, repl_end,
@@ -326,11 +335,13 @@ class Base(object):
# (Gtk textview is NOT threadsafe by itself!!)
def add_to_textview():
try: # textview closed in the meantime etc.
+ at_end = self.textview.at_the_end()
+
buffer_ = repl_start.get_buffer()
iter_ = buffer_.get_iter_at_mark(repl_start)
- buffer_.insert(iter_, "\n")
+ # buffer_.insert(iter_, "\n")
anchor = buffer_.create_child_anchor(iter_)
-
+
# Use url as tooltip for image
img = TextViewImage(anchor, url)
loader = GdkPixbuf.PixbufLoader()
@@ -343,21 +354,23 @@ class Base(object):
eb.show_all()
self.textview.tv.add_child_at_anchor(eb, anchor)
buffer_.delete(iter_,
- buffer_.get_iter_at_mark(repl_end))
+ buffer_.get_iter_at_mark(repl_end))
+
+ if at_end:
+ GLib.idle_add(self.textview.scroll_to_end_iter)
except Exception as ex:
- log.warn("Exception while loading %s: %s" % (str(url), str(ex)))
+ log.warn("Exception while loading %s: %s", url, ex)
return False
# add to mainloop --> make call threadsafe
- GObject.idle_add(add_to_textview)
+ GLib.idle_add(add_to_textview)
except Exception:
# URL is already displayed
- log.error('Could not display image for URL: %s'
- % url)
+ log.error('Could not display image for URL: %s', url)
raise
else:
# If image could not be downloaded, URL is already displayed
- log.error('Could not download image for URL: %s -- %s'
- % (url, alt))
+ log.error('Could not download image for URL: %s -- %s',
+ url, alt)
def _check_mime_size(self, tuple_arg,
url, repl_start, repl_end, filepaths,
@@ -366,23 +379,24 @@ class Base(object):
# Check if mime type is acceptable
if file_mime == '' and file_size == 0:
log.info("Failed to load HEAD Request for URL: '%s'"
- "(see debug log for more info)" % url)
+ "(see debug log for more info)", url)
# URL is already displayed
return
if file_mime.lower() not in ACCEPTED_MIME_TYPES:
- log.info("Not accepted mime type '%s' for URL: '%s'"
- % (file_mime.lower(), url))
+ log.info("Not accepted mime type '%s' for URL: '%s'",
+ file_mime.lower(), url)
# URL is already displayed
return
# Check if file size is acceptable
- if file_size > self.plugin.config['MAX_FILE_SIZE'] or file_size == 0:
- log.info("File size (%s) too big or unknown (zero) for URL: '%s'"
- % (str(file_size), url))
+ max_size = int(self.plugin.config['MAX_FILE_SIZE'])
+ if file_size > max_size or file_size == 0:
+ log.info("File size (%s) too big or unknown (zero) for URL: '%s'",
+ file_size, url)
# URL is already displayed
return
attributes = {'src': url,
- 'max_size': self.plugin.config['MAX_FILE_SIZE'],
+ 'max_size': max_size,
'filepaths': filepaths,
'key': key,
'iv': iv}
@@ -412,7 +426,7 @@ class Base(object):
_('Could not save file'),
_('Exception raised while saving image file'
' (see error log for more information)'),
- transient_for=self.chat_control.parent_win.window)
+ transient_for=self.parent_win)
log.error(str(e))
# Create thumbnail, write it to harddisk and return it
@@ -474,7 +488,7 @@ class Base(object):
xml.get_object('open_file_in_browser_menuitem')
extras_separator = \
xml.get_object('extras_separator')
-
+
if data["encrypted"]:
open_link_in_browser_menuitem.hide()
if app.config.get('autodetect_browser_mailer') \
@@ -507,6 +521,7 @@ class Base(object):
def on_save_as_menuitem_activate(self, menu, data):
filepath = data["filepath"]
original_filename = data["original_filename"]
+
def on_continue(response, target_path):
if response < 0:
return
@@ -572,10 +587,10 @@ class Base(object):
_('You cannot open encrypted files in your '
'browser directly. Try "Open Downloaded File '
'in Browser" instead.'),
- transient_for=self.chat_control.parent_win.window)
+ transient_for=self.parent_win)
else:
helpers.launch_browser_mailer('url', url)
-
+
def on_open_file_in_browser_menuitem_activate(self, menu, data):
if os.name == "nt":
filepath = "file://" + os.path.abspath(data["filepath"])
@@ -587,7 +602,7 @@ class Base(object):
_('Cannot open downloaded file in browser'),
_('You have to set a custom browser executable '
'in your gajim settings for this to work.'),
- transient_for=self.chat_control.parent_win.window)
+ transient_for=self.parent_win)
return
command = app.config.get('custombrowser')
command = helpers.build_command(command, filepath)
@@ -622,8 +637,8 @@ class Base(object):
# right klick
elif event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
menu = self.make_rightclick_menu(event, data)
- #menu.attach_to_widget(self.tv, None)
- #menu.popup(None, None, None, event.button, event.time)
+ # menu.attach_to_widget(self.tv, None)
+ # menu.popup(None, None, None, event.button, event.time)
menu.popup_at_pointer(event)
def disconnect_from_chat_control(self):