Package plugins.Freetalk.WoT

Source Code of plugins.Freetalk.WoT.WoTMessageListXML

/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package plugins.Freetalk.WoT;

import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.TimeZone;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import plugins.Freetalk.Board;
import plugins.Freetalk.Freetalk;
import plugins.Freetalk.Message;
import plugins.Freetalk.Message.MessageID;
import plugins.Freetalk.MessageList;
import plugins.Freetalk.OwnMessage;
import plugins.Freetalk.Version;
import plugins.Freetalk.exceptions.NoSuchMessageException;
import freenet.keys.FreenetURI;

/**
* Generators & parsers of {@link WoTMessageList} XML.
*/
public final class WoTMessageListXML {
 
  public static final int MAX_XML_SIZE = 128 * 1024;
 
  private static final int XML_FORMAT_VERSION = 1;
 
 
  private final SimpleDateFormat mDateFormat;
 
  private final DocumentBuilder mDocumentBuilder;
 
  private final DOMImplementation mDOM;
 
  private final Transformer mSerializer;
 
 
  public WoTMessageListXML() {
    try {
      DocumentBuilderFactory xmlFactory = DocumentBuilderFactory.newInstance();
      xmlFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
      // DOM parser uses .setAttribute() to pass to underlying Xerces
      xmlFactory.setAttribute("http://apache.org/xml/features/disallow-doctype-decl", true);
      mDocumentBuilder = xmlFactory.newDocumentBuilder();
      mDOM = mDocumentBuilder.getDOMImplementation();

      mSerializer = TransformerFactory.newInstance().newTransformer();
      mSerializer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
      mSerializer.setOutputProperty(OutputKeys.INDENT, "no");
      mSerializer.setOutputProperty(OutputKeys.STANDALONE, "no");
     
      mDateFormat = new SimpleDateFormat("yyyy-MM-dd");
      mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    }
    catch(Exception e) {
      throw new RuntimeException(e);
    }
  }

  public void encode(final WoTMessageManager messageManager, final WoTOwnMessageList list, final OutputStream os) throws TransformerException, ParserConfigurationException, NoSuchMessageException  {
    synchronized(list) {
      final Document xmlDoc;
      synchronized(mDocumentBuilder) {
        xmlDoc = mDOM.createDocument(null, Freetalk.PLUGIN_TITLE, null);
      }
     
      // 1.0 does not support all Unicode characters which the String class supports. To prevent us from having to filter all Strings, we use 1.1
      xmlDoc.setXmlVersion("1.1");
     
      final Element rootElement = xmlDoc.getDocumentElement();
      final Element messageListElement = xmlDoc.createElement("MessageList");
     
      // Versions
     
      // We include the Freetalk version to have an easy way of handling bogus XML which might be created by bugged versions.
      rootElement.setAttribute("Version", Long.toString(Version.getRealVersion()));
      messageListElement.setAttribute("Version", Integer.toString(XML_FORMAT_VERSION));
   
      // Messages
     
      /* Important: A OwnMessageList contains a single reference for each message. A MessageList however contains a message reference
       * for each board a message is posted to. If this function is changed to be able to encode non-own MessageLists then you need
       * to ensure that each message is only listed once, the for(each MessageReference) will return duplicates if a message is posted
       * to multiple boards.*/
      for(final MessageList.MessageReference ref : list) {
        final OwnMessage message = messageManager.getOwnMessage(ref.getMessageID());
       
        // Duplicate checks to prevent severe breakage, also done in message list constructor.
        if(message.getAuthor() != list.getAuthor())
          throw new RuntimeException("Message author does not match message list author");
       
        if(message.wasInserted() == false)
          throw new RuntimeException("Trying to convert a MessageList to XML which contains a not inserted message.");
       
        final Element messageElement = xmlDoc.createElement("Message");
       
        // ID
       
        messageElement.setAttribute("ID", message.getID());
       
        // URI

        messageElement.setAttribute("FreenetURI", message.getFreenetURI().toString());
       
        // Date
       
        synchronized(mDateFormat) {
          messageElement.setAttribute("Date", mDateFormat.format(message.getDate()));
        }
       
        // Boards
       
        for(final Board board : message.getBoards()) {
          final Element boardTag = xmlDoc.createElement("Board");
          boardTag.setAttribute("Name", board.getName());
          messageElement.appendChild(boardTag);
        }
 
        messageListElement.appendChild(messageElement);
      }
     
      rootElement.appendChild(messageListElement);

      final DOMSource domSource = new DOMSource(xmlDoc);
      final StreamResult resultStream = new StreamResult(os);
      synchronized(mSerializer) {
        mSerializer.transform(domSource, resultStream);
      }
    }
  }
 
