Exemplo n.º 1
0
    def to_python(cls, data):
        name = data.get('name')
        if not name:
            raise InterfaceValidationError("No 'name' value")

        version = data.get('version')
        if not version:
            raise InterfaceValidationError("No 'version' value")

        integrations = data.get('integrations')
        if integrations and not isinstance(integrations, list):
            raise InterfaceValidationError("'integrations' must be a list")

        packages = data.get('packages')
        if packages and not isinstance(packages, list):
            raise InterfaceValidationError("'packages' must be a list")

        kwargs = {
            'name': trim(name, 128),
            'version': trim(version, 128),
            'integrations': integrations,
            'packages': packages,
        }

        return cls(**kwargs)
Exemplo n.º 2
0
def merge_symbolicated_frame(new_frame, sfrm, platform=None):
    if sfrm.get('function'):
        raw_func = trim(sfrm['function'], 256)
        func = trim(trim_function_name(sfrm['function'], platform), 256)

        # if function and raw function match, we can get away without
        # storing a raw function
        if func == raw_func:
            new_frame['function'] = raw_func
        # otherwise we store both
        else:
            new_frame['raw_function'] = raw_func
            new_frame['function'] = func
    if sfrm.get('instruction_addr'):
        new_frame['instruction_addr'] = sfrm['instruction_addr']
    if sfrm.get('symbol'):
        new_frame['symbol'] = sfrm['symbol']
    if sfrm.get('abs_path'):
        new_frame['abs_path'] = sfrm['abs_path']
        new_frame['filename'] = posixpath.basename(sfrm['abs_path'])
    if sfrm.get('filename'):
        new_frame['filename'] = sfrm['filename']
    if sfrm.get('lineno'):
        new_frame['lineno'] = sfrm['lineno']
    if sfrm.get('colno'):
        new_frame['colno'] = sfrm['colno']
    if sfrm.get('package'):
        new_frame['package'] = sfrm['package']
    if sfrm.get('trust'):
        new_frame['trust'] = sfrm['trust']
    if sfrm.get('status'):
        frame_meta = new_frame.setdefault('data', {})
        frame_meta['symbolicator_status'] = sfrm['status']
Exemplo n.º 3
0
    def to_python(cls, data):
        data = data.copy()

        extra_data = data.pop('data', data)
        if not isinstance(extra_data, dict):
            extra_data = {}

        try:
            name = trim(data.pop('name'), 64)
        except KeyError:
            raise InterfaceValidationError("Missing or invalid value for 'name'")

        try:
            version = trim(data.pop('version'), 64)
        except KeyError:
            raise InterfaceValidationError("Missing or invalid value for 'version'")

        build = trim(data.pop('build', None), 64)

        kwargs = {
            'name': name,
            'version': version,
            'build': build,
            'data': trim_dict(data),
        }
        kwargs['data'] = trim_dict(data)
        return cls(**kwargs)
Exemplo n.º 4
0
    def to_python(cls, data):
        formatted = stringify(data.get('formatted'))
        message = stringify(data.get('message'))
        if formatted is None and message is None:
            raise InterfaceValidationError("No message present")

        params = data.get('params')
        if isinstance(params, (list, tuple)):
            params = tuple(p for p in params)
        elif isinstance(params, dict):
            params = {k: v for k, v in six.iteritems(params)}
        else:
            params = ()

        if formatted is None and params:
            try:
                if '%' in message:
                    formatted = message % params
                elif '{}' in message and isinstance(params, tuple):
                    formatted = message.format(*params)
                # NB: Named newstyle arguments were never supported
            except Exception:
                pass

        if formatted is None or message == formatted:
            formatted = message
            message = None

        return cls(
            formatted=trim(formatted, settings.SENTRY_MAX_MESSAGE_LENGTH),
            message=trim(message, settings.SENTRY_MAX_MESSAGE_LENGTH),
            params=trim(params, 1024),
        )
Exemplo n.º 5
0
    def normalize_crumb(cls, crumb):
        ty = crumb.get('type') or 'default'
        ts = parse_new_timestamp(crumb.get('timestamp'))
        if ts is None:
            raise InterfaceValidationError('Unable to determine timestamp '
                                           'for crumb')

        rv = {
            'type': ty,
            'timestamp': to_timestamp(ts),
        }

        level = crumb.get('level')
        if level not in (None, 'info'):
            rv['level'] = level

        msg = crumb.get('message')
        if msg is not None:
            rv['message'] = trim(unicode(msg), 4096)

        category = crumb.get('category')
        if category is not None:
            rv['category'] = trim(unicode(category), 256)

        event_id = crumb.get('event_id')
        if event_id is not None:
            rv['event_id'] = event_id

        if 'data' in crumb:
            rv['data'] = trim(crumb['data'], 4096)

        return rv
Exemplo n.º 6
0
    def normalize_crumb(cls, crumb):
        ty = crumb.get('type') or 'default'
        ts = parse_timestamp(crumb.get('timestamp'))
        if ts is None:
            raise InterfaceValidationError('Unable to determine timestamp '
                                           'for crumb')

        rv = {
            'type': ty,
            'timestamp': to_timestamp(ts),
        }

        level = crumb.get('level')
        if level not in (None, 'info'):
            rv['level'] = level

        msg = crumb.get('message')
        if msg is not None:
            rv['message'] = trim(six.text_type(msg), 4096)

        category = crumb.get('category')
        if category is not None:
            rv['category'] = trim(six.text_type(category), 256)

        event_id = crumb.get('event_id')
        if event_id is not None:
            rv['event_id'] = event_id

        if crumb.get('data'):
            for key, value in six.iteritems(crumb['data']):
                if not isinstance(value, six.string_types):
                    crumb['data'][key] = json.dumps(value)
            rv['data'] = trim(crumb['data'], 4096)

        return rv
Exemplo n.º 7
0
    def to_python(cls, data, rust_renormalized=RUST_RENORMALIZED_DEFAULT):
        if rust_renormalized:
            for key in (
                'abs_path',
                'filename',
                'context_line',
                'lineno',
                'pre_context',
                'post_context',
            ):
                data.setdefault(key, None)
            return cls(**data)

        is_valid, errors = validate_and_default_interface(data, cls.path)
        if not is_valid:
            raise InterfaceValidationError("Invalid template")

        kwargs = {
            'abs_path': trim(data.get('abs_path', None), 256),
            'filename': trim(data.get('filename', None), 256),
            'context_line': trim(data.get('context_line', None), 256),
            'lineno': int(data['lineno']) if data.get('lineno', None) is not None else None,
            # TODO(dcramer): trim pre/post_context
            'pre_context': data.get('pre_context'),
            'post_context': data.get('post_context'),
        }
        return cls(**kwargs)
