Example #1
0
def pretty_print(message):
    """Given a CoAP message, reshape its payload into something human-readable.
    The return value is a triple (infos, mime, text) where text represents the
    payload, mime is a type that could be used to syntax-highlight the text
    (not necessarily related to the original mime type, eg. a report of some
    binary data that's shaped like Markdown could use a markdown mime type),
    and some line of infos that give additional data (like the reason for a hex
    dump or the original mime type).

    >>> from aiocoap import Message
    >>> def build(payload, request_cf, response_cf):
    ...     response = Message(payload=payload, content_format=response_cf)
    ...     request = Message(accept=request_cf)
    ...     response.request = request
    ...     return response
    >>> pretty_print(Message(payload=b"Hello", content_format=0))
    ([], 'text/plain;charset=utf8', 'Hello')
    >>> print(pretty_print(Message(payload=b'{"hello":"world"}', content_format=50))[-1])
    {
        "hello": "world"
    }
    >>> # Erroneous inputs still go to the pretty printer as long as they're
    >>> #Unicode
    >>> pretty_print(Message(payload=b'{"hello":"world', content_format=50))
    (['Invalid JSON not re-formated'], 'application/json', '{"hello":"world')
    >>> pretty_print(Message(payload=b'<>,', content_format=40))
    (['Invalid application/link-format content was not re-formatted'], 'application/link-format', '<>,')
    >>> pretty_print(Message(payload=b'a', content_format=60)) # doctest: +ELLIPSIS
    (['Showing hex dump of application/cbor payload: CBOR value is invalid'], 'text/vnd.aiocoap.hexdump', '00000000  61 ...
    """
    infos = []
    info = infos.append

    cf = message.opt.content_format or message.request.opt.accept
    if cf is None:
        content_type = "type unknown"
    elif cf.is_known():
        content_type = cf.media_type
        if cf.encoding != 'identity':
            info("Content format is %s in %s encoding; treating as "
                 "application/octet-stream because decompression is not "
                 "supported yet" % (cf.media_type, cf.encoding))
    else:
        content_type = "type %d" % cf
    category = contenttype.categorize(content_type)

    show_hex = None

    if linkformat is not None and category == 'link-format':
        try:
            decoded = message.payload.decode('utf8')
            try:
                parsed = linkformat.link_header.parse(decoded)
            except linkformat.link_header.ParseException:
                info(
                    "Invalid application/link-format content was not re-formatted"
                )
                return (infos, 'application/link-format', decoded)
            else:
                info("application/link-format content was re-formatted")
                prettyprinted = ",\n".join(str(l) for l in parsed.links)
                return (infos, 'application/link-format', prettyprinted)
        except ValueError:
            # Handled later
            pass

    elif category == 'cbor':
        try:
            parsed = cbor.loads(message.payload)
        except cbor.CBORDecodeError:
            show_hex = "CBOR value is invalid"
        else:
            info("CBOR message shown in naïve Python decoding")
            # Formatting it via Python b/c that's reliably available (as
            # opposed to JSON which might not round-trip well). The repr for
            # tags might still not be parsable, but I think chances of good
            # highlighting are best this way
            #
            # Not sorting dicts to give a more faithful representation of the
            # original CBOR message
            if sys.version_info >= (3, 8):
                printer = pprint.PrettyPrinter(sort_dicts=False)
            else:
                printer = pprint.PrettyPrinter()
            formatted = printer.pformat(parsed)
            return (infos, 'text/x-python3', formatted)

    elif category == 'json':
        try:
            decoded = message.payload.decode('utf8')
        except ValueError:
            pass
        else:
            try:
                parsed = json.loads(decoded)
            except ValueError:
                info("Invalid JSON not re-formated")
                return (infos, 'application/json', decoded)
            else:
                info("JSON re-formated and indented")
                formatted = json.dumps(parsed, indent=4)
                return (infos, 'application/json', formatted)

    # That's about the formats we do for now.

    if show_hex is None:
        try:
            text = message.payload.decode('utf8')
        except UnicodeDecodeError:
            show_hex = "Message can not be parsed as UTF-8"
        else:
            return (infos, 'text/plain;charset=utf8', text)

    info("Showing hex dump of %s payload%s" %
         (content_type if cf is not None else "untyped",
          ": " + show_hex if show_hex is not None else ""))
    data = message.payload
    # Not the most efficient hex dumper, but we won't stream video over
    # this anyway
    formatted = []
    offset = 0
    while data:
        line, data = data[:16], data[16:]

        formatted.append("%08x  " % offset +
                         " ".join("%02x" % line[i] if i < len(line) else "  "
                                  for i in range(8)) + "  " +
                         " ".join("%02x" % line[i] if i < len(line) else "  "
                                  for i in range(8, 16)) + "  |" + "".join(
                                      chr(x) if 32 <= x < 127 else '.'
                                      for x in line) + "|\n")

        offset += len(line)
    if offset % 16 != 0:
        formatted.append("%08x\n" % offset)
    return (infos, MEDIATYPE_HEXDUMP, "".join(formatted))
