/// <summary> /// Processes an IBB 'data' request. /// </summary> /// <param name="sessionId">The mandatory session id attribute of the 'data' /// element.</param> /// <param name="stanza">The IQ stanza containing the request.</param> /// <exception cref="ArgumentNullException">The sessionId parameter or the /// stanza parameter is null.</exception> /// <exception cref="ArgumentException">The IQ stanza is missing the /// mandatory 'data' element, or the specified session id is not associated /// with a file-stream.</exception> /// <exception cref="FormatException">The data contained in the 'data' element /// is not a valid BASE64-encoded string.</exception> /// <exception cref="IOException">The data could not be written to the /// file-stream. Consult the InnerException property of the IOException object /// to obtain the specific reason.</exception> void Data(string sessionId, Iq stanza) { sessionId.ThrowIfNull("sessionId"); stanza.ThrowIfNull("stanza"); var data = stanza.Data["data"]; if (data == null) { throw new ArgumentException("Invalid stanza, missing data element."); } SISession session = siFileTransfer.GetSession(sessionId, stanza.From, im.Jid); if (session == null) { throw new ArgumentException("Invalid session-id."); } string base64 = data.InnerText; // Decode base64 string and write decoded binary data to file. byte[] bytes = Convert.FromBase64String(base64); try { session.Stream.Write(bytes, 0, bytes.Length); } catch (Exception e) { throw new IOException("The stream could not be written.", e); } // Update the byte count and raise the 'BytesTransferred' event. session.Count = session.Count + bytes.Length; BytesTransferred.Raise(this, new BytesTransferredEventArgs(session)); }
/// <summary> /// Performs the actual data-transfer implied by the specified /// SI session. /// </summary> /// <param name="session">The SI session whose data to transfer.</param> /// <exception cref="ArgumentNullException">The session parameter is /// null.</exception> /// <exception cref="NotSupportedException">The XMPP extension /// implementing this method is not supported by the intended recipient's /// XMPP client.</exception> /// <exception cref="XmppErrorException">The server or the XMPP entity /// with the specified JID returned an XMPP error code. Use the Error /// property of the XmppErrorException to obtain the specific error /// condition.</exception> /// <exception cref="XmppException">The server returned invalid data or /// another unspecified XMPP error occurred.</exception> public async Task Transfer(SISession session) { session.ThrowIfNull("session"); // Open the negotiated IBB. await OpenStream(session.To, session.Sid); byte[] buf = new byte[blockSize]; // 'seq' is defined as 16-bit unsigned short value that wraps around. ushort seq = 0; long left = session.Size; try { while (left > 0) { int read = session.Stream.Read(buf, 0, blockSize); left = left - read; if (read <= 0) { break; } string b64 = Convert.ToBase64String(buf, 0, read); var data = Xml.Element("data", "http://jabber.org/protocol/ibb") .Attr("sid", session.Sid) .Attr("seq", seq.ToString()) .Text(b64); seq++; Iq response = await im.IqRequest(IqType.Set, session.To, im.Jid, data); if (response.Type == IqType.Error) { throw Util.ExceptionFromError(response); } session.Count = session.Count + read; // Raise the 'BytesTransferred' event. BytesTransferred.Raise(this, new BytesTransferredEventArgs(session)); } } catch (ObjectDisposedException) { // This means the IO-stream has been disposed because we cancelled // the transfer. Just fall through. } catch { // The IQ response is of type 'error', the other site has cancelled // the transfer. TransferAborted.Raise(this, new TransferAbortedEventArgs(session)); // Rethrow. throw; } finally { // Gracefully close the IBB. await CloseStream(session.To, session.Sid); } }
/// <summary> /// Receives the actual data after SOCKS5 negotiation has been completed. /// </summary> /// <param name="stanza">The original requesting IQ stanza.</param> /// <param name="sid">The session-id associated with the /// data-transfer.</param> /// <param name="stream">The stream from which to read the incoming /// data.</param> /// <exception cref="XmppException">The specifid SID does not denote a /// valid SI session.</exception> private async Task ReceiveData(Iq stanza, string sid, Stream stream) { SISession session = siFileTransfer.GetSession(sid, stanza.From, stanza.To); if (session == null) { throw new XmppException("Invalid session-id: " + sid); } long left = session.Size; try { while (left > 0) { byte[] buffer = new byte[4096]; int read = await stream.ReadAsync(buffer, 0, buffer.Length); if (read <= 0) { break; } left = left - read; await session.Stream.WriteAsync(buffer, 0, read); // Update the byte count and raise the 'BytesTransferred' event. session.Count = session.Count + read; BytesTransferred.Raise(this, new BytesTransferredEventArgs(session)); } } catch (ObjectDisposedException) { // This means the IO-stream has been disposed because we cancelled // the transfer. Just fall through. } finally { // Tear down the SI session. siFileTransfer.InvalidateSession(sid); // If not all bytes have been transferred, the data-transfer must have // been aborted prematurely. if (session.Count < session.Size) { TransferAborted.Raise(this, new TransferAbortedEventArgs(session)); } } }
/// <summary> /// Sends the actual data associated with the specified session over the /// specified stream. /// </summary> /// <param name="session">The SI session whose data to transfer.</param> /// <param name="stream">The (network) stream representing the connection /// to the client.</param> private void SendData(SISession session, Stream stream) { long left = session.Size; try { while (left > 0) { byte[] buffer = new byte[4096]; int read = session.Stream.Read(buffer, 0, (int)Math.Min(left, buffer.Length)); if (read > 0) { stream.Write(buffer, 0, read); } else { break; } left = left - read; // Update the byte count and raise the 'BytesTransferred' event. session.Count = session.Count + read; BytesTransferred.Raise(this, new BytesTransferredEventArgs(session)); } } catch (ObjectDisposedException) { // This means the IO-stream has been disposed because we cancelled // the transfer. Just fall through. } finally { // Tear down the SI session. siFileTransfer.InvalidateSession(session.Sid); // If not all bytes have been transferred, the data-transfer must have // been aborted prematurely. if (session.Count < session.Size) { TransferAborted.Raise(this, new TransferAbortedEventArgs(session)); } } }