This file is indexed.

/usr/lib/python3/dist-packages/protorpc/protourlencode.py is in python3-protorpc-standalone 0.9.1-3.

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
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
#!/usr/bin/env python
#
# Copyright 2010 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

"""URL encoding support for messages types.

Protocol support for URL encoded form parameters.

Nested Fields:
  Nested fields are repesented by dot separated names.  For example, consider
  the following messages:

    class WebPage(Message):

      title = StringField(1)
      tags = StringField(2, repeated=True)

    class WebSite(Message):

      name = StringField(1)
      home = MessageField(WebPage, 2)
      pages = MessageField(WebPage, 3, repeated=True)

  And consider the object:

    page = WebPage()
    page.title = 'Welcome to NewSite 2010'

    site = WebSite()
    site.name = 'NewSite 2010'
    site.home = page

  The URL encoded representation of this constellation of objects is.

    name=NewSite+2010&home.title=Welcome+to+NewSite+2010

  An object that exists but does not have any state can be represented with
  a reference to its name alone with no value assigned to it.  For example:

    page = WebSite()
    page.name = 'My Empty Site'
    page.home = WebPage()

  is represented as:

    name=My+Empty+Site&home=

  This represents a site with an empty uninitialized home page.

Repeated Fields:
  Repeated fields are represented by the name of and the index of each value
  separated by a dash.  For example, consider the following message:

    home = Page()
    home.title = 'Nome'

    news = Page()
    news.title = 'News'
    news.tags = ['news', 'articles']

    instance = WebSite()
    instance.name = 'Super fun site'
    instance.pages = [home, news, preferences]

  An instance of this message can be represented as:

    name=Super+fun+site&page-0.title=Home&pages-1.title=News&...
    pages-1.tags-0=new&pages-1.tags-1=articles

Helper classes:

  URLEncodedRequestBuilder: Used for encapsulating the logic used for building
    a request message from a URL encoded RPC.
"""

__author__ = 'rafek@google.com (Rafe Kaplan)'

import cgi
import re
import urllib

from . import message_types
from . import messages
from . import util

__all__ = ['CONTENT_TYPE',
           'URLEncodedRequestBuilder',
           'encode_message',
           'decode_message',
           ]

CONTENT_TYPE = 'application/x-www-form-urlencoded'

_FIELD_NAME_REGEX = re.compile(r'^([a-zA-Z_][a-zA-Z_0-9]*)(?:-([0-9]+))?$')


