001/*
002 *
003 * Copyright 2017-2025 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.jivesoftware.smackx.jingle.provider;
018
019import java.io.IOException;
020import java.util.logging.Logger;
021
022import org.jivesoftware.smack.packet.IqData;
023import org.jivesoftware.smack.packet.StandardExtensionElement;
024import org.jivesoftware.smack.packet.XmlElement;
025import org.jivesoftware.smack.packet.XmlEnvironment;
026import org.jivesoftware.smack.parsing.SmackParsingException;
027import org.jivesoftware.smack.parsing.StandardExtensionElementProvider;
028import org.jivesoftware.smack.provider.IqProvider;
029import org.jivesoftware.smack.util.PacketParserUtils;
030import org.jivesoftware.smack.util.ParserUtils;
031import org.jivesoftware.smack.xml.XmlPullParser;
032import org.jivesoftware.smack.xml.XmlPullParserException;
033
034import org.jivesoftware.smackx.jingle.element.Jingle;
035import org.jivesoftware.smackx.jingle.element.JingleAction;
036import org.jivesoftware.smackx.jingle.element.JingleContent;
037import org.jivesoftware.smackx.jingle.element.JingleContentDescription;
038import org.jivesoftware.smackx.jingle.element.JingleContentTransport;
039import org.jivesoftware.smackx.jingle.element.JingleReason;
040import org.jivesoftware.smackx.jingle.element.JingleReason.Reason;
041import org.jivesoftware.smackx.jingle.element.UnknownJingleContentDescription;
042import org.jivesoftware.smackx.jingle.element.UnknownJingleContentTransport;
043
044import org.jxmpp.JxmppContext;
045import org.jxmpp.jid.FullJid;
046
047public class JingleProvider extends IqProvider<Jingle> {
048
049    private static final Logger LOGGER = Logger.getLogger(JingleProvider.class.getName());
050
051    @Override
052    public Jingle parse(XmlPullParser parser, int initialDepth, IqData iqData, XmlEnvironment xmlEnvironment,
053                    JxmppContext jxmppContext) throws XmlPullParserException, IOException, SmackParsingException {
054        Jingle.Builder builder = Jingle.builder(iqData);
055
056        String actionString = parser.getAttributeValue("", Jingle.ACTION_ATTRIBUTE_NAME);
057        if (actionString != null) {
058            JingleAction action = JingleAction.fromString(actionString);
059            builder.setAction(action);
060        }
061
062        FullJid initiator = ParserUtils.getFullJidAttribute(parser, Jingle.INITIATOR_ATTRIBUTE_NAME, jxmppContext);
063        builder.setInitiator(initiator);
064
065        FullJid responder = ParserUtils.getFullJidAttribute(parser, Jingle.RESPONDER_ATTRIBUTE_NAME, jxmppContext);
066        builder.setResponder(responder);
067
068        String sessionId = parser.getAttributeValue("", Jingle.SESSION_ID_ATTRIBUTE_NAME);
069        builder.setSessionId(sessionId);
070
071
072        outerloop: while (true) {
073            XmlPullParser.Event eventType = parser.next();
074            switch (eventType) {
075            case START_ELEMENT:
076                String tagName = parser.getName();
077                switch (tagName) {
078                case JingleContent.ELEMENT:
079                    JingleContent content = parseJingleContent(parser, parser.getDepth());
080                    builder.addJingleContent(content);
081                    break;
082                case JingleReason.ELEMENT:
083                    JingleReason reason = parseJingleReason(parser, jxmppContext);
084                    builder.setReason(reason);
085                    break;
086                default:
087                    LOGGER.severe("Unknown Jingle element: " + tagName);
088                    break;
089                }
090                break;
091            case END_ELEMENT:
092                if (parser.getDepth() == initialDepth) {
093                    break outerloop;
094                }
095                break;
096            default:
097                // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
098                break;
099            }
100        }
101
102        return builder.build();
103    }
104
105    public static JingleContent parseJingleContent(XmlPullParser parser, final int initialDepth)
106                    throws XmlPullParserException, IOException, SmackParsingException {
107        JingleContent.Builder builder = JingleContent.getBuilder();
108
109        String creatorString = parser.getAttributeValue("", JingleContent.CREATOR_ATTRIBUTE_NAME);
110        JingleContent.Creator creator = JingleContent.Creator.valueOf(creatorString);
111        builder.setCreator(creator);
112
113        String disposition = parser.getAttributeValue("", JingleContent.DISPOSITION_ATTRIBUTE_NAME);
114        builder.setDisposition(disposition);
115
116        String name = parser.getAttributeValue("", JingleContent.NAME_ATTRIBUTE_NAME);
117        builder.setName(name);
118
119        String sendersString = parser.getAttributeValue("", JingleContent.SENDERS_ATTRIBUTE_NAME);
120        if (sendersString != null) {
121            JingleContent.Senders senders = JingleContent.Senders.valueOf(sendersString);
122            builder.setSenders(senders);
123        }
124
125        outerloop: while (true) {
126            XmlPullParser.Event eventType = parser.next();
127            switch (eventType) {
128            case START_ELEMENT:
129                String tagName = parser.getName();
130                String namespace = parser.getNamespace();
131                switch (tagName) {
132                case JingleContentDescription.ELEMENT: {
133                    JingleContentDescription description;
134                    JingleContentDescriptionProvider<?> provider = JingleContentProviderManager.getJingleContentDescriptionProvider(namespace);
135                    if (provider == null) {
136                        StandardExtensionElement standardExtensionElement = StandardExtensionElementProvider.INSTANCE.parse(parser);
137                        description = new UnknownJingleContentDescription(standardExtensionElement);
138                    }
139                    else {
140                        description = provider.parse(parser);
141                    }
142                    builder.setDescription(description);
143                    break;
144                }
145                case JingleContentTransport.ELEMENT: {
146                    JingleContentTransport transport;
147                    JingleContentTransportProvider<?> provider = JingleContentProviderManager.getJingleContentTransportProvider(namespace);
148                    if (provider == null) {
149                        StandardExtensionElement standardExtensionElement = StandardExtensionElementProvider.INSTANCE.parse(parser);
150                        transport = new UnknownJingleContentTransport(standardExtensionElement);
151                    }
152                    else {
153                        transport = provider.parse(parser);
154                    }
155                    builder.setTransport(transport);
156                    break;
157                }
158                default:
159                    LOGGER.severe("Unknown Jingle content element: " + tagName);
160                    break;
161                }
162                break;
163            case END_ELEMENT:
164                if (parser.getDepth() == initialDepth) {
165                    break outerloop;
166                }
167                break;
168            default:
169                // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
170                break;
171            }
172        }
173
174        return builder.build();
175    }
176
177    public static JingleReason parseJingleReason(XmlPullParser parser, JxmppContext jxmppContext)
178                    throws XmlPullParserException, IOException, SmackParsingException {
179        ParserUtils.assertAtStartTag(parser);
180        final int initialDepth = parser.getDepth();
181        final String jingleNamespace = parser.getNamespace();
182
183        JingleReason.Reason reason = null;
184        XmlElement element = null;
185        String text = null;
186
187        // 'sid' is only set if the reason is 'alternative-session'.
188        String sid = null;
189
190        outerloop: while (true) {
191            XmlPullParser.TagEvent event = parser.nextTag();
192            switch (event) {
193            case START_ELEMENT:
194                String elementName = parser.getName();
195                String namespace = parser.getNamespace();
196                if (namespace.equals(jingleNamespace)) {
197                    switch (elementName) {
198                    case "text":
199                        text = parser.nextText();
200                        break;
201                    case "alternative-session":
202                        parser.next();
203                        sid = parser.nextText();
204                        break;
205                    default:
206                        reason = Reason.fromString(elementName);
207                        break;
208                    }
209                } else {
210                    // TODO: Pass proper XmlEnvironment here.
211                    element = PacketParserUtils.parseExtensionElement(elementName, namespace, parser, null, jxmppContext);
212                }
213                break;
214            case END_ELEMENT:
215                if (parser.getDepth() == initialDepth) {
216                    break outerloop;
217                }
218                break;
219            }
220        }
221
222        JingleReason res;
223        if (sid != null) {
224            res = new JingleReason.AlternativeSession(sid, text, element);
225        } else {
226            res = new JingleReason(reason, text, element);
227        }
228        return res;
229    }
230}