Exemplo n.º 8
0
    def to_python(cls, data, has_system_frames=None):
        if not (data.get('type') or data.get('value')):
            raise InterfaceValidationError("No 'type' or 'value' present")

        if data.get('stacktrace') and data['stacktrace'].get('frames'):
            stacktrace = Stacktrace.to_python(
                data['stacktrace'],
                has_system_frames=has_system_frames,
            )
        else:
            stacktrace = None

        type = data.get('type')
        value = data.get('value')
        if not type and ':' in value.split(' ', 1)[0]:
            type, value = value.split(':', 1)
            # in case of TypeError: foo (no space)
            value = value.strip()

        if value is not None and not isinstance(value, basestring):
            value = json.dumps(value)
        value = trim(value, 4096)

        kwargs = {
            'type': trim(type, 128),
            'value': value,
            'module': trim(data.get('module'), 128),
            'stacktrace': stacktrace,
        }

        return cls(**kwargs)
Exemplo n.º 9
0
    def to_python(cls, data, rust_renormalized=RUST_RENORMALIZED_DEFAULT):
        if rust_renormalized:
            for key in (
                'name',
                'version',
                'integrations',
                'packages',
            ):
                data.setdefault(key, None)

            return cls(**data)

        name = data.get('name')
        version = data.get('version')

        integrations = data.get('integrations')
        if integrations and not isinstance(integrations, list):
            raise InterfaceValidationError("'integrations' must be a list")

        packages = data.get('packages')
        if packages and not isinstance(packages, list):
            raise InterfaceValidationError("'packages' must be a list")

        kwargs = {
            'name': trim(name, 128),
            'version': trim(version, 128),
            'integrations': integrations,
            'packages': packages,
        }

        return cls(**kwargs)
Exemplo n.º 10
0
    def to_python(cls, data):
        data = data.copy()

        extra_data = data.pop("data", data)
        if not isinstance(extra_data, dict):
            extra_data = {}

        ident = trim(data.pop("id", None), 128)
        if ident:
            ident = unicode(ident)
        try:
            email = trim(validate_email(data.pop("email", None), False), 128)
        except ValueError:
            raise InterfaceValidationError("Invalid value for 'email'")
        username = trim(data.pop("username", None), 128)
        if username:
            username = unicode(username)

        try:
            ip_address = validate_ip(data.pop("ip_address", None), False)
        except ValueError:
            raise InterfaceValidationError("Invalid value for 'ip_address'")

        # TODO(dcramer): patch in fix to deal w/ old data but not allow new
        # if not (ident or email or username or ip_address):
        #     raise ValueError('No identifying value')

        kwargs = {"id": ident, "email": email, "username": username, "ip_address": ip_address}

        kwargs["data"] = trim_dict(extra_data)
        return cls(**kwargs)
Exemplo n.º 11
0
    def to_python(cls, data):
        abs_path = data.get('abs_path')
        filename = data.get('filename')

        if not abs_path:
            abs_path = filename

        if not filename:
            filename = abs_path

        if abs_path and is_url(abs_path):
            urlparts = urlparse(abs_path)
            if urlparts.path:
                filename = urlparts.path

        assert filename or data.get('function') or data.get('module')

        context_locals = data.get('vars') or {}
        if isinstance(context_locals, (list, tuple)):
            context_locals = dict(enumerate(context_locals))
        elif not isinstance(context_locals, dict):
            context_locals = {}
        context_locals = trim_dict(context_locals)

        # extra data is used purely by internal systems,
        # so we dont trim it
        extra_data = data.get('data') or {}
        if isinstance(extra_data, (list, tuple)):
            extra_data = dict(enumerate(extra_data))

        kwargs = {
            'abs_path': trim(abs_path, 256),
            'filename': trim(filename, 256),
            'module': trim(data.get('module'), 256),
            'function': trim(data.get('function'), 256),
            'in_app': validate_bool(data.get('in_app'), False),
            'context_line': trim(data.get('context_line'), 256),
            # TODO(dcramer): trim pre/post_context
            'pre_context': data.get('pre_context'),
            'post_context': data.get('post_context'),
            'vars': context_locals,
            'data': extra_data,
            'errors': data.get('errors'),
        }

        if data.get('lineno') is not None:
            lineno = int(data['lineno'])
            if lineno < 0:
                lineno = None
            kwargs['lineno'] = lineno
        else:
            kwargs['lineno'] = None

        if data.get('colno') is not None:
            kwargs['colno'] = int(data['colno'])
        else:
            kwargs['colno'] = None

        return cls(**kwargs)
Exemplo n.º 12
0
    def to_python(cls, data):
        if not data.get('url'):
            raise InterfaceValidationError("No value for 'url'")

        kwargs = {}

        if data.get('method'):
            method = data['method'].upper()
            if method not in HTTP_METHODS:
                raise InterfaceValidationError("Invalid value for 'method'")
            kwargs['method'] = method
        else:
            kwargs['method'] = None

        scheme, netloc, path, query_bit, fragment_bit = urlsplit(data['url'])

        query_string = data.get('query_string') or query_bit
        if query_string:
            # if querystring was a dict, convert it to a string
            if isinstance(query_string, dict):
                query_string = urlencode([(to_bytes(k), to_bytes(v))
                                          for k, v in query_string.items()])
            else:
                query_string = query_string
                if query_string[0] == '?':
                    # remove '?' prefix
                    query_string = query_string[1:]
            kwargs['query_string'] = trim(query_string, 4096)
        else:
            kwargs['query_string'] = ''

        fragment = data.get('fragment') or fragment_bit

        cookies = data.get('cookies')
        # if cookies were [also] included in headers we
        # strip them out
        headers = data.get('headers')
        if headers:
            headers, cookie_header = format_headers(headers)
            if not cookies and cookie_header:
                cookies = cookie_header
        else:
            headers = ()

        body = data.get('data')
        if isinstance(body, dict):
            body = json.dumps(body)

        if body:
            body = trim(body, settings.SENTRY_MAX_HTTP_BODY_SIZE)

        kwargs['cookies'] = trim_pairs(format_cookies(cookies))
        kwargs['env'] = trim_dict(data.get('env') or {})
        kwargs['headers'] = trim_pairs(headers)
        kwargs['data'] = body
        kwargs['url'] = urlunsplit((scheme, netloc, path, '', ''))
        kwargs['fragment'] = trim(fragment, 1024)

        return cls(**kwargs)
