PrivacyItem.java 10.7 KB
Newer Older
Gaston Dombiak's avatar
Gaston Dombiak committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/**
 * $RCSfile$
 * $Revision: $
 * $Date: $
 *
 * Copyright (C) 2006 Jive Software. All rights reserved.
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */

package org.jivesoftware.wildfire.privacy;

import org.dom4j.Element;
15 16
import org.jivesoftware.util.CacheSizes;
import org.jivesoftware.util.Cacheable;
Gaston Dombiak's avatar
Gaston Dombiak committed
17 18 19 20 21 22 23 24 25 26 27 28 29
import org.jivesoftware.wildfire.roster.Roster;
import org.jivesoftware.wildfire.roster.RosterItem;
import org.jivesoftware.wildfire.user.UserNotFoundException;
import org.xmpp.packet.*;

import java.util.Collection;
import java.util.Collections;

/**
 * A privacy item acts a rule that when matched defines if a packet should be blocked or not. 
 *
 * @author Gaston Dombiak
 */
30
class PrivacyItem implements Cacheable, Comparable {
Gaston Dombiak's avatar
Gaston Dombiak committed
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126

    private int order;
    private boolean allow;
    private Type type;
    private JID jidValue;
    private RosterItem.SubType subscriptionValue;
    private String groupValue;
    private boolean filterEverything;
    private boolean filterIQ;
    private boolean filterMessage;
    private boolean filterPresence_in;
    private boolean filterPresence_out;
    /**
     * Copy of the element that defined this item.
     */
    private Element itemElement;

    PrivacyItem(Element itemElement) {
        this.allow = "allow".equals(itemElement.attributeValue("action"));
        this.order = Integer.parseInt(itemElement.attributeValue("order"));
        String typeAttribute = itemElement.attributeValue("type");
        if (typeAttribute != null) {
            this.type = Type.valueOf(typeAttribute);
            // Decode the proper value based on the rule type
            String value = itemElement.attributeValue("value");
            if (type == Type.jid) {
                // Decode the specified JID
                this.jidValue = new JID(value);
            }
            else if (type == Type.subscription) {
                // Decode the specified subscription type
                if ("both".equals(value)) {
                    this.subscriptionValue = RosterItem.SUB_BOTH;
                }
                else if ("to".equals(value)) {
                    this.subscriptionValue = RosterItem.SUB_TO;
                }
                else if ("from".equals(value)) {
                    this.subscriptionValue = RosterItem.SUB_FROM;
                }
                else {
                    this.subscriptionValue = RosterItem.SUB_NONE;
                }
            }
            else {
                // Decode the specified group name
                this.groupValue = value;
            }
        }
        // Set what type of stanzas should be filters (i.e. blocked or allowed)
        this.filterIQ = itemElement.element("iq") != null;
        this.filterMessage = itemElement.element("message") != null;
        this.filterPresence_in = itemElement.element("presence-in") != null;
        this.filterPresence_out = itemElement.element("presence-out") != null;
        if (!filterIQ && !filterMessage && !filterPresence_in && !filterPresence_out) {
            // If none was defined then block all stanzas
            filterEverything = true;
        }
        // Keep a copy of the item element that defines this item
        this.itemElement = itemElement.createCopy();
    }

    Element asElement() {
        return itemElement.createCopy();
    }

    /**
     * Returns true if this privacy item needs the user roster to figure out
     * if a packet must be blocked.
     *
     * @return true if this privacy item needs the user roster to figure out
     *         if a packet must be blocked.
     */
    boolean isRosterRequired() {
        return type == Type.group || type == Type.subscription;
    }

    public int compareTo(Object object) {
        if (object instanceof PrivacyItem) {
            return this.order - ((PrivacyItem) object).order;
        }
        return getClass().getName().compareTo(object.getClass().getName());
    }

    /**
     * Returns true if the packet to analyze matches the condition defined by this rule.
     * Variables involved in the analysis are: type (e.g. jid, group, etc.), value (based
     * on the type) and granular control that defines which type of packets should be
     * considered.
     *
     * @param packet the packet to analyze if matches the rule's condition.
     * @param roster the roster of the owner of the privacy list.
     * @param userJID the JID of the owner of the privacy list.
     * @return true if the packet to analyze matches the condition defined by this rule.
     */
    boolean matchesCondition(Packet packet, Roster roster, JID userJID) {
127
        return matchesPacketSenderCondition(packet, roster, userJID) &&
Gaston Dombiak's avatar
Gaston Dombiak committed
128 129 130 131 132 133 134
                matchesPacketTypeCondition(packet, userJID);
    }

