1 // ------------------------------------------------------------------------------
2 // <copyright file="CommandStream.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // ------------------------------------------------------------------------------
11 using System.Collections;
13 using System.Security.Cryptography.X509Certificates ;
14 using System.Net.Sockets;
15 using System.Security.Permissions;
17 using System.Threading;
18 using System.Security.Authentication;
23 /// Impliments basic sending and receiving of network commands.
24 /// Handles generic parsing of server responses and provides
25 /// a Pipeline sequencing mechnism for sending the commands to the
29 internal class CommandStream : PooledStream {
31 private static readonly AsyncCallback m_WriteCallbackDelegate = new AsyncCallback(WriteCallback);
32 private static readonly AsyncCallback m_ReadCallbackDelegate = new AsyncCallback(ReadCallback);
34 private bool m_RecoverableFailure;
37 // Active variables used for the command state machine
40 protected WebRequest m_Request;
41 protected bool m_Async;
42 private bool m_Aborted;
44 protected PipelineEntry [] m_Commands;
45 protected int m_Index;
46 private bool m_DoRead;
47 private bool m_DoSend;
48 private ResponseDescription m_CurrentResponseDescription;
49 protected string m_AbortReason;
51 const int _WaitingForPipeline = 1;
52 const int _CompletedPipeline = 2;
57 /// Setups and Creates a NetworkStream connection to the server
58 /// perform any initalization if needed
61 internal CommandStream(
62 ConnectionPool connectionPool,
65 ) : base(connectionPool, lifetime, checkLifetime) {
66 m_Decoder = m_Encoding.GetDecoder();
70 internal virtual void Abort(Exception e) {
71 GlobalLog.Print("CommandStream"+ValidationHelper.HashString(this)+"::Abort() - closing control Stream");
85 InvokeRequestCallback(e);
87 InvokeRequestCallback(null);
93 /// <para>Used to reset the connection</para>
95 protected override void Dispose(bool disposing) {
96 GlobalLog.Print("CommandStream"+ValidationHelper.HashString(this)+"::Close()");
97 InvokeRequestCallback(null);
99 // Do not call base.Dispose(bool), which would close the web request.
100 // This stream effectively should be a wrapper around a web
101 // request that does not own the web request.
105 /// <para>A WebRequest can use a different Connection after an Exception is set, or a null is passed
106 /// to mark completion. We shouldn't continue calling the Request.RequestCallback after that point</para>
108 protected void InvokeRequestCallback(object obj) {
109 WebRequest webRequest = m_Request;
110 if (webRequest != null) {
111 webRequest.RequestCallback(obj);
116 /// <para>Indicates that we caught an error that should allow us to resubmit a request</para>
118 internal bool RecoverableFailure {
120 return m_RecoverableFailure;
125 /// <para>We only offer recovery, if we're at the start of the first command</para>
127 protected void MarkAsRecoverableFailure() {
129 m_RecoverableFailure = true;
135 /// Setups and Creates a NetworkStream connection to the server
136 /// perform any initalization if needed
140 internal Stream SubmitRequest(WebRequest request, bool async, bool readInitalResponseOnConnect) {
143 PipelineEntry [] commands = BuildCommandsList(request);
144 InitCommandPipeline(request, commands, async);
145 if(readInitalResponseOnConnect && JustConnected){
149 return ContinueCommandPipeline();
152 protected virtual void ClearState() {
153 InitCommandPipeline(null, null, false);
156 protected virtual PipelineEntry [] BuildCommandsList(WebRequest request) {
160 protected Exception GenerateException(WebExceptionStatus status, Exception innerException) {
161 return new WebException(
162 NetRes.GetWebStatusString("net_connclosed", status),
165 null /* no response */ );
169 protected Exception GenerateException(FtpStatusCode code, string statusDescription, Exception innerException) {
171 return new WebException(SR.GetString(SR.net_servererror,NetRes.GetWebStatusCodeString(code, statusDescription)),
172 innerException,WebExceptionStatus.ProtocolError,null );
176 protected void InitCommandPipeline(WebRequest request, PipelineEntry [] commands, bool async) {
177 m_Commands = commands;
183 m_CurrentResponseDescription = null;
185 m_RecoverableFailure = false;
186 m_AbortReason = string.Empty;
189 internal void CheckContinuePipeline()
194 ContinueCommandPipeline();
195 } catch (Exception e) {
200 /// Pipelined command resoluton, how this works:
201 /// a list of commands that need to be sent to the FTP server are spliced together into a array,
202 /// each command such STOR, PORT, etc, is sent to the server, then the response is parsed into a string,
203 /// with the response, the delegate is called, which returns an instruction (either continue, stop, or read additional
204 /// responses from server).
206 /// When done calling Close() to Notify ConnectionGroup that we are free
207 protected Stream ContinueCommandPipeline()
209 // In async case, The BeginWrite can actually result in a
210 // series of synchronous completions that eventually close
211 // the connection. So we need to save the members that
212 // we need to access, since they may not be valid after
213 // BeginWrite returns
214 bool async = m_Async;
215 while (m_Index < m_Commands.Length)
220 throw new InternalException();
222 byte[] sendBuffer = Encoding.GetBytes(m_Commands[m_Index].Command);
225 string sendCommand = m_Commands[m_Index].Command.Substring(0, m_Commands[m_Index].Command.Length-2);
226 if (m_Commands[m_Index].HasFlag(PipelineEntryFlags.DontLogParameter))
228 int index = sendCommand.IndexOf(' ');
230 sendCommand = sendCommand.Substring(0, index) + " ********";
232 Logging.PrintInfo(Logging.Web, this, SR.GetString(SR.net_log_sending_command, sendCommand));
236 BeginWrite(sendBuffer, 0, sendBuffer.Length, m_WriteCallbackDelegate, this);
238 Write(sendBuffer, 0, sendBuffer.Length);
240 } catch (IOException) {
241 MarkAsRecoverableFailure();
252 Stream stream = null;
253 bool isReturn = PostSendCommandProcessing(ref stream);
268 private bool PostSendCommandProcessing(ref Stream stream)
271 ** I don;t see how this code can be still relevant, remove it of no problems observed **
274 // This is a general race condition in Sync mode, if the server returns an error
275 // after we open the data connection, we will be off reading the data connection,
276 // and not the control connection. The best we can do is try to poll, and in the
277 // the worst case, we will timeout on establishing the data connection.
279 if (!m_DoRead && !m_Async) {
280 m_DoRead = Poll(100 * 1000, SelectMode.SelectRead); // Poll is in Microseconds.
285 // In async case, The next call can actually result in a
286 // series of synchronous completions that eventually close
287 // the connection. So we need to save the members that
288 // we need to access, since they may not be valid after the
290 bool async = m_Async;
292 PipelineEntry[] commands = m_Commands;
295 ResponseDescription response = ReceiveCommandResponse();
299 m_CurrentResponseDescription = response;
301 // If we get an exception on the QUIT command (which is
302 // always the last command), ignore the final exception
303 // and continue with the pipeline regardlss of sync/async
304 if (index < 0 || index >= commands.Length ||
305 commands[index].Command != "QUIT\r\n")
309 return PostReadCommandProcessing(ref stream);
312 private bool PostReadCommandProcessing(ref Stream stream)
314 if (m_Index >= m_Commands.Length)
317 // Set up front to prevent a race condition on result == PipelineInstruction.Pause
321 PipelineInstruction result;
326 entry = m_Commands[m_Index];
328 // Final QUIT command may get exceptions since the connectin
329 // may be already closed by the server. So there is no response
330 // to process, just advance the pipeline to continue
331 if (m_CurrentResponseDescription == null && entry.Command == "QUIT\r\n")
332 result = PipelineInstruction.Advance;
334 result = PipelineCallback(entry, m_CurrentResponseDescription, false, ref stream);
336 if (result == PipelineInstruction.Abort)
339 if (m_AbortReason != string.Empty)
340 exception = new WebException(m_AbortReason);
342 exception = GenerateException(WebExceptionStatus.ServerProtocolViolation, null);
346 else if (result == PipelineInstruction.Advance)
348 m_CurrentResponseDescription = null;
354 else if (result == PipelineInstruction.Pause)
357 // PipelineCallback did an async operation and will have to re-enter again
362 else if (result == PipelineInstruction.GiveStream)
365 // We will have another response coming, don't send
367 m_CurrentResponseDescription = null;
371 // If they block in the requestcallback we should still continue the pipeline
372 ContinueCommandPipeline();
373 InvokeRequestCallback(stream);
377 else if (result == PipelineInstruction.Reread)
379 // Another response is expected after this one
380 m_CurrentResponseDescription = null;
386 internal enum PipelineInstruction {
387 Abort, // aborts the pipeline
388 Advance, // advances to the next pipelined command
389 Pause, // Let async callback to continue the pipeline
390 Reread, // rereads from the command socket
391 GiveStream, // returns with open data stream, let stream close to continue
395 internal enum PipelineEntryFlags {
397 GiveDataStream = 0x2,
398 CreateDataConnection = 0x4,
399 DontLogParameter = 0x8
402 internal class PipelineEntry {
403 internal PipelineEntry(string command) {
406 internal PipelineEntry(string command, PipelineEntryFlags flags) {
410 internal bool HasFlag(PipelineEntryFlags flags) {
411 return (Flags & flags) != 0;
413 internal string Command;
414 internal PipelineEntryFlags Flags;
417 protected virtual PipelineInstruction PipelineCallback(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream) {
418 return PipelineInstruction.Abort;
422 // I/O callback methods
426 /// <para>Provides a wrapper for the async operations, so that the code can be shared with sync</para>
428 private static void ReadCallback(IAsyncResult asyncResult) {
429 ReceiveState state = (ReceiveState)asyncResult.AsyncState;
431 Stream stream = (Stream)state.Connection;
434 bytesRead = stream.EndRead(asyncResult);
436 state.Connection.CloseSocket();
438 catch (IOException) {
439 state.Connection.MarkAsRecoverableFailure();
446 state.Connection.ReceiveCommandResponseCallback(state, bytesRead);
447 } catch (Exception e) {
448 state.Connection.Abort(e);
454 /// <para>Provides a wrapper for the async write operations</para>
456 private static void WriteCallback(IAsyncResult asyncResult) {
457 CommandStream connection = (CommandStream)asyncResult.AsyncState;
460 connection.EndWrite(asyncResult);
462 catch (IOException) {
463 connection.MarkAsRecoverableFailure();
469 Stream stream = null;
470 if (connection.PostSendCommandProcessing(ref stream))
472 connection.ContinueCommandPipeline();
473 } catch (Exception e) {
479 // Read parsing methods and privates
482 private string m_Buffer = string.Empty;
483 private Encoding m_Encoding = Encoding.UTF8;
484 private Decoder m_Decoder;
487 protected Encoding Encoding {
493 m_Decoder = m_Encoding.GetDecoder();
498 /// This function is called a derived class to determine whether a response is valid, and when it is complete.
500 protected virtual bool CheckValid(ResponseDescription response, ref int validThrough, ref int completeLength) {
505 /// Kicks off an asynchronous or sync request to receive a response from the server.
506 /// Uses the Encoding <code>encoding</code> to transform the bytes received into a string to be
507 /// returned in the GeneralResponseDescription's StatusDescription field.
509 private ResponseDescription ReceiveCommandResponse()
511 // These are the things that will be needed to maintain state
512 ReceiveState state = new ReceiveState(this);
516 // If a string of nonzero length was decoded from the buffered bytes after the last complete response, then we
517 // will use this string as our first string to append to the response StatusBuffer, and we will
518 // forego a Connection.Receive here.
519 if(m_Buffer.Length > 0)
521 ReceiveCommandResponseCallback(state, -1);
529 BeginRead(state.Buffer, 0, state.Buffer.Length, m_ReadCallbackDelegate, state);
532 bytesRead = Read(state.Buffer, 0, state.Buffer.Length);
535 ReceiveCommandResponseCallback(state, bytesRead);
538 catch (IOException) {
539 MarkAsRecoverableFailure();
548 if (e is WebException)
550 throw GenerateException(WebExceptionStatus.ReceiveFailure, e);
557 /// ReceiveCommandResponseCallback is the main "while loop" of the ReceiveCommandResponse function family.
558 /// In general, what is does is perform an EndReceive() to complete the previous retrieval of bytes from the
559 /// server (unless it is using a buffered response) It then processes what is received by using the
560 /// implementing class's CheckValid() function, as described above. If the response is complete, it returns the single complete
561 /// response in the GeneralResponseDescription created in BeginReceiveComamndResponse, and buffers the rest as described above.
563 /// If the resposne is not complete, it issues another Connection.BeginReceive, with callback ReceiveCommandResponse2,
564 /// so the action will continue at the next invocation of ReceiveCommandResponse2.
566 /// <param name="asyncResult"></param>
568 private void ReceiveCommandResponseCallback(ReceiveState state, int bytesRead)
570 // completeLength will be set to a nonnegative number by CheckValid if the response is complete:
571 // it will set completeLength to the length of a complete response.
572 int completeLength = -1;
576 int validThrough = state.ValidThrough; // passed to checkvalid
579 // If we have a Buffered response (ie data was received with the last response that was past the end of that response)
580 // deal with it as if we had just received it now instead of actually doing another receive
581 if(m_Buffer.Length > 0)
583 // Append the string we got from the buffer, and flush it out.
584 state.Resp.StatusBuffer.Append(m_Buffer);
585 m_Buffer = string.Empty;
587 // invoke checkvalid.
588 if(!CheckValid(state.Resp, ref validThrough, ref completeLength)) {
589 throw GenerateException(WebExceptionStatus.ServerProtocolViolation, null);
592 else // we did a Connection.BeginReceive. Note that in this case, all bytes received are in the receive buffer (because bytes from
593 // the buffer were transferred there if necessary
595 // this indicates the connection was closed.
597 throw GenerateException(WebExceptionStatus.ServerProtocolViolation, null);
600 // decode the bytes in the receive buffer into a string, append it to the statusbuffer, and invoke checkvalid.
601 // Decoder automatically takes care of caching partial codepoints at the end of a buffer.
603 char[] chars = new char[m_Decoder.GetCharCount(state.Buffer, 0, bytesRead)];
604 int numChars = m_Decoder.GetChars(state.Buffer, 0, bytesRead, chars, 0, false);
606 string szResponse = new string(chars, 0, numChars);
608 state.Resp.StatusBuffer.Append(szResponse);
609 if(!CheckValid(state.Resp, ref validThrough, ref completeLength))
611 throw GenerateException(WebExceptionStatus.ServerProtocolViolation, null);
614 // If the response is complete, then determine how many characters are left over...these bytes need to be set into Buffer.
615 if(completeLength >= 0)
617 int unusedChars = state.Resp.StatusBuffer.Length - completeLength;
618 if (unusedChars > 0) {
619 m_Buffer = szResponse.Substring(szResponse.Length-unusedChars, unusedChars);
624 // Now, in general, if the response is not complete, update the "valid through" length for the efficiency of checkValid.
625 // and perform the next receive.
626 // Note that there may NOT be bytes in the beginning of the receive buffer (even if there were partial characters left over after the
627 // last encoding), because they get tracked in the Decoder.
628 if(completeLength < 0)
630 state.ValidThrough = validThrough;
633 BeginRead(state.Buffer, 0, state.Buffer.Length, m_ReadCallbackDelegate, state);
636 bytesRead = Read(state.Buffer, 0, state.Buffer.Length);
642 catch (IOException) {
643 MarkAsRecoverableFailure();
650 // the response is completed
655 // Otherwise, we have a complete response.
656 string responseString = state.Resp.StatusBuffer.ToString();
657 state.Resp.StatusDescription = responseString.Substring(0, completeLength);
658 // set the StatusDescription to the complete part of the response. Note that the Buffer has already been taken care of above.
660 if (Logging.On) Logging.PrintInfo(Logging.Web, this, SR.GetString(SR.net_log_received_response, responseString.Substring(0, completeLength-2)));
663 // Tell who is listening what was received.
664 if (state.Resp != null) {
665 m_CurrentResponseDescription = state.Resp;
667 Stream stream = null;
668 if (PostReadCommandProcessing(ref stream))
670 ContinueCommandPipeline();
674 } // class CommandStream
678 /// Contains the parsed status line from the server
680 internal class ResponseDescription {
681 internal const int NoStatus = -1;
682 internal bool Multiline = false;
684 internal int Status = NoStatus;
685 internal string StatusDescription;
686 internal StringBuilder StatusBuffer = new StringBuilder();
688 internal string StatusCodeString;
690 internal bool PositiveIntermediate { get { return (Status >= 100 && Status <= 199); } }
691 internal bool PositiveCompletion { get { return (Status >= 200 && Status <= 299); } }
692 //internal bool PositiveAuthRelated { get { return (Status >= 300 && Status <= 399); } }
693 internal bool TransientFailure { get { return (Status >= 400 && Status <= 499); } }
694 internal bool PermanentFailure { get { return (Status >= 500 && Status <= 599); } }
695 internal bool InvalidStatusCode { get { return (Status < 100 || Status > 599); } }
700 /// State information that is used during ReceiveCommandResponse()'s async operations
702 internal class ReceiveState
704 private const int bufferSize = 1024;
706 internal ResponseDescription Resp;
707 internal int ValidThrough;
708 internal byte[] Buffer;
709 internal CommandStream Connection;
711 internal ReceiveState(CommandStream connection)
713 Connection = connection;
714 Resp = new ResponseDescription();
715 Buffer = new byte[bufferSize]; //1024
722 } // namespace System.Net