1 /** 2 * $RCSfile$ 3 * $Revision$ 4 * $Date$ 5 * 6 * Copyright 2003-2006 Jive Software. 7 * 8 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); 9 * you may not use this file except in compliance with the License. 10 * You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 package org.jivesoftware.smackx.packet; 21 22 import java.util.Date; 23 24 import org.jivesoftware.smack.packet.IQ; 25 import org.jivesoftware.smack.packet.PacketExtension; 26 import org.jivesoftware.smack.util.StringUtils; 27 28 /** 29 * The process by which two entities initiate a stream. 30 * 31 * @author Alexander Wenckus 32 */ 33 public class StreamInitiation extends IQ { 34 35 private String id; 36 37 private String mimeType; 38 39 private File file; 40 41 private Feature featureNegotiation; 42 43 /** 44 * The "id" attribute is an opaque identifier. This attribute MUST be 45 * present on type='set', and MUST be a valid string. This SHOULD NOT be 46 * sent back on type='result', since the <iq/> "id" attribute provides the 47 * only context needed. This value is generated by the Sender, and the same 48 * value MUST be used throughout a session when talking to the Receiver. 49 * 50 * @param id The "id" attribute. 51 */ 52 public void setSesssionID(final String id) { 53 this.id = id; 54 } 55 56 /** 57 * Uniquely identifies a stream initiation to the recipient. 58 * 59 * @return The "id" attribute. 60 * @see #setSesssionID(String) 61 */ 62 public String getSessionID() { 63 return id; 64 } 65 66 /** 67 * The "mime-type" attribute identifies the MIME-type for the data across 68 * the stream. This attribute MUST be a valid MIME-type as registered with 69 * the Internet Assigned Numbers Authority (IANA) [3] (specifically, as 70 * listed at <http://www.iana.org/assignments/media-types>). During 71 * negotiation, this attribute SHOULD be present, and is otherwise not 72 * required. If not included during negotiation, its value is assumed to be 73 * "binary/octect-stream". 74 * 75 * @param mimeType The valid mime-type. 76 */ 77 public void setMimeType(final String mimeType) { 78 this.mimeType = mimeType; 79 } 80 81 /** 82 * Identifies the type of file that is desired to be transfered. 83 * 84 * @return The mime-type. 85 * @see #setMimeType(String) 86 */ 87 public String getMimeType() { 88 return mimeType; 89 } 90 91 /** 92 * Sets the file which contains the information pertaining to the file to be 93 * transfered. 94 * 95 * @param file The file identified by the stream initiator to be sent. 96 */ 97 public void setFile(final File file) { 98 this.file = file; 99 } 100 101 /** 102 * Returns the file containing the information about the request. 103 * 104 * @return Returns the file containing the information about the request. 105 */ 106 public File getFile() { 107 return file; 108 } 109 110 /** 111 * Sets the data form which contains the valid methods of stream neotiation 112 * and transfer. 113 * 114 * @param form The dataform containing the methods. 115 */ 116 public void setFeatureNegotiationForm(final DataForm form) { 117 this.featureNegotiation = new Feature(form); 118 } 119 120 /** 121 * Returns the data form which contains the valid methods of stream 122 * neotiation and transfer. 123 * 124 * @return Returns the data form which contains the valid methods of stream 125 * neotiation and transfer. 126 */ 127 public DataForm getFeatureNegotiationForm() { 128 return featureNegotiation.getData(); 129 } 130 131 /* 132 * (non-Javadoc) 133 * 134 * @see org.jivesoftware.smack.packet.IQ#getChildElementXML() 135 */ 136 public String getChildElementXML() { 137 StringBuilder buf = new StringBuilder(); 138 if (this.getType().equals(IQ.Type.SET)) { 139 buf.append("<si xmlns=\"http://jabber.org/protocol/si\" "); 140 if (getSessionID() != null) { 141 buf.append("id=\"").append(getSessionID()).append("\" "); 142 } 143 if (getMimeType() != null) { 144 buf.append("mime-type=\"").append(getMimeType()).append("\" "); 145 } 146 buf 147 .append("profile=\"http://jabber.org/protocol/si/profile/file-transfer\">"); 148 149 // Add the file section if there is one. 150 String fileXML = file.toXML(); 151 if (fileXML != null) { 152 buf.append(fileXML); 153 } 154 } 155 else if (this.getType().equals(IQ.Type.RESULT)) { 156 buf.append("<si xmlns=\"http://jabber.org/protocol/si\">"); 157 } 158 else { 159 throw new IllegalArgumentException("IQ Type not understood"); 160 } 161 if (featureNegotiation != null) { 162 buf.append(featureNegotiation.toXML()); 163 } 164 buf.append("</si>"); 165 return buf.toString(); 166 } 167 168 /** 169 * <ul> 170 * <li>size: The size, in bytes, of the data to be sent.</li> 171 * <li>name: The name of the file that the Sender wishes to send.</li> 172 * <li>date: The last modification time of the file. This is specified 173 * using the DateTime profile as described in Jabber Date and Time Profiles.</li> 174 * <li>hash: The MD5 sum of the file contents.</li> 175 * </ul> 176 * <p/> 177 * <p/> 178 * <desc> is used to provide a sender-generated description of the 179 * file so the receiver can better understand what is being sent. It MUST 180 * NOT be sent in the result. 181 * <p/> 182 * <p/> 183 * When <range> is sent in the offer, it should have no attributes. 184 * This signifies that the sender can do ranged transfers. When a Stream 185 * Initiation result is sent with the <range> element, it uses these 186 * attributes: 187 * <p/> 188 * <ul> 189 * <li>offset: Specifies the position, in bytes, to start transferring the 190 * file data from. This defaults to zero (0) if not specified.</li> 191 * <li>length - Specifies the number of bytes to retrieve starting at 192 * offset. This defaults to the length of the file from offset to the end.</li> 193 * </ul> 194 * <p/> 195 * <p/> 196 * Both attributes are OPTIONAL on the <range> element. Sending no 197 * attributes is synonymous with not sending the <range> element. When 198 * no <range> element is sent in the Stream Initiation result, the 199 * Sender MUST send the complete file starting at offset 0. More generally, 200 * data is sent over the stream byte for byte starting at the offset 201 * position for the length specified. 202 * 203 * @author Alexander Wenckus 204 */ 205 public static class File implements PacketExtension { 206 207 private final String name; 208 209 private final long size; 210 211 private String hash; 212 213 private Date date; 214 215 private String desc; 216 217 private boolean isRanged; 218 219 /** 220 * Constructor providing the name of the file and its size. 221 * 222 * @param name The name of the file. 223 * @param size The size of the file in bytes. 224 */ 225 public File(final String name, final long size) { 226 if (name == null) { 227 throw new NullPointerException("name cannot be null"); 228 } 229 230 this.name = name; 231 this.size = size; 232 } 233 234 /** 235 * Returns the file's name. 236 * 237 * @return Returns the file's name. 238 */ 239 public String getName() { 240 return name; 241 } 242 243 /** 244 * Returns the file's size. 245 * 246 * @return Returns the file's size. 247 */ 248 public long getSize() { 249 return size; 250 } 251 252 /** 253 * Sets the MD5 sum of the file's contents 254 * 255 * @param hash The MD5 sum of the file's contents. 256 */ 257 public void setHash(final String hash) { 258 this.hash = hash; 259 } 260 261 /** 262 * Returns the MD5 sum of the file's contents 263 * 264 * @return Returns the MD5 sum of the file's contents 265 */ 266 public String getHash() { 267 return hash; 268 } 269 270 /** 271 * Sets the date that the file was last modified. 272 * 273 * @param date The date that the file was last modified. 274 */ 275 public void setDate(Date date) { 276 this.date = date; 277 } 278 279 /** 280 * Returns the date that the file was last modified. 281 * 282 * @return Returns the date that the file was last modified. 283 */ 284 public Date getDate() { 285 return date; 286 } 287 288 /** 289 * Sets the description of the file. 290 * 291 * @param desc The description of the file so that the file reciever can 292 * know what file it is. 293 */ 294 public void setDesc(final String desc) { 295 this.desc = desc; 296 } 297 298 /** 299 * Returns the description of the file. 300 * 301 * @return Returns the description of the file. 302 */ 303 public String getDesc() { 304 return desc; 305 } 306 307 /** 308 * True if a range can be provided and false if it cannot. 309 * 310 * @param isRanged True if a range can be provided and false if it cannot. 311 */ 312 public void setRanged(final boolean isRanged) { 313 this.isRanged = isRanged; 314 } 315 316 /** 317 * Returns whether or not the initiator can support a range for the file 318 * tranfer. 319 * 320 * @return Returns whether or not the initiator can support a range for 321 * the file tranfer. 322 */ 323 public boolean isRanged() { 324 return isRanged; 325 } 326 327 public String getElementName() { 328 return "file"; 329 } 330 331 public String getNamespace() { 332 return "http://jabber.org/protocol/si/profile/file-transfer"; 333 } 334 335 public String toXML() { 336 StringBuilder buffer = new StringBuilder(); 337 338 buffer.append("<").append(getElementName()).append(" xmlns=\"") 339 .append(getNamespace()).append("\" "); 340 341 if (getName() != null) { 342 buffer.append("name=\"").append(StringUtils.escapeForXML(getName())).append("\" "); 343 } 344 345 if (getSize() > 0) { 346 buffer.append("size=\"").append(getSize()).append("\" "); 347 } 348 349 if (getDate() != null) { 350 buffer.append("date=\"").append(StringUtils.formatXEP0082Date(date)).append("\" "); 351 } 352 353 if (getHash() != null) { 354 buffer.append("hash=\"").append(getHash()).append("\" "); 355 } 356 357 if ((desc != null && desc.length() > 0) || isRanged) { 358 buffer.append(">"); 359 if (getDesc() != null && desc.length() > 0) { 360 buffer.append("<desc>").append(StringUtils.escapeForXML(getDesc())).append("</desc>"); 361 } 362 if (isRanged()) { 363 buffer.append("<range/>"); 364 } 365 buffer.append("</").append(getElementName()).append(">"); 366 } 367 else { 368 buffer.append("/>"); 369 } 370 return buffer.toString(); 371 } 372 } 373 374 /** 375 * The feature negotiation portion of the StreamInitiation packet. 376 * 377 * @author Alexander Wenckus 378 * 379 */ 380 public class Feature implements PacketExtension { 381 382 private final DataForm data; 383 384 /** 385 * The dataform can be provided as part of the constructor. 386 * 387 * @param data The dataform. 388 */ 389 public Feature(final DataForm data) { 390 this.data = data; 391 } 392 393 /** 394 * Returns the dataform associated with the feature negotiation. 395 * 396 * @return Returns the dataform associated with the feature negotiation. 397 */ 398 public DataForm getData() { 399 return data; 400 } 401 402 public String getNamespace() { 403 return "http://jabber.org/protocol/feature-neg"; 404 } 405 406 public String getElementName() { 407 return "feature"; 408 } 409 410 public String toXML() { 411 StringBuilder buf = new StringBuilder(); 412 buf 413 .append("<feature xmlns=\"http://jabber.org/protocol/feature-neg\">"); 414 buf.append(data.toXML()); 415 buf.append("</feature>"); 416 return buf.toString(); 417 } 418 } 419 } 420