001/**
002 *
003 * Copyright 2017-2019 Florian Schmaus, 2018 Paul Schaub.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.ox.element;
018
019import java.io.ByteArrayInputStream;
020import java.io.InputStream;
021import java.nio.charset.Charset;
022import java.util.Collections;
023import java.util.Date;
024import java.util.List;
025import java.util.Set;
026
027import javax.xml.namespace.QName;
028
029import org.jivesoftware.smack.packet.ExtensionElement;
030import org.jivesoftware.smack.util.MultiMap;
031import org.jivesoftware.smack.util.Objects;
032import org.jivesoftware.smack.util.PacketUtil;
033import org.jivesoftware.smack.util.XmlStringBuilder;
034
035import org.jxmpp.jid.Jid;
036import org.jxmpp.util.XmppDateTime;
037
038/**
039 * This class describes an OpenPGP content element. It defines the elements and fields that OpenPGP content elements
040 * do have in common.
041 */
042public abstract class OpenPgpContentElement implements ExtensionElement {
043
044    public static final String ELEM_TO = "to";
045    public static final String ATTR_JID = "jid";
046    public static final String ELEM_TIME = "time";
047    public static final String ATTR_STAMP = "stamp";
048    public static final String ELEM_PAYLOAD = "payload";
049
050    private final Set<? extends Jid> to;
051    private final Date timestamp;
052    private final MultiMap<QName, ExtensionElement> payload;
053
054    private String timestampString;
055
056    protected OpenPgpContentElement(Set<? extends Jid> to, Date timestamp, List<ExtensionElement> payload) {
057        this.to = to;
058        this.timestamp = Objects.requireNonNull(timestamp);
059        this.payload = new MultiMap<>();
060        for (ExtensionElement e : payload) {
061            this.payload.put(e.getQName(), e);
062        }
063    }
064
065    /**
066     * Return the set of recipients.
067     *
068     * @return recipients.
069     */
070    public final Set<? extends Jid> getTo() {
071        return to;
072    }
073
074    /**
075     * Return the timestamp on which the encrypted element has been created.
076     * This should be checked for sanity by the client.
077     *
078     * @return timestamp.
079     */
080    public final Date getTimestamp() {
081        return timestamp;
082    }
083
084    /**
085     * Return the payload of the message.
086     *
087     * @return payload.
088     */
089    public final List<ExtensionElement> getExtensions() {
090        synchronized (payload) {
091            return payload.values();
092        }
093    }
094
095    /**
096     * Return a list of all extensions with the given element name <em>and</em> namespace.
097     * <p>
098     * Changes to the returned set will update the stanza extensions, if the returned set is not the empty set.
099     * </p>
100     *
101     * @param elementName the element name, must not be null.
102     * @param namespace the namespace of the element(s), must not be null.
103     * @return a set of all matching extensions.
104     */
105    public List<ExtensionElement> getExtensions(String elementName, String namespace) {
106        QName key = new QName(namespace, elementName);
107        return payload.getAll(key);
108    }
109
110    /**
111     * Returns the first extension of this stanza that has the given namespace.
112     * <p>
113     * When possible, use {@link #getExtension(String, String)} instead.
114     * </p>
115     *
116     * @param namespace the namespace of the extension that is desired.
117     * @return the stanza extension with the given namespace.
118     */
119    public ExtensionElement getExtension(String namespace) {
120        return PacketUtil.extensionElementFrom(getExtensions(), null, namespace);
121    }
122
123    /**
124     * Returns the first extension that matches the specified element name and
125     * namespace, or <code>null</code> if it doesn't exist. If the provided elementName is null,
126     * only the namespace is matched. Extensions are
127     * are arbitrary XML elements in standard XMPP stanzas.
128     *
129     * @param elementName the XML element name of the extension. (May be null)
130     * @param namespace the XML element namespace of the extension.
131     * @param <PE> type of the ExtensionElement.
132     * @return the extension, or <code>null</code> if it doesn't exist.
133     */
134    @SuppressWarnings("unchecked")
135    public <PE extends ExtensionElement> PE getExtension(String elementName, String namespace) {
136        if (namespace == null) {
137            return null;
138        }
139        QName key = new QName(namespace, elementName);
140        ExtensionElement packetExtension;
141        synchronized (payload) {
142            packetExtension = payload.getFirst(key);
143        }
144        if (packetExtension == null) {
145            return null;
146        }
147        return (PE) packetExtension;
148    }
149
150
151    @Override
152    public String getNamespace() {
153        return OpenPgpElement.NAMESPACE;
154    }
155
156    protected void ensureTimestampStringSet() {
157        if (timestampString != null) return;
158
159        timestampString = XmppDateTime.formatXEP0082Date(timestamp);
160    }
161
162    protected void addCommonXml(XmlStringBuilder xml) {
163        for (Jid toJid : to != null ? to : Collections.<Jid>emptySet()) {
164            xml.halfOpenElement(ELEM_TO).attribute(ATTR_JID, toJid).closeEmptyElement();
165        }
166
167        ensureTimestampStringSet();
168        xml.halfOpenElement(ELEM_TIME).attribute(ATTR_STAMP, timestampString).closeEmptyElement();
169
170        xml.openElement(ELEM_PAYLOAD);
171        for (ExtensionElement element : payload.values()) {
172            xml.append(element.toXML(getNamespace()));
173        }
174        xml.closeElement(ELEM_PAYLOAD);
175    }
176
177    /**
178     * Return a {@link ByteArrayInputStream} that reads the bytes of the XML representation of this element.
179     *
180     * @return InputStream over xml.
181     */
182    public InputStream toInputStream() {
183        byte[] encoded = toXML().toString().getBytes(Charset.forName("UTF-8"));
184        return new ByteArrayInputStream(encoded);
185    }
186}