Exemplo n.º 13
0
    def to_python(cls, data):
        assert data.get('query')

        kwargs = {
            'query': trim(data['query'], 1024),
            'engine': trim(data.get('engine'), 128),
        }
        return cls(**kwargs)
Exemplo n.º 14
0
    def get_metadata(self):
        exception = self.data['sentry.interfaces.Exception']['values'][0]

        # in some situations clients are submitting non-string data for these
        return {
            'type': trim(exception.get('type', 'Error'), 128),
            'value': trim(exception.get('value', ''), 1024),
        }
Exemplo n.º 15
0
    def to_python(cls, data, slim_frames=True, rust_renormalized=RUST_RENORMALIZED_DEFAULT):
        if not rust_renormalized:
            is_valid, errors = validate_and_default_interface(data, cls.path)
            if not is_valid:
                raise InterfaceValidationError("Invalid exception")

            if not (data.get('type') or data.get('value')):
                raise InterfaceValidationError("No 'type' or 'value' present")

        if get_path(data, 'stacktrace', 'frames', filter=True):
            stacktrace = Stacktrace.to_python(
                data['stacktrace'],
                slim_frames=slim_frames,
                rust_renormalized=rust_renormalized
            )
        else:
            stacktrace = None

        if get_path(data, 'raw_stacktrace', 'frames', filter=True):
            raw_stacktrace = Stacktrace.to_python(
                data['raw_stacktrace'], slim_frames=slim_frames, raw=True,
                rust_renormalized=rust_renormalized
            )
        else:
            raw_stacktrace = None

        type = data.get('type')
        value = data.get('value')

        if not rust_renormalized:
            if isinstance(value, six.string_types):
                if type is None:
                    m = _type_value_re.match(value)
                    if m:
                        type = m.group(1)
                        value = m.group(2).strip()
            elif value is not None:
                value = json.dumps(value)

            value = trim(value, 4096)

        if data.get('mechanism'):
            mechanism = Mechanism.to_python(data['mechanism'],
                                            rust_renormalized=rust_renormalized)
        else:
            mechanism = None

        kwargs = {
            'type': trim(type, 128),
            'value': value,
            'module': trim(data.get('module'), 128),
            'mechanism': mechanism,
            'stacktrace': stacktrace,
            'thread_id': trim(data.get('thread_id'), 40),
            'raw_stacktrace': raw_stacktrace,
        }

        return cls(**kwargs)
Exemplo n.º 16
0
    def to_python(cls, data):
        if not data.get('query'):
            raise InterfaceValidationError("No 'query' value")

        kwargs = {
            'query': trim(data['query'], 1024),
            'engine': trim(data.get('engine'), 128),
        }
        return cls(**kwargs)
Exemplo n.º 17
0
    def to_python(cls, data):
        abs_path = data.get("abs_path")
        filename = data.get("filename")

        if not abs_path:
            abs_path = filename

        if not filename:
            filename = abs_path

        if abs_path and is_url(abs_path):
            urlparts = urlparse(abs_path)
            if urlparts.path:
                filename = urlparts.path

        assert filename or data.get("function") or data.get("module")

        context_locals = data.get("vars") or {}
        if isinstance(context_locals, (list, tuple)):
            context_locals = dict(enumerate(context_locals))
        elif not isinstance(context_locals, dict):
            context_locals = {}
        context_locals = trim_dict(context_locals)

        # extra data is used purely by internal systems,
        # so we dont trim it
        extra_data = data.get("data") or {}
        if isinstance(extra_data, (list, tuple)):
            extra_data = dict(enumerate(extra_data))

        kwargs = {
            "abs_path": trim(abs_path, 256),
            "filename": trim(filename, 256),
            "module": trim(data.get("module"), 256),
            "function": trim(data.get("function"), 256),
            "in_app": validate_bool(data.get("in_app"), False),
            "context_line": trim(data.get("context_line"), 256),
            # TODO(dcramer): trim pre/post_context
            "pre_context": data.get("pre_context"),
            "post_context": data.get("post_context"),
            "vars": context_locals,
            "data": extra_data,
        }

        if data.get("lineno") is not None:
            kwargs["lineno"] = int(data["lineno"])
        else:
            kwargs["lineno"] = None

        if data.get("colno") is not None:
            kwargs["colno"] = int(data["colno"])
        else:
            kwargs["colno"] = None

        return cls(**kwargs)
Exemplo n.º 18
0
    def to_python(cls, data):
        name = data.get("name")
        if not name:
            raise InterfaceValidationError("No 'name' value")

        version = data.get("version")
        if not version:
            raise InterfaceValidationError("No 'version' value")

        kwargs = {"name": trim(name, 128), "version": trim(version, 128)}
        return cls(**kwargs)
Exemplo n.º 19
0
def validate_query(payload):
    rv = {}
    for key in 'params', 'duration', 'classifier':
        value = payload.get(key)
        if value is not None:
            rv[key] = trim(value, 1024)
    if 'query' not in payload:
        raise InterfaceValidationError("Query not provided for 'query' "
                                       "breadcrumb.")
    rv['query'] = trim(payload['query'], 4096)
    return rv
Exemplo n.º 20
0
    def to_python(cls, data):
        data = data.copy()

        kwargs = {
            'id': trim(data.pop('id', None), 128),
            'email': trim(data.pop('email', None), 128),
            'username': trim(data.pop('username', None), 128),
            'ip_address': validate_ip(data.pop('ip_address', None), False),
        }
        kwargs['data'] = trim_dict(data.pop('data', data))
        return cls(**kwargs)
