/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* 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 net.java.sip.communicator.slick.protocol.icq;
import java.io.*;
import java.util.*;
import net.kano.joscar.*;
import net.kano.joscar.flapcmd.*;
import net.kano.joscar.snac.*;
import net.kano.joscar.tlv.*;
/**
* Command for retreiving user info and setting user info,
* user info which is stored on the server
*
* @author Damian Minkov
*/
public class FullUserInfoCmd
extends SnacCommand
{
/** A TLV type containing the ICQ-specific data. */
private static final int TYPE_ICQ_DATA = 0x0001;
private static CommandFactory commandFactory = new CommandFactory();
private String senderUIN;
// data that is send to the server
private ByteArrayOutputStream icqDataOut = new ByteArrayOutputStream();
private int primaryType = -1;
private int secondaryType = -1;
private int requestID = -1;
boolean lastOfSequences = false;
private static Hashtable<Integer, Hashtable<String, Object>> retreivedInfo
= new Hashtable<Integer, Hashtable<String, Object>>();
// properties for the retreived info
final static String LAST_NAME = "LastName";
final static String PHONE_NUMBER = "PhoneNumber";
final static String SPEAK_LANG = "SpeakingLanguage";
final static String HOME_COUNTRY = "HomeCountry";
/**
* Used when sending commands
*
* @param senderUIN String
*/
public FullUserInfoCmd(String senderUIN)
{
super(21, 2);
this.senderUIN = senderUIN;
}
/**
* For constructing incoming commands
*
* @param packet SnacPacket
*/
public FullUserInfoCmd(SnacPacket packet)
{
super(21, 3);
Tlv icqDataTlv = TlvTools.readChain(packet.getData()).getLastTlv(TYPE_ICQ_DATA);
if (icqDataTlv != null)
{
ByteBlock icqBlock = icqDataTlv.getData();
ByteBlock icqData;
int hdrlen = 8; // The expected header length, not counting the length field itself.
senderUIN = String.valueOf(getUInt(icqBlock, 2));
requestID = getUShort(icqBlock, 8); // request id
this.primaryType = getUShort(icqBlock, 6);
if (primaryType >= 1000)
{
this.secondaryType = getUShort(icqBlock, 10);
hdrlen = 10;
}
if (icqBlock.getLength() >= hdrlen + 2)
{
icqData = icqBlock.subBlock(hdrlen + 2);
}
else
{
icqData = null;
}
processICQData(icqData);
}
}
/**
* Process the data in the received packet
*
* @param icqData ByteBlock
*/
private void processICQData(ByteBlock icqData)
{
switch (secondaryType)
{
case 0x00C8 //USER_INFORMATION_BASIC
: readBasicUserInfo(icqData, requestID);break;
case 0x00DC //USER_INFORMATION_MORE
: readMoreUserInfo(icqData, requestID);break;
case 0x00EB //USER_INFORMATION_EXTENDED_EMAIL
: readEmailUserInfo(icqData, requestID);break;
case 0x010E //USER_INFORMATION_HOMEPAGE_CATEGORY
: readHomePageUserInfo(icqData, requestID);break;
case 0x00D2 //USER_INFORMATION_WORK
: readWorkUserInfo(icqData, requestID);break;
case 0x00E6 //USER_INFORMATION_ABOUT
: readUserAboutInfo(icqData, requestID);break;
case 0x00F0 //USER_INFORMATION_INTERESTS
: readInterestsUserInfo(icqData, requestID);break;
case 0x00FA //USER_INFORMATION_AFFILATIONS
: readAffilationsUserInfo(icqData, requestID);break;
}
}
/**
* Writes this command's SNAC data block to the given stream.
*
* @param out the stream to which to write the SNAC data
* @throws IOException if an I/O error occurs
*/
@Override
public void writeData(OutputStream out)
throws IOException
{
ByteArrayOutputStream icqout = new ByteArrayOutputStream();
int hdrlen = 10; // The expected header length, not counting the length field itself.
int primary = 0x07D0;
int secondary = 0x0c3a;
long icqUINlong = Long.parseLong(senderUIN);
int length = hdrlen + icqDataOut.size();
writeUShort(icqout, length);
writeUInt(icqout, icqUINlong);
writeUShort(icqout, primary);
writeUShort(icqout, 0x0002); // the sequence
writeUShort(icqout, secondary);
icqDataOut.writeTo(icqout);
new Tlv(TYPE_ICQ_DATA, ByteBlock.wrap(icqout.toByteArray())).write(out);
}
/**
* Returns the stored info so far on the specified request
*
* @param requestID int
* @return Hashtable
*/
private Hashtable<String, Object> getInfoForRequest(int requestID)
{
Hashtable<String, Object> res
= retreivedInfo.get(new Integer(requestID));
if (res == null)
{
// this indicates that the info data
// doesn't exists, so this is the first packet
// from the sequence (basic info)
res = new Hashtable<String, Object>();
retreivedInfo.put(new Integer(requestID), res);
}
return res;
}
/**
* Return the retreived info from the last received request
* @return Hashtable
*/
public Hashtable<String, Object> getInfo()
{
return getInfoForRequest(requestID);
}
/**
* Method for parsing incoming data
* Read data in BasicUserInfo command
* @param block ByteBlock
* @param requestID int
*/
private void readBasicUserInfo(ByteBlock block, int requestID)
{
Hashtable<String, Object> infoData = getInfoForRequest(requestID);
// sequence of 11 String fields
String bscInfo[] = new String[11];
int offset = readStrings(block, bscInfo, 1);
int homeCountryCode = getUShort(block, offset);
offset += 2;
infoData.put(HOME_COUNTRY, new Integer(homeCountryCode));
// the following are not used
// short GMT_Offset = LEBinaryTools.getUByte(block, offset);
// offset++;
// short authFlag = LEBinaryTools.getUByte(block, offset);
// offset++;
// short webAwareFlag = LEBinaryTools.getUByte(block, offset);
// offset++;
// short directConnectionPermissionsFlag = LEBinaryTools.getUByte(block, offset);
// offset++;
// short publishPrimaryEmailFlag = LEBinaryTools.getUByte(block, offset);
// offset++;
// everything is read lets store it
// infoData.add(new ServerStoredDetails.NicknameDetail(bscInfo[0]));
// infoData.add(new ServerStoredDetails.FirstNameDetail(bscInfo[1]));
if(bscInfo[2] != null)
infoData.put(LAST_NAME, bscInfo[2]);
// infoData.add(new ServerStoredDetails.EmailAddressDetail(bscInfo[3]));
// infoData.add(new ServerStoredDetails.CityDetail(bscInfo[4]));
// infoData.add(new ServerStoredDetails.ProvinceDetail(bscInfo[5]));
if(bscInfo[6] != null)
infoData.put(PHONE_NUMBER, bscInfo[6]);
// infoData.add(new ServerStoredDetails.FaxDetail(bscInfo[7]));
// infoData.add(new ServerStoredDetails.AddressDetail(bscInfo[8]));
// infoData.add(new ServerStoredDetails.MobilePhoneDetail(bscInfo[9]));
// infoData.add(new ServerStoredDetails.PostalCodeDetail(bscInfo[10]));
}
/**
* Method for parsing incoming data
* Read data in MoreUserInfo command
* @param block ByteBlock
* @param requestID int
*/
private void readMoreUserInfo(ByteBlock block, int requestID)
{
Hashtable<String, Object> infoData = getInfoForRequest(requestID);
int offset = 1;
String[] tmp = new String[1];
//
// int age = LEBinaryTools.getUShort(block, offset);
offset += 2;
//
// short gender = LEBinaryTools.getUByte(block, offset);
// infoData.add(genders[gender]);
offset += 1;
//
offset = readStrings(block, tmp, offset);
// try
// {
// infoData.add(new ServerStoredDetails.WebPageDetail(new URL(tmp[0])));
// }
// catch (MalformedURLException ex)
// {}
//
// int birthdayYear = LEBinaryTools.getUShort(block, offset);
offset += 2;
//
// short birthdayMonth = LEBinaryTools.getUByte(block, offset);
offset += 1;
//
// short birthdayDay = LEBinaryTools.getUByte(block, offset);
offset += 1;
//
// if(birthdayYear == 0 || birthdayMonth == 0 || birthdayDay == 0)
// {
// infoData.add(new ServerStoredDetails.BirthDateDetail(null));
// }
// else
// {
// Calendar birthDate = Calendar.getInstance();
// birthDate.set(Calendar.YEAR, birthdayYear);
// birthDate.set(Calendar.MONTH, birthdayMonth);
// birthDate.set(Calendar.DAY_OF_MONTH, birthdayDay);
//
// infoData.add(new ServerStoredDetails.BirthDateDetail(birthDate));
// }
//
ArrayList<Integer> langs = new ArrayList<Integer>();
short speakingLanguage1 = getUByte(block, offset);
offset += 1;
if(speakingLanguage1 != 0 && speakingLanguage1 != 255)
{
langs.add(new Integer(speakingLanguage1));
}
short speakingLanguage2 = getUByte(block, offset);
offset += 1;
if(speakingLanguage2 != 0 && speakingLanguage2 != 255)
{
langs.add(new Integer(speakingLanguage2));
}
short speakingLanguage3 = getUByte(block, offset);
offset += 1;
if(speakingLanguage3 != 0 && speakingLanguage3 != 255)
{
langs.add(new Integer(speakingLanguage3));
}
infoData.put(SPEAK_LANG, langs);
// int moreInfoUnknown = LEBinaryTools.getUShort(block, offset);
// offset += 2;
//
// offset = readStrings(block, tmp, offset);
// infoData.add(new OriginCityDetail(tmp[0]));
//
// offset = readStrings(block, tmp, offset);
// infoData.add(new OriginProvinceDetail(tmp[0]));
//
// int originCountryCode = LEBinaryTools.getUShort(block, offset);
// offset += 2;
// infoData.add(new OriginCountryDetail(getCountry(originCountryCode)));
//
// short userGMTOffset = LEBinaryTools.getUByte(block, offset);
// offset += 1;
//
// TimeZone userTimeZone = null;
// if(userGMTOffset >= 0)
// userTimeZone = TimeZone.getTimeZone("GMT+" + userGMTOffset);
// else
// userTimeZone = TimeZone.getTimeZone("GMT" + userGMTOffset);
//
// infoData.add(new ServerStoredDetails.TimeZoneDetail("GMT Offest", userTimeZone));
}
/**
* Method for parsing incoming data
* Read data in EmailUserInfo command
* @param block ByteBlock
* @param requestID int
*/
private void readEmailUserInfo(ByteBlock block, int requestID)
{
// Vector infoData = getInfoForRequest(requestID);
//
// int offset = 1;
// String[] tmp = new String[1];
//
// short emailCount = LEBinaryTools.getUByte(block, offset);
// offset += 1;
//
// String[] emails = new String[emailCount];
// short[] emailRights = new short[emailCount];
//
// for (int i = 0; i < emailCount; i++)
// {
// // per email rights
// short publish = LEBinaryTools.getUByte(block, offset);
// offset += 1;
//
// offset = readStrings(block, tmp, offset);
// infoData.add(new ServerStoredDetails.EmailAddressDetail(tmp[0]));
// emailRights[i] = publish;
// }
}
/**
* Method for parsing incoming data
* Read data in HomePageUserInfo command
* @param block ByteBlock
* @param requestID int
*/
private void readHomePageUserInfo(ByteBlock block, int requestID)
{
// Vector infoData = getInfoForRequest(requestID);
//
// int offset = 1;
//
// //1-enabled, 0-disabled
// short enabled = LEBinaryTools.getUByte(block, offset);
// offset += 1;
//
// int homePageCategory = LEBinaryTools.getUShort(block, offset);
// offset += 2;
//
// String[] tmp = new String[1];
// offset = readStrings(block, tmp, offset);
//
// try
// {
// infoData.add(new ServerStoredDetails.WebPageDetail(new URL(tmp[0])));
// }
// catch (MalformedURLException ex)
// {}
}
/**
* Method for parsing incoming data
* Read data in WorkUserInfo command
* @param block ByteBlock
* @param requestID int
*/
private void readWorkUserInfo(ByteBlock block, int requestID)
{
// Vector infoData = getInfoForRequest(requestID);
//
// int offset = 1;
//
// String[] workAddress = new String[6];
// offset = readStrings(block, workAddress, offset);
// infoData.add(new ServerStoredDetails.WorkCityDetail(workAddress[0]));
// infoData.add(new ServerStoredDetails.WorkProvinceDetail(workAddress[1]));
// infoData.add(new ServerStoredDetails.WorkPhoneDetail(workAddress[2]));
// infoData.add(new WorkFaxDetail(workAddress[3]));
// infoData.add(new ServerStoredDetails.WorkAddressDetail(workAddress[4]));
// infoData.add(new ServerStoredDetails.WorkPostalCodeDetail(workAddress[5]));
//
// int workCountryCode = LEBinaryTools.getUShort(block, offset);
// offset += 2;
// infoData.add(
// new ServerStoredDetails.WorkCountryDetail(getCountry(workCountryCode)));
//
// String[] workInfo = new String[3];
// offset = readStrings(block, workInfo, offset);
// infoData.add(new ServerStoredDetails.WorkOrganizationNameDetail(workInfo[0]));
// infoData.add(new WorkDepartmentNameDetail(workInfo[1]));
// infoData.add(new WorkPositionNameDetail(workInfo[2]));
//
// int workOccupationCode = LEBinaryTools.getUShort(block, offset);
// offset += 2;
// if(workOccupationCode == 99)
// infoData.add(new WorkOcupationDetail(occupations[occupations.length - 1]));
// else
// infoData.add(new WorkOcupationDetail(occupations[workOccupationCode]));
//
// String[] tmp = new String[1];
// offset = readStrings(block, tmp, offset);
//
// try
// {
// infoData.add(new ServerStoredDetails.WorkPageDetail(new URL(tmp[0])));
// }
// catch (MalformedURLException ex)
// {}
}
/**
* Method for parsing incoming data
* Read data in UserAboutInfo command
* @param block ByteBlock
* @param requestID int
*/
private void readUserAboutInfo(ByteBlock block, int requestID)
{
// Vector infoData = getInfoForRequest(requestID);
//
// int offset = 1;
// String[] tmp = new String[1];
// offset = readStrings(block, tmp, offset);
//
// infoData.add(new NotesDetail(tmp[0]));
}
/**
* Method for parsing incoming data
* Read data in InterestsUserInfo command
* @param block ByteBlock
* @param requestID int
*/
private void readInterestsUserInfo(ByteBlock block, int requestID)
{
// Vector infoData = getInfoForRequest(requestID);
//
// int offset = 1;
// String[] tmp = new String[1];
//
// short interestsCount = LEBinaryTools.getUByte(block, offset);
// offset += 1;
//
// String[] interests = new String[interestsCount];
// int[] categories = new int[interestsCount];
//
// for (int i = 0; i < interestsCount; i++)
// {
// // per interest
// int categoty = LEBinaryTools.getUShort(block, offset);
// offset += 2;
//
// offset = readStrings(block, tmp, offset);
//
// if(categoty != 0)
// {
// // as the categories are between 100 and 150 we shift them
// // because their string representations are stored in array
// categoty = categoty - 99;
// }
// infoData.add(new InterestDetail(tmp[0], interestsCategories[categoty]));
// }
}
/**
* Not used for now
* @param block ByteBlock data
* @param requestID int the request id
*/
private void readAffilationsUserInfo(ByteBlock block, int requestID)
{
// Vector infoData = getInfoForRequest(requestID);
//
// int offset = 1;
// String[] tmp = new String[1];
//
// short pastCategoryCount = LEBinaryTools.getUByte(block, offset);
// offset += 1;
//
// int[] pastCategoryCodes = new int[pastCategoryCount];
// String[] pastCategories = new String[pastCategoryCount];
//
// for (int i = 0; i < pastCategoryCount; i++)
// {
// pastCategoryCodes[i] = LEBinaryTools.getUShort(block, offset);
// offset += 2;
//
// offset = readStrings(block, tmp, offset);
// pastCategories[i] = tmp[0];
// }
//
// short affCategoryCount = LEBinaryTools.getUByte(block, offset);
// offset += 1;
//
// int[] affCategoryCodes = new int[pastCategoryCount];
// String[] affCategories = new String[pastCategoryCount];
//
// for (int i = 0; i < affCategoryCount; i++)
// {
// affCategoryCodes[i] = LEBinaryTools.getUShort(block, offset);
// offset += 2;
//
// offset = readStrings(block, tmp, offset);
// affCategories[i] = tmp[0];
// }
// this is the last packet
lastOfSequences = true;
}
/**
* Writes Byte data to the icqDataOut
* which is send to the server
* @param dataType int the data type used in the Tlv
* @param value int
*/
protected void writeOutByte(int dataType, int value)
{
try
{
writeUShort(icqDataOut, dataType);
writeUShort(icqDataOut, 1);
writeUByte(icqDataOut, value);
}
catch (IOException ex)
{}
}
/**
* Writes Short data to the icqDataOut
* which is send to the server
* @param dataType int the data type used in the Tlv
* @param value int
*/
protected void writeOutShort(int dataType, int value)
{
try
{
writeUShort(icqDataOut, dataType);
writeUShort(icqDataOut, 2);
writeUShort(icqDataOut, value);
}
catch (IOException ex)
{}
}
/**
* Writes String data to the icqDataOut
* which is send to the server
* @param dataType int the data type used in the Tlv
* @param value String
*/
protected void writeOutString(int dataType, String value)
{
try
{
byte[] data = BinaryTools.getAsciiBytes(value);
writeUShort(icqDataOut, dataType);
writeUShort(icqDataOut, data.length + 2);
writeUShort(icqDataOut, data.length);
icqDataOut.write(data);
}
catch (IOException ex)
{}
}
/**
* Writes Int data to the icqDataOut
* which is send to the server
* @param out OutputStream
* @param number long the data type used in the Tlv
* @throws IOException
*/
private static void writeUInt(final OutputStream out, final long number)
throws IOException
{
out.write(new byte[] {
(byte)((number) & 0xff),
(byte)((number >> 8) & 0xff),
(byte)((number >> 16) & 0xff),
(byte)((number >> 24) & 0xff)
});
}
/**
* Writes Short data to the stream
* @param out OutputStream
* @param number int
* @throws IOException
*/
private static void writeUShort(OutputStream out, int number)
throws IOException
{
out.write(new byte[]
{
(byte)(number & 0xff),
(byte)((number >> 8) & 0xff)
});
}
/**
* Writes Byte data to the stream
* @param out OutputStream
* @param number int
* @throws IOException
*/
private static void writeUByte(OutputStream out, int number)
throws IOException
{
out.write(new byte[]{(byte) (number & 0xff)});
}
/**
* Extracts Int from the given byte block
* starting from the specified position
* @param data ByteBlock
* @param pos int
* @return long
*/
private static long getUInt(final ByteBlock data, final int pos)
{
if (data.getLength() - pos < 4)
{
return -1;
}
return ( ( data.get(pos + 3) & 0xffL) << 24)
| ( ( data.get(pos + 2) & 0xffL) << 16)
| ( ( data.get(pos + 1) & 0xffL) << 8)
| ( data.get(pos) & 0xffL);
}
/**
* Extracts Short from the given byte block
* starting from the specified position
* @param data ByteBlock
* @param pos int
* @return int
*/
private static int getUShort(final ByteBlock data, final int pos)
{
if (data.getLength() - pos < 2)
{
return -1;
}
return ( (data.get(pos + 1) & 0xff) << 8) | (data.get(pos) & 0xff);
}
/**
* Extracts Byte from the given byte block
* starting from the specified position
* @param data ByteBlock
* @param pos int
* @return short
*/
public static short getUByte(final ByteBlock data, final int pos)
{
if (data.getLength() - pos < 1)
{
return -1;
}
return (short) (data.get(pos) & 0xff);
}
/**
* Extracts String from the given byte block
* starting from the specified position
*
* @param block ByteBlock
* @param result String[] the result strings
* @param offset int
* @return int
*/
private static int readStrings(ByteBlock block, String[] result, int offset)
{
for (int i = 0; i < result.length; i++)
{
final int textlen = getUShort(block, offset) - 1; // Don't include the ending NUL.
offset += 2;
if (textlen > 0)
{
ByteBlock field = block.subBlock(offset, textlen);
result[i] = OscarTools.getString(field, "US-ASCII");
offset += textlen;
}
offset++; // Skip trailing NUL.
}
return offset;
}
/**
* The factory used to pass incoming commands
*
* @return SnacCmdFactory
*/
protected static SnacCmdFactory getCommandFactory()
{
return commandFactory;
}
/**
* Return the command for requesting full user info
* @param senderUIN String the uin of the sender
* @param userInfoUIN String the uin of the requested user info
* @return SnacCommand
*/
protected static SnacCommand getFullInfoRequestCommand(String senderUIN, String userInfoUIN)
{
return new FullInfoRequest(senderUIN, userInfoUIN);
}
/**
* Factory used for registering FullUserInfoCmd
* for receiving command
*/
private static class CommandFactory
implements SnacCmdFactory
{
static final List<CmdType> SUPPORTED_TYPES =
DefensiveTools.asUnmodifiableList(new CmdType[]
{new CmdType(21, 3)});
public SnacCommand genSnacCommand(SnacPacket packet)
{
// we are handling only one type of icq old style messages
// so we will return it. We are sure that this command is 21,3
return new FullUserInfoCmd(packet);
}
public List<CmdType> getSupportedTypes()
{
return SUPPORTED_TYPES;
}
}
/**
* Command used for requestin full user info
*/
private static class FullInfoRequest
extends SnacCommand
{
private String senderUIN;
private String userInfoUIN;
/**
* Creating request for the specified user info
* from specified sender
*
* @param senderUIN String
* @param userInfoUIN String
*/
FullInfoRequest(String senderUIN, String userInfoUIN)
{
super(21, 2);
this.senderUIN = senderUIN;
this.userInfoUIN = userInfoUIN;
}
/**
* Writing data to the stream sending it to server
* @param out OutputStream
* @throws IOException
*/
@Override
public void writeData(OutputStream out) throws IOException
{
ByteArrayOutputStream icqout = new ByteArrayOutputStream();
ByteArrayOutputStream icqDataOut = new ByteArrayOutputStream();
writeUInt(icqDataOut, Long.parseLong(userInfoUIN));
int hdrlen = 10; // The expected header length, not counting the length field itself.
int primary = 0x07D0;
int secondary = 0x04B2;
long icqUINlong = Long.parseLong(senderUIN);
int length = hdrlen + icqDataOut.size();
writeUShort(icqout, length);
writeUInt(icqout, icqUINlong);
writeUShort(icqout, primary);
writeUShort(icqout, 0x0002); // the sequence
writeUShort(icqout, secondary);
icqDataOut.writeTo(icqout);
new Tlv(TYPE_ICQ_DATA, ByteBlock.wrap(icqout.toByteArray())).write(out);
}
}
}