git-cola v4.3.1
[git-cola.git] / cola / gravatar.py
blob872b36c9c61c8c1c70409b1733b8d2bea5c74310
1 from __future__ import absolute_import, division, print_function, unicode_literals
2 import time
3 import hashlib
5 from qtpy import QtCore
6 from qtpy import QtGui
7 from qtpy import QtWidgets
8 from qtpy import QtNetwork
10 from . import compat
11 from . import core
12 from . import icons
13 from . import qtutils
14 from .compat import parse
15 from .models import prefs
16 from .widgets import defs
19 class Gravatar(object):
20 @staticmethod
21 def url_for_email(email, imgsize):
22 email_hash = md5_hexdigest(email)
23 # Python2.6 requires byte strings for urllib2.quote() so we have
24 # to force
25 default_url = 'https://git-cola.github.io/images/git-64x64.jpg'
26 encoded_url = parse.quote(core.encode(default_url), core.encode(''))
27 query = '?s=%d&d=%s' % (imgsize, core.decode(encoded_url))
28 url = 'https://gravatar.com/avatar/' + email_hash + query
29 return url
32 def md5_hexdigest(value):
33 """Return the md5 hexdigest for a value.
35 Used for implementing the gravatar API. Not used for security purposes.
36 """
37 encoded_value = core.encode(value)
38 result = ''
39 try:
40 result = hashlib.md5(encoded_value).hexdigest()
41 except ValueError:
42 pass
43 if not result and compat.PY_VERSION >= (3, 6):
44 try:
45 # pylint: disable=unexpected-keyword-arg
46 result = hashlib.md5(encoded_value, usedforsecurity=False).hexdigest()
47 except ValueError:
48 # https://github.com/git-cola/git-cola/issues/1157
49 # ValueError: error:060800A3:
50 # digital envelope routines: EVP_DigestInit_ex: disabled for fips
52 # Newer versions of Python, including Centos8's patched Python3.6 and
53 # mainline Python 3.9+ have a "usedoforsecurity" parameter which allows us
54 # to continue using hashlib.md5().
55 pass
56 return core.decode(result)
59 class GravatarLabel(QtWidgets.QLabel):
60 def __init__(self, context, parent=None):
61 QtWidgets.QLabel.__init__(self, parent)
63 self.context = context
64 self.email = None
65 self.response = None
66 self.timeout = 0
67 self.imgsize = defs.medium_icon
68 self.pixmaps = {}
69 self._default_pixmap_bytes = None
71 self.network = QtNetwork.QNetworkAccessManager()
72 # pylint: disable=no-member
73 self.network.finished.connect(self.network_finished)
75 def set_email(self, email):
76 """Update the author icon based on the specified email"""
77 pixmap = self.pixmaps.get(email, None)
78 if pixmap is not None:
79 self.setPixmap(pixmap)
80 return
81 if self.timeout > 0 and (int(time.time()) - self.timeout) < (5 * 60):
82 self.set_pixmap_from_response()
83 return
84 if email == self.email and self.response is not None:
85 self.set_pixmap_from_response()
86 return
87 self.email = email
88 self.request(email)
90 def request(self, email):
91 if prefs.enable_gravatar(self.context):
92 url = Gravatar.url_for_email(email, self.imgsize)
93 self.network.get(QtNetwork.QNetworkRequest(QtCore.QUrl(url)))
94 else:
95 self.pixmaps[email] = self.set_pixmap_from_response()
97 def default_pixmap_as_bytes(self):
98 if self._default_pixmap_bytes is None:
99 xres = self.imgsize
100 pixmap = icons.cola().pixmap(xres)
101 byte_array = QtCore.QByteArray()
102 buf = QtCore.QBuffer(byte_array)
103 buf.open(QtCore.QIODevice.WriteOnly)
104 pixmap.save(buf, 'PNG')
105 buf.close()
106 self._default_pixmap_bytes = byte_array
107 else:
108 byte_array = self._default_pixmap_bytes
109 return byte_array
111 def network_finished(self, reply):
112 email = self.email
114 header = QtCore.QByteArray(b'Location')
115 location = core.decode(bytes(reply.rawHeader(header))).strip()
116 if location:
117 request_location = Gravatar.url_for_email(self.email, self.imgsize)
118 relocated = location != request_location
119 else:
120 relocated = False
121 no_error = qtutils.enum_value(
122 QtNetwork.QNetworkReply.NetworkError.NoError # pylint: disable=no-member
124 if reply.error() == no_error:
125 if relocated:
126 # We could do get_url(parse.unquote(location)) to
127 # download the default image.
128 # Save bandwidth by using a pixmap.
129 self.response = self.default_pixmap_as_bytes()
130 else:
131 self.response = reply.readAll()
132 self.timeout = 0
133 else:
134 self.response = self.default_pixmap_as_bytes()
135 self.timeout = int(time.time())
137 pixmap = self.set_pixmap_from_response()
139 # If the email has not changed (e.g. no other requests)
140 # then we know that this pixmap corresponds to this specific
141 # email address. We can't blindly trust self.email else
142 # we may add cache entries for thee wrong email address.
143 url = Gravatar.url_for_email(email, self.imgsize)
144 if url == reply.url().toString():
145 self.pixmaps[email] = pixmap
147 def set_pixmap_from_response(self):
148 if self.response is None:
149 self.response = self.default_pixmap_as_bytes()
150 pixmap = QtGui.QPixmap()
151 pixmap.loadFromData(self.response)
152 self.setPixmap(pixmap)
153 return pixmap