This file is indexed.

/usr/lib/python2.7/dist-packages/webassets/cache.py is in python-webassets 3:0.10.1-2.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
"""Caches are used for multiple things:

    - To speed up asset building. Filter operations every step
      of the way can be cached, so that individual parts of a
      build that haven't changed can be reused.

    - Bundle definitions are cached when a bundle is built so we
      can determine whether they have changed and whether a rebuild
      is required.

This data is not all stored in the same cache necessarily. The
classes in this module provide the "environment.cache" object, but
also serve in other places.
"""

import os
from os import path
import errno
import tempfile
import warnings
from webassets import six
from webassets.merge import BaseHunk
from webassets.filter import Filter, freezedicts
from webassets.utils import md5_constructor, pickle
import types


__all__ = ('FilesystemCache', 'MemoryCache', 'get_cache',)


def make_hashable(data):
    """Ensures ``data`` can be hashed().

    Mostly needs to support dict. The other special types we use
    as hash keys (Hunks, Filters) already have a proper hash() method.

    See also ``make_md5``.

    Note that we do not actually hash the data for the memory cache.
    """
    return freezedicts(data)


def make_md5(*data):
    """Make a md5 hash based on``data``.

    Specifically, this knows about ``Hunk`` objects, and makes sure
    the actual content is hashed.

    This is very conservative, and raises an exception if there are
    data types that it does not explicitly support. This is because
    we had in the past some debugging headaches with the cache not
    working for this very reason.

    MD5 is faster than sha, and we don't care so much about collisions.
    We care enough however not to use hash().
    """
    def walk(obj):
        if isinstance(obj, (tuple, list, frozenset)):
            for item in obj:
                for d in walk(item): yield d
        elif isinstance(obj, (dict)):
            for k in sorted(obj.keys()):
                for d in walk(k): yield d
                for d in walk(obj[k]): yield d
        elif isinstance(obj, BaseHunk):
            yield obj.data().encode('utf-8')
        elif isinstance(obj, int):
            yield str(obj).encode('utf-8')
        elif isinstance(obj, six.text_type):
            yield obj.encode('utf-8')
        elif isinstance(obj, six.binary_type):
            yield obj
        elif hasattr(obj, "id"):
            for i in walk(obj.id()):
                yield i
        elif obj is None:
            yield "None".encode('utf-8')
        elif isinstance(obj, types.FunctionType):
            yield str(hash(obj)).encode('utf-8')
        else:
            raise ValueError('Cannot MD5 type %s' % type(obj))
    md5 = md5_constructor()
    for d in walk(data):
        md5.update(d)
    return md5.hexdigest()


def safe_unpickle(string):
    """Unpickle the string, or return ``None`` if that fails."""
    try:
        return pickle.loads(string)
    except:
        return None


class BaseCache(object):
    """Abstract base class.

    The cache key must be something that is supported by the Python hash()
    function. The cache value may be a string, or anything that can be pickled.

    Since the cache is used for multiple purposes, all webassets-internal code
    should always tag its keys with an id, like so:

        key = ("tag", actual_key)

    One cache instance can only be used safely with a single Environment.
    """

    def get(self, key):
        """Should return the cache contents, or False.
        """
        raise NotImplementedError()

    def set(self, key, value):
        raise NotImplementedError()


class MemoryCache(BaseCache):
    """Caches stuff in the process memory.

    WARNING: Do NOT use this in a production environment, where you
    are likely going to have multiple processes serving the same app!

    Note that the keys are used as-is, not passed through hash() (which is
    a difference: http://stackoverflow.com/a/9022664/15677). However, the
    reason we don't is because the original value is nicer to debug.
    """

    def __init__(self, capacity):
        self.capacity = capacity
        self.keys = []
        self.cache = {}

    def __eq__(self, other):
        """Return equality with the config values that instantiate
        this instance.
        """
        return False == other or \
               None == other or \
               id(self) == id(other)

    def get(self, key):
        key = make_md5(make_hashable(key))
        return self.cache.get(key, None)

    def set(self, key, value):
        key = make_md5(make_hashable(key))
        self.cache[key] = value
        try:
            self.keys.remove(key)
        except ValueError:
            pass
        self.keys.append(key)

        # limit cache to the given capacity
        to_delete = self.keys[0:max(0, len(self.keys)-self.capacity)]
        self.keys = self.keys[len(to_delete):]
        for item in to_delete:
            del self.cache[item]


class FilesystemCache(BaseCache):
    """Uses a temporary directory on the disk.
    """

    V = 2   # We have changed the cache format once

    def __init__(self, directory):
        self.directory = directory

    def __eq__(self, other):
        """Return equality with the config values
        that instantiate this instance.
        """
        return True == other or \
               self.directory == other or \
               id(self) == id(other)

    def get(self, key):
        filename = path.join(self.directory, '%s' % make_md5(self.V, key))
        try:
            f = open(filename, 'rb')
        except IOError as e:
            if e.errno != errno.ENOENT:
                raise
            return None
        try:
            result = f.read()
        finally:
            f.close()

        unpickled = safe_unpickle(result)
        if unpickled is None:
            warnings.warn('Ignoring corrupted cache file %s' % filename)
        return unpickled

    def set(self, key, data):
        md5 = '%s' % make_md5(self.V, key)
        filename = path.join(self.directory, md5)
        fd, temp_filename = tempfile.mkstemp(prefix='.' + md5,
                dir=self.directory)
        try:
            with os.fdopen(fd, 'wb') as f:
                pickle.dump(data, f)
                f.flush()
            os.rename(temp_filename, filename)
        except:
            os.unlink(temp_filename)
            raise


def get_cache(option, ctx):
    """Return a cache instance based on ``option``.
    """
    if not option:
        return None

    if isinstance(option, BaseCache):
        return option
    elif isinstance(option, type) and issubclass(option, BaseCache):
        return option()

    if option is True:
        directory = path.join(ctx.directory, '.webassets-cache')
        # Auto-create the default directory
        if not path.exists(directory):
            os.makedirs(directory)
    else:
        directory = option
    return FilesystemCache(directory)