Exemplo n.º 21
0
    def to_python(cls, data, has_system_frames=None, slim_frames=True):
        if not (data.get('type') or data.get('value')):
            raise InterfaceValidationError("No 'type' or 'value' present")

        if data.get('stacktrace') and data['stacktrace'].get('frames'):
            stacktrace = Stacktrace.to_python(
                data['stacktrace'],
                has_system_frames=has_system_frames,
                slim_frames=slim_frames,
            )
        else:
            stacktrace = None

        if data.get('raw_stacktrace') and data['raw_stacktrace'].get('frames'):
            raw_stacktrace = Stacktrace.to_python(
                data['raw_stacktrace'],
                has_system_frames=has_system_frames,
                slim_frames=slim_frames,
                raw=True
            )
        else:
            raw_stacktrace = None

        type = data.get('type')
        value = data.get('value')
        if not type and ':' in value.split(' ', 1)[0]:
            type, value = value.split(':', 1)
            # in case of TypeError: foo (no space)
            value = value.strip()

        if value is not None and not isinstance(value, six.string_types):
            value = json.dumps(value)
        value = trim(value, 4096)

        mechanism = data.get('mechanism')
        if mechanism is not None:
            if not isinstance(mechanism, dict):
                raise InterfaceValidationError('Bad value for mechanism')
            mechanism = trim(data.get('mechanism'), 4096)
            mechanism.setdefault('type', 'generic')

        kwargs = {
            'type': trim(type, 128),
            'value': value,
            'module': trim(data.get('module'), 128),
            'mechanism': mechanism,
            'stacktrace': stacktrace,
            'thread_id': trim(data.get('thread_id'), 40),
            'raw_stacktrace': raw_stacktrace,
        }

        return cls(**kwargs)
Exemplo n.º 22
0
    def to_python(cls, data):
        assert data.get('message')

        kwargs = {
            'message': trim(data['message'], 2048)
        }

        if data.get('params'):
            kwargs['params'] = trim(data['params'], 1024)
        else:
            kwargs['params'] = ()

        return cls(**kwargs)
Exemplo n.º 23
0
    def to_python(cls, data):
        data = data.copy()

        ident = data.pop('id', None)
        if ident is not None:
            ident = trim(six.text_type(ident), 128)

        try:
            email = trim(validate_email(data.pop('email', None), False), MAX_EMAIL_FIELD_LENGTH)
        except ValueError:
            raise InterfaceValidationError("Invalid value for 'email'")

        username = data.pop('username', None)
        if username is not None:
            username = trim(six.text_type(username), 128)

        name = data.pop('name', None)
        if name is not None:
            name = trim(six.text_type(name), 128)

        try:
            ip_address = validate_ip(data.pop('ip_address', None), False)
        except ValueError:
            raise InterfaceValidationError("Invalid value for 'ip_address'")

        geo = data.pop('geo', None)
        if not geo and ip_address:
            geo = Geo.from_ip_address(ip_address)
        elif geo:
            geo = Geo.to_python(geo)

        extra_data = data.pop('data', None)
        if not isinstance(extra_data, dict):
            extra_data = {}
        extra_data.update(data)

        # TODO(dcramer): patch in fix to deal w/ old data but not allow new
        # if not (ident or email or username or ip_address):
        #     raise ValueError('No identifying value')

        kwargs = {
            'id': ident,
            'email': email,
            'username': username,
            'ip_address': ip_address,
            'name': name,
            'geo': geo,
            'data': trim_dict(extra_data)
        }

        return cls(**kwargs)
Exemplo n.º 24
0
    def to_python(cls, data):
        if not data.get('message'):
            raise InterfaceValidationError("No 'message' present")

        kwargs = {
            'message': trim(data['message'], 2048)
        }

        if data.get('params'):
            kwargs['params'] = trim(data['params'], 1024)
        else:
            kwargs['params'] = ()

        return cls(**kwargs)
Exemplo n.º 25
0
    def to_python(cls, data):
        if not data.get('message'):
            raise InterfaceValidationError("No 'message' present")

        # TODO(dcramer): some day we should stop people from sending arbitrary
        # crap to the server
        if not isinstance(data['message'], six.string_types):
            data['message'] = json.dumps(data['message'])

        kwargs = {
            'message': trim(data['message'], settings.SENTRY_MAX_MESSAGE_LENGTH),
            'formatted': data.get('formatted'),
        }

        if data.get('params'):
            kwargs['params'] = trim(data['params'], 1024)
        else:
            kwargs['params'] = ()

        if kwargs['formatted']:
            if not isinstance(kwargs['formatted'], six.string_types):
                data['formatted'] = json.dumps(data['formatted'])
        # support python-esque formatting (e.g. %s)
        elif '%' in kwargs['message'] and kwargs['params']:
            if isinstance(kwargs['params'], list):
                kwargs['params'] = tuple(kwargs['params'])

            try:
                kwargs['formatted'] = trim(
                    kwargs['message'] % kwargs['params'],
                    settings.SENTRY_MAX_MESSAGE_LENGTH,
                )
            except Exception:
                pass
        # support very basic placeholder formatters (non-typed)
        elif '{}' in kwargs['message'] and kwargs['params']:
            try:
                kwargs['formatted'] = trim(
                    kwargs['message'].format(kwargs['params']),
                    settings.SENTRY_MAX_MESSAGE_LENGTH,
                )
            except Exception:
                pass

        # don't wastefully store formatted message twice
        if kwargs['formatted'] == kwargs['message']:
            kwargs['formatted'] = None

        return cls(**kwargs)
Exemplo n.º 26
0
    def to_python(cls, data):
        data = upgrade_legacy_mechanism(data)
        is_valid, errors = validate_and_default_interface(data, cls.path)
        if not is_valid:
            raise InterfaceValidationError("Invalid mechanism")

        if not data.get('type'):
            raise InterfaceValidationError("No 'type' present")

        meta = data.get('meta', {})
        mach_exception = meta.get('mach_exception')
        if mach_exception is not None:
            mach_exception = prune_empty_keys({
                'exception': mach_exception['exception'],
                'code': mach_exception['code'],
                'subcode': mach_exception['subcode'],
                'name': mach_exception.get('name'),
            })

        signal = meta.get('signal')
        if signal is not None:
            signal = prune_empty_keys({
                'number': signal['number'],
                'code': signal.get('code'),
                'name': signal.get('name'),
                'code_name': signal.get('code_name'),
            })

        errno = meta.get('errno')
        if errno is not None:
            errno = prune_empty_keys({
                'number': errno['number'],
                'name': errno.get('name'),
            })

        kwargs = {
            'type': trim(data['type'], 128),
            'description': trim(data.get('description'), 1024),
            'help_link': trim(data.get('help_link'), 1024),
            'handled': data.get('handled'),
            'data': trim(data.get('data'), 4096),
            'meta': {
                'errno': errno,
                'mach_exception': mach_exception,
                'signal': signal,
            },
        }

        return cls(**kwargs)
