<commit_before>#!/usr/bin/env python
# mammon - a useless ircd
#
# Copyright (c) 2015, <NAME> <<EMAIL>>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

import string

from mammon.server import eventmgr_core, eventmgr_rfc1459, get_context
from mammon.capability import Capability
from mammon.utility import CaseInsensitiveDict, CaseInsensitiveList

from . import monitor

cap_metadata_notify = Capability('metadata-notify')

metadata_key_allowed_chars = string.ascii_letters + string.digits + '_.:'
metadata_key_allowed_chars_tbl = str.maketrans('', '', metadata_key_allowed_chars)

def validate_metadata_key(key_name):
    badchars = key_name.translate(metadata_key_allowed_chars_tbl)
    return badchars == ''

def metadata_GET(cli, ev_msg, target_name, target):
    if len(ev_msg['params']) > 2:
        keys = ev_msg['params'][2:]
    else:
        cli.dump_numeric('461', ['METADATA', 'Not enough parameters'])
        return

    restricted_keys = cli.ctx.conf.metadata.get('restricted_keys', [])

    for key in keys:
        if key in target.metadata:
            # check restricted keys
            visibility = '*'
            if key in restricted_keys:
                if cli.role and key in cli.role.metakeys_get:
                    visibility = 'server:restricted'
                else:
                    cli.dump_numeric('766', [target_name, key, 'no matching keys'], add_target=False)
                    continue

            # XXX - make sure user has privs to set this key through channel ACL

            args = [target_name, key, visibility]
            if isinstance(target.metadata[key], str):
                args.append(target.metadata[key])
            cli.dump_numeric('761', args, add_target=False)
        elif not validate_metadata_key(key):
            cli.dump_numeric('767', [key, 'invalid metadata key'], add_target=False)
        else:
            cli.dump_numeric('766', [target_name, key, 'no matching keys'], add_target=False)

def metadata_LIST(cli, ev_msg, target_name, target):
    for key, visibility in get_visible_keys(cli, target):
        args = [target_name, key, visibility]

        data = target.metadata[key]
        if isinstance(data, str):
            args.append(data)

        cli.dump_numeric('761', args, add_target=False)

    cli.dump_numeric('762', ['end of metadata'], add_target=False)

def get_visible_keys(cli, target):
    visible_keys = []
    restricted_keys = cli.ctx.conf.metadata.get('restricted_keys', [])

    for key, data in target.metadata.items():
        # check restricted keys
        visibility = '*'
        if key in restricted_keys:
            if cli.role and key in cli.role.metakeys_get:
                visibility = 'server:restricted'
            else:
                continue

        # it's viewable!
        visible_keys.append([key, visibility])

    return visible_keys

