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}