Exemplo n.º 27
0
    def to_python(cls, data):
        assert data.get("filename")
        assert data.get("context_line")
        assert data.get("lineno")

        kwargs = {
            "abs_path": trim(data.get("abs_path", None), 256),
            "filename": trim(data["filename"], 256),
            "context_line": trim(data.get("context_line", None), 256),
            "lineno": int(data["lineno"]),
            # TODO(dcramer): trim pre/post_context
            "pre_context": data.get("pre_context"),
            "post_context": data.get("post_context"),
        }
        return cls(**kwargs)
Exemplo n.º 28
0
    def to_python(cls, data):
        is_valid, errors = validate_and_default_interface(data, cls.path)
        if not is_valid:
            raise InterfaceValidationError("Invalid template")

        kwargs = {
            'abs_path': trim(data.get('abs_path', None), 256),
            'filename': trim(data.get('filename', None), 256),
            'context_line': trim(data.get('context_line', None), 256),
            'lineno': int(data['lineno']) if data.get('lineno', None) is not None else None,
            # TODO(dcramer): trim pre/post_context
            'pre_context': data.get('pre_context'),
            'post_context': data.get('post_context'),
        }
        return cls(**kwargs)
Exemplo n.º 29
0
    def to_python(cls, data):
        threads = []

        for thread in data.get('values') or ():
            threads.append({
                'stacktrace': get_stacktrace(thread.get('stacktrace')),
                'raw_stacktrace': get_stacktrace(thread.get('raw_stacktrace'),
                                                 raw=True),
                'id': trim(thread.get('id'), 40),
                'crashed': bool(thread.get('crashed')),
                'current': bool(thread.get('current')),
                'name': trim(thread.get('name'), 200),
            })

        return cls(values=threads)
Exemplo n.º 30
0
    def to_python(cls, data):
        assert data.get('filename')
        assert data.get('context_line')
        assert data.get('lineno')

        kwargs = {
            'abs_path': trim(data.get('abs_path', None), 256),
            'filename': trim(data['filename'], 256),
            'context_line': trim(data.get('context_line', None), 256),
            'lineno': int(data['lineno']),
            # TODO(dcramer): trim pre/post_context
            'pre_context': data.get('pre_context'),
            'post_context': data.get('post_context'),
        }
        return cls(**kwargs)
Exemplo n.º 31
0
    def to_python(cls, data):
        assert data.get('type') or data.get('value')

        if data.get('stacktrace'):
            stacktrace = Stacktrace.to_python(data['stacktrace'])
        else:
            stacktrace = None

        kwargs = {
            'type': trim(data.get('type'), 128),
            'value': trim(data.get('value'), 256),
            'module': trim(data.get('module'), 128),
            'stacktrace': stacktrace,
        }

        return cls(**kwargs)
Exemplo n.º 32
0
 def __init__(self, alias, data):
     self.alias = alias
     ctx_data = {}
     for key, value in trim(data).iteritems():
         if value not in EMPTY_VALUES:
             ctx_data[force_text(key)] = force_text(value)
     self.data = ctx_data
Exemplo n.º 33
0
    def to_python(cls, data):
        kwargs = {k: trim(data.get(k, None), 1024) for k in REPORT_KEYS}

        if kwargs['effective_directive'] not in DIRECTIVES:
            raise InterfaceValidationError(
                "Invalid value for 'effective-directive'")

        # Some reports from Chrome report blocked-uri as just 'about'.
        # In this case, this is not actionable and is just noisy.
        # Observed in Chrome 45 and 46.
        if kwargs['blocked_uri'] == 'about':
            raise InterfaceValidationError("Invalid value for 'blocked-uri'")

        # Here, we want to block reports that are coming from browser extensions
        # and other sources that are meaningless
        if kwargs['source_file'] is not None:
            if kwargs['source_file'].startswith(DISALLOWED_SOURCES):
                raise InterfaceValidationError(
                    "Invalid value for 'source-file'")

        # Anything resulting from an "inline" whatever violation is either sent
        # as 'self', or left off. In the case if it missing, we want to noramalize.
        if not kwargs['blocked_uri']:
            kwargs['blocked_uri'] = 'self'

        return cls(**kwargs)
Exemplo n.º 34
0
    def from_raw(cls, raw):
        # Firefox doesn't send effective-directive, so parse it from
        # violated-directive but prefer effective-directive when present
        #
        # refs: https://bugzil.la/1192684#c8
        try:
            report = raw['csp-report']
            report['effective-directive'] = report.get(
                'effective-directive',
                report['violated-directive'].split(None, 1)[0])
        except (KeyError, IndexError):
            pass

        # Validate the raw data against the input schema (raises on failure)
        schema = INPUT_SCHEMAS[cls.path]
        jsonschema.validate(raw, schema)

        # For CSP, the values we want are nested under the 'csp-report' key.
        raw = raw['csp-report']
        # Trim values and convert keys to use underscores
        kwargs = {
            k.replace('-', '_'): trim(v, 1024)
            for k, v in six.iteritems(raw)
        }

        return cls.to_python(kwargs)
Exemplo n.º 35
0
def validate_event(payload):
    rv = {}
    for key in 'type', 'target', 'classifier':
        value = payload.get(key)
        if value is not None:
            rv[key] = trim(value, 1024)
    return rv
Exemplo n.º 36
0
def validate_error(payload):
    rv = {}
    for key in 'type', 'message', 'event_id':
        value = payload.get(key)
        if value is not None:
            rv[key] = trim(value, 1024)
    return rv
Exemplo n.º 37
0
    def search_message(self):
        """
        The internal search_message attribute is only used for search purposes.
        It adds a bunch of data from the metadata and the culprit.
        """
        data = self.data
        culprit = self.culprit

        event_metadata = self.get_event_metadata()

        if event_metadata is None:
            event_metadata = eventtypes.get(
                self.get_event_type())().get_metadata(self.data)

        message = ""

        if data.get("logentry"):
            message += data["logentry"].get(
                "formatted") or data["logentry"].get("message") or ""

        if event_metadata:
            for value in event_metadata.values():
                value_u = force_text(value, errors="replace")
                if value_u not in message:
                    message = f"{message} {value_u}"

        if culprit and culprit not in message:
            culprit_u = force_text(culprit, errors="replace")
            message = f"{message} {culprit_u}"

        return trim(message.strip(), settings.SENTRY_MAX_MESSAGE_LENGTH)
