pycom / pycom-micropython-sigfox

A fork of MicroPython with the ESP32 port customized to run on Pycom's IoT multi-network modules.
MIT License
198 stars 167 forks source link

ujson.dumps() does not escape <>'s #530

Closed pd-giz-dave closed 3 years ago

pd-giz-dave commented 3 years ago

This is on a FiPy with firmware: "Pycom MicroPython 1.20.2.r4 [v1.11-ffb0e1c] on 2021-01-12; FiPy with ESP32"

os.uname() is: "(sysname='FiPy', nodename='FiPy', release='1.20.2.r4', version='v1.11-ffb0e1c on 2021-01-12', machine='FiPy with ESP32', lorawan='1.0.2', sigfox='1.0.1', pybytes='1.6.1')"

To illustrate, do this in the repl:

import sys 1/0 # to generate an exception used to create a tuple object with '<' characters in it e = sys.exc_info() # e = (<class 'ZeroDivisionError'>, ZeroDivisionError('divide by zero',), None) ee = ujson.dumps(e) # ee = '[<class \'ZeroDivisionError\'>, ["divide by zero"], null]'

ee is not legal json, browsers choke on the '<' in the second character and the later '>'

amotl commented 3 years ago

Hi Dave,

while you are right that ujson apparently does a bad job on trying to serialize an exception object to JSON instead of rejecting that right away, if you really want to do that, you should approach a different strategy.

CPython

CPython will reject trying to serialize an Exception object.

import json

try:
    1/0
except Exception as ex:
    response = json.dumps(ex)
    print(response)
$ python json_exception.py
Traceback (most recent call last):
  File "json_exception.py", line 4, in <module>
    1/0
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "json_exception.py", line 6, in <module>
    response = json.dumps(ex)
  File "/usr/local/opt/python@3.8/Frameworks/Python.framework/Versions/3.8/lib/python3.8/json/__init__.py", line 231, in dumps
    return _default_encoder.encode(obj)
  File "/usr/local/opt/python@3.8/Frameworks/Python.framework/Versions/3.8/lib/python3.8/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/local/opt/python@3.8/Frameworks/Python.framework/Versions/3.8/lib/python3.8/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/usr/local/opt/python@3.8/Frameworks/Python.framework/Versions/3.8/lib/python3.8/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type ZeroDivisionError is not JSON serializable

CPython + MicroPython

Instead, you might approach serializing Exception messages like this. I confirmed it works on CPython, but it might also work on MicroPython.

import json

try:
    1/0
except Exception as ex:
    errordata = {"message": str(ex)}
    response = json.dumps(errordata)
    print(response)

With kind regards, Andreas.

amotl commented 3 years ago

See also https://github.com/micropython/micropython/issues/1719 for more background of (why) MicroPython is not able to raise an exception when the object is not serializable.

@pfalcon also notes:

It also should be pointed out that MicroPython provides the ujson module, which can't be compared one-to-one to CPython.

pd-giz-dave commented 3 years ago

Thanks for the insight. I'm new to Micropython (and Python) so finding it difficult to distinguish between my ignorance and bugs. I resolved my particular issue in the end like this:

except Exception as e: ee = sys.exc_info()[1]

Which gave me the exception name as a string.