Completed
Push — file-actions ( eff3c9...3ab188 )
by Felipe A.
02:39
created

Clipboard.from_request()   B

Complexity

Conditions 4

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 26
rs 8.5806
cc 4
1
#!/usr/bin/env python
2
# -*- coding: UTF-8 -*-
3
4
import os
5
import json
6
import base64
7
import logging
8
import hashlib
9
10
from flask import request
11
from browsepy.compat import range
12
13
14
logger = logging.getLogger(__name__)
15
16
17
class Clipboard(set):
18
    cookie_secret = os.urandom(256)
19
    cookie_sign_name = 'clipboard-signature'
20
    cookie_mode_name = 'clipboard-mode'
21
    cookie_list_name = 'clipboard-{:x}'
22
    cookie_path = '/'
23
    request_cache_field = '_browsepy_file_actions_clipboard_cache'
24
    max_pages = 0xffffffff
25
26
    @classmethod
27
    def count(cls, request=request):
28
        return len(cls.from_request(request))
29
30
    @classmethod
31
    def from_request(cls, request=request):
32
        '''
33
        Create clipboard object from request cookies.
34
        Uses request itself for cache.
35
36
        :param request: optional request, defaults to current flask request
37
        :type request: werkzeug.Request
38
        :returns: clipboard instance
39
        :rtype: Clipboard
40
        '''
41
        cached = getattr(request, cls.request_cache_field, None)
42
        if cached is not None:
43
            return cached
44
        self = cls()
45
        setattr(request, cls.request_cache_field, self)
46
        signature = cls._cookiebytes(cls.cookie_sign_name, request)
47
        data = cls._read_paginated_cookie(request)
48
        mode = cls._cookietext(cls.cookie_mode_name, request)
49
        if cls._signature(data, mode) == signature:
50
            try:
51
                self.mode = mode
52
                self.update(json.loads(base64.b64decode(data).decode('utf-8')))
53
            except BaseException as e:
54
                logger.warn('Bad cookie')
55
        return self
56
57
    @classmethod
58
    def _cookiebytes(cls, name, request=request):
59
        return request.cookies.get(name, '').encode('ascii')
60
61
    @classmethod
62
    def _cookietext(cls, name, request=request):
63
        return request.cookies.get(name, '')
64
65
    @classmethod
66
    def _paginated_cookie_length(cls, page=0):
67
        name_fnc = cls.cookie_list_name.format
68
        return 3990 - len(name_fnc(page) + cls.cookie_path)
69
70
    @classmethod
71
    def _read_paginated_cookie(cls, request=request):
72
        chunks = []
73
        if request:
74
            name_fnc = cls.cookie_list_name.format
75
            for i in range(cls.max_pages):  # 2 ** 32 - 1
76
                cookie = request.cookies.get(name_fnc(i), '').encode('ascii')
77
                chunks.append(cookie)
78
                if len(cookie) < cls._paginated_cookie_length(i):
79
                    break
80
        return b''.join(chunks)
81
82
    @classmethod
83
    def _write_paginated_cookie(cls, data, response):
84
        name_fnc = cls.cookie_list_name.format
85
        start = 0
86
        size = len(data)
87
        for i in range(cls.max_pages):
88
            end = cls._paginated_cookie_length(i)
89
            response.set_cookie(name_fnc(i), data[start:end].decode('ascii'))
90
            start = end
91
            if start > size:  # we need an empty page after start == size
92
                return i
93
        return 0
94
95
    @classmethod
96
    def _delete_paginated_cookie(cls, response, start=0, request=request):
97
        name_fnc = cls.cookie_list_name.format
98
        for i in range(start, cls.max_pages):
99
            name = name_fnc(i)
100
            if name not in request.cookies:
101
                break
102
            response.set_cookie(name, '', expires=0)
103
104
    @classmethod
105
    def _signature(cls, data, method):
106
        data = cls.cookie_secret + method.encode('utf-8') + data
107
        return base64.b64encode(hashlib.sha512(data).digest())
108
109
    def __init__(self, iterable=(), mode='copy'):
110
        self.mode = mode
111
        super(Clipboard, self).__init__(iterable)
112
113
    def to_response(self, response, request=request):
114
        if self:
115
            data = base64.b64encode(json.dumps(list(self)).encode('utf-8'))
116
            signature = self._signature(data, self.mode)
117
            start = self._write_paginated_cookie(data, response) + 1
118
            response.set_cookie(self.cookie_mode_name, self.mode)
119
            response.set_cookie(self.cookie_sign_name, signature)
120
        else:
121
            start = 0
122
            response.set_cookie(self.cookie_mode_name, '', expires=0)
123
            response.set_cookie(self.cookie_sign_name, '', expires=0)
124
        self._delete_paginated_cookie(response, start, request)
125