/**
* $RCSfile$
* $Revision: 2407 $
* $Date: 2004-11-02 17:37:00 -0600 (Tue, 02 Nov 2004) $
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.
*/
package org.jivesoftware.smackx.packet;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.util.*;
import org.jivesoftware.smackx.*;
import org.jivesoftware.smackx.filetransfer.*;
import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
/**
* represents information passed in <si> (stream initiation) packet
*
*@author Lukasz Wiechec
*@version 1.0
*/
public class StreamInitiation extends IQ {
private final static String NAMESPACE = "http://jabber.org/protocol/si";
private final static String PROFILE =
"http://jabber.org/protocol/si/profile/file-transfer";
private FileDetails fileDetails = null;
private Feature feature = null;
private String id = null;
private int preferred = -1;
/**
* Creates a new StreamInitiation object and sets a random session id
*/
public StreamInitiation(int type) {
this.preferred = type;
setType(IQ.Type.SET);
id = StringUtils.randomString(20);
feature = new Feature(type);
}
public StreamInitiation()
{
this(-1);
}
/**
* Returns details about the file in the stream initiation
*
*@return The details of the file in this stream initiation
*/
public FileDetails getFileDetails() {
return fileDetails;
}
/**
* Sets the file details of the file sent/received in this packet
*
*@param aFile The new file details
*/
public void setFileDetails(FileDetails aFile) {
fileDetails = aFile;
}
/**
* Returns the session id for this packet
*
*@return The session id
*/
public String getSid() {
return id;
}
/**
* Sets the feature for this packet
*
*@param aFeature the new feature
*/
public void setFeature(Feature aFeature) {
feature = aFeature;
}
/**
* Returns the features of this packet
*@return Feature of this packet
*/
public Feature getFeature() { return feature; }
/**
* Sets the session id for this packet
*
*@param aID The new session id
*/
public void setSid(String aID) {
id = aID;
}
/**
* Returns the XML representation of this packet
*
*@return The XML representation of this packet
*/
public String getChildElementXML() {
StringBuffer buf = new StringBuffer();
buf.append("<si xmlns=\"" + NAMESPACE + "\" profile=\"" + PROFILE + "\"");
if (id != null) {
buf.append(" id=\"" + id + "\"");
}
buf.append(">");
if (fileDetails != null) {
buf.append(fileDetails.getXML());
}
if (feature != null) {
buf.append(feature.getXML());
}
buf.append("</si>");
return buf.toString();
}
/**
* helper method for creating "confirmation" <si> messages
*
*@return StreamInitiation message of type RESULT
*/
public StreamInitiation createConfirmationMessage(int type) {
// prepare a confirmation <si> message; it should have null stream id
// now we're replying, so to=from and vice versa
StreamInitiation confirmSI = new StreamInitiation();
confirmSI.setFrom(getTo());
confirmSI.setTo(getFrom());
confirmSI.setType(IQ.Type.RESULT);
confirmSI.setPacketID(getPacketID());
Feature feature = new Feature("submit");
FormField formfield = new FormField("stream-method");
formfield.setType(null);
if(this.feature.providesBytestreamOption() || type == FileTransferManager.TYPE_SOCKS5)
{
formfield.addValue("http://jabber.org/protocol/bytestreams");
}
else if(this.feature.providesIBBOption() || type == FileTransferManager.TYPE_IBB);
{
formfield.addValue("http://jabber.org/protocol/ibb");
}
feature.getDataForm().addField(formfield);
confirmSI.setFeature(feature);
confirmSI.setSid(null);
return confirmSI;
}
/**
* helper method for creating "confirmation" <si> messages
*
*@return StreamInitiation message of type RESULT
*/
public StreamInitiation createConfirmationMessage() {
return createConfirmationMessage(-1);
}
/**
* subclass that carry file information
*/
public static class FileDetails {
private File fFile;
private String fFileName;
private long fFileSize = 0;
private String fDescription;
private String fHash;
private String fDate = DateTimeUtils.getDateTime();
private File fDestFile;
/**
* Creates a file details based on name, description, size
*
*@param name The name of the file
*@param description Description of file
*@param size Size of the file
*/
public FileDetails(String name, String description, long size) {
fFileSize = size;
fDescription = description;
fFileName = name;
}
/**
* Creates a file details object based on a <tt>File</tt> object
*
*@param aFile The file for which to create the details
*/
public FileDetails(File aFile) {
fFile = aFile;
fFileName = aFile.getName();
fFileSize = aFile.length();
fHash = createMD5Sum();
fDestFile = new File(aFile.getName());
}
/**
* Creates a FileDetails object based on the location of the file
*
*@param aFilename The location of the file
*@throws FileNotFoundException if the file cannot be found
*/
public FileDetails(String aFilename) throws FileNotFoundException {
fFile = new File(aFilename);
fFileName = fFile.getName();
fFileSize = fFile.length();
fHash = new String();
fDestFile = new File(aFilename);
}
/**
* Gets the name of the file
*
*@return The name of the file
*/
public String getFileName() {
return fFileName;
}
/**
* Sets the name of the file
*
*@param aFileName The new name of the file
*/
public void setFileName(String aFileName) {
this.fFileName = aFileName;
}
/**
* Gets the size of the file
*
*@return The size of the file
*/
public long getFileSize() {
return fFileSize;
}
/**
* Sets the size of a file
*
*@param aFileSize The new size of the file
*/
public void setFileSize(long aFileSize) {
this.fFileSize = aFileSize;
}
/**
* Gets the description of the file
*
*@return The description of the file
*/
public String getDescription() {
return fDescription;
}
/**
* Sets the description of the file
*
*@param aDescription The new description
*/
public void setDescription(String aDescription) {
this.fDescription = aDescription;
}
/**
* Gets the md5 hash of the file
*
*@return The md5 hash of the file, or <tt>null</tt> if it isn't available
*/
public String getHash() {
return fHash;
}
/**
* Sets the md5 hash of the file
*
*@param aHash The new md5 hash of the file
*/
public void setHash(String aHash) {
this.fHash = aHash;
}
/**
* Gets the creation date of the file
*
*@return The creation date of the file, or <tt>null</tt> if it isn't available
*/
public String getDate() {
return fDate;
}
/**
* Gets the <tt>File</tt> object for this file
*
*@return The <tt>File</tt> for this file, or <tt>null</tt> if it's not available
*/
public File getFile() {
return fFile;
}
/**
* Sets the <tt>File</tt> object for this file
*
*@param aFile The <tt>File</tt> object for this file
*/
public void setFile(File aFile) {
this.fFile = aFile;
}
/**
* Returns the XML representation of this packet
*
*@return Returns the XML representation of this packet
*/
public String getXML() {
StringBuffer buf = new StringBuffer();
buf.append("<file xmlns=\"").append(PROFILE).append("\"");
if (getFileName() != null) {
buf.append(" name=\"").append(getFileName()).append("\"");
}
if (getFileSize() != 0) {
buf.append(" size=\"").append(getFileSize()).append("\"");
}
if (getDate() != null) {
buf.append(" date=\"").append(getDate()).append("\"");
}
if (getHash() != null) {
buf.append(" hash=\"").append(getHash()).append("\"");
}
buf.append(">");
if (getDescription() != null) {
buf.append("<desc>").append(getDescription()).append("</desc>");
}
buf.append("</file>");
return buf.toString();
}
/**
* returns MD5 sum of a file
*
*@return returns the md5 sum of a file
*/
public String createMD5Sum() {
String out = "";
try {
byte[] digest;
MessageDigest md = MessageDigest.getInstance("MD5");
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fFile));
byte[] buff = new byte[1024];
int chunk = 1024;
int bytesRead = 0;
while ((bytesRead = bis.read(buff, 0, chunk)) != -1) {
md.update(buff, 0, bytesRead);
}
digest = md.digest();
String hexChar = null;
if (digest != null) {
for (int i = 0; i < digest.length; i++) {
if (digest[i] > 0) {
hexChar = Integer.toHexString(digest[i]);
} else if (digest[i] < 0) {
hexChar = Integer.toHexString(digest[i]).substring(6);
} else {
hexChar = "00";
}
out += hexChar;
}
}
return out;
} catch (FileNotFoundException e) {
return null;
} catch (NoSuchAlgorithmException e) {
return null;
} catch (IOException e) {
}
return null;
}
}
/**
* class that will hold feature information re-using DataForm code to
*
*/
public static class Feature {
private final static String BYTESTREAMS_FEATURE =
"http://jabber.org/protocol/bytestreams";
private final static String IBB_FEATURE = "http://jabber.org/protocol/ibb";
DataForm fDataForm;
int preferred = -1;
/**
* Creates the "features" portion of the StreamInitiation packet
*/
public Feature(int type) {
this("form");
preferred = type;
createForm();
}
public Feature()
{
this(-1);
}
/**
* Adds the various data fields to the data form
**/
private void createForm() {
FormField field = new FormField("stream-method");
field.setType(FormField.TYPE_LIST_SINGLE);
if(preferred == -1 || preferred == FileTransferManager.TYPE_SOCKS5)
field.addOption(new FormField.Option("http://jabber.org/protocol/bytestreams"));
if(preferred == -1 || preferred == FileTransferManager.TYPE_IBB)
field.addOption(new FormField.Option("http://jabber.org/protocol/ibb"));
fDataForm.addField(field);
}
/**
* Creates the "features" portion of the StreamInitiation object
*
*@param aDataFormType The type of data form you wish to use for this packet
*/
public Feature(String aDataFormType) {
fDataForm = new DataForm(aDataFormType);
}
/**
* Returns the data form for this packet
*@return the DataForm for this packet
*/
public DataForm getDataForm() { return fDataForm; }
/**
* Gets the XML representation of this part of the packet
*
*@return the XML representation of this part of the packet
*/
public String getXML() {
StringBuffer buf = new StringBuffer();
buf.append("<feature xmlns=\"http://jabber.org/protocol/feature-neg\">");
if (fDataForm != null) {
buf.append(fDataForm.toXML());
}
buf.append("</feature>");
return buf.toString();
}
/**
* Sets the DataForm to be used
*
*@param fFeatureForm The new DataForm
*/
public void setDataForm(DataForm fFeatureForm) {
this.fDataForm = fFeatureForm;
}
/**
* Determines if this packet supports IBB
*
*@return <tt>true</tt> if this packet supports IBB
*/
public boolean providesIBBOption() {
return providesOption(IBB_FEATURE);
}
/**
* check if this Feature provides bytestream option this method works
* with both "set" and "result" <si> requests, the one in form:
*
*@return <tt>true</tt> if this feature provides bytestreams
*/
public boolean providesBytestreamOption() {
return providesOption(BYTESTREAMS_FEATURE);
}
/**
* Checks if this packet supports a feature
*
*@param o The feature to check
*@return <tt>true</tt> if the feature is supported
*/
private boolean providesOption(String o) {
// check if the caller asks for bytestream feature
Iterator iFields = fDataForm.getFields();
while (iFields.hasNext()) {
FormField field = (FormField) iFields.next();
if (field.getVariable().equals("stream-method")) {
Iterator iOptions = field.getOptions();
while (iOptions.hasNext()) {
FormField.Option option = (FormField.Option) iOptions.next();
if (option.getValue().equals(o)) {
return true;
}
}
Iterator iValues = field.getValues();
while (iValues.hasNext()) {
String value = (String) iValues.next();
if (value.equals(o)) {
return true;
}
}
}
}
return false;
}
}
}