Exemplo n.º 38
0
    def to_python(cls, data):
        data = data.copy()

        extra_data = data.pop('data', data)
        if not isinstance(extra_data, dict):
            extra_data = {}

        kwargs = {
            'id': trim(data.pop('id', None), 128),
            'email': trim(data.pop('email', None), 128),
            'username': trim(data.pop('username', None), 128),
            'ip_address': validate_ip(data.pop('ip_address', None), False),
        }

        kwargs['data'] = trim_dict(extra_data)
        return cls(**kwargs)
Exemplo n.º 39
0
    def get_search_message(self, event_metadata=None, culprit=None):
        """This generates the internal event.message attribute which is used
        for search purposes.  It adds a bunch of data from the metadata and
        the culprit.
        """
        if event_metadata is None:
            event_metadata = self.get_event_type().get_metadata(self._data)
        if culprit is None:
            culprit = self.get_culprit()

        data = self._data
        message = ""

        if data.get("logentry"):
            message += data["logentry"].get(
                "formatted") or data["logentry"].get("message") or ""

        if event_metadata:
            for value in six.itervalues(event_metadata):
                value_u = force_text(value, errors="replace")
                if value_u not in message:
                    message = u"{} {}".format(message, value_u)

        if culprit and culprit not in message:
            culprit_u = force_text(culprit, errors="replace")
            message = u"{} {}".format(message, culprit_u)

        return trim(message.strip(), settings.SENTRY_MAX_MESSAGE_LENGTH)
Exemplo n.º 40
0
    def to_python(cls, data):
        threads = []

        for thread in data.get('values') or ():
            threads.append(
                {
                    'stacktrace': get_stacktrace(thread.get('stacktrace')),
                    'raw_stacktrace': get_stacktrace(thread.get('raw_stacktrace'), raw=True),
                    'id': trim(thread.get('id'), 40),
                    'crashed': bool(thread.get('crashed')),
                    'current': bool(thread.get('current')),
                    'name': trim(thread.get('name'), 200),
                }
            )

        return cls(values=threads)
Exemplo n.º 41
0
def _merge_frame(new_frame, symbolicated):
    if symbolicated.get("function"):
        raw_func = trim(symbolicated["function"], 256)
        func = trim(trim_function_name(symbolicated["function"], "native"),
                    256)

        # if function and raw function match, we can get away without
        # storing a raw function
        if func == raw_func:
            new_frame["function"] = raw_func
        # otherwise we store both
        else:
            new_frame["raw_function"] = raw_func
            new_frame["function"] = func
    if symbolicated.get("instruction_addr"):
        new_frame["instruction_addr"] = symbolicated["instruction_addr"]
    if symbolicated.get("symbol"):
        new_frame["symbol"] = symbolicated["symbol"]
    if symbolicated.get("abs_path"):
        new_frame["abs_path"] = symbolicated["abs_path"]
        new_frame["filename"] = posixpath.basename(symbolicated["abs_path"])
    if symbolicated.get("filename"):
        new_frame["filename"] = symbolicated["filename"]
    if symbolicated.get("lineno"):
        new_frame["lineno"] = symbolicated["lineno"]
    if symbolicated.get("colno"):
        new_frame["colno"] = symbolicated["colno"]
    if symbolicated.get("package"):
        new_frame["package"] = symbolicated["package"]
    if symbolicated.get("trust"):
        new_frame["trust"] = symbolicated["trust"]
    if symbolicated.get("pre_context"):
        new_frame["pre_context"] = symbolicated["pre_context"]
    if symbolicated.get("context_line") is not None:
        new_frame["context_line"] = symbolicated["context_line"]
    if symbolicated.get("post_context"):
        new_frame["post_context"] = symbolicated["post_context"]

    addr_mode = symbolicated.get("addr_mode")
    if addr_mode is None:
        new_frame.pop("addr_mode", None)
    else:
        new_frame["addr_mode"] = addr_mode

    if symbolicated.get("status"):
        frame_meta = new_frame.setdefault("data", {})
        frame_meta["symbolicator_status"] = symbolicated["status"]
Exemplo n.º 42
0
    def get_metadata(self):
        exception = get_path(self.data, 'exception', 'values', -1)

        # in some situations clients are submitting non-string data for these
        rv = {
            'type': trim(get_path(exception, 'type', default='Error'), 128),
            'value': trim(get_path(exception, 'value', default=''), 1024),
        }

        # Attach crash location
        stacktrace = exception.get('stacktrace')
        if stacktrace:
            fn = get_crash_file(stacktrace)
            if fn is not None:
                rv['filename'] = fn

        return rv
Exemplo n.º 43
0
    def get_metadata(self):
        exception = self.data['sentry.interfaces.Exception']['values'][-1]

        # in some situations clients are submitting non-string data for these
        rv = {
            'type': trim(exception.get('type', 'Error'), 128),
            'value': trim(exception.get('value', ''), 1024),
        }

        # Attach crash location
        stacktrace = exception.get('stacktrace')
        if stacktrace:
            fn = get_crash_file(stacktrace)
            if fn is not None:
                rv['filename'] = fn

        return rv
    def normalize_crumb(cls, crumb):
        ty = crumb.get('type') or 'default'
        ts = parse_timestamp(crumb.get('timestamp'))
        if ts is None:
            raise InterfaceValidationError('Unable to determine timestamp '
                                           'for crumb')

        rv = {
            'type': ty,
            'timestamp': to_timestamp(ts),
        }

        level = crumb.get('level')
        if level not in (None, 'info'):
            rv['level'] = level

        msg = crumb.get('message')
        if msg is not None:
            rv['message'] = trim(six.text_type(msg), 4096)

        category = crumb.get('category')
        if category is not None:
            rv['category'] = trim(six.text_type(category), 256)

        event_id = crumb.get('event_id')
        if event_id is not None:
            rv['event_id'] = event_id

        if crumb.get('data'):
            try:
                for key, value in six.iteritems(crumb['data']):
                    if not isinstance(value, six.string_types):
                        crumb['data'][key] = json.dumps(value)
            except AttributeError:
                # TODO(dcramer): we dont want to discard the the rest of the
                # crumb, but it'd be nice if we could record an error
                # raise InterfaceValidationError(
                #     'The ``data`` on breadcrumbs must be a mapping (received {})'.format(
                #         type(crumb['data']),
                #     )
                # )
                pass
            else:
                rv['data'] = trim(crumb['data'], 4096)

        return rv