    boolean isAllow() {
        return allow;
    }

135
    private boolean matchesPacketSenderCondition(Packet packet, Roster roster, JID userJID) {
Gaston Dombiak's avatar
Gaston Dombiak committed
136 137 138 139 140
        if (type == null) {
            // This is the "fall-through" case
            return true;
        }
        boolean isPresence = packet.getClass().equals(Presence.class);
141 142 143 144
        boolean incoming = true;
        if (packet.getFrom() != null) {
            incoming = !userJID.toBareJID().equals(packet.getFrom().toBareJID());
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
145
        boolean matches = false;
146
        if (isPresence && !incoming && (filterEverything || filterPresence_out)) {
Gaston Dombiak's avatar
Gaston Dombiak committed
147 148 149 150
            // If this is an outgoing presence and we are filtering by outgoing presence
            // notification then use the receipient of the packet in the analysis
            matches = verifyJID(packet.getTo(), roster);
        }
151 152
        if (!matches && incoming &&
                (filterEverything || filterPresence_in || filterIQ || filterMessage)) {
Gaston Dombiak's avatar
Gaston Dombiak committed
153 154 155 156 157 158
            matches = verifyJID(packet.getFrom(), roster);
        }
        return matches;
    }

    private boolean verifyJID(JID jid, Roster roster) {
Gaston Dombiak's avatar
Gaston Dombiak committed
159 160 161
        if (jid == null) {
            return false;
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
        if (type == Type.jid) {
            if (jidValue.getResource() != null) {
                // Rule is filtering by exact resource match
                // (e.g. <user@domain/resource> or <domain/resource>)
                return jid.equals(jidValue);
            }
            else if (jidValue.getNode() != null) {
                // Rule is filtering by any resource matches (e.g. <user@domain>)
                return jid.toBareJID().equals(jidValue.toBareJID());
            }
            else {
                // Rule is filtering by domain (e.g. <domain>)
                return jid.getDomain().equals(jidValue.getDomain());
            }
        }
        else if (type == Type.group) {
Gaston Dombiak's avatar
Gaston Dombiak committed
178
            Collection<String> contactGroups;
Gaston Dombiak's avatar
Gaston Dombiak committed
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
            try {
                // Get the groups where the contact belongs
                RosterItem item = roster.getRosterItem(jid);
                contactGroups = item.getGroups();
            }
            catch (UserNotFoundException e) {
                // Sender is not in the user's roster
                contactGroups = Collections.emptyList();
            }
            // Check if the contact belongs to the specified group
            return contactGroups.contains(groupValue);
        }
        else {
            RosterItem.SubType contactSubscription = RosterItem.SUB_NONE;
            try {
                // Get the groups where the contact belongs
                RosterItem item = roster.getRosterItem(jid);
                contactSubscription = item.getSubStatus();
            }
            catch (UserNotFoundException e) {
                // Sender is not in the user's roster
            }
            // Check if the contact has the specified subscription status
            return contactSubscription == subscriptionValue;
        }
    }

    private boolean matchesPacketTypeCondition(Packet packet, JID userJID) {
        if (filterEverything) {
            // This includes all type of packets (including subscription-related presences)
            return true;
        }
        Class packetClass = packet.getClass();
        if (Message.class.equals(packetClass)) {
            return filterMessage;
        }
        else if (Presence.class.equals(packetClass)) {
            Presence.Type presenceType = ((Presence) packet).getType();
            // Only filter presences of type available or unavailable
            // (ignore subscription-related presences)
            if (presenceType == null || presenceType == Presence.Type.unavailable) {
                // Calculate if packet is being received by the user
                JID to = packet.getTo();
                boolean incoming = to != null && to.toBareJID().equals(userJID.toBareJID());
                if (incoming) {
                    return filterPresence_in;
                }
                else {
                    return filterPresence_out;
                }
            }
        }
        else if (IQ.class.equals(packetClass)) {
            return filterIQ;
        }
        return false;
    }

237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
    public int getCachedSize() {
        // Approximate the size of the object in bytes by calculating the size
        // of each field.
        int size = 0;
        size += CacheSizes.sizeOfObject();                      // overhead of object
        size += CacheSizes.sizeOfInt();                         // order
        size += CacheSizes.sizeOfBoolean();                     // allow
        //size += CacheSizes.sizeOfString(jidValue.toString());   // type
        if (jidValue != null ) {
            size += CacheSizes.sizeOfString(jidValue.toString()); // jidValue
        }
        //size += CacheSizes.sizeOfString(name);                  // subscriptionValue
        if (groupValue != null) {
            size += CacheSizes.sizeOfString(groupValue);        // groupValue
        }
        size += CacheSizes.sizeOfBoolean();                     // filterEverything
        size += CacheSizes.sizeOfBoolean();                     // filterIQ
        size += CacheSizes.sizeOfBoolean();                     // filterMessage
        size += CacheSizes.sizeOfBoolean();                     // filterPresence_in
        size += CacheSizes.sizeOfBoolean();                     // filterPresence_out
        return size;
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
    /**
     * Type defines if the rule is based on JIDs, roster groups or presence subscription types.
     */
    private static enum Type {
        /**
         * JID being analyzed should belong to a roster group of the list's owner.
         */
        group,
        /**
         * JID being analyzed should have a resource match, domain match or bare JID match.
         */
        jid,
        /**
         * JID being analyzed should belong to a contact present in the owner's roster with
         * the specified subscription status.
         */
Gaston Dombiak's avatar
Gaston Dombiak committed
276
        subscription
Gaston Dombiak's avatar
Gaston Dombiak committed
277 278
    }
}