Example #2
0
async def single_request(args, context=None):
    parser = build_parser()
    options = parser.parse_args(args)

    pretty_print_modules = aiocoap.defaults.prettyprint_missing_modules()
    if pretty_print_modules and \
            (options.color is True or options.pretty_print is True):
        parser.error("Color and pretty printing require the following"
                     " additional module(s) to be installed: %s" %
                     ", ".join(pretty_print_modules))
    if options.color is None:
        options.color = sys.stdout.isatty() and not pretty_print_modules
    if options.pretty_print is None:
        options.pretty_print = sys.stdout.isatty() and not pretty_print_modules

    configure_logging((options.verbose or 0) - (options.quiet or 0))

    try:
        code = getattr(aiocoap.numbers.codes.Code, options.method.upper())
    except AttributeError:
        try:
            code = aiocoap.numbers.codes.Code(int(options.method))
        except ValueError:
            raise parser.error("Unknown method")

    if context is None:
        context = await aiocoap.Context.create_client_context()

    if options.credentials is not None:
        apply_credentials(context, options.credentials, parser.error)

    request = aiocoap.Message(
        code=code, mtype=aiocoap.NON if options.non else aiocoap.CON)
    try:
        request.set_request_uri(options.url)
    except ValueError as e:
        raise parser.error(e)

    if not request.opt.uri_host and not request.unresolved_remote:
        raise parser.error("Request URLs need to be absolute.")

    if options.accept:
        try:
            request.opt.accept = int(options.accept)
        except ValueError:
            try:
                request.opt.accept = aiocoap.numbers.media_types_rev[
                    options.accept]
            except KeyError:
                raise parser.error("Unknown accept type")

    if options.observe:
        request.opt.observe = 0
        observation_is_over = asyncio.Future()

    if options.content_format:
        try:
            request.opt.content_format = int(options.content_format)
        except ValueError:
            try:
                request.opt.content_format = aiocoap.numbers.media_types_rev[
                    options.content_format]
            except KeyError:
                raise parser.error("Unknown content format")

    if options.payload:
        if options.payload.startswith('@'):
            filename = options.payload[1:]
            if filename == "-":
                f = sys.stdin.buffer
            else:
                f = open(filename, 'rb')
            try:
                request.payload = f.read()
            except OSError as e:
                raise parser.error("File could not be opened: %s" % e)
        else:
            if contenttype.categorize(
                    aiocoap.numbers.media_types.get(request.opt.content_format,
                                                    "")) == 'cbor':
                try:
                    import cbor2 as cbor
                except ImportError as e:
                    raise parser.error("CBOR recoding not available (%s)" % e)
                import json
                try:
                    decoded = json.loads(options.payload)
                except json.JSONDecodeError as e:
                    import ast
                    try:
                        decoded = ast.literal_eval(options.payload)
                    except ValueError:
                        raise parser.error(
                            "JSON and Python recoding failed. Make sure quotation marks are escaped from the shell. JSON error: %s"
                            % e)
                request.payload = cbor.dumps(decoded)
            else:
                request.payload = options.payload.encode('utf8')

    if options.payload_initial_szx is not None:
        request.opt.block1 = aiocoap.optiontypes.BlockOption.BlockwiseTuple(
            0,
            False,
            options.payload_initial_szx,
        )

    if options.proxy is None:
        interface = context
    else:
        interface = aiocoap.proxy.client.ProxyForwarder(options.proxy, context)

    try:
        requester = interface.request(request)

        if options.observe:
            requester.observation.register_errback(
                observation_is_over.set_result)
            requester.observation.register_callback(
                lambda data, options=options: incoming_observation(
                    options, data))

        try:
            response_data = await requester.response
        except aiocoap.error.ResolutionError as e:
            print("Name resolution error:", e, file=sys.stderr)
            sys.exit(1)
        except aiocoap.error.NetworkError as e:
            print("Network error:", e, file=sys.stderr)
            sys.exit(1)
        # Fallback while not all backends raise NetworkErrors
        except OSError as e:
            text = str(e)
            if not text:
                text = repr(e)
            if not text:
                # eg ConnectionResetError flying out of a misconfigured SSL server
                text = type(e)
            print("Error:", text, file=sys.stderr)
            sys.exit(1)

        if response_data.code.is_successful():
            present(response_data, options)
        else:
            print(colored(response_data.code, options, 'red'), file=sys.stderr)
            present(response_data, options, file=sys.stderr)
            sys.exit(1)

        if options.observe:
            exit_reason = await observation_is_over
            print("Observation is over: %r" % (exit_reason, ), file=sys.stderr)
    finally:
        if not requester.response.done():
            requester.response.cancel()
        if options.observe and not requester.observation.cancelled:
            requester.observation.cancel()