Exemplo n.º 45
0
    def to_python(cls, data):
        if not data.get('message'):
            raise InterfaceValidationError("No 'message' present")

        # TODO(dcramer): some day we should stop people from sending arbitrary
        # crap to the server
        if not isinstance(data['message'], basestring):
            data['message'] = json.dumps(data['message'])

        kwargs = {
            'message': trim(data['message'], settings.SENTRY_MAX_MESSAGE_LENGTH),
            'formatted': None,
        }

        if data.get('params'):
            kwargs['params'] = trim(data['params'], 1024)
        else:
            kwargs['params'] = ()

        if kwargs['formatted']:
            if not isinstance(kwargs['formatted'], basestring):
                data['formatted'] = json.dumps(data['formatted'])
        # support python-esque formatting (e.g. %s)
        elif '%' in kwargs['message'] and kwargs['params']:
            if isinstance(kwargs['params'], list):
                kwargs['params'] = tuple(kwargs['params'])

            try:
                kwargs['formatted'] = trim(
                    kwargs['message'] % kwargs['params'],
                    settings.SENTRY_MAX_MESSAGE_LENGTH,
                )
            except Exception:
                pass
        # support very basic placeholder formatters (non-typed)
        elif '{}' in kwargs['message'] and kwargs['params']:
            try:
                kwargs['formatted'] = trim(
                    kwargs['message'].format(kwargs['params']),
                    settings.SENTRY_MAX_MESSAGE_LENGTH,
                )
            except Exception:
                pass

        return cls(**kwargs)
Exemplo n.º 46
0
    def normalize_crumb(cls, crumb):
        ty = crumb.get('type') or 'default'
        level = crumb.get('level')
        if not isinstance(level, six.string_types) or \
           (level not in LOG_LEVELS_MAP and level != 'critical'):
            level = 'info'

        ts = parse_timestamp(crumb.get('timestamp'))
        if ts is None:
            raise InterfaceValidationError(
                'Unable to determine timestamp for crumb')
        ts = to_timestamp(ts)

        msg = crumb.get('message')
        if msg is not None:
            msg = trim(six.text_type(msg), 4096)

        category = crumb.get('category')
        if category is not None:
            category = trim(six.text_type(category), 256)

        event_id = crumb.get('event_id')

        data = crumb.get('data')
        if not isinstance(data, dict):
            # TODO(dcramer): we dont want to discard the the rest of the
            # crumb, but it'd be nice if we could record an error
            # raise InterfaceValidationError(
            #     'The ``data`` on breadcrumbs must be a mapping (received {})'.format(
            #         type(crumb['data']),
            #     )
            # )
            data = None
        else:
            data = trim(data, 4096)

        return {
            'type': ty,
            'level': level,
            'timestamp': ts,
            'message': msg,
            'category': category,
            'event_id': event_id,
            'data': data
        }
Exemplo n.º 47
0
    def normalize_crumb(cls, crumb):
        ty = crumb.get('type') or 'default'
        level = crumb.get('level') or 'info'
        ts = parse_timestamp(crumb.get('timestamp'))
        if ts is None:
            raise InterfaceValidationError('Unable to determine timestamp for crumb')
        ts = to_timestamp(ts)

        msg = crumb.get('message')
        if msg is not None:
            msg = trim(six.text_type(msg), 4096)

        category = crumb.get('category')
        if category is not None:
            category = trim(six.text_type(category), 256)

        event_id = crumb.get('event_id')

        data = crumb.get('data')
        if data:
            try:
                for key, value in six.iteritems(data):
                    if not isinstance(value, six.string_types):
                        data[key] = json.dumps(value)
            except AttributeError:
                # TODO(dcramer): we dont want to discard the the rest of the
                # crumb, but it'd be nice if we could record an error
                # raise InterfaceValidationError(
                #     'The ``data`` on breadcrumbs must be a mapping (received {})'.format(
                #         type(crumb['data']),
                #     )
                # )
                data = None
            else:
                data = trim(data, 4096)

        return {
            'type': ty,
            'level': level,
            'timestamp': ts,
            'message': msg,
            'category': category,
            'event_id': event_id,
            'data': data
        }
Exemplo n.º 48
0
 def to_python(cls, data):
     kwargs = {
         'abs_path':
         trim(data.get('abs_path', None), 256),
         'filename':
         trim(data.get('filename', None), 256),
         'context_line':
         trim(data.get('context_line', None), 256),
         'lineno':
         int(data['lineno'])
         if data.get('lineno', None) is not None else None,
         # TODO(dcramer): trim pre/post_context
         'pre_context':
         data.get('pre_context'),
         'post_context':
         data.get('post_context'),
     }
     return cls(**kwargs)
Exemplo n.º 49
0
    def from_raw(cls, raw):
        # Validate the raw data against the input schema (raises on failure)
        schema = INPUT_SCHEMAS[cls.path]
        jsonschema.validate(raw, schema)

        # Trim values and convert keys to use underscores
        kwargs = {k.replace('-', '_'): trim(v, 1024) for k, v in six.iteritems(raw)}

        return cls.to_python(kwargs)
    def to_python(cls, data):
        if not data.get('filename'):
            raise InterfaceValidationError("Missing 'filename'")
        if not data.get('context_line'):
            raise InterfaceValidationError("Missing 'context_line'")
        if not data.get('lineno'):
            raise InterfaceValidationError("Missing 'lineno'")

        kwargs = {
            'abs_path': trim(data.get('abs_path', None), 256),
            'filename': trim(data['filename'], 256),
            'context_line': trim(data.get('context_line', None), 256),
            'lineno': int(data['lineno']),
            # TODO(dcramer): trim pre/post_context
            'pre_context': data.get('pre_context'),
            'post_context': data.get('post_context'),
        }
        return cls(**kwargs)
Exemplo n.º 51
0
 def __init__(self, alias, data):
     self.alias = alias
     ctx_data = {}
     for key, value in six.iteritems(trim(data)):
         # we use simple checks here, rathern than ' in set()' to avoid
         # issues with maps/lists
         if value is not None and value != '':
             ctx_data[force_text(key)] = value
     self.data = ctx_data
