This file is indexed.

/usr/lib/python3/dist-packages/icalendar/parser.py is in python3-icalendar 3.8-1.

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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# -*- coding: utf-8 -*-
"""This module parses and generates contentlines as defined in RFC 2445
(iCalendar), but will probably work for other MIME types with similar syntax.
Eg. RFC 2426 (vCard)

It is stupid in the sense that it treats the content purely as strings. No type
conversion is attempted.
"""
from icalendar import compat
from icalendar.caselessdict import CaselessDict
from icalendar.parser_tools import DEFAULT_ENCODING
from icalendar.parser_tools import SEQUENCE_TYPES
from icalendar.parser_tools import to_unicode

import re


def escape_char(text):
    """Format value according to iCalendar TEXT escaping rules.
    """
    assert isinstance(text, (compat.unicode_type, compat.bytes_type))
    # NOTE: ORDER MATTERS!
    return text.replace(r'\N', '\n')\
               .replace('\\', '\\\\')\
               .replace(';', r'\;')\
               .replace(',', r'\,')\
               .replace('\r\n', r'\n')\
               .replace('\n', r'\n')


def unescape_char(text):
    assert isinstance(text, (compat.unicode_type, compat.bytes_type))
    # NOTE: ORDER MATTERS!
    if isinstance(text, compat.unicode_type):
        return text.replace(u'\\N', u'\\n')\
                   .replace(u'\r\n', u'\n')\
                   .replace(u'\\n', u'\n')\
                   .replace(u'\\,', u',')\
                   .replace(u'\\;', u';')\
                   .replace(u'\\\\', u'\\')
    elif isinstance(text, compat.bytes_type):
        return text.replace(b'\N', b'\n')\
                   .replace(b'\r\n', b'\n')\
                   .replace(b'\n', b'\n')\
                   .replace(b'\,', b',')\
                   .replace(b'\;', b';')\
                   .replace(b'\\\\', b'\\')


def tzid_from_dt(dt):
    tzid = None
    if hasattr(dt.tzinfo, 'zone'):
        tzid = dt.tzinfo.zone  # pytz implementation
    elif hasattr(dt.tzinfo, 'tzname'):
        try:
            tzid = dt.tzinfo.tzname(dt)  # dateutil implementation
        except AttributeError:
            # No tzid available
            pass
    return tzid


def foldline(line, limit=75, fold_sep=u'\r\n '):
    """Make a string folded as defined in RFC5545
    Lines of text SHOULD NOT be longer than 75 octets, excluding the line
    break.  Long content lines SHOULD be split into a multiple line
    representations using a line "folding" technique.  That is, a long
    line can be split between any two characters by inserting a CRLF
    immediately followed by a single linear white-space character (i.e.,
    SPACE or HTAB).
    """
    assert isinstance(line, compat.unicode_type)
    assert u'\n' not in line

    ret_line = u''
    byte_count = 0
    for char in line:
        char_byte_len = len(char.encode(DEFAULT_ENCODING))
        byte_count += char_byte_len
        if byte_count >= limit:
            ret_line += fold_sep
            byte_count = char_byte_len
        ret_line += char

    return ret_line


#################################################################
# Property parameter stuff

def param_value(value):
    """Returns a parameter value.
    """
    if isinstance(value, SEQUENCE_TYPES):
        return q_join(value)
    return dquote(value)


# Could be improved

# [\w-] because of the iCalendar RFC
# \. because of the vCard RFC
NAME = re.compile('[\w\.-]+')

UNSAFE_CHAR = re.compile('[\x00-\x08\x0a-\x1f\x7F",:;]')
QUNSAFE_CHAR = re.compile('[\x00-\x08\x0a-\x1f\x7F"]')
FOLD = re.compile(b'(\r?\n)+[ \t]')
uFOLD = re.compile(u'(\r?\n)+[ \t]')
NEWLINE = re.compile(r'\r?\n')


def validate_token(name):
    match = NAME.findall(name)
    if len(match) == 1 and name == match[0]:
        return
    raise ValueError(name)


def validate_param_value(value, quoted=True):
    validator = QUNSAFE_CHAR if quoted else UNSAFE_CHAR
    if validator.findall(value):
        raise ValueError(value)


# chars presence of which in parameter value will be cause the value
# to be enclosed in double-quotes
QUOTABLE = re.compile("[,;: ’']")


def dquote(val):
    """Enclose parameter values containing [,;:] in double quotes.
    """
    # a double-quote character is forbidden to appear in a parameter value
    # so replace it with a single-quote character
    val = val.replace('"', "'")
    if QUOTABLE.search(val):
        return '"%s"' % val
    return val


# parsing helper
def q_split(st, sep=','):
    """Splits a string on char, taking double (q)uotes into considderation.
    """
    result = []
    cursor = 0
    length = len(st)
    inquote = 0
    for i in range(length):
        ch = st[i]
        if ch == '"':
            inquote = not inquote
        if not inquote and ch == sep:
            result.append(st[cursor:i])
            cursor = i + 1
        if i + 1 == length:
            result.append(st[cursor:])
    return result


def q_join(lst, sep=','):
    """Joins a list on sep, quoting strings with QUOTABLE chars.
    """
    return sep.join(dquote(itm) for itm in lst)


class Parameters(CaselessDict):
    """Parser and generator of Property parameter strings. It knows nothing of
    datatypes. Its main concern is textual structure.
    """

    def params(self):
        """In rfc2445 keys are called parameters, so this is to be consitent
        with the naming conventions.
        """
        return self.keys()

