001/** 002 * 003 * Copyright © 2015-2021 Florian Schmaus 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.jxmpp.xml.splitter; 018 019import java.io.IOException; 020import java.util.Collections; 021import java.util.Map; 022 023/** 024 * A XML splitter for XMPP. Unlike {@link XmlSplitter}, this splitter is aware 025 * of the special semantics of XMPP's {@code <stream:stream>} element. 026 */ 027public class XmppXmlSplitter extends XmlSplitter { 028 029 private final XmppElementCallback xmppElementCallback; 030 private final int maxElementSize; 031 032 private String streamPrefix; 033 034 /** 035 * Construct a new XMPP XML splitter with a max element size of 10000. 036 * <p> 037 * RFC 6120 § 13.12 4. requires XMPP servers to use nothing less then 10000 as maximum stanza size. 038 * </p> 039 * @param xmppElementCallback the callback invoked once a complete element has been processed. 040 */ 041 public XmppXmlSplitter(XmppElementCallback xmppElementCallback) { 042 this(10000, xmppElementCallback); 043 } 044 045 /** 046 * Construct a new XMPP XML splitter with a max element size of 10000. 047 * <p> 048 * RFC 6120 § 13.12 4. requires XMPP servers to use nothing less then 10000 as maximum stanza size. 049 * </p> 050 * @param xmppElementCallback the callback invoked once a complete element has been processed. 051 * @param declarationCallback a optional callback for XML Declarations. 052 * @param processingInstructionCallback a optional callback for XML Processing Instructions. 053 */ 054 public XmppXmlSplitter(XmppElementCallback xmppElementCallback, DeclarationCallback declarationCallback, 055 ProcessingInstructionCallback processingInstructionCallback) { 056 this(10000, xmppElementCallback, declarationCallback, processingInstructionCallback); 057 } 058 059 /** 060 * Construct a new XMPP XML splitter. 061 * 062 * @param maxElementSize the maximum size of a single top level element in bytes. 063 * @param xmppElementCallback the callback invoked once a complete element has been processed. 064 */ 065 public XmppXmlSplitter(int maxElementSize, XmppElementCallback xmppElementCallback) { 066 this(maxElementSize, xmppElementCallback, null, null); 067 } 068 069 /** 070 * Construct a new XMPP XML splitter. 071 * 072 * @param maxElementSize the maximum size of a single top level element in bytes. 073 * @param xmppElementCallback the callback invoked once a complete element has been processed. 074 * @param declarationCallback a optional callback for XML Declarations. 075 * @param processingInstructionCallback a optional callback for XML Processing Instructions. 076 */ 077 public XmppXmlSplitter(int maxElementSize, XmppElementCallback xmppElementCallback, 078 DeclarationCallback declarationCallback, ProcessingInstructionCallback processingInstructionCallback) { 079 this(maxElementSize, xmppElementCallback, declarationCallback, processingInstructionCallback, null); 080 } 081 082 /** 083 * Constructs a new XMPP XML splitter without any maximum element size restrictions using the given XML printer. 084 * 085 * @param xmlPrinter the optional XML printer to use. 086 */ 087 public XmppXmlSplitter(XmlPrinter xmlPrinter) { 088 this(-1, null, xmlPrinter); 089 } 090 091 /** 092 * Constructs a new XMPP XML splitter. 093 * 094 * @param maxElementSize the maximum size of a single top level element in bytes. 095 * @param xmppElementCallback the callback invoked once a complete element has been processed. 096 * @param xmlPrinter the optional XML printer to use. 097 */ 098 public XmppXmlSplitter(int maxElementSize, XmppElementCallback xmppElementCallback, XmlPrinter xmlPrinter) { 099 this(maxElementSize, xmppElementCallback, null, null, xmlPrinter); 100 } 101 102 /** 103 * Construct a new XMPP XML splitter. 104 * 105 * @param maxElementSize the maximum size of a single top level element in bytes. 106 * @param xmppElementCallback the callback invoked once a complete element has been processed. 107 * @param declarationCallback a optional callback for XML Declarations. 108 * @param processingInstructionCallback a optional callback for XML Processing Instructions. 109 * @param xmlPrinter The optional XML printer to use. 110 */ 111 public XmppXmlSplitter(int maxElementSize, XmppElementCallback xmppElementCallback, 112 DeclarationCallback declarationCallback, ProcessingInstructionCallback processingInstructionCallback, 113 XmlPrinter xmlPrinter) { 114 super(maxElementSize, xmppElementCallback, declarationCallback, processingInstructionCallback, xmlPrinter); 115 this.maxElementSize = maxElementSize; 116 this.xmppElementCallback = xmppElementCallback; 117 } 118 119 @Override 120 protected void onNextChar() throws IOException { 121 if (maxElementSize > 0 && getCurrentSplittedPartSize() >= maxElementSize) { 122 throw new IOException("Max element size exceeded"); 123 } 124 } 125 126 @Override 127 protected void onStartTag(String prefix, String localpart, Map<String, String> attributes) { 128 if (!"stream".equals(localpart)) { 129 // If the open tag's name is not 'stream' then we are not interested. 130 return; 131 } 132 133 if ("http://etherx.jabber.org/streams".equals(attributes.get("xmlns:" + prefix))) { 134 streamPrefix = prefix; 135 newSplittedPart(); 136 if (xmppElementCallback != null) { 137 xmppElementCallback.streamOpened(prefix, Collections.unmodifiableMap(attributes)); 138 } 139 } 140 } 141 142 @Override 143 protected void onEndTag(String qName) { 144 if (streamPrefix == null || !qName.startsWith(streamPrefix)) { 145 // Shortcut if streamPrefix is not yet set or if qName does not even 146 // start with it. 147 return; 148 } 149 150 if ((streamPrefix + ":stream").equals(qName) && xmppElementCallback != null) { 151 xmppElementCallback.streamClosed(); 152 } 153 } 154}