Exemplo n.º 52
0
    def to_python(cls, data):
        kwargs = {k: trim(data.get(k, None), 1024) for k in REPORT_KEYS}

        # Anything resulting from an "inline" whatever violation is either sent
        # as 'self', or left off. In the case if it missing, we want to noramalize.
        if not kwargs['blocked_uri']:
            kwargs['blocked_uri'] = 'self'

        return cls(**kwargs)
    def to_python(cls, data):
        is_valid, errors = validate_and_default_interface(data, cls.path)
        if not is_valid:
            raise InterfaceValidationError("Invalid device")

        data = data.copy()

        extra_data = data.pop('data', data)
        name = trim(data.pop('name'), 64)
        version = trim(data.pop('version'), 64)
        build = trim(data.pop('build', None), 64)

        kwargs = {
            'name': name,
            'version': version,
            'build': build,
            'data': trim_dict(extra_data),
        }
        return cls(**kwargs)
Exemplo n.º 54
0
    def to_python(cls, data):
        name = data.get('name')
        if not name:
            raise InterfaceValidationError("No 'name' value")

        version = data.get('version')
        if not version:
            raise InterfaceValidationError("No 'version' value")

        integrations = data.get('integrations')
        if integrations and not isinstance(integrations, list):
            raise InterfaceValidationError("'integrations' must be a list")

        kwargs = {
            'name': trim(name, 128),
            'version': trim(version, 128),
            'integrations': integrations,
        }
        return cls(**kwargs)
Exemplo n.º 55
0
    def to_python(cls, data):
        data = data.copy()

        extra_data = data.pop('data', data)
        if not isinstance(extra_data, dict):
            extra_data = {}

        ident = trim(data.pop('id', None), 128)
        if ident:
            ident = six.text_type(ident)
        try:
            email = trim(validate_email(data.pop('email', None), False),
                         MAX_EMAIL_FIELD_LENGTH)
        except ValueError:
            raise InterfaceValidationError("Invalid value for 'email'")

        username = trim(data.pop('username', None), 128)
        if username:
            username = six.text_type(username)

        name = trim(data.pop('name', None), 128)
        if name:
            name = six.text_type(name)

        try:
            ip_address = validate_ip(data.pop('ip_address', None), False)
        except ValueError:
            raise InterfaceValidationError("Invalid value for 'ip_address'")

        # TODO(dcramer): patch in fix to deal w/ old data but not allow new
        # if not (ident or email or username or ip_address):
        #     raise ValueError('No identifying value')

        kwargs = {
            'id': ident,
            'email': email,
            'username': username,
            'ip_address': ip_address,
            'name': name,
        }

        kwargs['data'] = trim_dict(extra_data)
        return cls(**kwargs)
Exemplo n.º 56
0
def validate_rpc(payload):
    rv = {}
    for key in 'endpoint', 'params', 'classifier':
        value = payload.get(key)
        if value is not None:
            rv[key] = trim(value, 1024)
    if not rv.get('endpoint'):
        raise InterfaceValidationError("No endpoint provided for "
                                       "'rpc' breadcrumb.")
    return rv
Exemplo n.º 57
0
def validate_query(payload):
    rv = {}
    for key in 'query', 'params', 'duration', 'classifier':
        value = payload.get(key)
        if value is not None:
            rv[key] = trim(value, 1024)
    if 'query' not in rv:
        raise InterfaceValidationError("Query not provided for 'query' "
                                       "breadcrumb.")
    return rv
Exemplo n.º 58
0
    def to_python(cls, data, rust_renormalized=RUST_RENORMALIZED_DEFAULT):
        if rust_renormalized:
            for key in (
                'message',
                'formatted',
                'params',
            ):
                data.setdefault(key, None)
            return cls(**data)

        formatted = stringify(data.get('formatted'))
        message = stringify(data.get('message'))
        if formatted is None and message is None:
            raise InterfaceValidationError("No message present")

        params = data.get('params')
        if isinstance(params, (list, tuple)):
            params = tuple(p for p in params)
        elif isinstance(params, dict):
            params = {k: v for k, v in six.iteritems(params)}
        else:
            params = ()

        if formatted is None and params:
            try:
                if '%' in message:
                    formatted = message % params
                elif '{}' in message and isinstance(params, tuple):
                    formatted = message.format(*params)
                # NB: Named newstyle arguments were never supported
            except Exception:
                pass

        if formatted is None or message == formatted:
            formatted = message
            message = None

        return cls(
            formatted=trim(formatted, settings.SENTRY_MAX_MESSAGE_LENGTH),
            message=trim(message, settings.SENTRY_MAX_MESSAGE_LENGTH),
            params=trim(params, 1024),
        )
Exemplo n.º 59
0
    def to_python(cls, data, has_system_frames=None, slim_frames=True):
        if not (data.get('type') or data.get('value')):
            raise InterfaceValidationError("No 'type' or 'value' present")

        if data.get('stacktrace') and data['stacktrace'].get('frames'):
            stacktrace = Stacktrace.to_python(
                data['stacktrace'],
                has_system_frames=has_system_frames,
                slim_frames=slim_frames,
            )
        else:
            stacktrace = None

        if data.get('raw_stacktrace') and data['raw_stacktrace'].get('frames'):
            raw_stacktrace = Stacktrace.to_python(
                data['raw_stacktrace'],
                has_system_frames=has_system_frames,
                slim_frames=slim_frames,
            )
        else:
            raw_stacktrace = None

        type = data.get('type')
        value = data.get('value')
        if not type and ':' in value.split(' ', 1)[0]:
            type, value = value.split(':', 1)
            # in case of TypeError: foo (no space)
            value = value.strip()

        if value is not None and not isinstance(value, basestring):
            value = json.dumps(value)
        value = trim(value, 4096)

        kwargs = {
            'type': trim(type, 128),
            'value': value,
            'module': trim(data.get('module'), 128),
            'stacktrace': stacktrace,
            'raw_stacktrace': raw_stacktrace,
        }

        return cls(**kwargs)
Exemplo n.º 60
0
    def to_python(cls, data):
        threads = []

        for thread in data.get("values") or ():
            if thread is None:
                # XXX(markus): We should handle this in the UI and other
                # consumers of this interface
                continue
            threads.append(
                {
                    "stacktrace": get_stacktrace(thread.get("stacktrace")),
                    "raw_stacktrace": get_stacktrace(thread.get("raw_stacktrace"), raw=True),
                    "id": trim(thread.get("id"), 40),
                    "crashed": bool(thread.get("crashed")),
                    "current": bool(thread.get("current")),
                    "name": trim(thread.get("name"), 200),
                }
            )

        return cls(values=threads)