# TODO?
# Later, when I get more time... need to finish this off now. The last major
# thing missing.
#   def _encode(self, name, value, cond=1):
#       # internal, for conditional convertion of values.
#       if cond:
#           klass = types_factory.for_property(name)
#           return klass(value)
#       return value
#
#   def add(self, name, value, encode=0):
#       "Add a parameter value and optionally encode it."
#       if encode:
#           value = self._encode(name, value, encode)
#       self[name] = value
#
#   def decoded(self, name):
#       "returns a decoded value, or list of same"

    def to_ical(self, sorted=True):
        result = []
        items = list(self.items())
        if sorted:
            items.sort()

        for key, value in items:
            value = param_value(value)
            if isinstance(value, compat.unicode_type):
                value = value.encode(DEFAULT_ENCODING)
            # CaselessDict keys are always unicode
            key = key.upper().encode(DEFAULT_ENCODING)
            result.append(key + b'=' + value)
        return b';'.join(result)

    @classmethod
    def from_ical(cls, st, strict=False):
        """Parses the parameter format from ical text format."""

        # parse into strings
        result = cls()
        for param in q_split(st, ';'):
            try:
                key, val = q_split(param, '=')
                validate_token(key)
                # Property parameter values that are not in quoted
                # strings are case insensitive.
                vals = []
                for v in q_split(val, ','):
                    if v.startswith('"') and v.endswith('"'):
                        v = v.strip('"')
                        validate_param_value(v, quoted=True)
                        vals.append(v)
                    else:
                        validate_param_value(v, quoted=False)
                        if strict:
                            vals.append(v.upper())
                        else:
                            vals.append(v)
                if not vals:
                    result[key] = val
                else:
                    if len(vals) == 1:
                        result[key] = vals[0]
                    else:
                        result[key] = vals
            except ValueError as exc:
                raise ValueError('%r is not a valid parameter string: %s'
                                 % (param, exc))
        return result


def escape_string(val):
    # '%{:02X}'.format(i)
    return val.replace(r'\,', '%2C').replace(r'\:', '%3A')\
              .replace(r'\;', '%3B').replace(r'\\', '%5C')


def unescape_string(val):
    return val.replace('%2C', ',').replace('%3A', ':')\
              .replace('%3B', ';').replace('%5C', '\\')


def unescape_list_or_string(val):
    if isinstance(val, list):
        return [unescape_string(s) for s in val]
    else:
        return unescape_string(val)


#########################################
# parsing and generation of content lines

class Contentline(compat.unicode_type):
    """A content line is basically a string that can be folded and parsed into
    parts.
    """
    def __new__(cls, value, strict=False, encoding=DEFAULT_ENCODING):
        value = to_unicode(value, encoding=encoding)
        assert u'\n' not in value, ('Content line can not contain unescaped '
                                    'new line characters.')
        self = super(Contentline, cls).__new__(cls, value)
        self.strict = strict
        return self

    @classmethod
    def from_parts(cls, name, params, values, sorted=True):
        """Turn a parts into a content line.
        """
        assert isinstance(params, Parameters)
        if hasattr(values, 'to_ical'):
            values = values.to_ical()
        else:
            values = vText(values).to_ical()
        # elif isinstance(values, basestring):
        #    values = escape_char(values)

        # TODO: after unicode only, remove this
        # Convert back to unicode, after to_ical encoded it.
        name = to_unicode(name)
        values = to_unicode(values)
        if params:
            params = to_unicode(params.to_ical(sorted=sorted))
            return cls(u'%s;%s:%s' % (name, params, values))
        return cls(u'%s:%s' % (name, values))

    def parts(self):
        """Split the content line up into (name, parameters, values) parts.
        """
        try:
            st = escape_string(self)
            name_split = None
            value_split = None
            in_quotes = False
            for i, ch in enumerate(st):
                if not in_quotes:
                    if ch in ':;' and not name_split:
                        name_split = i
                    if ch == ':' and not value_split:
                        value_split = i
                if ch == '"':
                    in_quotes = not in_quotes
            name = unescape_string(st[:name_split])
            if not name:
                raise ValueError('Key name is required')
            validate_token(name)
            if not name_split or name_split + 1 == value_split:
                raise ValueError('Invalid content line')
            params = Parameters.from_ical(st[name_split + 1: value_split],
                                          strict=self.strict)
            params = Parameters(
                (unescape_string(key), unescape_list_or_string(value))
                for key, value in compat.iteritems(params)
            )
            values = unescape_string(st[value_split + 1:])
            return (name, params, values)
        except ValueError as exc:
            raise ValueError(
                u"Content line could not be parsed into parts: %r: %s"
                % (self, exc)
            )

    @classmethod
    def from_ical(cls, ical, strict=False):
        """Unfold the content lines in an iCalendar into long content lines.
        """
        ical = to_unicode(ical)
        # a fold is carriage return followed by either a space or a tab
        return cls(uFOLD.sub('', ical), strict=strict)

    def to_ical(self):
        """Long content lines are folded so they are less than 75 characters
        wide.
        """
        return foldline(self).encode(DEFAULT_ENCODING)


class Contentlines(list):
    """I assume that iCalendar files generally are a few kilobytes in size.
    Then this should be efficient. for Huge files, an iterator should probably
    be used instead.
    """
    def to_ical(self):
        """Simply join self.
        """
        return b'\r\n'.join(line.to_ical() for line in self if line) + b'\r\n'

    @classmethod
    def from_ical(cls, st):
        """Parses a string into content lines.
        """
        st = to_unicode(st)
        try:
            # a fold is carriage return followed by either a space or a tab
            unfolded = uFOLD.sub('', st)
            lines = cls(Contentline(line) for
                        line in unfolded.splitlines() if line)
            lines.append('')  # '\r\n' at the end of every content line
            return lines
        except:
            raise ValueError('Expected StringType with content lines')


# XXX: what kind of hack is this? import depends to be at end
from icalendar.prop import vText