001/** 002 * 003 * Copyright 2003-2006 Jive Software. 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.si.packet; 018 019import java.util.Date; 020 021import org.jivesoftware.smack.packet.IQ; 022import org.jivesoftware.smack.packet.PacketExtension; 023import org.jivesoftware.smack.util.StringUtils; 024import org.jivesoftware.smack.util.XmppDateTime; 025import org.jivesoftware.smackx.xdata.packet.DataForm; 026 027/** 028 * The process by which two entities initiate a stream. 029 * 030 * @author Alexander Wenckus 031 */ 032public class StreamInitiation extends IQ { 033 034 private String id; 035 036 private String mimeType; 037 038 private File file; 039 040 private Feature featureNegotiation; 041 042 /** 043 * The "id" attribute is an opaque identifier. This attribute MUST be 044 * present on type='set', and MUST be a valid string. This SHOULD NOT be 045 * sent back on type='result', since the <iq/> "id" attribute provides the 046 * only context needed. This value is generated by the Sender, and the same 047 * value MUST be used throughout a session when talking to the Receiver. 048 * 049 * @param id The "id" attribute. 050 */ 051 public void setSessionID(final String id) { 052 this.id = id; 053 } 054 055 /** 056 * Uniquely identifies a stream initiation to the recipient. 057 * 058 * @return The "id" attribute. 059 * @see #setSessionID(String) 060 */ 061 public String getSessionID() { 062 return id; 063 } 064 065 /** 066 * The "mime-type" attribute identifies the MIME-type for the data across 067 * the stream. This attribute MUST be a valid MIME-type as registered with 068 * the Internet Assigned Numbers Authority (IANA) [3] (specifically, as 069 * listed at <http://www.iana.org/assignments/media-types>). During 070 * negotiation, this attribute SHOULD be present, and is otherwise not 071 * required. If not included during negotiation, its value is assumed to be 072 * "binary/octect-stream". 073 * 074 * @param mimeType The valid mime-type. 075 */ 076 public void setMimeType(final String mimeType) { 077 this.mimeType = mimeType; 078 } 079 080 /** 081 * Identifies the type of file that is desired to be transfered. 082 * 083 * @return The mime-type. 084 * @see #setMimeType(String) 085 */ 086 public String getMimeType() { 087 return mimeType; 088 } 089 090 /** 091 * Sets the file which contains the information pertaining to the file to be 092 * transfered. 093 * 094 * @param file The file identified by the stream initiator to be sent. 095 */ 096 public void setFile(final File file) { 097 this.file = file; 098 } 099 100 /** 101 * Returns the file containing the information about the request. 102 * 103 * @return Returns the file containing the information about the request. 104 */ 105 public File getFile() { 106 return file; 107 } 108 109 /** 110 * Sets the data form which contains the valid methods of stream neotiation 111 * and transfer. 112 * 113 * @param form The dataform containing the methods. 114 */ 115 public void setFeatureNegotiationForm(final DataForm form) { 116 this.featureNegotiation = new Feature(form); 117 } 118 119 /** 120 * Returns the data form which contains the valid methods of stream 121 * neotiation and transfer. 122 * 123 * @return Returns the data form which contains the valid methods of stream 124 * neotiation and transfer. 125 */ 126 public DataForm getFeatureNegotiationForm() { 127 return featureNegotiation.getData(); 128 } 129 130 /* 131 * (non-Javadoc) 132 * 133 * @see org.jivesoftware.smack.packet.IQ#getChildElementXML() 134 */ 135 public String getChildElementXML() { 136 StringBuilder buf = new StringBuilder(); 137 if (this.getType().equals(IQ.Type.SET)) { 138 buf.append("<si xmlns=\"http://jabber.org/protocol/si\" "); 139 if (getSessionID() != null) { 140 buf.append("id=\"").append(getSessionID()).append("\" "); 141 } 142 if (getMimeType() != null) { 143 buf.append("mime-type=\"").append(getMimeType()).append("\" "); 144 } 145 buf 146 .append("profile=\"http://jabber.org/protocol/si/profile/file-transfer\">"); 147 148 // Add the file section if there is one. 149 String fileXML = file.toXML(); 150 if (fileXML != null) { 151 buf.append(fileXML); 152 } 153 } 154 else if (this.getType().equals(IQ.Type.RESULT)) { 155 buf.append("<si xmlns=\"http://jabber.org/protocol/si\">"); 156 } 157 else { 158 throw new IllegalArgumentException("IQ Type not understood"); 159 } 160 if (featureNegotiation != null) { 161 buf.append(featureNegotiation.toXML()); 162 } 163 buf.append("</si>"); 164 return buf.toString(); 165 } 166 167 /** 168 * <ul> 169 * <li>size: The size, in bytes, of the data to be sent.</li> 170 * <li>name: The name of the file that the Sender wishes to send.</li> 171 * <li>date: The last modification time of the file. This is specified 172 * using the DateTime profile as described in Jabber Date and Time Profiles.</li> 173 * <li>hash: The MD5 sum of the file contents.</li> 174 * </ul> 175 * <p/> 176 * <p/> 177 * <desc> is used to provide a sender-generated description of the 178 * file so the receiver can better understand what is being sent. It MUST 179 * NOT be sent in the result. 180 * <p/> 181 * <p/> 182 * When <range> is sent in the offer, it should have no attributes. 183 * This signifies that the sender can do ranged transfers. When a Stream 184 * Initiation result is sent with the <range> element, it uses these 185 * attributes: 186 * <p/> 187 * <ul> 188 * <li>offset: Specifies the position, in bytes, to start transferring the 189 * file data from. This defaults to zero (0) if not specified.</li> 190 * <li>length - Specifies the number of bytes to retrieve starting at 191 * offset. This defaults to the length of the file from offset to the end.</li> 192 * </ul> 193 * <p/> 194 * <p/> 195 * Both attributes are OPTIONAL on the <range> element. Sending no 196 * attributes is synonymous with not sending the <range> element. When 197 * no <range> element is sent in the Stream Initiation result, the 198 * Sender MUST send the complete file starting at offset 0. More generally, 199 * data is sent over the stream byte for byte starting at the offset 200 * position for the length specified. 201 * 202 * @author Alexander Wenckus 203 */ 204 public static class File implements PacketExtension { 205 206 private final String name; 207 208 private final long size; 209 210 private String hash; 211 212 private Date date; 213 214 private String desc; 215 216 private boolean isRanged; 217 218 /** 219 * Constructor providing the name of the file and its size. 220 * 221 * @param name The name of the file. 222 * @param size The size of the file in bytes. 223 */ 224 public File(final String name, final long size) { 225 if (name == null) { 226 throw new NullPointerException("name cannot be null"); 227 } 228 229 this.name = name; 230 this.size = size; 231 } 232 233 /** 234 * Returns the file's name. 235 * 236 * @return Returns the file's name. 237 */ 238 public String getName() { 239 return name; 240 } 241 242 /** 243 * Returns the file's size. 244 * 245 * @return Returns the file's size. 246 */ 247 public long getSize() { 248 return size; 249 } 250 251 /** 252 * Sets the MD5 sum of the file's contents 253 * 254 * @param hash The MD5 sum of the file's contents. 255 */ 256 public void setHash(final String hash) { 257 this.hash = hash; 258 } 259 260 /** 261 * Returns the MD5 sum of the file's contents 262 * 263 * @return Returns the MD5 sum of the file's contents 264 */ 265 public String getHash() { 266 return hash; 267 } 268 269 /** 270 * Sets the date that the file was last modified. 271 * 272 * @param date The date that the file was last modified. 273 */ 274 public void setDate(Date date) { 275 this.date = date; 276 } 277 278 /** 279 * Returns the date that the file was last modified. 280 * 281 * @return Returns the date that the file was last modified. 282 */ 283 public Date getDate() { 284 return date; 285 } 286 287 /** 288 * Sets the description of the file. 289 * 290 * @param desc The description of the file so that the file reciever can 291 * know what file it is. 292 */ 293 public void setDesc(final String desc) { 294 this.desc = desc; 295 } 296 297 /** 298 * Returns the description of the file. 299 * 300 * @return Returns the description of the file. 301 */ 302 public String getDesc() { 303 return desc; 304 } 305 306 /** 307 * True if a range can be provided and false if it cannot. 308 * 309 * @param isRanged True if a range can be provided and false if it cannot. 310 */ 311 public void setRanged(final boolean isRanged) { 312 this.isRanged = isRanged; 313 } 314 315 /** 316 * Returns whether or not the initiator can support a range for the file 317 * tranfer. 318 * 319 * @return Returns whether or not the initiator can support a range for 320 * the file tranfer. 321 */ 322 public boolean isRanged() { 323 return isRanged; 324 } 325 326 public String getElementName() { 327 return "file"; 328 } 329 330 public String getNamespace() { 331 return "http://jabber.org/protocol/si/profile/file-transfer"; 332 } 333 334 public String toXML() { 335 StringBuilder buffer = new StringBuilder(); 336 337 buffer.append("<").append(getElementName()).append(" xmlns=\"") 338 .append(getNamespace()).append("\" "); 339 340 if (getName() != null) { 341 buffer.append("name=\"").append(StringUtils.escapeForXML(getName())).append("\" "); 342 } 343 344 if (getSize() > 0) { 345 buffer.append("size=\"").append(getSize()).append("\" "); 346 } 347 348 if (getDate() != null) { 349 buffer.append("date=\"").append(XmppDateTime.formatXEP0082Date(date)).append("\" "); 350 } 351 352 if (getHash() != null) { 353 buffer.append("hash=\"").append(getHash()).append("\" "); 354 } 355 356 if ((desc != null && desc.length() > 0) || isRanged) { 357 buffer.append(">"); 358 if (getDesc() != null && desc.length() > 0) { 359 buffer.append("<desc>").append(StringUtils.escapeForXML(getDesc())).append("</desc>"); 360 } 361 if (isRanged()) { 362 buffer.append("<range/>"); 363 } 364 buffer.append("</").append(getElementName()).append(">"); 365 } 366 else { 367 buffer.append("/>"); 368 } 369 return buffer.toString(); 370 } 371 } 372 373 /** 374 * The feature negotiation portion of the StreamInitiation packet. 375 * 376 * @author Alexander Wenckus 377 * 378 */ 379 public class Feature implements PacketExtension { 380 381 private final DataForm data; 382 383 /** 384 * The dataform can be provided as part of the constructor. 385 * 386 * @param data The dataform. 387 */ 388 public Feature(final DataForm data) { 389 this.data = data; 390 } 391 392 /** 393 * Returns the dataform associated with the feature negotiation. 394 * 395 * @return Returns the dataform associated with the feature negotiation. 396 */ 397 public DataForm getData() { 398 return data; 399 } 400 401 public String getNamespace() { 402 return "http://jabber.org/protocol/feature-neg"; 403 } 404 405 public String getElementName() { 406 return "feature"; 407 } 408 409 public String toXML() { 410 StringBuilder buf = new StringBuilder(); 411 buf 412 .append("<feature xmlns=\"http://jabber.org/protocol/feature-neg\">"); 413 buf.append(data.toXML()); 414 buf.append("</feature>"); 415 return buf.toString(); 416 } 417 } 418}