class URLEncodedRequestBuilder(object):
  """Helper that encapsulates the logic used for building URL encoded messages.

  This helper is used to map query parameters from a URL encoded RPC to a
  message instance.
  """

  @util.positional(2)
  def __init__(self, message, prefix=''):
    """Constructor.

    Args:
      message: Message instance to build from parameters.
      prefix: Prefix expected at the start of valid parameters.
    """
    self.__parameter_prefix = prefix

    # The empty tuple indicates the root message, which has no path.
    # __messages is a full cache that makes it very easy to look up message
    # instances by their paths.  See make_path for details about what a path
    # is.
    self.__messages = {(): message}

    # This is a cache that stores paths which have been checked for
    # correctness.  Correctness means that an index is present for repeated
    # fields on the path and absent for non-repeated fields.  The cache is
    # also used to check that indexes are added in the right order so that
    # dicontiguous ranges of indexes are ignored.
    self.__checked_indexes = set([()])

  def make_path(self, parameter_name):
    """Parse a parameter name and build a full path to a message value.

    The path of a method is a tuple of 2-tuples describing the names and
    indexes within repeated fields from the root message (the message being
    constructed by the builder) to an arbitrarily nested message within it.

    Each 2-tuple node of a path (name, index) is:
      name: The name of the field that refers to the message instance.
      index: The index within a repeated field that refers to the message
        instance, None if not a repeated field.

    For example, consider:

      class VeryInner(messages.Message):
        ...

      class Inner(messages.Message):

        very_inner = messages.MessageField(VeryInner, 1, repeated=True)

      class Outer(messages.Message):

        inner = messages.MessageField(Inner, 1)

    If this builder is building an instance of Outer, that instance is
    referred to in the URL encoded parameters without a path.  Therefore
    its path is ().

    The child 'inner' is referred to by its path (('inner', None)).

    The first child of repeated field 'very_inner' on the Inner instance
    is referred to by (('inner', None), ('very_inner', 0)).

    Examples:
      # Correct reference to model where nation is a Message, district is
      # repeated Message and county is any not repeated field type.
      >>> make_path('nation.district-2.county')
      (('nation', None), ('district', 2), ('county', None))

      # Field is not part of model.
      >>> make_path('nation.made_up_field')
      None

      # nation field is not repeated and index provided.
      >>> make_path('nation-1')
      None

      # district field is repeated and no index provided.
      >>> make_path('nation.district')
      None

    Args:
      parameter_name: Name of query parameter as passed in from the request.
        in order to make a path, this parameter_name must point to a valid
        field within the message structure.  Nodes of the path that refer to
        repeated fields must be indexed with a number, non repeated nodes must
        not have an index.

    Returns:
      Parsed version of the parameter_name as a tuple of tuples:
        attribute: Name of attribute associated with path.
        index: Postitive integer index when it is a repeated field, else None.
      Will return None if the parameter_name does not have the right prefix,
      does not point to a field within the message structure, does not have
      an index if it is a repeated field or has an index but is not a repeated
      field.
    """
    if parameter_name.startswith(self.__parameter_prefix):
      parameter_name = parameter_name[len(self.__parameter_prefix):]
    else:
      return None

    path = []
    name = []
    message_type = type(self.__messages[()])  # Get root message.

    for item in parameter_name.split('.'):
      # This will catch sub_message.real_message_field.not_real_field
      if not message_type:
        return None

      item_match = _FIELD_NAME_REGEX.match(item)
      if not item_match:
        return None
      attribute = item_match.group(1)
      index = item_match.group(2)
      if index:
        index = int(index)

      try:
        field = message_type.field_by_name(attribute)
      except KeyError:
        return None

      if field.repeated != (index is not None):
        return None

      if isinstance(field, messages.MessageField):
        message_type = field.message_type
      else:
        message_type = None

      # Path is valid so far.  Append node and continue.
      path.append((attribute, index))

    return tuple(path)

  def __check_index(self, parent_path, name, index):
    """Check correct index use and value relative to a given path.

    Check that for a given path the index is present for repeated fields
    and that it is in range for the existing list that it will be inserted
    in to or appended to.

    Args:
      parent_path: Path to check against name and index.
      name: Name of field to check for existance.
      index: Index to check.  If field is repeated, should be a number within
        range of the length of the field, or point to the next item for
        appending.
    """
    # Don't worry about non-repeated fields.
    # It's also ok if index is 0 because that means next insert will append.
    if not index:
      return True

    parent = self.__messages.get(parent_path, None)
    value_list = getattr(parent, name, None)
    # If the list does not exist then the index should be 0.  Since it is
    # not, path is not valid.
    if not value_list:
      return False

    # The index must either point to an element of the list or to the tail.
    return len(value_list) >= index

  def __check_indexes(self, path):
    """Check that all indexes are valid and in the right order.

    This method must iterate over the path and check that all references
    to indexes point to an existing message or to the end of the list, meaning
    the next value should be appended to the repeated field.

    Args:
      path: Path to check indexes for.  Tuple of 2-tuples (name, index).  See
        make_path for more information.

    Returns:
      True if all the indexes of the path are within range, else False.
    """
    if path in self.__checked_indexes:
      return True

    # Start with the root message.
    parent_path = ()

    for name, index in path:
      next_path = parent_path + ((name, index),)
      # First look in the checked indexes cache.
      if next_path not in self.__checked_indexes:
        if not self.__check_index(parent_path, name, index):
          return False
        self.__checked_indexes.add(next_path)

      parent_path = next_path

    return True

  def __get_or_create_path(self, path):
    """Get a message from the messages cache or create it and add it.

    This method will also create any parent messages based on the path.

    When a new instance of a given message is created, it is stored in
    __message by its path.

    Args:
      path: Path of message to get.  Path must be valid, in other words
        __check_index(path) returns true.  Tuple of 2-tuples (name, index).
        See make_path for more information.

    Returns:
      Message instance if the field being pointed to by the path is a
      message, else will return None for non-message fields.
    """
    message = self.__messages.get(path, None)
    if message:
      return message

    parent_path = ()
    parent = self.__messages[()]  # Get the root object

    for name, index in path:
      field = parent.field_by_name(name)
      next_path = parent_path + ((name, index),)
      next_message = self.__messages.get(next_path, None)
      if next_message is None:
        next_message = field.message_type()
        self.__messages[next_path] = next_message
        if not field.repeated:
          setattr(parent, field.name, next_message)
        else:
          list_value = getattr(parent, field.name, None)
          if list_value is None:
            setattr(parent, field.name, [next_message])
          else:
            list_value.append(next_message)

      parent_path = next_path
      parent = next_message

    return parent

  def add_parameter(self, parameter, values):
    """Add a single parameter.

    Adds a single parameter and its value to the request message.

    Args:
      parameter: Query string parameter to map to request.
      values: List of values to assign to request message.

    Returns:
      True if parameter was valid and added to the message, else False.

    Raises:
      DecodeError if the parameter refers to a valid field, and the values
      parameter does not have one and only one value.  Non-valid query
      parameters may have multiple values and should not cause an error.
    """
    path = self.make_path(parameter)

    if not path:
      return False

    # Must check that all indexes of all items in the path are correct before
    # instantiating any of them.  For example, consider:
    #
    #   class Repeated(object):
    #     ...
    #
    #   class Inner(object):
    #
    #     repeated = messages.MessageField(Repeated, 1, repeated=True)
    #
    #   class Outer(object):
    #
    #     inner = messages.MessageField(Inner, 1)
    #
    #   instance = Outer()
    #   builder = URLEncodedRequestBuilder(instance)
    #   builder.add_parameter('inner.repeated')
    #
    #   assert not hasattr(instance, 'inner')
    #
    # The check is done relative to the instance of Outer pass in to the
    # constructor of the builder.  This instance is not referred to at all
    # because all names are assumed to be relative to it.
    #
    # The 'repeated' part of the path is not correct because it is missing an
    # index.  Because it is missing an index, it should not create an instance
    # of Repeated.  In this case add_parameter will return False and have no
    # side effects.
    #
    # A correct path that would cause a new Inner instance to be inserted at
    # instance.inner and a new Repeated instance to be appended to the
    # instance.inner.repeated list would be 'inner.repeated-0'.
    if not self.__check_indexes(path):
      return False

    # Ok to build objects.
    parent_path = path[:-1]
    parent = self.__get_or_create_path(parent_path)
    name, index = path[-1]
    field = parent.field_by_name(name)

    if len(values) != 1:
      raise messages.DecodeError(
          'Found repeated values for field %s.' % field.name)

    value = values[0]

    if isinstance(field, messages.IntegerField):
      converted_value = int(value)
    elif isinstance(field, message_types.DateTimeField):
      try:
        converted_value = util.decode_datetime(value)
      except ValueError as e:
        raise messages.DecodeError(e)
    elif isinstance(field, messages.MessageField):
      # Just make sure it's instantiated.  Assignment to field or
      # appending to list is done in __get_or_create_path.
      self.__get_or_create_path(path)
      return True
    elif isinstance(field, messages.StringField):
      converted_value = value.decode('utf-8')
    elif isinstance(field, messages.BooleanField):
      converted_value = value.lower() == 'true' and True or False
    else:
      try:
        converted_value = field.type(value)
      except TypeError:
        raise messages.DecodeError('Invalid enum value "%s"' % value)

    if field.repeated:
      value_list = getattr(parent, field.name, None)
      if value_list is None:
        setattr(parent, field.name, [converted_value])
      else:
        if index == len(value_list):
          value_list.append(converted_value)
        else:
          # Index should never be above len(value_list) because it was
          # verified during the index check above.
          value_list[index] = converted_value
    else:
      setattr(parent, field.name, converted_value)

    return True


