001/** 002 * 003 * Copyright 2017-2021 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.packet.XmlElement; 031import org.jivesoftware.smack.util.MultiMap; 032import org.jivesoftware.smack.util.Objects; 033import org.jivesoftware.smack.util.PacketUtil; 034import org.jivesoftware.smack.util.XmlStringBuilder; 035 036import org.jxmpp.jid.Jid; 037import org.jxmpp.util.XmppDateTime; 038 039/** 040 * This class describes an OpenPGP content element. It defines the elements and fields that OpenPGP content elements 041 * do have in common. 042 */ 043public abstract class OpenPgpContentElement implements ExtensionElement { 044 045 public static final String ELEM_TO = "to"; 046 public static final String ATTR_JID = "jid"; 047 public static final String ELEM_TIME = "time"; 048 public static final String ATTR_STAMP = "stamp"; 049 public static final String ELEM_PAYLOAD = "payload"; 050 051 private final Set<? extends Jid> to; 052 private final Date timestamp; 053 private final MultiMap<QName, XmlElement> payload; 054 055 private String timestampString; 056 057 protected OpenPgpContentElement(Set<? extends Jid> to, Date timestamp, List<ExtensionElement> payload) { 058 this.to = to; 059 this.timestamp = Objects.requireNonNull(timestamp); 060 this.payload = new MultiMap<>(); 061 for (ExtensionElement e : payload) { 062 this.payload.put(e.getQName(), e); 063 } 064 } 065 066 /** 067 * Return the set of recipients. 068 * 069 * @return recipients. 070 */ 071 public final Set<? extends Jid> getTo() { 072 return to; 073 } 074 075 /** 076 * Return the timestamp on which the encrypted element has been created. 077 * This should be checked for sanity by the client. 078 * 079 * @return timestamp. 080 */ 081 public final Date getTimestamp() { 082 return timestamp; 083 } 084 085 /** 086 * Return the payload of the message. 087 * 088 * @return payload. 089 */ 090 public final List<XmlElement> getExtensions() { 091 synchronized (payload) { 092 return payload.values(); 093 } 094 } 095 096 /** 097 * Return a list of all extensions with the given element name <em>and</em> namespace. 098 * <p> 099 * Changes to the returned set will update the stanza extensions, if the returned set is not the empty set. 100 * </p> 101 * 102 * @param elementName the element name, must not be null. 103 * @param namespace the namespace of the element(s), must not be null. 104 * @return a set of all matching extensions. 105 */ 106 public List<XmlElement> getExtensions(String elementName, String namespace) { 107 QName key = new QName(namespace, elementName); 108 return payload.getAll(key); 109 } 110 111 /** 112 * Returns the first extension of this stanza that has the given namespace. 113 * <p> 114 * When possible, use {@link #getExtension(String, String)} instead. 115 * </p> 116 * 117 * @param namespace the namespace of the extension that is desired. 118 * @return the stanza extension with the given namespace. 119 */ 120 public ExtensionElement getExtension(String namespace) { 121 return PacketUtil.extensionElementFrom(getExtensions(), null, namespace); 122 } 123 124 /** 125 * Returns the first extension that matches the specified element name and 126 * namespace, or <code>null</code> if it doesn't exist. If the provided elementName is null, 127 * only the namespace is matched. Extensions are 128 * are arbitrary XML elements in standard XMPP stanzas. 129 * 130 * @param elementName the XML element name of the extension. (May be null) 131 * @param namespace the XML element namespace of the extension. 132 * @param <PE> type of the ExtensionElement. 133 * @return the extension, or <code>null</code> if it doesn't exist. 134 */ 135 @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) 136 public <PE extends ExtensionElement> PE getExtension(String elementName, String namespace) { 137 if (namespace == null) { 138 return null; 139 } 140 QName key = new QName(namespace, elementName); 141 XmlElement packetExtension; 142 synchronized (payload) { 143 packetExtension = payload.getFirst(key); 144 } 145 if (packetExtension == null) { 146 return null; 147 } 148 return (PE) packetExtension; 149 } 150 151 152 @Override 153 public String getNamespace() { 154 return OpenPgpElement.NAMESPACE; 155 } 156 157 protected void ensureTimestampStringSet() { 158 if (timestampString != null) return; 159 160 timestampString = XmppDateTime.formatXEP0082Date(timestamp); 161 } 162 163 protected void addCommonXml(XmlStringBuilder xml) { 164 for (Jid toJid : to != null ? to : Collections.<Jid>emptySet()) { 165 xml.halfOpenElement(ELEM_TO).attribute(ATTR_JID, toJid).closeEmptyElement(); 166 } 167 168 ensureTimestampStringSet(); 169 xml.halfOpenElement(ELEM_TIME).attribute(ATTR_STAMP, timestampString).closeEmptyElement(); 170 171 xml.openElement(ELEM_PAYLOAD); 172 for (XmlElement element : payload.values()) { 173 xml.append(element.toXML(getNamespace())); 174 } 175 xml.closeElement(ELEM_PAYLOAD); 176 } 177 178 /** 179 * Return a {@link ByteArrayInputStream} that reads the bytes of the XML representation of this element. 180 * 181 * @return InputStream over xml. 182 */ 183 public InputStream toInputStream() { 184 byte[] encoded = toXML().toString().getBytes(Charset.forName("UTF-8")); 185 return new ByteArrayInputStream(encoded); 186 } 187}