package com.kixeye.kixmpp;
/*
* #%L
* KIXMPP Parent
* %%
* Copyright (C) 2014 KIXEYE, Inc
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.jdom2.Content;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Namespace;
import org.jdom2.input.SAXBuilder;
import org.jdom2.input.sax.XMLReaderSAX2Factory;
import org.jdom2.output.XMLOutputter;
import org.jdom2.util.IteratorIterable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An XMPP codec for the client.
* It implements the following spec: http://tools.ietf.org/html/draft-ietf-xmpp-websocket-00
*
*/
public class KixmppWebSocketCodec extends MessageToMessageCodec<Object, Object> {
private static final Logger logger = LoggerFactory.getLogger(KixmppWebSocketCodec.class);
private XMLReaderSAX2Factory readerFactory = new XMLReaderSAX2Factory(false);
@Override
public boolean acceptInboundMessage(Object msg) throws Exception {
return msg instanceof WebSocketFrame;
}
@Override
public boolean acceptOutboundMessage(Object msg) throws Exception {
return msg instanceof Element || msg instanceof KixmppStreamStart ||
msg instanceof KixmppStreamEnd || msg instanceof String ||
msg instanceof ByteBuf;
}
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
WebSocketFrame frame = null;
if (msg instanceof Element) {
Element element = (Element)msg;
if (element.getNamespace() == null || element.getNamespace() == Namespace.NO_NAMESPACE) {
if ("stream".equals(element.getNamespacePrefix())) {
element.setNamespace(Namespace.getNamespace("http://etherx.jabber.org/streams"));
} else {
element.setNamespace(Namespace.getNamespace("jabber:client"));
IteratorIterable<Content> descendants = element.getDescendants();
while (descendants.hasNext()) {
Content content = descendants.next();
if (content instanceof Element) {
Element descendantElement = (Element)content;
if (descendantElement.getNamespace() == null || descendantElement.getNamespace() == Namespace.NO_NAMESPACE) {
descendantElement.setNamespace(element.getNamespace());
}
}
}
}
}
ByteBuf binaryData = ctx.alloc().buffer();
new XMLOutputter().output((Element)msg, new ByteBufOutputStream(binaryData));
frame = new TextWebSocketFrame(binaryData);
} else if (msg instanceof KixmppStreamStart) {
KixmppStreamStart streamStart = (KixmppStreamStart)msg;
StringWriter writer = new StringWriter();
if (streamStart.doesIncludeXmlHeader()) {
writer.append("<?xml version='1.0' encoding='UTF-8'?>");
}
writer.append("<stream:stream ");
if (streamStart.getId() != null) {
writer.append(String.format("id=\"%s\" ", streamStart.getId()));
}
if (streamStart.getFrom() != null) {
writer.append(String.format("from=\"%s\" ", streamStart.getFrom().getFullJid()));
}
if (streamStart.getTo() != null) {
writer.append(String.format("to=\"%s\" ", streamStart.getTo()));
}
writer.append("version=\"1.0\" xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\">");
frame = new TextWebSocketFrame(writer.toString());
} else if (msg instanceof KixmppStreamEnd) {
frame = new TextWebSocketFrame("</stream:stream>");
} else if (msg instanceof String) {
frame = new TextWebSocketFrame((String)msg);
} else if (msg instanceof ByteBuf) {
frame = new TextWebSocketFrame((ByteBuf)msg);
}
if (frame != null) {
if (logger.isDebugEnabled()) {
logger.debug("Sending: [{}]", frame.content().toString(StandardCharsets.UTF_8));
}
out.add(frame);
}
}
@Override
protected void decode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
WebSocketFrame frame = (WebSocketFrame)msg;
ByteBuf content = frame.retain().content();
String frameString = content.toString(StandardCharsets.UTF_8);
if (logger.isDebugEnabled()) {
logger.debug("Received: [{}]", frameString);
}
if (frameString.startsWith("<?xml")) {
frameString = frameString.replaceFirst("<\\?xml.*?\\?>", "");
}
if (frameString.startsWith("<stream:stream")) {
out.add(new KixmppStreamStart(null, true));
} else if (frameString.startsWith("</stream:stream")) {
out.add(new KixmppStreamEnd());
} else {
SAXBuilder saxBuilder = new SAXBuilder(readerFactory);
Document document = saxBuilder.build(new ByteBufInputStream(content));
Element element = document.getRootElement();
out.add(element);
}
}
}