  /**
   * @param inputStream An InputStream which must not return more than {@link MAX_XML_SIZE} bytes.
   */
  public WoTMessageList decode(Freetalk freetalk, WoTIdentity author, FreenetURI uri, InputStream inputStream) throws Exception {
    // May not be accurate by definition of available(). So the JavaDoc requires the callers to obey the size limit, this is a double-check.
    if(inputStream.available() > MAX_XML_SIZE)
      throw new IllegalArgumentException("XML contains too many bytes: " + inputStream.available());
   
    final Document xml;
    synchronized(mDocumentBuilder) {
      xml = mDocumentBuilder.parse(inputStream);
    }
   
    final Element listElement = (Element)xml.getDocumentElement().getElementsByTagName("MessageList").item(0);
   
    // Version check
   
    if(Integer.parseInt(listElement.getAttribute("Version")) > XML_FORMAT_VERSION)
      throw new Exception("Version " + listElement.getAttribute("Version") + " > " + XML_FORMAT_VERSION);
 
    // Messages
   
    final NodeList messageElements = listElement.getElementsByTagName("Message");
   
    // Prevent memory DoS as early as possible - the MessageList constructor also does it but we don't even want to construct the list here.
    if(messageElements.getLength() > MessageList.MAX_MESSAGES_PER_MESSAGELIST)
      throw new IllegalArgumentException("Too many messages in MessageList: " + messageElements.getLength());
   
    // The message count is multiplied by 2 because if a message is posted to multiple boards, a MessageReference has to be created for each
    final ArrayList<MessageList.MessageReference> messages = new ArrayList<MessageList.MessageReference>(messageElements.getLength() * 2);
   
    for(int messageIndex = 0; messageIndex < messageElements.getLength(); ++messageIndex) {
      final Element messageElement = (Element)messageElements.item(messageIndex);
     
      // ID
     
      final MessageID messageID = MessageID.construct(messageElement.getAttribute("ID"));
      messageID.throwIfAuthorDoesNotMatch(author); // Duplicate check to prevent severe breakage, also done in message list constructor.
     
      // URI
     
      final FreenetURI messageURI = new FreenetURI(messageElement.getAttribute("FreenetURI")); // TODO: FreenetURI won't throw if too long
     
      // Date
     
      final Date messageDate;
      synchronized(mDateFormat) {
        messageDate = mDateFormat.parse(messageElement.getAttribute("Date"));
      }
   
      // Boards
     
      final NodeList boardElements = messageElement.getElementsByTagName("Board");
   
      // Prevent memory DoS as early as possible - the MessageList constructor also does it but we don't even want to construct the list here.
      if(boardElements.getLength() > Message.MAX_BOARDS_PER_MESSAGE)
        throw new IllegalArgumentException("Too many boards for message " + messageID + ": " + boardElements.getLength());
     
      final ArrayList<Board> messageBoards = new ArrayList<Board>(boardElements.getLength() + 1);
     
      for(int boardIndex = 0; boardIndex < boardElements.getLength(); ++boardIndex) {
        final Element boardElement = (Element)boardElements.item(boardIndex);
        messageBoards.add(freetalk.getMessageManager().getOrCreateBoard(boardElement.getAttribute("Name")));
      }
     
      for(final Board board : messageBoards)
        messages.add(new MessageList.MessageReference(messageID, messageURI, board, messageDate));
    }
   
    return new WoTMessageList(freetalk, author, uri, messages);
  }
}
TOP

Related Classes of plugins.Freetalk.WoT.WoTMessageListXML

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.