Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System / net / System / Net / _CommandStream.cs
1 // ------------------------------------------------------------------------------
2 // <copyright file="CommandStream.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // ------------------------------------------------------------------------------
6 //
7
8
9 namespace System.Net {
10
11     using System.Collections;
12     using System.IO;
13     using System.Security.Cryptography.X509Certificates ;
14     using System.Net.Sockets;
15     using System.Security.Permissions;
16     using System.Text;
17     using System.Threading;
18     using System.Security.Authentication;
19
20
21     /// <devdoc>
22     /// <para>
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
26     ///     server.
27     /// </para>
28     /// </devdoc>
29     internal class CommandStream : PooledStream {
30
31         private static readonly AsyncCallback m_WriteCallbackDelegate = new AsyncCallback(WriteCallback);
32         private static readonly AsyncCallback m_ReadCallbackDelegate = new AsyncCallback(ReadCallback);
33
34         private bool m_RecoverableFailure;
35
36         //
37         // Active variables used for the command state machine
38         //
39
40         protected WebRequest        m_Request;
41         protected bool              m_Async;
42         private   bool              m_Aborted;
43
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;
50
51         const int _WaitingForPipeline = 1;
52         const int _CompletedPipeline  = 2;
53
54
55         /// <devdoc>
56         ///    <para>
57         ///     Setups and Creates a NetworkStream connection to the server
58         ///     perform any initalization if needed
59         ///    </para>
60         /// </devdoc>
61         internal CommandStream(
62             ConnectionPool connectionPool,
63             TimeSpan lifetime,
64             bool checkLifetime
65             ) : base(connectionPool, lifetime, checkLifetime) {
66                 m_Decoder = m_Encoding.GetDecoder();
67         }
68
69
70         internal virtual void Abort(Exception e) {
71             GlobalLog.Print("CommandStream"+ValidationHelper.HashString(this)+"::Abort() - closing control Stream");
72
73             lock (this) {
74                 if (m_Aborted)
75                     return;
76                 m_Aborted = true;
77                 CanBePooled = false;
78             }
79
80             try {
81                 base.Close(0);
82             }
83             finally {
84                 if (e != null) {
85                     InvokeRequestCallback(e);
86                 } else {
87                     InvokeRequestCallback(null);
88                 }
89             }
90         }
91
92         /// <summary>
93         ///    <para>Used to reset the connection</para>
94         /// </summary>
95         protected override void Dispose(bool disposing) {
96             GlobalLog.Print("CommandStream"+ValidationHelper.HashString(this)+"::Close()");
97             InvokeRequestCallback(null);
98
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.
102         }
103
104         /// <summary>
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>
107         /// </summary>
108         protected void InvokeRequestCallback(object obj) {
109             WebRequest webRequest = m_Request;
110             if (webRequest != null) {
111                 webRequest.RequestCallback(obj);
112             }
113         }
114
115         /// <summary>
116         ///    <para>Indicates that we caught an error that should allow us to resubmit a request</para>
117         /// </summary>
118         internal bool RecoverableFailure {
119             get {
120                 return m_RecoverableFailure;
121             }
122         }
123
124         /// <summary>
125         ///    <para>We only offer recovery, if we're at the start of the first command</para>
126         /// </summary>
127         protected void MarkAsRecoverableFailure() {
128             if (m_Index <= 1) {
129                 m_RecoverableFailure = true;
130             }
131         }
132
133         /// <devdoc>
134         ///    <para>
135         ///     Setups and Creates a NetworkStream connection to the server
136         ///     perform any initalization if needed
137         ///    </para>
138         /// </devdoc>
139
140         internal Stream SubmitRequest(WebRequest request, bool async, bool readInitalResponseOnConnect) {
141             ClearState();
142             UpdateLifetime();
143             PipelineEntry [] commands = BuildCommandsList(request);
144             InitCommandPipeline(request, commands, async);
145             if(readInitalResponseOnConnect && JustConnected){
146                 m_DoSend = false;
147                 m_Index = -1;
148             }
149             return ContinueCommandPipeline();
150         }
151
152         protected virtual void ClearState() {
153             InitCommandPipeline(null, null, false);
154         }
155
156         protected virtual PipelineEntry [] BuildCommandsList(WebRequest request) {
157             return null;
158         }
159
160         protected Exception GenerateException(WebExceptionStatus status, Exception innerException) {
161             return new WebException(
162                             NetRes.GetWebStatusString("net_connclosed", status),
163                             innerException,
164                             status,
165                             null /* no response */ );
166         }
167
168
169         protected Exception GenerateException(FtpStatusCode code, string statusDescription, Exception innerException) {
170
171             return new WebException(SR.GetString(SR.net_servererror,NetRes.GetWebStatusCodeString(code, statusDescription)),
172                                     innerException,WebExceptionStatus.ProtocolError,null );
173         }
174
175
176         protected void InitCommandPipeline(WebRequest request, PipelineEntry [] commands, bool async) {
177             m_Commands = commands;
178             m_Index = 0;
179             m_Request = request;
180             m_Aborted = false;
181             m_DoRead = true;
182             m_DoSend = true;
183             m_CurrentResponseDescription = null;
184             m_Async = async;
185             m_RecoverableFailure = false;
186             m_AbortReason = string.Empty;
187         }
188
189         internal void CheckContinuePipeline() 
190         {
191             if (m_Async)
192                 return;
193             try {
194                 ContinueCommandPipeline();
195             } catch (Exception e) {
196                 Abort(e);
197             }
198         }
199
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).
205         ///
206         /// When done calling Close() to Notify ConnectionGroup that we are free
207         protected Stream ContinueCommandPipeline()
208         {
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)
216             {
217                 if (m_DoSend)
218                 {
219                     if (m_Index < 0)
220                         throw new InternalException();
221
222                     byte[] sendBuffer = Encoding.GetBytes(m_Commands[m_Index].Command);
223                     if (Logging.On) 
224                     {
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))
227                         {
228                             int index = sendCommand.IndexOf(' ');
229                             if (index != -1)
230                             sendCommand = sendCommand.Substring(0, index) + " ********";
231                         }
232                         Logging.PrintInfo(Logging.Web, this, SR.GetString(SR.net_log_sending_command, sendCommand));
233                     }
234                     try {
235                         if (async) {
236                             BeginWrite(sendBuffer, 0, sendBuffer.Length, m_WriteCallbackDelegate, this);
237                         } else {
238                             Write(sendBuffer, 0, sendBuffer.Length);
239                         }
240                     } catch (IOException) {
241                         MarkAsRecoverableFailure();
242                         throw;
243                     } catch {
244                         throw;
245                     }
246
247                     if (async) {
248                         return null;
249                     }
250                 }
251
252                 Stream stream = null;
253                 bool isReturn = PostSendCommandProcessing(ref stream);
254                 if (isReturn)
255                 {
256                     return stream;
257                 }
258             }
259
260             lock (this)
261             {
262                 Close();
263             }
264
265             return null;
266         }
267         //
268         private bool PostSendCommandProcessing(ref Stream stream)
269         {
270 /*
271             ** I don;t see how this code can be still relevant, remove it of no problems observed **
272
273             //
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.
278             //
279             if (!m_DoRead && !m_Async) {
280                 m_DoRead = Poll(100 * 1000, SelectMode.SelectRead);   // Poll is in Microseconds.
281             }
282 */
283             if (m_DoRead)
284             {
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 
289                 // next call returns
290                 bool async               = m_Async;
291                 int index                = m_Index;
292                 PipelineEntry[] commands = m_Commands;
293
294                 try {
295                     ResponseDescription response = ReceiveCommandResponse();
296                     if (async) {
297                         return true;
298                     }
299                     m_CurrentResponseDescription = response;
300                 } catch {
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")
306                         throw;
307                 }
308             }
309             return PostReadCommandProcessing(ref stream);
310         }
311         //
312         private bool PostReadCommandProcessing(ref Stream stream)
313         {
314             if (m_Index >= m_Commands.Length)
315                 return false;
316
317             // Set up front to prevent a race condition on result == PipelineInstruction.Pause
318             m_DoSend = false;
319             m_DoRead = false;
320
321             PipelineInstruction result;
322             PipelineEntry entry;
323             if(m_Index == -1)
324                 entry = null;
325             else
326                 entry = m_Commands[m_Index];
327
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;
333             else 
334                 result = PipelineCallback(entry, m_CurrentResponseDescription, false, ref stream);
335
336             if (result == PipelineInstruction.Abort)
337             {
338                 Exception exception;
339                 if (m_AbortReason != string.Empty)
340                     exception = new WebException(m_AbortReason);
341                 else
342                     exception = GenerateException(WebExceptionStatus.ServerProtocolViolation, null);
343                 Abort(exception);
344                 throw exception;
345             }
346             else if (result == PipelineInstruction.Advance)
347             {
348                 m_CurrentResponseDescription = null;
349                 m_DoSend = true;
350                 m_DoRead = true;
351                 m_Index++;
352
353             }
354             else if (result == PipelineInstruction.Pause)
355             {
356                 //
357                 // PipelineCallback did an async operation and will have to re-enter again
358                 // Hold on for now
359                 //
360                 return true;
361             }
362             else if (result == PipelineInstruction.GiveStream)
363             {
364                 //
365                 // We will have another response coming, don't send
366                 //
367                 m_CurrentResponseDescription = null;
368                 m_DoRead = true;
369                 if (m_Async)
370                 {
371                     // If they block in the requestcallback we should still continue the pipeline
372                     ContinueCommandPipeline();
373                     InvokeRequestCallback(stream);
374                 }
375                 return true;
376             }
377             else if (result == PipelineInstruction.Reread)
378             {
379                 // Another response is expected after this one
380                 m_CurrentResponseDescription = null;
381                 m_DoRead = true;
382             }
383             return false;
384         }
385
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
392         }
393
394         [Flags]
395         internal enum PipelineEntryFlags {
396             UserCommand           = 0x1,
397             GiveDataStream        = 0x2,
398             CreateDataConnection  = 0x4,
399             DontLogParameter      = 0x8
400         }
401
402         internal class PipelineEntry {
403             internal PipelineEntry(string command) {
404                 Command = command;
405             }
406             internal PipelineEntry(string command, PipelineEntryFlags flags) {
407                 Command = command;
408                 Flags = flags;
409             }
410             internal bool HasFlag(PipelineEntryFlags flags) {
411                 return (Flags & flags) != 0;
412             }
413             internal string Command;
414             internal PipelineEntryFlags Flags;
415         }
416
417         protected virtual PipelineInstruction PipelineCallback(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream) {
418             return PipelineInstruction.Abort;
419         }
420
421         //
422         // I/O callback methods
423         //
424
425         /// <summary>
426         ///    <para>Provides a wrapper for the async operations, so that the code can be shared with sync</para>
427         /// </summary>
428         private static void ReadCallback(IAsyncResult asyncResult) {
429             ReceiveState state = (ReceiveState)asyncResult.AsyncState;
430             try {
431                 Stream stream = (Stream)state.Connection;
432                 int bytesRead = 0;
433                 try {
434                     bytesRead = stream.EndRead(asyncResult);
435                     if (bytesRead == 0)
436                         state.Connection.CloseSocket();
437                 } 
438                 catch (IOException) {
439                     state.Connection.MarkAsRecoverableFailure();
440                     throw;
441                 }
442                 catch {
443                     throw;
444                 }
445
446                 state.Connection.ReceiveCommandResponseCallback(state, bytesRead);
447             } catch (Exception e) {
448                 state.Connection.Abort(e);
449             }
450         }
451
452
453         /// <summary>
454         ///    <para>Provides a wrapper for the async write operations</para>
455         /// </summary>
456         private static void WriteCallback(IAsyncResult asyncResult) {
457             CommandStream connection = (CommandStream)asyncResult.AsyncState;
458             try {
459                 try {
460                     connection.EndWrite(asyncResult);
461                 } 
462                 catch (IOException) {
463                     connection.MarkAsRecoverableFailure();
464                     throw;
465                 }
466                 catch {
467                     throw;
468                 }
469                 Stream stream = null;
470                 if (connection.PostSendCommandProcessing(ref stream))
471                     return;
472                 connection.ContinueCommandPipeline();
473             } catch (Exception e) {
474                 connection.Abort(e);
475             }
476         }
477
478         //
479         // Read parsing methods and privates
480         //
481
482         private string m_Buffer = string.Empty;
483         private Encoding m_Encoding = Encoding.UTF8;
484         private Decoder m_Decoder;
485
486
487         protected Encoding Encoding {
488             get {
489                 return m_Encoding;
490             }
491             set {
492                 m_Encoding = value;
493                 m_Decoder = m_Encoding.GetDecoder();
494             }
495         }
496
497         /// <summary>
498         /// This function is called a derived class to determine whether a response is valid, and when it is complete.
499         /// </summary>
500         protected virtual bool CheckValid(ResponseDescription response, ref int validThrough, ref int completeLength) {
501             return false;
502         }
503
504         /// <summary>
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.
508         /// </summary>
509         private ResponseDescription ReceiveCommandResponse()
510         {
511             // These are the things that will be needed to maintain state
512             ReceiveState state = new ReceiveState(this);
513
514             try
515             {
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)
520                 {
521                     ReceiveCommandResponseCallback(state, -1);
522                 }
523                 else
524                 {
525                     int bytesRead;
526
527                     try {
528                         if (m_Async) {
529                             BeginRead(state.Buffer, 0, state.Buffer.Length, m_ReadCallbackDelegate, state);
530                             return null;
531                         } else {
532                             bytesRead = Read(state.Buffer, 0, state.Buffer.Length);
533                             if (bytesRead == 0)
534                                 CloseSocket();
535                             ReceiveCommandResponseCallback(state, bytesRead);
536                         }
537                     } 
538                     catch (IOException) {
539                         MarkAsRecoverableFailure();
540                         throw;
541                     }
542                     catch {
543                         throw;
544                     }
545                 }
546             }
547             catch(Exception e) {
548                 if (e is WebException)
549                     throw;
550                 throw GenerateException(WebExceptionStatus.ReceiveFailure, e);
551             }
552             return state.Resp;
553         }
554
555
556         /// <summary>
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.
562         ///
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.
565         /// </summary>
566         /// <param name="asyncResult"></param>
567         ///
568         private void ReceiveCommandResponseCallback(ReceiveState state, int bytesRead)
569         {
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;
573
574             while (true)
575             {
576                 int validThrough = state.ValidThrough; // passed to checkvalid
577
578
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)
582                 {
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;
586
587                     // invoke checkvalid.
588                     if(!CheckValid(state.Resp, ref validThrough, ref completeLength)) {
589                         throw GenerateException(WebExceptionStatus.ServerProtocolViolation, null);
590                     }
591                 }
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
594                 {
595                     // this indicates the connection was closed.
596                     if(bytesRead <= 0)  {
597                         throw GenerateException(WebExceptionStatus.ServerProtocolViolation, null);
598                     }
599
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.
602
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);
605                     
606                     string szResponse = new string(chars, 0, numChars);
607
608                     state.Resp.StatusBuffer.Append(szResponse);
609                     if(!CheckValid(state.Resp, ref validThrough, ref completeLength))
610                     {
611                         throw GenerateException(WebExceptionStatus.ServerProtocolViolation, null);
612                     }
613
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)
616                     {
617                         int unusedChars = state.Resp.StatusBuffer.Length - completeLength;
618                         if (unusedChars > 0) {
619                             m_Buffer = szResponse.Substring(szResponse.Length-unusedChars, unusedChars);
620                         }
621                     }
622                 }
623
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)
629                 {
630                     state.ValidThrough = validThrough;
631                     try {
632                         if (m_Async) {
633                             BeginRead(state.Buffer, 0, state.Buffer.Length, m_ReadCallbackDelegate, state);
634                             return;
635                         } else {
636                             bytesRead = Read(state.Buffer, 0, state.Buffer.Length);
637                             if (bytesRead == 0)
638                                 CloseSocket();
639                             continue;
640                         }
641                     } 
642                     catch (IOException) {
643                         MarkAsRecoverableFailure();
644                         throw;
645                     }
646                     catch {
647                         throw;
648                     }
649                 }
650                 // the response is completed
651                 break;
652             }
653
654
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.
659
660             if (Logging.On) Logging.PrintInfo(Logging.Web, this, SR.GetString(SR.net_log_received_response, responseString.Substring(0, completeLength-2)));
661
662             if (m_Async) {
663                 // Tell who is listening what was received.
664                 if (state.Resp != null) {
665                     m_CurrentResponseDescription = state.Resp;
666                 }
667                 Stream stream = null;
668                 if (PostReadCommandProcessing(ref stream))
669                     return;
670                 ContinueCommandPipeline();
671             }
672         }
673
674     } // class CommandStream
675
676
677     /// <summary>
678     /// Contains the parsed status line from the server
679     /// </summary>
680     internal class ResponseDescription {
681         internal const int NoStatus = -1;
682         internal bool Multiline = false;
683
684         internal int           Status = NoStatus;
685         internal string        StatusDescription;
686         internal StringBuilder StatusBuffer = new StringBuilder();
687
688         internal string        StatusCodeString;
689
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); }    }
696     }
697
698
699     /// <summary>
700     /// State information that is used during ReceiveCommandResponse()'s async operations
701     /// </summary>
702     internal class ReceiveState
703     {
704         private const int bufferSize = 1024;
705
706         internal ResponseDescription Resp;
707         internal int ValidThrough;
708         internal byte[] Buffer;
709         internal CommandStream Connection;
710
711         internal ReceiveState(CommandStream connection)
712         {
713             Connection = connection;
714             Resp = new ResponseDescription();
715             Buffer = new byte[bufferSize];  //1024
716             ValidThrough = 0;
717         }
718     }
719
720
721
722 } // namespace System.Net