In Google App Engine, how to check if the key generated by urlsafe is correct?

Suppose I am creating a key from user input. websafe url

key = ndb.Key(urlsafe=some_user_input)

      

How to check if it is valid some_user_input

?

My current experiment shows that the above statement throws an exception ProtocolBufferDecodeError (Unable to merge from string.)

if some_user_input

invalid, but cannot find anything about it from the API. Can anyone confirm this, and point me to a better way to validate user input instead of catching the exception?

Thank you so much!

+3


source to share


1 answer


If you try to create a key with invalid urlsafe parameter

key = ndb.Key(urlsafe='bogus123')

      

you will get an error like

Traceback (most recent call last):
  File "/opt/google/google_appengine/google/appengine/runtime/wsgi.py", line 240, in Handle
    handler = _config_handle.add_wsgi_middleware(self._LoadHandler())
  File "/opt/google/google_appengine/google/appengine/runtime/wsgi.py", line 299, in _LoadHandler
    handler, path, err = LoadObject(self._handler)
  File "/opt/google/google_appengine/google/appengine/runtime/wsgi.py", line 85, in LoadObject
    obj = __import__(path[0])
  File "/home/tim/git/teledap-appengine/main.py", line 10, in <module>
    from src.tim import handlers as handlers_
  File "/home/tim/git/teledap-appengine/src/tim/handlers.py", line 42, in <module>
    class ResetHandler(BaseHandler):
  File "/home/tim/git/teledap-appengine/src/tim/handlers.py", line 47, in ResetHandler
    key = ndb.Key(urlsafe='bogus123')
  File "/opt/google/google_appengine/google/appengine/ext/ndb/key.py", line 212, in __new__
    self.__reference = _ConstructReference(cls, **kwargs)
  File "/opt/google/google_appengine/google/appengine/ext/ndb/utils.py", line 142, in positional_wrapper
    return wrapped(*args, **kwds)
  File "/opt/google/google_appengine/google/appengine/ext/ndb/key.py", line 642, in _ConstructReference
    reference = _ReferenceFromSerialized(serialized)
  File "/opt/google/google_appengine/google/appengine/ext/ndb/key.py", line 773, in _ReferenceFromSerialized
    return entity_pb.Reference(serialized)
  File "/opt/google/google_appengine/google/appengine/datastore/entity_pb.py", line 1710, in __init__
    if contents is not None: self.MergeFromString(contents)
  File "/opt/google/google_appengine/google/net/proto/ProtocolBuffer.py", line 152, in MergeFromString
    self.MergePartialFromString(s)
  File "/opt/google/google_appengine/google/net/proto/ProtocolBuffer.py", line 168, in MergePartialFromString
    self.TryMerge(d)
  File "/opt/google/google_appengine/google/appengine/datastore/entity_pb.py", line 1839, in TryMerge
    d.skipData(tt)
  File "/opt/google/google_appengine/google/net/proto/ProtocolBuffer.py", line 677, in skipData
    raise ProtocolBufferDecodeError, "corrupted"
ProtocolBufferDecodeError: corrupted

      

Interesting here is

File "/opt/google/google_appengine/google/appengine/ext/ndb/key.py", line 773, in _ReferenceFromSerialized
  return entity_pb.Reference(serialized)

      

which is the last code executed in the key.py module :

def _ReferenceFromSerialized(serialized):
  """Construct a Reference from a serialized Reference."""
  if not isinstance(serialized, basestring):
    raise TypeError('serialized must be a string; received %r' % serialized)
  elif isinstance(serialized, unicode):
    serialized = serialized.encode('utf8')
  return entity_pb.Reference(serialized)

      

serialized

there will be a decoded urlsafe string, you can read more about that in the source code link.

another interesting one is the last one:

File "/opt/google/google_appengine/google/appengine/datastore/entity_pb.py",   line 1839, in TryMerge

      



in entity_pb.py module which looks like this

  def TryMerge(self, d):
    while d.avail() > 0:
      tt = d.getVarInt32()
      if tt == 106:
        self.set_app(d.getPrefixedString())
        continue
      if tt == 114:
        length = d.getVarInt32()
        tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length)
        d.skip(length)
        self.mutable_path().TryMerge(tmp)
        continue
      if tt == 162:
        self.set_name_space(d.getPrefixedString())
        continue


      if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError
      d.skipData(tt)

      

where the actual attempt to "concatenate the input into the key" occurs.


In the source code, you can see that not everything can go wrong when creating a key from the urlsafe parameter. First, it checks if the input is a string, and if not, then it TypeError

occurs if it is, but it is not "valid" but really ProtocolBufferDecodeError

.


My current experiment shows that the above statement will throw a ProtocolBufferDecodeError (Cannot be merged from string.) Exception if some_user_input is invalid, but couldn't find anything about it from the API. Can someone kindly confirm this.

Confirmed - we now know that a TypeError can also be raised.

and point me to a better way to validate user input instead of catching an exception?

This is a great way to verify correctness! Why the checks themselves, if they are already made by appengine? A code snippet might look like this (not working code, just an example)

def get(self):
  # first, fetch the user_input from somewhere

  try:
    key = ndb.Key(urlsafe=user_input)
  except TypeError:
    return 'Sorry, only string is allowed as urlsafe input'
  except ProtocolBufferDecodeError:
    return 'Sorry, the urlsafe string seems to be invalid'

      

+9


source







All Articles