001/** 002 * 003 * Copyright 2017 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 static org.jivesoftware.smack.util.StringUtils.requireNotNullNorEmpty; 020 021import java.io.ByteArrayInputStream; 022import java.io.InputStream; 023import java.nio.charset.Charset; 024import java.util.Collections; 025import java.util.Date; 026import java.util.List; 027import java.util.Set; 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; 037import org.jxmpp.util.XmppStringUtils; 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<Jid> to; 052 private final Date timestamp; 053 private final MultiMap<String, ExtensionElement> payload; 054 055 private String timestampString; 056 057 protected OpenPgpContentElement(Set<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(XmppStringUtils.generateKey(e.getElementName(), e.getNamespace()), e); 063 } 064 } 065 066 /** 067 * Return the set of recipients. 068 * 069 * @return recipients. 070 */ 071 public final Set<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<ExtensionElement> 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<ExtensionElement> getExtensions(String elementName, String namespace) { 107 requireNotNullNorEmpty(elementName, "elementName must not be null or empty"); 108 requireNotNullNorEmpty(namespace, "namespace must not be null or empty"); 109 String key = XmppStringUtils.generateKey(elementName, namespace); 110 return payload.getAll(key); 111 } 112 113 /** 114 * Returns the first extension of this stanza that has the given namespace. 115 * <p> 116 * When possible, use {@link #getExtension(String,String)} instead. 117 * </p> 118 * 119 * @param namespace the namespace of the extension that is desired. 120 * @return the stanza extension with the given namespace. 121 */ 122 public ExtensionElement getExtension(String namespace) { 123 return PacketUtil.extensionElementFrom(getExtensions(), null, namespace); 124 } 125 126 /** 127 * Returns the first extension that matches the specified element name and 128 * namespace, or <tt>null</tt> if it doesn't exist. If the provided elementName is null, 129 * only the namespace is matched. Extensions are 130 * are arbitrary XML elements in standard XMPP stanzas. 131 * 132 * @param elementName the XML element name of the extension. (May be null) 133 * @param namespace the XML element namespace of the extension. 134 * @param <PE> type of the ExtensionElement. 135 * @return the extension, or <tt>null</tt> if it doesn't exist. 136 */ 137 @SuppressWarnings("unchecked") 138 public <PE extends ExtensionElement> PE getExtension(String elementName, String namespace) { 139 if (namespace == null) { 140 return null; 141 } 142 String key = XmppStringUtils.generateKey(elementName, namespace); 143 ExtensionElement packetExtension; 144 synchronized (payload) { 145 packetExtension = payload.getFirst(key); 146 } 147 if (packetExtension == null) { 148 return null; 149 } 150 return (PE) packetExtension; 151 } 152 153 154 @Override 155 public String getNamespace() { 156 return OpenPgpElement.NAMESPACE; 157 } 158 159 protected void ensureTimestampStringSet() { 160 if (timestampString != null) return; 161 162 timestampString = XmppDateTime.formatXEP0082Date(timestamp); 163 } 164 165 protected void addCommonXml(XmlStringBuilder xml) { 166 for (Jid toJid : (to != null ? to : Collections.<Jid>emptySet())) { 167 xml.halfOpenElement(ELEM_TO).attribute(ATTR_JID, toJid).closeEmptyElement(); 168 } 169 170 ensureTimestampStringSet(); 171 xml.halfOpenElement(ELEM_TIME).attribute(ATTR_STAMP, timestampString).closeEmptyElement(); 172 173 xml.openElement(ELEM_PAYLOAD); 174 for (ExtensionElement element : payload.values()) { 175 xml.append(element.toXML(getNamespace())); 176 } 177 xml.closeElement(ELEM_PAYLOAD); 178 } 179 180 /** 181 * Return a {@link ByteArrayInputStream} that reads the bytes of the XML representation of this element. 182 * 183 * @return InputStream over xml. 184 */ 185 public InputStream toInputStream() { 186 byte[] encoded = toXML(null).toString().getBytes(Charset.forName("UTF-8")); 187 return new ByteArrayInputStream(encoded); 188 } 189}