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}