@util.positional(1)
def encode_message(message, prefix=''):
  """Encode Message instance to url-encoded string.

  Args:
    message: Message instance to encode in to url-encoded string.
    prefix: Prefix to append to field names of contained values.

  Returns:
    String encoding of Message in URL encoded format.

  Raises:
    messages.ValidationError if message is not initialized.
  """
  message.check_initialized()

  parameters = []
  def build_message(parent, prefix):
    """Recursively build parameter list for URL response.

    Args:
      parent: Message to build parameters for.
      prefix: Prefix to append to field names of contained values.

    Returns:
      True if some value of parent was added to the parameters list,
      else False, meaning the object contained no values.
    """
    has_any_values = False
    for field in sorted(parent.all_fields(), key=lambda f: f.number):
      next_value = parent.get_assigned_value(field.name)
      if next_value is None:
        continue

      # Found a value.  Ultimate return value should be True.
      has_any_values = True

      # Normalize all values in to a list.
      if not field.repeated:
        next_value = [next_value]

      for index, item in enumerate(next_value):
        # Create a name with an index if it is a repeated field.
        if field.repeated:
          field_name = '%s%s-%s' % (prefix, field.name, index)
        else:
          field_name = prefix + field.name

        if isinstance(field, message_types.DateTimeField):
          # DateTimeField stores its data as a RFC 3339 compliant string.
          parameters.append((field_name, item.isoformat()))
        elif isinstance(field, messages.MessageField):
          # Message fields must be recursed in to in order to construct
          # their component parameter values.
          if not build_message(item, field_name + '.'):
            # The nested message is empty.  Append an empty value to
            # represent it.
            parameters.append((field_name, ''))
        elif isinstance(field, messages.BooleanField):
          parameters.append((field_name, item and 'true' or 'false'))
        else:
          if isinstance(item, unicode):
            item = item.encode('utf-8')
          parameters.append((field_name, str(item)))

    return has_any_values

  build_message(message, prefix)

  # Also add any unrecognized values from the decoded string.
  for key in message.all_unrecognized_fields():
    values, _ = message.get_unrecognized_field_info(key)
    if not isinstance(values, (list, tuple)):
      values = (values,)
    for value in values:
      parameters.append((key, value))

  return urllib.urlencode(parameters)


def decode_message(message_type, encoded_message, **kwargs):
  """Decode urlencoded content to message.

  Args:
    message_type: Message instance to merge URL encoded content into.
    encoded_message: URL encoded message.
    prefix: Prefix to append to field names of contained values.

  Returns:
    Decoded instance of message_type.
  """
  message = message_type()
  builder = URLEncodedRequestBuilder(message, **kwargs)
  arguments = cgi.parse_qs(encoded_message, keep_blank_values=True)
  for argument, values in sorted(arguments.iteritems()):
    added = builder.add_parameter(argument, values)
    # Save off any unknown values, so they're still accessible.
    if not added:
      message.set_unrecognized_field(argument, values, messages.Variant.STRING)
  message.check_initialized()
  return message