def metadata_SET(cli, ev_msg, target_name, target):
    if len(ev_msg['params']) > 2:
        key = ev_msg['params'][2]
        if len(ev_msg['params']) > 3:
            value = ev_msg['params'][3]
        else:
            value = None
    else:
        cli.dump_numeric('461', ['METADATA', 'Not enough parameters'])
        return

    # check user has permission for target
    if not cli.able_to_edit_metadata(target):
        cli.dump_numeric('769', [target_name, '*', 'permission denied'], add_target=False)
        return

    restricted_keys = cli.ctx.conf.metadata.get('restricted_keys', [])

    # check key is valid, and if we're using white/blacklists, check those too
    whitelist = cli.ctx.conf.metadata.get('whitelist', [])
    blacklist = cli.ctx.conf.metadata.get('blacklist', [])

    is_valid = False
    if validate_metadata_key(key):
        if key not in blacklist:
            if key in whitelist or not whitelist or key in restricted_keys:
                is_valid = True

    if not is_valid:
        cli.dump_numeric('767', [key, 'invalid metadata key'], add_target=False)
        return

    # check restricted keys
    key_restricted = False
    visibility = '*'
    if key in restricted_keys:
        if cli.role and key in cli.role.metakeys_set:
            visibility = 'server:restricted'
        else:
            key_restricted = True

    # XXX - make sure user has privs to set this key through channel ACL

    if key_restricted:
        cli.dump_numeric('769', [target_name, key, 'permission denied'], add_target=False)
        return

    # if setting a new, non-restricted key, take metadata limits into account
    # NOTE: we check these here instead of in dispatch handler because we should only
    #   throw valid events, makes more sense to check it before we send the event
    if value:
        if key not in target.user_set_metadata and key not in restricted_keys:
            limit = cli.ctx.conf.metadata.get('limit', None)
            if limit is not None:
                if len(target.user_set_metadata) + 1 > limit:
                    cli.dump_numeric('764', [target_name, 'metadata limit reached'], add_target=False)
                    return

    # throw change
    info = {
        'key': key,
        'value': value,<commit_msg>mammon.core.ircv3.metadata: Implement recent METADATA spec clarifications<commit_after>#!/usr/bin/env python
# mammon - a useless ircd
#
# Copyright (c) 2015, <NAME> <<EMAIL>>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

import string

from mammon.server import eventmgr_core, eventmgr_rfc1459, get_context
from mammon.capability import Capability
from mammon.utility import CaseInsensitiveDict, CaseInsensitiveList

from . import monitor

cap_metadata_notify = Capability('metadata-notify')

metadata_key_allowed_chars = string.ascii_letters + string.digits + '_.:'
metadata_key_allowed_chars_tbl = str.maketrans('', '', metadata_key_allowed_chars)

def validate_metadata_key(key_name):
    badchars = key_name.translate(metadata_key_allowed_chars_tbl)
    return badchars == ''

def metadata_GET(cli, ev_msg, target_name, target):
    if len(ev_msg['params']) > 2:
        keys = ev_msg['params'][2:]
    else:
        cli.dump_numeric('461', ['METADATA', 'Not enough parameters'])
        return

    restricted_keys = cli.ctx.conf.metadata.get('restricted_keys', [])

    for key in keys:
        if key in target.metadata:
            # check restricted keys
            visibility = '*'
            if key in restricted_keys:
                if cli.role and key in cli.role.metakeys_get:
                    visibility = 'server:restricted'
                else:
                    cli.dump_numeric('766', [target_name, key, 'no matching keys'], add_target=False)
                    continue

            # XXX - make sure user has privs to set this key through channel ACL

            args = [target_name, key, visibility]
            if isinstance(target.metadata[key], str):
                args.append(target.metadata[key])
            cli.dump_numeric('761', args, add_target=False)
        elif not validate_metadata_key(key):
            cli.dump_numeric('767', [key, 'invalid metadata key'], add_target=False)
        else:
            cli.dump_numeric('766', [target_name, key, 'no matching keys'], add_target=False)

def metadata_LIST(cli, ev_msg, target_name, target):
    for key, visibility in get_visible_keys(cli, target):
        args = [target_name, key, visibility]

        data = target.metadata[key]
        if isinstance(data, str):
            args.append(data)

        cli.dump_numeric('761', args, add_target=False)

    cli.dump_numeric('762', ['end of metadata'], add_target=False)

def get_visible_keys(cli, target):
    visible_keys = []
    restricted_keys = cli.ctx.conf.metadata.get('restricted_keys', [])

    for key, data in target.metadata.items():
        # check restricted keys
        visibility = '*'
        if key in restricted_keys:
            if cli.role and key in cli.role.metakeys_get:
                visibility = 'server:restricted'
            else:
                continue

        # it's viewable!
        visible_keys.append([key, visibility])

    return visible_keys

def metadata_SET(cli, ev_msg, target_name, target):
    if len(ev_msg['params']) > 2:
        key = ev_msg['params'][2]
        if len(ev_msg['params']) > 3:
            value = ev_msg['params'][3]
        else:
            value = None
    else:
        cli.dump_numeric('461', ['METADATA', 'Not enough parameters'])
        return

    # check user has permission for target
    if not cli.able_to_edit_metadata(target):
        cli.dump_numeric('769', [target_name, '*', 'permission denied'], add_target=False)
        return

    restricted_keys = cli.ctx.conf.metadata.get('restricted_keys', [])

    # check key is valid, and if we're using white/blacklists, check those too
    whitelist = cli.ctx.conf.metadata.get('whitelist', [])
    blacklist = cli.ctx.conf.metadata.get('blacklist', [])

    is_valid = False
    if validate_metadata_key(key):
        if key not in blacklist:
            if key in whitelist or not whitelist or key in restricted_keys:
                is_valid = True

    if not is_valid:
        cli.dump_numeric('767', [key, 'invalid metadata key'], add_target=False)
        return

    # check restricted keys
    key_restricted = False
    visibility = '*'
    if key in restricted_keys:
        if cli.role and key in cli.role.metakeys_set:
            visibility = 'server:restricted'
        else:
            key_restricted = True

    # XXX - make sure user has privs to set this key through channel ACL

    if key_restricted:
        cli.dump_numeric('769', [target_name, key, 'permission denied'], add_target=False)
        return

    # if setting a new, non-restricted key, take metadata limits into account
    # NOTE: we check these here instead of in dispatch handler because we should only
    #   throw valid events, makes more sense to check it before we send the event
    if value:
        if key not in target.user_set_metadata and key not in restricted_keys:
            limit = cli.ctx.conf.metadata.get('limit', None)
            if limit is not None:
                if len(target.user_set_metadata) + 1 > limit:
                    cli.dump_numeric('764', [target_name, 'metadata limit reached'], add_target=False)
                    return

    if not value and key not in target.metadata:
        cli.dump_numeric('768', [target_name, key, 'key not set'], add_target=False)
        return

    # throw change
    info = {
        'key': key,
        'value': value,
{'fraction_non_alphanumeric': 0.09437141894169195, 'fraction_numerical': 0.008931580721267273, 'mean_word_length': 2.767936507936508, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4096038', 'n_tokens_mistral': 3569, 'n_tokens_neox': 3304, 'n_words': 1066}