Example #3
0
def pretty_print(message):
    """Given a CoAP message, reshape its payload into something human-readable.
    The return value is a triple (infos, mime, text) where text represents the
    payload, mime is a type that could be used to syntax-highlight the text
    (not necessarily related to the original mime type, eg. a report of some
    binary data that's shaped like Markdown could use a markdown mime type),
    and some line of infos that give additional data (like the reason for a hex
    dump or the original mime type).
    """
    infos = []
    info = lambda m: infos.append(m)

    cf = message.opt.content_format
    if cf is None:
        cf = message.request.opt.accept
    content_type = media_types.get(cf, "type %s" % cf)
    category = contenttype.categorize(content_type)

    show_hex = None

    if linkformat is not None and category == 'link-format':
        try:
            parsed = linkformat.link_header.parse(message.payload.decode('utf8'))
        except ValueError:
            pass
        else:
            info("application/link-format content was re-formatted")
            prettyprinted = ",\n".join(str(l) for l in parsed.links)
            return (infos, 'application/link-format', prettyprinted)

    elif category == 'cbor':
        try:
            parsed = cbor.loads(message.payload)
        except ValueError:
            show_hex = "CBOR value is invalid"
        else:
            info("CBOR message shown in naïve Python decoding")
            # Formatting it via Python b/c that's reliably available (as
            # opposed to JSON which might not round-trip well). The repr for
            # tags might still not be parsable, but I think chances of good
            # highlighting are best this way
            #
            # Not sorting dicts to give a more faithful representation of the
            # original CBOR message
            if sys.version_info >= (3, 8):
                printer = pprint.PrettyPrinter(sort_dicts=False)
            else:
                printer = pprint.PrettyPrinter()
            formatted = printer.pformat(parsed)
            return (infos, 'text/x-python3', formatted)

    elif category == 'json':
        try:
            parsed = json.loads(message.payload.decode('utf8'))
        except ValueError:
            pass
        else:
            info("JSON re-formated and indented")
            formatted = json.dumps(parsed, indent=4)
            return (infos, 'application/json', formatted)

    # That's about the formats we do for now.

    if show_hex is None:
        try:
            text = message.payload.decode('utf8')
        except UnicodeDecodeError:
            show_hex = "Message can not be parsed as UTF-8"
        else:
            return (infos, 'text/plain;charset=utf8', text)

    info("Showing hex dump of %s payload%s" % (
        content_type if cf is not None else "untyped",
        ": " + show_hex if show_hex is not None else ""))
    data = message.payload
    # Not the most efficient hex dumper, but we won't stream video over
    # this anyway
    formatted = []
    offset = 0
    while data:
        line, data = data[:16], data[16:]

        formatted.append("%08x  " % offset + \
                " ".join("%02x" % line[i] if i < len(line) else "  " for i in range(8)) + "  " + \
                " ".join("%02x" % line[i] if i < len(line) else "  " for i in range(8, 16)) + "  |" + \
                "".join(chr(x) if 32 <= x < 127 else '.' for x in line) + \
                "|\n")

        offset += len(line)
    if offset % 16 != 0:
        formatted.append("%08x\n" % offset)
    return (infos, MEDIATYPE_HEXDUMP, "".join(formatted))