diff --git a/src/mibewjava/org.mibew.jabber/.classpath b/src/mibewjava/org.mibew.jabber/.classpath new file mode 100644 index 00000000..4257a5dc --- /dev/null +++ b/src/mibewjava/org.mibew.jabber/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/mibewjava/org.mibew.jabber/.project b/src/mibewjava/org.mibew.jabber/.project new file mode 100644 index 00000000..6a12ba44 --- /dev/null +++ b/src/mibewjava/org.mibew.jabber/.project @@ -0,0 +1,17 @@ + + + org.mibew.jabber + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/src/mibewjava/org.mibew.jabber/libs/smack.jar b/src/mibewjava/org.mibew.jabber/libs/smack.jar new file mode 100644 index 00000000..73d676a4 Binary files /dev/null and b/src/mibewjava/org.mibew.jabber/libs/smack.jar differ diff --git a/src/mibewjava/org.mibew.jabber/libs/smackx-debug.jar b/src/mibewjava/org.mibew.jabber/libs/smackx-debug.jar new file mode 100644 index 00000000..5d1d8760 Binary files /dev/null and b/src/mibewjava/org.mibew.jabber/libs/smackx-debug.jar differ diff --git a/src/mibewjava/org.mibew.jabber/libs/smackx-jingle.jar b/src/mibewjava/org.mibew.jabber/libs/smackx-jingle.jar new file mode 100644 index 00000000..ae044e4b Binary files /dev/null and b/src/mibewjava/org.mibew.jabber/libs/smackx-jingle.jar differ diff --git a/src/mibewjava/org.mibew.jabber/libs/smackx.jar b/src/mibewjava/org.mibew.jabber/libs/smackx.jar new file mode 100644 index 00000000..ad73bed7 Binary files /dev/null and b/src/mibewjava/org.mibew.jabber/libs/smackx.jar differ diff --git a/src/mibewjava/org.mibew.jabber/src/mibew-sample.ini b/src/mibewjava/org.mibew.jabber/src/mibew-sample.ini new file mode 100644 index 00000000..febf3866 --- /dev/null +++ b/src/mibewjava/org.mibew.jabber/src/mibew-sample.ini @@ -0,0 +1,9 @@ +# +# Configuration file for Mibew-to-jabber gate +# + +jabber.host=jabber.org +jabber.login=webim_notifier +jabber.password=123 + +jabber.admin=yourmail@jabber.org diff --git a/src/mibewjava/org.mibew.jabber/src/org/mibew/api/MibewConnection.java b/src/mibewjava/org.mibew.jabber/src/org/mibew/api/MibewConnection.java new file mode 100644 index 00000000..cb849018 --- /dev/null +++ b/src/mibewjava/org.mibew.jabber/src/org/mibew/api/MibewConnection.java @@ -0,0 +1,150 @@ +package org.mibew.api; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.mibew.api.handlers.LoginHandler; + +/** + * @author inspirer + */ +public class MibewConnection { + + private static final int REQUEST_TIMEOUT = 5000; + + private final String fUrl; + + private final Map fCookies; + + /** + * @param url Ex: http://yourserver.com/webim/ + * @param login admin + * @param password operators password + */ + public MibewConnection(String url, String login, String password) + throws UnsupportedEncodingException, NoSuchAlgorithmException, + MalformedURLException { + this.fUrl = url; + this.fCookies = new HashMap(); + this.fCookies.put("webim_lite", URLEncoder.encode(login + "," + Utils.md5(Utils.md5(password)), "UTF-8")); + } + + /** + * Connects to the server and tries to login, returns true if successful. + */ + public boolean connect() throws ParserConfigurationException { + try { + MibewResponse response = request("operator/autologin.php", ""); + SAXParser sp = SAXParserFactory.newInstance().newSAXParser(); + LoginHandler handler = new LoginHandler(); + sp.parse(new ByteArrayInputStream(response.getResponse()), handler); + String status = handler.getStatus(); + return status.equals("OK"); + } catch(Exception e) { + handleError(e.getMessage(), e); + return false; + } + } + + public void disconnect() { + } + + /** + * Request server. + * @param suburl ex: operator/update.php + * @param urlParameters post content + */ + public final synchronized MibewResponse request(String suburl, String urlParameters) throws IOException { + HttpURLConnection connection = null; + + try { + connection = (HttpURLConnection) new URL(fUrl+suburl).openConnection(); + connection.setConnectTimeout(REQUEST_TIMEOUT); + connection.setReadTimeout(REQUEST_TIMEOUT); + + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + connection.setRequestProperty("Content-Length", Integer.toString(urlParameters.getBytes().length)); + if(fCookies.size() > 0) { + StringBuilder sb = new StringBuilder(); + for(Entry cookie : fCookies.entrySet()) { + if(sb.length() > 0) { + sb.append("; "); + } + sb.append(cookie.getKey()+"="+cookie.getValue()); + } + connection.addRequestProperty("Cookie", sb.toString()); + } + + + connection.setUseCaches(false); + connection.setDoInput(true); + connection.setDoOutput(true); + + // create request + DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); + wr.writeBytes(urlParameters); + wr.flush(); + wr.close(); + + // read response + InputStream is = connection.getInputStream(); + int len = connection.getContentLength(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(len < 256 ? 256 : len); + byte b[] = new byte[1024]; + int size = 0; + while((size=is.read(b)) >= 0) { + buffer.write(b, 0, size); + } + is.close(); + + // load cookies + List cookies = connection.getHeaderFields().get("Set-Cookie"); + if(cookies != null) { + for(String cookie : cookies) { + Matcher matcher = COOKIESET.matcher(cookie); + if(matcher.find()) { + String name = matcher.group(1); + String value = matcher.group(2); + fCookies.put(name, value); + } + } + } + return new MibewResponse(connection.getResponseCode(), buffer.toByteArray()); + } catch (Exception e) { + if(e instanceof IOException) { + throw (IOException)e; + } + throw new IOException("cannot connect: " + e.getMessage()); + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + + protected void handleError(String message, Exception ex) { + System.err.println(message); + } + + private static Pattern COOKIESET = Pattern.compile("\\A(\\w+)=([^;]+);"); +} diff --git a/src/mibewjava/org.mibew.jabber/src/org/mibew/api/MibewResponse.java b/src/mibewjava/org.mibew.jabber/src/org/mibew/api/MibewResponse.java new file mode 100644 index 00000000..ead28e6b --- /dev/null +++ b/src/mibewjava/org.mibew.jabber/src/org/mibew/api/MibewResponse.java @@ -0,0 +1,50 @@ +package org.mibew.api; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; + +/** + * @author inspirer + */ +public class MibewResponse { + + public int code; + public byte[] response; + + public MibewResponse(int code, byte[] response) { + this.code = code; + this.response = response; + } + + public int getCode() { + return code; + } + + public byte[] getResponse() { + return response; + } + + public String getResponseText() { + try { + Reader r = new InputStreamReader(new ByteArrayInputStream(response), "UTF-8"); + StringBuilder sb = new StringBuilder(); + char[] c = new char[1024]; + int size = 0; + while((size = r.read(c)) != -1) { + sb.append(c, 0, size); + } + return sb.toString(); + } catch(IOException ex) { + return ""; + } + } + + @Override + public String toString() { + return + "Response code: " + code + "\n" + + "Text: " + getResponseText(); + } +} diff --git a/src/mibewjava/org.mibew.jabber/src/org/mibew/api/MibewThread.java b/src/mibewjava/org.mibew.jabber/src/org/mibew/api/MibewThread.java new file mode 100644 index 00000000..f9d31280 --- /dev/null +++ b/src/mibewjava/org.mibew.jabber/src/org/mibew/api/MibewThread.java @@ -0,0 +1,38 @@ +package org.mibew.api; + +/** + * @author inspirer + */ +public class MibewThread { + + public final long fId; + public String fState; + public String fClientName = ""; + public String fAgent = ""; + public String fAddress = ""; + public String fFirstMessage = ""; + public boolean fCanOpen = false; + public boolean fCanView = false; + public boolean fCanBan = false; + public String fStateText; + + public MibewThread(long id, String state) { + fId = id; + fState = state; + } + + public void updateFrom(MibewThread updated) { + if(fId != updated.fId) { + throw new IllegalArgumentException("different threads"); + } + fState = updated.fState; + fClientName = updated.fClientName; + fAgent = updated.fAgent; + fAddress = updated.fAddress; + fFirstMessage = updated.fFirstMessage; + fCanOpen = updated.fCanOpen; + fCanView = updated.fCanView; + fCanBan = updated.fCanBan; + fStateText = updated.fStateText; + } +} diff --git a/src/mibewjava/org.mibew.jabber/src/org/mibew/api/MibewTracker.java b/src/mibewjava/org.mibew.jabber/src/org/mibew/api/MibewTracker.java new file mode 100644 index 00000000..96aa3188 --- /dev/null +++ b/src/mibewjava/org.mibew.jabber/src/org/mibew/api/MibewTracker.java @@ -0,0 +1,85 @@ +package org.mibew.api; + +import java.io.ByteArrayInputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.mibew.api.handlers.UpdateHandler; + +/** + * @author inspirer + */ +public class MibewTracker { + + private final MibewConnection fConnection; + private final MibewTrackerListener fListener; + private long fSince = 0; + private long fLastUpdate = 0; + + private final Map fThreads; + + public MibewTracker(MibewConnection conn, MibewTrackerListener listener) { + this.fConnection = conn; + this.fListener = listener; + this.fThreads = new HashMap(); + } + + public void track() throws InterruptedException { + for(int i = 0; i < 5; i++) { + try { + MibewResponse response = fConnection.request("operator/update.php", "since="+fSince); + SAXParser sp = SAXParserFactory.newInstance().newSAXParser(); + UpdateHandler handler = new UpdateHandler(); + sp.parse(new ByteArrayInputStream(response.getResponse()), handler); + handleResponse(response, handler); + } catch(Exception e) { + System.err.println("update exception: " + e.getMessage()); + } + Thread.sleep(1000); + } + } + + private void handleResponse(MibewResponse response, UpdateHandler handler) { + if(handler.getResponse() == UpdateHandler.UPD_ERROR) { + System.out.println("Update error: " + handler.getMessage()); + } else if(handler.getResponse() == UpdateHandler.UPD_THREADS) { + System.out.println("Updated.... " + handler.getRevision()); + fSince = handler.getRevision(); + fLastUpdate = handler.getTime(); + List threads = handler.getThreads(); + if(threads != null && threads.size() > 0) { + processUpdate(threads); + } + } else { + System.out.println("Update error"); + System.out.println(response.getResponseText()); + } + } + + private void processUpdate(List updated) { + for(MibewThread mt : updated) { + MibewThread existing = fThreads.get(mt.fId); + boolean isClosed = mt.fState.equals("closed"); + if(existing == null) { + if(!isClosed) { + fThreads.put(mt.fId, mt); + fListener.threadCreated(mt); + } + } else if(isClosed) { + fThreads.remove(mt.fId); + fListener.threadClosed(existing); + } else { + existing.updateFrom(mt); + fListener.threadChanged(existing); + } + } + } + + public long getLastUpdate() { + return fLastUpdate; + } +} diff --git a/src/mibewjava/org.mibew.jabber/src/org/mibew/api/MibewTrackerListener.java b/src/mibewjava/org.mibew.jabber/src/org/mibew/api/MibewTrackerListener.java new file mode 100644 index 00000000..637bebac --- /dev/null +++ b/src/mibewjava/org.mibew.jabber/src/org/mibew/api/MibewTrackerListener.java @@ -0,0 +1,18 @@ +package org.mibew.api; + + +/** + * @author inspirer + */ +public abstract class MibewTrackerListener { + + public void threadCreated(MibewThread thread) { + } + + public void threadChanged(MibewThread thread) { + } + + public void threadClosed(MibewThread thread) { + } + +} diff --git a/src/mibewjava/org.mibew.jabber/src/org/mibew/api/Utils.java b/src/mibewjava/org.mibew.jabber/src/org/mibew/api/Utils.java new file mode 100644 index 00000000..bf79cb7f --- /dev/null +++ b/src/mibewjava/org.mibew.jabber/src/org/mibew/api/Utils.java @@ -0,0 +1,31 @@ +package org.mibew.api; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * @author inspirer + */ +public class Utils { + + private static final String HEX_DIGITS = "0123456789abcdef"; + + public static String md5(String string) throws NoSuchAlgorithmException, UnsupportedEncodingException { + return md5(string.getBytes("utf-8")); + } + + private static String md5(byte[] string) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance( "MD5" ); + md.update(string); + byte[] digest = md.digest(); + StringBuilder sb = new StringBuilder(); + for ( byte b : digest ) { + sb.append(HEX_DIGITS.charAt((b&0xff)>>4)); + sb.append(HEX_DIGITS.charAt(b&0xf)); + } + return sb.toString(); + } + + +} diff --git a/src/mibewjava/org.mibew.jabber/src/org/mibew/api/handlers/LoginHandler.java b/src/mibewjava/org.mibew.jabber/src/org/mibew/api/handlers/LoginHandler.java new file mode 100644 index 00000000..b987c9d0 --- /dev/null +++ b/src/mibewjava/org.mibew.jabber/src/org/mibew/api/handlers/LoginHandler.java @@ -0,0 +1,41 @@ +package org.mibew.api.handlers; + +import java.util.Stack; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +/** + * @author inspirer + */ +public class LoginHandler extends DefaultHandler { + + private Stack fPath = new Stack(); + private String status = ""; + + @Override + public void startElement(String uri, String localName, String name, + Attributes attributes) throws SAXException { + fPath.push(name); + } + + @Override + public void endElement(String uri, String localName, String name) + throws SAXException { + fPath.pop(); + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + String current = fPath.peek(); + if(fPath.size() != 2 || !current.equals("status") || !fPath.get(0).equals("login")) { + throw new SAXException("unexpected characters"); + } + status += new String(ch,start,length); + } + + public String getStatus() { + return status; + } +} diff --git a/src/mibewjava/org.mibew.jabber/src/org/mibew/api/handlers/UpdateHandler.java b/src/mibewjava/org.mibew.jabber/src/org/mibew/api/handlers/UpdateHandler.java new file mode 100644 index 00000000..58dc8b44 --- /dev/null +++ b/src/mibewjava/org.mibew.jabber/src/org/mibew/api/handlers/UpdateHandler.java @@ -0,0 +1,137 @@ +package org.mibew.api.handlers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +import org.mibew.api.MibewThread; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +/** + * @author inspirer + */ +public class UpdateHandler extends DefaultHandler { + + public static final int UPD_ERROR = 1; + public static final int UPD_THREADS = 2; + + private int fResponse = 0; + private String fMessage = ""; + private long fRevision; + private long fTime; + private List fUpdated; + + private Stack fPath = new Stack(); + private MibewThread fCurrentThread; + + @Override + public void startElement(String uri, String localName, String name, + Attributes attributes) throws SAXException { + try { + if (fPath.size() == 0) { + if (name.equals("error")) { + fResponse = UPD_ERROR; + } else if (name.equals("threads")) { + fResponse = UPD_THREADS; + fTime = Long.parseLong(attributes.getValue("time")); + fRevision = Long.parseLong(attributes.getValue("revision")); + } else { + throw new SAXException("unknown root element: " + name); + } + } + if (fResponse == UPD_THREADS && fPath.size() == 1 + && name.equals("thread")) { + long id = Long.parseLong(attributes.getValue("id")); + String stateid = attributes.getValue("stateid"); + fCurrentThread = new MibewThread(id, stateid); + + if(!stateid.equals("closed")) { + fCurrentThread.fStateText = attributes.getValue("state"); + fCurrentThread.fCanOpen = booleanAttribute(attributes.getValue("canopen")); + fCurrentThread.fCanView = booleanAttribute(attributes.getValue("canview")); + fCurrentThread.fCanBan = booleanAttribute(attributes.getValue("canban")); + } + + } + } catch (NumberFormatException ex) { + throw new SAXException(ex.getMessage()); + } + fPath.push(name); + } + + private boolean booleanAttribute(String value) { + if(value != null && value.equals("true")) { + return true; + } + return false; + } + + @Override + public void endElement(String uri, String localName, String name) + throws SAXException { + fPath.pop(); + if (fResponse == UPD_THREADS && fPath.size() == 1 + && name.equals("thread")) { + if(fUpdated == null) { + fUpdated = new ArrayList(); + } + fUpdated.add(fCurrentThread); + fCurrentThread = null; + } + } + + @Override + public void characters(char[] ch, int start, int length) + throws SAXException { + if (fResponse == UPD_ERROR) { + String current = fPath.peek(); + if (fPath.size() != 2 || !current.equals("descr")) { + throw new SAXException("unexpected characters"); + } + fMessage += new String(ch, start, length); + } else if (fResponse == UPD_THREADS) { + if(fCurrentThread == null || fPath.size() != 3) { + throw new SAXException("unknown characters"); + } + + String subvar = fPath.peek(); + String value = new String(ch, start, length); + if("name".equals(subvar)) { + fCurrentThread.fClientName += value; + } else if("addr".equals(subvar)) { + fCurrentThread.fAddress += value; + } else if("message".equals(subvar)) { + fCurrentThread.fFirstMessage += value; + } else if("agent".equals(subvar)) { + fCurrentThread.fAgent += value; + } + + // TODO + + } else { + throw new SAXException("unexpected characters: no root"); + } + } + + public int getResponse() { + return fResponse; + } + + public String getMessage() { + return fMessage; + } + + public long getTime() { + return fTime; + } + + public long getRevision() { + return fRevision; + } + + public List getThreads() { + return fUpdated; + } +} diff --git a/src/mibewjava/org.mibew.jabber/src/org/mibew/jabber/Application.java b/src/mibewjava/org.mibew.jabber/src/org/mibew/jabber/Application.java new file mode 100644 index 00000000..961c3970 --- /dev/null +++ b/src/mibewjava/org.mibew.jabber/src/org/mibew/jabber/Application.java @@ -0,0 +1,63 @@ +package org.mibew.jabber; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; + +import javax.xml.parsers.ParserConfigurationException; + +import org.jivesoftware.smack.Chat; +import org.jivesoftware.smack.MessageListener; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.Message; +import org.mibew.api.MibewConnection; +import org.mibew.api.MibewThread; +import org.mibew.api.MibewTracker; +import org.mibew.api.MibewTrackerListener; +import org.xml.sax.SAXException; + +/** + * @author inspirer + */ +public class Application { + + public static void main(String[] args) throws NoSuchAlgorithmException, IOException, XMPPException, InterruptedException, ParserConfigurationException, SAXException { + System.out.println("Mibew Jabber transport application"); + + Parameters p = new Parameters(args); + if(!p.load()) { + return; + } + + XMPPConnection connection = new XMPPConnection(p.fJabberServer); + connection.connect(); + connection.login(p.fJabberLogin, p.fJabberPassword); + final Chat chat = connection.getChatManager().createChat(p.fJabberAdmin, new MessageListener() { + + public void processMessage(Chat chat, Message message) { + System.out.println("Received message: " + message.getThread() + " " + message.getBody()); + } + }); + + MibewConnection conn = new MibewConnection("http://localhost:8080/webim/", "admin", ""); + if(!conn.connect()) { + System.err.println("Wrong server, login or password."); + return; + } + MibewTracker mt = new MibewTracker(conn, new MibewTrackerListener(){ + + @Override + public void threadCreated(MibewThread thread) { + try { + chat.sendMessage(thread.fId + ": " + thread.fAddress + " " + thread.fClientName); + } catch (XMPPException e) { + e.printStackTrace(); + } + } + + }); + mt.track(); + + connection.disconnect(); + } +} diff --git a/src/mibewjava/org.mibew.jabber/src/org/mibew/jabber/Parameters.java b/src/mibewjava/org.mibew.jabber/src/org/mibew/jabber/Parameters.java new file mode 100644 index 00000000..028673e6 --- /dev/null +++ b/src/mibewjava/org.mibew.jabber/src/org/mibew/jabber/Parameters.java @@ -0,0 +1,54 @@ +package org.mibew.jabber; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * @author inspirer + */ +public class Parameters { + + private final String[] fArguments; + + public String fJabberServer; + public String fJabberLogin; + public String fJabberPassword; + + public String fJabberAdmin; + + public Parameters(String[] args) { + this.fArguments = args; + } + + private String getProperty(Properties p, String name) throws IOException { + String result = p.getProperty(name); + if(result == null || result.trim().length() == 0) { + throw new IOException("No '"+name+"' property"); + } + return result; + } + + public boolean load() { + try { + InputStream is = getClass().getClassLoader().getResourceAsStream("mibew.ini"); + if(is != null) { + Properties p = new Properties(); + p.load(is); + + fJabberServer = getProperty(p, "jabber.host"); + fJabberLogin = getProperty(p, "jabber.login"); + fJabberPassword = getProperty(p, "jabber.password"); + fJabberAdmin = getProperty(p, "jabber.admin"); + + return true; + } + } catch (IOException e) { + System.err.println(e.getMessage()); + return false; + } + + System.err.println("Cannot find mibew.ini"); + return false; + } +}