File: gravatar.py

package info (click to toggle)
git-cola 4.13.0-1
  • links: PTS
  • area: main
  • in suites: sid
  • size: 6,480 kB
  • sloc: python: 36,938; sh: 304; makefile: 223; xml: 100; tcl: 62
file content (146 lines) | stat: -rw-r--r-- 5,301 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import time
import hashlib

from qtpy import QtCore
from qtpy import QtGui
from qtpy import QtWidgets
from qtpy import QtNetwork

from . import core
from . import icons
from . import qtutils
from .compat import parse
from .models import prefs
from .widgets import defs


class Gravatar:
    @staticmethod
    def url_for_email(email, imgsize):
        email_hash = md5_hexdigest(email)
        # Python2.6 requires byte strings for urllib2.quote() so we have
        # to force
        default_url = 'https://git-cola.github.io/images/git-64x64.jpg'
        encoded_url = parse.quote(core.encode(default_url), core.encode(''))
        query = '?s=%d&d=%s' % (imgsize, core.decode(encoded_url))
        url = 'https://gravatar.com/avatar/' + email_hash + query
        return url


def md5_hexdigest(value):
    """Return the md5 hexdigest for a value.

    Used for implementing the gravatar API. Not used for security purposes.
    """
    # https://github.com/git-cola/git-cola/issues/1157
    #  ValueError: error:060800A3:
    #   digital envelope routines: EVP_DigestInit_ex: disabled for fips
    #
    # Newer versions of Python, including Centos8's patched Python3.6 and
    # mainline Python 3.9+ have a "usedoforsecurity" parameter which allows us
    # to continue using hashlib.md5().
    encoded_value = core.encode(value)
    result = ''
    try:
        # This could raise ValueError in theory but we always use encoded bytes
        # so that does not happen in practice.
        result = hashlib.md5(encoded_value, usedforsecurity=False).hexdigest()
    except TypeError:
        # Fallback to trying hashlib.md5 directly.
        result = hashlib.md5(encoded_value).hexdigest()
    return core.decode(result)


class GravatarLabel(QtWidgets.QLabel):
    def __init__(self, context, parent=None):
        QtWidgets.QLabel.__init__(self, parent)

        self.context = context
        self.email = None
        self.response = None
        self.timeout = 0
        self.imgsize = defs.medium_icon
        self.pixmaps = {}
        self._default_pixmap_bytes = None

        self.network = QtNetwork.QNetworkAccessManager()
        self.network.finished.connect(self.network_finished)

    def set_email(self, email):
        """Update the author icon based on the specified email"""
        pixmap = self.pixmaps.get(email, None)
        if pixmap is not None:
            self.setPixmap(pixmap)
            return
        if self.timeout > 0 and (int(time.time()) - self.timeout) < (5 * 60):
            self.set_pixmap_from_response()
            return
        if email == self.email and self.response is not None:
            self.set_pixmap_from_response()
            return
        self.email = email
        self.request(email)

    def request(self, email):
        if prefs.enable_gravatar(self.context):
            url = Gravatar.url_for_email(email, self.imgsize)
            self.network.get(QtNetwork.QNetworkRequest(QtCore.QUrl(url)))
        else:
            self.pixmaps[email] = self.set_pixmap_from_response()

    def default_pixmap_as_bytes(self):
        if self._default_pixmap_bytes is None:
            xres = self.imgsize
            pixmap = icons.cola().pixmap(xres)
            byte_array = QtCore.QByteArray()
            buf = QtCore.QBuffer(byte_array)
            buf.open(QtCore.QIODevice.WriteOnly)
            pixmap.save(buf, 'PNG')
            buf.close()
            self._default_pixmap_bytes = byte_array
        else:
            byte_array = self._default_pixmap_bytes
        return byte_array

    def network_finished(self, reply):
        email = self.email

        header = QtCore.QByteArray(b'Location')
        location = core.decode(bytes(reply.rawHeader(header))).strip()
        if location:
            request_location = Gravatar.url_for_email(self.email, self.imgsize)
            relocated = location != request_location
        else:
            relocated = False
        no_error = qtutils.enum_value(QtNetwork.QNetworkReply.NetworkError.NoError)
        reply_error = qtutils.enum_value(reply.error())
        if reply_error == no_error:
            if relocated:
                # We could do get_url(parse.unquote(location)) to
                # download the default image.
                # Save bandwidth by using a pixmap.
                self.response = self.default_pixmap_as_bytes()
            else:
                self.response = reply.readAll()
            self.timeout = 0
        else:
            self.response = self.default_pixmap_as_bytes()
            self.timeout = int(time.time())

        pixmap = self.set_pixmap_from_response()

        # If the email has not changed (e.g. no other requests)
        # then we know that this pixmap corresponds to this specific
        # email address.  We can't blindly trust self.email else
        # we may add cache entries for thee wrong email address.
        url = Gravatar.url_for_email(email, self.imgsize)
        if url == reply.url().toString():
            self.pixmaps[email] = pixmap

    def set_pixmap_from_response(self):
        if self.response is None:
            self.response = self.default_pixmap_as_bytes()
        pixmap = QtGui.QPixmap()
        pixmap.loadFromData(self.response)
        self.setPixmap(pixmap)
        return pixmap