Beispiel #1
0
        private void AUTH(string cmdText)
        {
            // RFC 5321 3.1.
            if(m_SessionRejected){
                WriteLine("503 Bad sequence of commands: Session rejected.");
                return;
            }

            /* RFC 4954 
			    AUTH mechanism [initial-response]

                Arguments:
                    mechanism: A string identifying a [SASL] authentication mechanism.

                    initial-response: An optional initial client response.  If
                    present, this response MUST be encoded as described in Section
                    4 of [BASE64] or contain a single character "=".

                Restrictions:
                    After an AUTH command has been successfully completed, no more
                    AUTH commands may be issued in the same session.  After a
                    successful AUTH command completes, a server MUST reject any
                    further AUTH commands with a 503 reply.

                    The AUTH command is not permitted during a mail transaction.
                    An AUTH command issued during a mail transaction MUST be
                    rejected with a 503 reply.
             
                A server challenge is sent as a 334 reply with the text part
                containing the [BASE64] encoded string supplied by the SASL
                mechanism.  This challenge MUST NOT contain any text other
                than the BASE64 encoded challenge.
             
                In SMTP, a server challenge that contains no data is defined 
                as a 334 reply with no text part. Note that there is still a space 
                following the reply code, so the complete response line is "334 ".
             
                If the client wishes to cancel the authentication exchange, 
                it issues a line with a single "*". If the server receives 
                such a response, it MUST reject the AUTH command by sending a 501 reply.
			*/
            
			if(this.IsAuthenticated){
				WriteLine("503 Bad sequence of commands: you are already authenticated.");
				return;
			}
            if(m_pFrom != null){
                WriteLine("503 Bad sequence of commands: The AUTH command is not permitted during a mail transaction.");
				return;
            }

            #region Parse parameters

            string[] arguments = cmdText.Split(' ');
            if(arguments.Length > 2){
                WriteLine("501 Syntax error, syntax: AUTH SP mechanism [SP initial-response] CRLF");
                return;
            }
            byte[] initialClientResponse = new byte[0];
            if(arguments.Length == 2){
                if(arguments[1] == "="){
                    // Skip.
                }
                else{
                    try{
                        initialClientResponse = Convert.FromBase64String(arguments[1]);
                    }
                    catch{
                        WriteLine("501 Syntax error: Parameter 'initial-response' value must be BASE64 or contain a single character '='.");
                        return;
                    }
                }
            }
            string mechanism = arguments[0];

            #endregion

            if(!this.Authentications.ContainsKey(mechanism)){
                WriteLine("501 Not supported authentication mechanism.");
                return;
            }

            byte[] clientResponse = initialClientResponse;
            AUTH_SASL_ServerMechanism auth = this.Authentications[mechanism];
            auth.Reset();
            while(true){
                byte[] serverResponse = auth.Continue(clientResponse);
                // Authentication completed.
                if(auth.IsCompleted){
                    if(auth.IsAuthenticated){
                        m_pUser = new GenericIdentity(auth.UserName,"SASL-" + auth.Name);

                        WriteLine("235 2.7.0 Authentication succeeded.");
                    }
                    else{
                        WriteLine("535 5.7.8 Authentication credentials invalid.");
                    }
                    break;
                }
                // Authentication continues.
                else{
                    // Send server challange.
                    if(serverResponse.Length == 0){
                        WriteLine("334 ");
                    }
                    else{
                        WriteLine("334 " + Convert.ToBase64String(serverResponse));
                    }

                    // Read client response. 
                    SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException);
                    this.TcpStream.ReadLine(readLineOP,false);
                    if(readLineOP.Error != null){
                        throw readLineOP.Error;
                    }                    
                    // Log
                    if(this.Server.Logger != null){
                        this.Server.Logger.AddRead(this.ID,this.AuthenticatedUserIdentity,readLineOP.BytesInBuffer,"base64 auth-data",this.LocalEndPoint,this.RemoteEndPoint);
                    }

                    // Client canceled authentication.
                    if(readLineOP.LineUtf8 == "*"){
                        WriteLine("501 Authentication canceled.");
                        return;
                    }
                    // We have base64 client response, decode it.
                    else{
                        try{
                            clientResponse = Convert.FromBase64String(readLineOP.LineUtf8);
                        }
                        catch{
                            WriteLine("501 Invalid client response '" + clientResponse + "'.");
                            return;
                        }
                    }
                }
            }
        }
Beispiel #2
0
        private void AUTH(string cmdText)
        {
            /* RFC 1734
				
				AUTH mechanism

					Arguments:
						a string identifying an IMAP4 authentication mechanism,
						such as defined by [IMAP4-AUTH].  Any use of the string
						"imap" used in a server authentication identity in the
						definition of an authentication mechanism is replaced with
						the string "pop".
						
					Possible Responses:
						+OK maildrop locked and ready
						-ERR authentication exchange failed

					Restrictions:
						may only be given in the AUTHORIZATION state

					Discussion:
						The AUTH command indicates an authentication mechanism to
						the server.  If the server supports the requested
						authentication mechanism, it performs an authentication
						protocol exchange to authenticate and identify the user.
						Optionally, it also negotiates a protection mechanism for
						subsequent protocol interactions.  If the requested
						authentication mechanism is not supported, the server						
						should reject the AUTH command by sending a negative
						response.

						The authentication protocol exchange consists of a series
						of server challenges and client answers that are specific
						to the authentication mechanism.  A server challenge,
						otherwise known as a ready response, is a line consisting
						of a "+" character followed by a single space and a BASE64
						encoded string.  The client answer consists of a line
						containing a BASE64 encoded string.  If the client wishes
						to cancel an authentication exchange, it should issue a
						line with a single "*".  If the server receives such an
						answer, it must reject the AUTH command by sending a
						negative response.

						A protection mechanism provides integrity and privacy
						protection to the protocol session.  If a protection
						mechanism is negotiated, it is applied to all subsequent
						data sent over the connection.  The protection mechanism
						takes effect immediately following the CRLF that concludes
						the authentication exchange for the client, and the CRLF of
						the positive response for the server.  Once the protection
						mechanism is in effect, the stream of command and response
						octets is processed into buffers of ciphertext.  Each
						buffer is transferred over the connection as a stream of
						octets prepended with a four octet field in network byte
						order that represents the length of the following data.
						The maximum ciphertext buffer length is defined by the
						protection mechanism.

						The server is not required to support any particular
						authentication mechanism, nor are authentication mechanisms
						required to support any protection mechanisms.  If an AUTH
						command fails with a negative response, the session remains
						in the AUTHORIZATION state and client may try another
						authentication mechanism by issuing another AUTH command,
						or may attempt to authenticate by using the USER/PASS or
						APOP commands.  In other words, the client may request
						authentication types in decreasing order of preference,
						with the USER/PASS or APOP command as a last resort.

						Should the client successfully complete the authentication
						exchange, the POP3 server issues a positive response and
						the POP3 session enters the TRANSACTION state.
						
				Examples:
							S: +OK POP3 server ready
							C: AUTH KERBEROS_V4
							S: + AmFYig==
							C: BAcAQU5EUkVXLkNNVS5FRFUAOCAsho84kLN3/IJmrMG+25a4DT
								+nZImJjnTNHJUtxAA+o0KPKfHEcAFs9a3CL5Oebe/ydHJUwYFd
								WwuQ1MWiy6IesKvjL5rL9WjXUb9MwT9bpObYLGOKi1Qh
							S: + or//EoAADZI=
							C: DiAF5A4gA+oOIALuBkAAmw==
							S: +OK Kerberos V4 authentication successful
								...
							C: AUTH FOOBAR
							S: -ERR Unrecognized authentication type
			 
			*/

            if(m_SessionRejected){
                WriteLine("-ERR Bad sequence of commands: Session rejected.");

                return;
            }
            if(this.IsAuthenticated){
                this.TcpStream.WriteLine("-ERR Re-authentication error.");

                return;
            }

            string mechanism = cmdText;

            /* MS specific or someone knows where in RFC let me know about this.
                Empty AUTH commands causes authentication mechanisms listing. 
             
                C: AUTH
                S: PLAIN
                S: .
                
                http://msdn.microsoft.com/en-us/library/cc239199.aspx
            */
            if(string.IsNullOrEmpty(mechanism)){
                StringBuilder resp = new StringBuilder();
                resp.Append("+OK\r\n");
                foreach(AUTH_SASL_ServerMechanism m in m_pAuthentications.Values){
                    resp.Append(m.Name + "\r\n");
                }
                resp.Append(".\r\n");

                WriteLine(resp.ToString());

                return;
            }

            if(!this.Authentications.ContainsKey(mechanism)){
                WriteLine("-ERR Not supported authentication mechanism.");
                return;
            }

            byte[] clientResponse = new byte[0];
            AUTH_SASL_ServerMechanism auth = this.Authentications[mechanism];
            auth.Reset();
            while(true){
                byte[] serverResponse = auth.Continue(clientResponse);
                // Authentication completed.
                if(auth.IsCompleted){
                    if(auth.IsAuthenticated){
                        m_pUser = new GenericIdentity(auth.UserName,"SASL-" + auth.Name);

                        // Get mailbox messages.
                        POP3_e_GetMessagesInfo eMessages = OnGetMessagesInfo();
                        int seqNo = 1;
                        foreach(POP3_ServerMessage message in eMessages.Messages){
                            message.SequenceNumber = seqNo++;
                            m_pMessages.Add(message.UID,message);
                        }

                        WriteLine("+OK Authentication succeeded.");
                    }
                    else{
                        WriteLine("-ERR Authentication credentials invalid.");
                    }
                    break;
                }
                // Authentication continues.
                else{
                    // Send server challange.
                    if(serverResponse.Length == 0){
                        WriteLine("+ ");
                    }
                    else{
                        WriteLine("+ " + Convert.ToBase64String(serverResponse));
                    }

                    // Read client response. 
                    SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException);
                    this.TcpStream.ReadLine(readLineOP,false);
                    if(readLineOP.Error != null){
                        throw readLineOP.Error;
                    }
                    // Log
                    if(this.Server.Logger != null){
                        this.Server.Logger.AddRead(this.ID,this.AuthenticatedUserIdentity,readLineOP.BytesInBuffer,"base64 auth-data",this.LocalEndPoint,this.RemoteEndPoint);
                    }

                    // Client canceled authentication.
                    if(readLineOP.LineUtf8 == "*"){
                        WriteLine("-ERR Authentication canceled.");
                        return;
                    }
                    // We have base64 client response, decode it.
                    else{
                        try{
                            clientResponse = Convert.FromBase64String(readLineOP.LineUtf8);
                        }
                        catch{
                            WriteLine("-ERR Invalid client response '" + clientResponse + "'.");
                            return;
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Parses MIME header from the specified stream.
        /// </summary>
        /// <param name="stream">MIME header stream.</param>
        /// <param name="encoding">Headers fields reading encoding. If not sure, UTF-8 is recommended.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>stream</b> or <b>encoding</b> is null.</exception>
        public void Parse(SmartStream stream,Encoding encoding)
        {
            if(stream == null){
                throw new ArgumentNullException("stream");
            }
            if(encoding == null){
                throw new ArgumentNullException("encoding");
            }

            StringBuilder               currentHeader = new StringBuilder();
            SmartStream.ReadLineAsyncOP readLineOP    = new SmartStream.ReadLineAsyncOP(new byte[4*1024*1024],SizeExceededAction.ThrowException);
            while(true){                
                stream.ReadLine(readLineOP,false);
                if(readLineOP.Error != null){
                    throw readLineOP.Error;
                }
                // We reached end of stream.
                else if(readLineOP.BytesInBuffer == 0){
                    if(currentHeader.Length > 0){
                        Add(currentHeader.ToString());
                    }
                    m_IsModified = false;

                    return;
                }
                // We got blank header terminator line.
                else if(readLineOP.LineBytesInBuffer == 0){
                    if(currentHeader.Length > 0){
                        Add(currentHeader.ToString());
                    }
                    m_IsModified = false;

                    return;
                }
                else{
                    string line = encoding.GetString(readLineOP.Buffer,0,readLineOP.BytesInBuffer);
 
                    // New header field starts.
                    if(currentHeader.Length == 0){
                         currentHeader.Append(line);
                    }
                    // Header field continues.
                    else if(char.IsWhiteSpace(line[0])){
                        currentHeader.Append(line);
                    }
                    // Current header field closed, new starts.
                    else{
                        Add(currentHeader.ToString());

                        currentHeader = new StringBuilder();
                        currentHeader.Append(line);
                    }
                }
            }        
        }
        /// <summary>
        /// Starts reading incoming command from the connected client.
        /// </summary>
        private void BeginReadCmd()
        {
            if(this.IsDisposed){
                return;
            }

            try{
                SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException);
                // This event is raised only when read next coomand completes asynchronously.
                readLineOP.Completed += new EventHandler<EventArgs<SmartStream.ReadLineAsyncOP>>(delegate(object sender,EventArgs<SmartStream.ReadLineAsyncOP> e){                
                    if(ProcessCmd(readLineOP)){
                        BeginReadCmd();
                    }
                });
                // Process incoming commands while, command reading completes synchronously.
                while(this.TcpStream.ReadLine(readLineOP,true)){
                    if(!ProcessCmd(readLineOP)){
                        break;
                    }
                }
            }
            catch(Exception x){
                OnError(x);
            }
        }
            /// <summary>
            /// Default constructor.
            /// </summary>
            /// <param name="stream">Stream from where to read body part.</param>
            /// <param name="boundary">Boundry ID what separates body parts.</param>
            /// <exception cref="ArgumentNullException">Is raised when <b>stream</b> or <b>boundary</b> is null reference.</exception>
            public _MultipartReader(SmartStream stream,string boundary)
            {
                if(stream == null){
                    throw new ArgumentNullException("stream");
                }
                if(boundary == null){
                    throw new ArgumentNullException("boundary");
                }

                m_pStream  = stream;
                m_Boundary = boundary;

                m_pReadLineOP   = new SmartStream.ReadLineAsyncOP(new byte[stream.LineBufferSize],SizeExceededAction.ThrowException);
                m_pTextPreamble = new StringBuilder();
                m_pTextEpilogue = new StringBuilder();
            }
        private void AUTHENTICATE(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.2.2. AUTHENTICATE Command.
                Arguments:  authentication mechanism name

                Responses:  continuation data can be requested

                Result:     OK - authenticate completed, now in authenticated state
                            NO - authenticate failure: unsupported authentication
                                 mechanism, credentials rejected
                            BAD - command unknown or arguments invalid,
                                  authentication exchange cancelled

                The AUTHENTICATE command indicates a [SASL] authentication
                mechanism to the server.  If the server supports the requested
                authentication mechanism, it performs an authentication protocol
                exchange to authenticate and identify the client.  It MAY also
                negotiate an OPTIONAL security layer for subsequent protocol
                interactions.  If the requested authentication mechanism is not
                supported, the server SHOULD reject the AUTHENTICATE command by
                sending a tagged NO response.

                The AUTHENTICATE command does not support the optional "initial
                response" feature of [SASL].  Section 5.1 of [SASL] specifies how
                to handle an authentication mechanism which uses an initial
                response.

                The service name specified by this protocol's profile of [SASL] is
                "imap".

                The authentication protocol exchange consists of a series of
                server challenges and client responses that are specific to the
                authentication mechanism.  A server challenge consists of a
                command continuation request response with the "+" token followed
                by a BASE64 encoded string.  The client response consists of a
                single line consisting of a BASE64 encoded string.  If the client
                wishes to cancel an authentication exchange, it issues a line
                consisting of a single "*".  If the server receives such a
                response, it MUST reject the AUTHENTICATE command by sending a
                tagged BAD response.

                If a security layer is negotiated through the [SASL]
                authentication exchange, it takes effect immediately following the
                CRLF that concludes the authentication exchange for the client,
                and the CRLF of the tagged OK response for the server.

                While client and server implementations MUST implement the
                AUTHENTICATE command itself, it is not required to implement any
                authentication mechanisms other than the PLAIN mechanism described
                in [IMAP-TLS].  Also, an authentication mechanism is not required
                to support any security layers.

                    Note: a server implementation MUST implement a
                    configuration in which it does NOT permit any plaintext
                    password mechanisms, unless either the STARTTLS command
                    has been negotiated or some other mechanism that
                    protects the session from password snooping has been
                    provided.  Server sites SHOULD NOT use any configuration
                    which permits a plaintext password mechanism without
                    such a protection mechanism against password snooping.
                    Client and server implementations SHOULD implement
                    additional [SASL] mechanisms that do not use plaintext
                    passwords, such the GSSAPI mechanism described in [SASL]
                    and/or the [DIGEST-MD5] mechanism.

                Servers and clients can support multiple authentication
                mechanisms.  The server SHOULD list its supported authentication
                mechanisms in the response to the CAPABILITY command so that the
                client knows which authentication mechanisms to use.

                A server MAY include a CAPABILITY response code in the tagged OK
                response of a successful AUTHENTICATE command in order to send
                capabilities automatically.  It is unnecessary for a client to
                send a separate CAPABILITY command if it recognizes these
                automatic capabilities.  This should only be done if a security
                layer was not negotiated by the AUTHENTICATE command, because the
                tagged OK response as part of an AUTHENTICATE command is not
                protected by encryption/integrity checking.  [SASL] requires the
                client to re-issue a CAPABILITY command in this case.
                            
                The authorization identity passed from the client to the server
                during the authentication exchange is interpreted by the server as
                the user name whose privileges the client is requesting.

                Example:    S: * OK IMAP4rev1 Server
                            C: A001 AUTHENTICATE GSSAPI
                            S: +
                            C: YIIB+wYJKoZIhvcSAQICAQBuggHqMIIB5qADAgEFoQMCAQ6iBw
                               MFACAAAACjggEmYYIBIjCCAR6gAwIBBaESGxB1Lndhc2hpbmd0
                               b24uZWR1oi0wK6ADAgEDoSQwIhsEaW1hcBsac2hpdmFtcy5jYW
                               Mud2FzaGluZ3Rvbi5lZHWjgdMwgdCgAwIBAaEDAgEDooHDBIHA
                               cS1GSa5b+fXnPZNmXB9SjL8Ollj2SKyb+3S0iXMljen/jNkpJX
                               AleKTz6BQPzj8duz8EtoOuNfKgweViyn/9B9bccy1uuAE2HI0y
                               C/PHXNNU9ZrBziJ8Lm0tTNc98kUpjXnHZhsMcz5Mx2GR6dGknb
                               I0iaGcRerMUsWOuBmKKKRmVMMdR9T3EZdpqsBd7jZCNMWotjhi
                               vd5zovQlFqQ2Wjc2+y46vKP/iXxWIuQJuDiisyXF0Y8+5GTpAL
                               pHDc1/pIGmMIGjoAMCAQGigZsEgZg2on5mSuxoDHEA1w9bcW9n
                               FdFxDKpdrQhVGVRDIzcCMCTzvUboqb5KjY1NJKJsfjRQiBYBdE
                               NKfzK+g5DlV8nrw81uOcP8NOQCLR5XkoMHC0Dr/80ziQzbNqhx
                               O6652Npft0LQwJvenwDI13YxpwOdMXzkWZN/XrEqOWp6GCgXTB
                               vCyLWLlWnbaUkZdEYbKHBPjd8t/1x5Yg==
                            S: + YGgGCSqGSIb3EgECAgIAb1kwV6ADAgEFoQMCAQ+iSzBJoAMC
                               AQGiQgRAtHTEuOP2BXb9sBYFR4SJlDZxmg39IxmRBOhXRKdDA0
                                uHTCOT9Bq3OsUTXUlk0CsFLoa8j+gvGDlgHuqzWHPSQg==
                            C:
                            S: + YDMGCSqGSIb3EgECAgIBAAD/////6jcyG4GE3KkTzBeBiVHe
                               ceP2CWY0SR0fAQAgAAQEBAQ=
                            C: YDMGCSqGSIb3EgECAgIBAAD/////3LQBHXTpFfZgrejpLlLImP
                               wkhbfa2QteAQAgAG1yYwE=
                            S: A001 OK GSSAPI authentication successful

                            Note: The line breaks within server challenges and client
                            responses are for editorial clarity and are not in real
                            authenticators.
            */

            /* RFC 4959.l7 SASL-IR 7.
                The following syntax specification uses the Augmented Backus-Naur
                Form [RFC4234] notation.  [RFC3501] defines the non-terminals
                capability, auth-type, and base64.

                capability    =/ "SASL-IR"

                authenticate  = "AUTHENTICATE" SP auth-type [SP (base64 / "=")]
                                *(CRLF base64)
                                ;;redefine AUTHENTICATE from [RFC3501]
            
            */

            if(m_SessionRejected){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Bad sequence of commands: Session rejected."));

                return;
            }
            if(this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Re-authentication error."));

                return;
            }

            #region Parse parameters

            string[] arguments = cmdText.Split(' ');
            if(arguments.Length > 2){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }
            byte[] initialClientResponse = new byte[0];
            if(arguments.Length == 2){
                if(arguments[1] == "="){
                    // Skip.
                }
                else{
                    try{
                        initialClientResponse = Convert.FromBase64String(arguments[1]);
                    }
                    catch{
                        m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Syntax error: Parameter 'initial-response' value must be BASE64 or contain a single character '='."));

                        return;
                    }
                }
            }
            string mechanism = arguments[0];

            #endregion
                        
            if(!this.Authentications.ContainsKey(mechanism)){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Not supported authentication mechanism."));

                return;
            }

            byte[] clientResponse = initialClientResponse;
            AUTH_SASL_ServerMechanism auth = this.Authentications[mechanism];            
            auth.Reset();
            while(true){
                byte[] serverResponse = auth.Continue(clientResponse);
                // Authentication completed.
                if(auth.IsCompleted){
                    if(auth.IsAuthenticated){
                        m_pUser = new GenericIdentity(auth.UserName,"SASL-" + auth.Name);
                              
                        m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","Authentication succeeded."));
                    }
                    else{
                        m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","Authentication credentials invalid."));
                    }
                    break;
                }
                // Authentication continues.
                else{
                    // Send server challange.
                    if(serverResponse.Length == 0){
                        m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus("+",""));
                    }
                    else{
                        m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus("+",Convert.ToBase64String(serverResponse)));
                    }

                    // Read client response. 
                    SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException);
                    this.TcpStream.ReadLine(readLineOP,false);
                    if(readLineOP.Error != null){
                        throw readLineOP.Error;
                    }
                    // Log
                    if(this.Server.Logger != null){
                        this.Server.Logger.AddRead(this.ID,this.AuthenticatedUserIdentity,readLineOP.BytesInBuffer,"base64 auth-data",this.LocalEndPoint,this.RemoteEndPoint);
                    }

                    // Client canceled authentication.
                    if(readLineOP.LineUtf8 == "*"){
                        m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication canceled."));

                        return;
                    }
                    // We have base64 client response, decode it.
                    else{
                        try{
                            clientResponse = Convert.FromBase64String(readLineOP.LineUtf8);
                        }
                        catch{
                            m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Invalid client response '" + clientResponse + "'."));

                            return;
                        }
                    }
                }
            }
        }
        private void APPEND(string cmdTag,string cmdText)
        {
            /* RFC 3501 6.3.11. APPEND Command.
                Arguments:  mailbox name
                            OPTIONAL flag parenthesized list
                            OPTIONAL date/time string
                            message literal

                Responses:  no specific responses for this command

                Result:     OK - append completed
                            NO - append error: can't append to that mailbox, error
                                 in flags or date/time or message text
                            BAD - command unknown or arguments invalid

                The APPEND command appends the literal argument as a new message
                to the end of the specified destination mailbox.  This argument
                SHOULD be in the format of an [RFC-2822] message.  8-bit
                characters are permitted in the message.  A server implementation
                that is unable to preserve 8-bit data properly MUST be able to
                reversibly convert 8-bit APPEND data to 7-bit using a [MIME-IMB]
                content transfer encoding.

                    Note: There MAY be exceptions, e.g., draft messages, in
                    which required [RFC-2822] header lines are omitted in
                    the message literal argument to APPEND.  The full
                    implications of doing so MUST be understood and
                    carefully weighed.

                If a flag parenthesized list is specified, the flags SHOULD be set
                in the resulting message; otherwise, the flag list of the
                resulting message is set to empty by default.  In either case, the
                Recent flag is also set.

                If a date-time is specified, the internal date SHOULD be set in
                the resulting message; otherwise, the internal date of the
                resulting message is set to the current date and time by default.

                If the append is unsuccessful for any reason, the mailbox MUST be
                restored to its state before the APPEND attempt; no partial
                appending is permitted.

                If the destination mailbox does not exist, a server MUST return an
                error, and MUST NOT automatically create the mailbox.  Unless it
                is certain that the destination mailbox can not be created, the
                server MUST send the response code "[TRYCREATE]" as the prefix of
                the text of the tagged NO response.  This gives a hint to the
                client that it can attempt a CREATE command and retry the APPEND
                if the CREATE is successful.

                If the mailbox is currently selected, the normal new message
                actions SHOULD occur.  Specifically, the server SHOULD notify the
                client immediately via an untagged EXISTS response.  If the server
                does not do so, the client MAY issue a NOOP command (or failing
                that, a CHECK command) after one or more APPEND commands.

                Example:    C: A003 APPEND saved-messages (\Seen) {310}
                            S: + Ready for literal data
                            C: Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)
                            C: From: Fred Foobar <*****@*****.**>
                            C: Subject: afternoon meeting
                            C: To: [email protected]
                            C: Message-Id: <*****@*****.**>
                            C: MIME-Version: 1.0
                            C: Content-Type: TEXT/PLAIN; CHARSET=US-ASCII
                            C:
                            C: Hello Joe, do you think we can meet at 3:30 tomorrow?
                            C:
                            S: A003 OK APPEND completed

                    Note: The APPEND command is not used for message delivery,
                    because it does not provide a mechanism to transfer [SMTP]
                    envelope information.
            */

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return;
            }

            // Store start time
			long startTime = DateTime.Now.Ticks;

            #region Parse arguments

            StringReader r = new StringReader(cmdText);
            r.ReadToFirstChar();
            string folder = null;
            if(r.StartsWith("\"")){
                folder = IMAP_Utils.DecodeMailbox(r.ReadWord());
            }
            else{
                folder = IMAP_Utils.DecodeMailbox(r.QuotedReadToDelimiter(' '));
            }
            r.ReadToFirstChar();
            List<string> flags = new List<string>();
            if(r.StartsWith("(")){                
                foreach(string f in r.ReadParenthesized().Split(' ')){
                    if(f.Length > 0 && !flags.Contains(f.Substring(1))){
                        flags.Add(f.Substring(1));
                    }
                }
            }
            r.ReadToFirstChar();
            DateTime date = DateTime.MinValue;
            if(!r.StartsWith("{")){
                date = IMAP_Utils.ParseDate(r.ReadWord());
            }
            int size = Convert.ToInt32(r.ReadParenthesized());
            if(r.Available > 0){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"BAD","Error in arguments."));

                return;
            }

            #endregion

            IMAP_e_Append e = OnAppend(folder,flags.ToArray(),date,size,new IMAP_r_ServerStatus(cmdTag,"OK","APPEND command completed in %exectime seconds."));
            
            if(e.Response.IsError){
                m_pResponseSender.SendResponseAsync(e.Response);
            }
            else if(e.Stream == null){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Internal server error: No storage stream available."));
            }
            else{
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus("+","Ready for literal data."));
                
                // Create callback which is called when BeginReadFixedCount completes.
                AsyncCallback readLiteralCompletedCallback = delegate(IAsyncResult ar){
                    try{
                        this.TcpStream.EndReadFixedCount(ar);
                        // Log.
                        LogAddRead(size,"Readed " + size + " bytes.");

                        // TODO: Async
                        // Read command line terminating CRLF.
                        SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException);
                        this.TcpStream.ReadLine(readLineOP,false);
                        // Read command line terminating CRLF failed.
                        if(readLineOP.Error != null){
                            OnError(readLineOP.Error);
                        }
                        // Read command line terminating CRLF succeeded.
                        else{
                            LogAddRead(readLineOP.BytesInBuffer,readLineOP.LineUtf8);

                            // Raise Completed event.
                            e.OnCompleted();

                            m_pResponseSender.SendResponseAsync(IMAP_r_ServerStatus.Parse(e.Response.ToString().TrimEnd().Replace("%exectime",((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2"))));
                            BeginReadCmd();
                        }
                    }
                    catch(Exception x){
                        OnError(x);
                    }
                };

                this.TcpStream.BeginReadFixedCount(e.Stream,size,readLiteralCompletedCallback,null);
            }
        }
Beispiel #8
0
        /// <summary>
        /// Deletes specified folder.
        /// </summary>
        /// <param name="folderName">Folder name.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when IMAP client is not connected and authenticated.</exception>
        public void DeleteFolder(string folderName)
        {
            if (this.IsDisposed)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("You must connect first.");
            }
            if (!this.IsAuthenticated)
            {
                throw new InvalidOperationException("The DELETE command is only valid in authenticated state.");
            }

            string line = GetNextCmdTag() + " DELETE " + TextUtils.QuoteString(Core.Encode_IMAP_UTF7_String(folderName));
            int countWritten = this.TcpStream.WriteLine(line);
            LogAddWrite(countWritten, line);

            SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[32000], SizeExceededAction.JunkAndThrowException);
            this.TcpStream.ReadLine(args, false);
            if (args.Error != null)
            {
                throw args.Error;
            }
            line = args.LineUtf8;
            LogAddRead(args.BytesInBuffer, line);
            line = RemoveCmdTag(line);
            if (!line.ToUpper().StartsWith("OK"))
            {
                throw new IMAP_ClientException(line);
            }
        }
Beispiel #9
0
        /// <summary>
        /// Fetches specifes messages specified fetch items.
        /// </summary>
        /// <param name="sequence_set">IMAP sequence-set.</param>
        /// <param name="fetchFlags">Specifies what data to fetch from IMAP server.</param>
        /// <param name="setSeenFlag">If true message seen flag is setted.</param>
        /// <param name="uidFetch">Specifies if sequence-set contains message UIDs or message numbers.</param>
        /// <returns>Returns requested fetch items.</returns>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when IMAP client is not connected,not authenticated and folder not selected.</exception>
        public IMAP_FetchItem[] FetchMessages(IMAP_SequenceSet sequence_set, IMAP_FetchItem_Flags fetchFlags, bool setSeenFlag, bool uidFetch)
        {
            if (this.IsDisposed)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("You must connect first.");
            }
            if (!this.IsAuthenticated)
            {
                throw new InvalidOperationException("The command is only valid in authenticated state.");
            }
            if (m_SelectedFolder.Length == 0)
            {
                throw new InvalidOperationException("The command is only valid in selected state.");
            }

            List<IMAP_FetchItem> fetchItems = new List<IMAP_FetchItem>();

            //--- Construct FETCH command line -----------------------------------------------------------------------//
            string fetchCmdLine = GetNextCmdTag();
            if (uidFetch)
            {
                fetchCmdLine += " UID";
            }
            fetchCmdLine += " FETCH " + sequence_set.ToSequenceSetString() + " (UID";

            // FLAGS
            if ((fetchFlags & IMAP_FetchItem_Flags.MessageFlags) != 0)
            {
                fetchCmdLine += " FLAGS";
            }
            // RFC822.SIZE
            if ((fetchFlags & IMAP_FetchItem_Flags.Size) != 0)
            {
                fetchCmdLine += " RFC822.SIZE";
            }
            // INTERNALDATE
            if ((fetchFlags & IMAP_FetchItem_Flags.InternalDate) != 0)
            {
                fetchCmdLine += " INTERNALDATE";
            }
            // ENVELOPE
            if ((fetchFlags & IMAP_FetchItem_Flags.Envelope) != 0)
            {
                fetchCmdLine += " ENVELOPE";
            }
            // BODYSTRUCTURE
            if ((fetchFlags & IMAP_FetchItem_Flags.BodyStructure) != 0)
            {
                fetchCmdLine += " BODYSTRUCTURE";
            }
            // BODY[] or BODY.PEEK[]
            if ((fetchFlags & IMAP_FetchItem_Flags.Message) != 0)
            {
                if (setSeenFlag)
                {
                    fetchCmdLine += " BODY[]";
                }
                else
                {
                    fetchCmdLine += " BODY.PEEK[]";
                }
            }
            // BODY[HEADER] or BODY.PEEK[HEADER] ---> This needed only if full message isn't requested.
            if ((fetchFlags & IMAP_FetchItem_Flags.Message) == 0 && (fetchFlags & IMAP_FetchItem_Flags.Header) != 0)
            {
                if (setSeenFlag)
                {
                    fetchCmdLine += " BODY[HEADER]";
                }
                else
                {
                    fetchCmdLine += " BODY.PEEK[HEADER]";
                }
            }
            //--------------------------------------------------------------------------------------------------------//

            fetchCmdLine += ")";

            // Send fetch command line to server.
            int countWritten = this.TcpStream.WriteLine(fetchCmdLine);
            LogAddWrite(countWritten, fetchCmdLine);

            // Read un-tagged response lines while we get final response line.
            byte[] lineBuffer = new byte[100000];
            string line = "";
            while (true)
            {
                SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(lineBuffer, SizeExceededAction.JunkAndThrowException);
                this.TcpStream.ReadLine(args, false);
                if (args.Error != null)
                {
                    throw args.Error;
                }
                line = args.LineUtf8;
                LogAddRead(args.BytesInBuffer, line);

                // We have un-tagged resposne.
                if (line.StartsWith("*"))
                {
                    if (IsStatusResponse(line))
                    {
                        ProcessStatusResponse(line);
                    }
                    else
                    {
                        int no = 0;
                        int uid = 0;
                        int size = 0;
                        byte[] data = null;
                        IMAP_MessageFlags flags = IMAP_MessageFlags.Recent;
                        string envelope = "";
                        string bodystructure = "";
                        string internalDate = "";

                        // Remove *
                        line = RemoveCmdTag(line);

                        // Get message number
                        no = Convert.ToInt32(line.Substring(0, line.IndexOf(" ")));

                        // Get rid of FETCH  and parse params. Reply:* 1 FETCH (UID 12 BODY[] ...)
                        line = line.Substring(line.IndexOf("FETCH (") + 7);

                        StringReader r = new StringReader(line);
                        // Loop fetch result fields
                        while (r.Available > 0)
                        {
                            r.ReadToFirstChar();

                            // Fetch command closing ) parenthesis
                            if (r.SourceString == ")")
                            {
                                break;
                            }

                            #region UID <value>

                            // UID <value>
                            else if (r.StartsWith("UID", false))
                            {
                                // Remove UID word from reply
                                r.ReadSpecifiedLength("UID".Length);
                                r.ReadToFirstChar();

                                // Read <value>
                                string word = r.ReadWord();
                                if (word == null)
                                {
                                    throw new Exception("IMAP server didn't return UID <value> !");
                                }
                                else
                                {
                                    uid = Convert.ToInt32(word);
                                }
                            }

                            #endregion

                            #region RFC822.SIZE <value>

                            // RFC822.SIZE <value>
                            else if (r.StartsWith("RFC822.SIZE", false))
                            {
                                // Remove RFC822.SIZE word from reply
                                r.ReadSpecifiedLength("RFC822.SIZE".Length);
                                r.ReadToFirstChar();

                                // Read <value>
                                string word = r.ReadWord();
                                if (word == null)
                                {
                                    throw new Exception("IMAP server didn't return RFC822.SIZE <value> !");
                                }
                                else
                                {
                                    try
                                    {
                                        size = Convert.ToInt32(word);
                                    }
                                    catch
                                    {
                                        throw new Exception("IMAP server returned invalid RFC822.SIZE <value> '" + word + "' !");
                                    }
                                }
                            }

                            #endregion

                            #region INTERNALDATE <value>

                            // INTERNALDATE <value>
                            else if (r.StartsWith("INTERNALDATE", false))
                            {
                                // Remove INTERNALDATE word from reply
                                r.ReadSpecifiedLength("INTERNALDATE".Length);
                                r.ReadToFirstChar();

                                // Read <value>
                                string word = r.ReadWord();
                                if (word == null)
                                {
                                    throw new Exception("IMAP server didn't return INTERNALDATE <value> !");
                                }
                                else
                                {
                                    internalDate = word;
                                }
                            }

                            #endregion

                            #region ENVELOPE (<envelope-string>)

                            else if (r.StartsWith("ENVELOPE", false))
                            {
                                // Remove ENVELOPE word from reply
                                r.ReadSpecifiedLength("ENVELOPE".Length);
                                r.ReadToFirstChar();

                                /*
                                    Handle string literals {count-to-read}<CRLF>data(length = count-to-read).
                                    (string can be quoted string or literal)
                                    Loop while get envelope,invalid response or timeout.
                                */

                                while (true)
                                {
                                    try
                                    {
                                        envelope = r.ReadParenthesized();
                                        break;
                                    }
                                    catch (Exception x)
                                    {
                                        string s = r.ReadToEnd();

                                        /* partial_envelope {count-to-read}
                                            Example: ENVELOPE ("Mon, 03 Apr 2006 10:10:10 GMT" {35}
                                        */
                                        if (s.EndsWith("}"))
                                        {
                                            // Get partial envelope and append it back to reader
                                            r.AppenString(s.Substring(0, s.LastIndexOf('{')));

                                            // Read remaining envelope and append it to reader.
                                            int countToRead = Convert.ToInt32(s.Substring(s.LastIndexOf('{') + 1, s.LastIndexOf('}') - s.LastIndexOf('{') - 1));
                                            string reply = this.TcpStream.ReadFixedCountString(countToRead);
                                            LogAddRead(countToRead, reply);
                                            r.AppenString(TextUtils.QuoteString(reply));

                                            // Read fetch continuing line.
                                            this.TcpStream.ReadLine(args, false);
                                            if (args.Error != null)
                                            {
                                                throw args.Error;
                                            }
                                            line = args.LineUtf8;
                                            LogAddRead(args.BytesInBuffer, line);
                                            r.AppenString(line);
                                        }
                                        // Unexpected response
                                        else
                                        {
                                            throw x;
                                        }
                                    }
                                }
                            }

                            #endregion

                            #region BODYSTRUCTURE (<bodystructure-string>)

                            else if (r.StartsWith("BODYSTRUCTURE", false))
                            {
                                // Remove BODYSTRUCTURE word from reply
                                r.ReadSpecifiedLength("BODYSTRUCTURE".Length);
                                r.ReadToFirstChar();

                                bodystructure = r.ReadParenthesized();
                            }

                            #endregion

                            #region BODY[] or BODY[HEADER]

                            // BODY[] or BODY[HEADER]
                            else if (r.StartsWith("BODY", false))
                            {
                                if (r.StartsWith("BODY[]", false))
                                {
                                    // Remove BODY[]
                                    r.ReadSpecifiedLength("BODY[]".Length);
                                }
                                else if (r.StartsWith("BODY[HEADER]", false))
                                {
                                    // Remove BODY[HEADER]
                                    r.ReadSpecifiedLength("BODY[HEADER]".Length);
                                }
                                else
                                {
                                    throw new Exception("Invalid FETCH response: " + r.SourceString);
                                }
                                r.ReadToFirstChar();

                                // We must now have {<size-to-read>}, or there is error
                                if (!r.StartsWith("{"))
                                {
                                    throw new Exception("Invalid FETCH BODY[] or BODY[HEADER] response: " + r.SourceString);
                                }
                                // Read <size-to-read>
                                int dataLength = Convert.ToInt32(r.ReadParenthesized());

                                // Read data
                                MemoryStream storeStrm = new MemoryStream(dataLength);
                                this.TcpStream.ReadFixedCount(storeStrm, dataLength);
                                LogAddRead(dataLength, "Readed " + dataLength.ToString() + " bytes.");
                                data = storeStrm.ToArray();

                                // Read fetch continuing line.
                                this.TcpStream.ReadLine(args, false);
                                if (args.Error != null)
                                {
                                    throw args.Error;
                                }
                                line = args.LineUtf8;
                                LogAddRead(args.BytesInBuffer, line);
                                r.AppenString(line);
                            }

                            #endregion

                            #region FLAGS (<flags-list>)

                            // FLAGS (<flags-list>)
                            else if (r.StartsWith("FLAGS", false))
                            {
                                // Remove FLAGS word from reply
                                r.ReadSpecifiedLength("FLAGS".Length);
                                r.ReadToFirstChar();

                                // Read (<flags-list>)
                                string flagsList = r.ReadParenthesized();
                                if (flagsList == null)
                                {
                                    throw new Exception("IMAP server didn't return FLAGS (<flags-list>) !");
                                }
                                else
                                {
                                    flags = IMAP_Utils.ParseMessageFlags(flagsList);
                                }
                            }

                            #endregion

                            else
                            {
                                throw new Exception("Not supported fetch reply: " + r.SourceString);
                            }
                        }

                        fetchItems.Add(new IMAP_FetchItem(no, uid, size, data, flags, internalDate, envelope, bodystructure, fetchFlags));
                    }
                }
                else
                {
                    break;
                }
            }

            if (!RemoveCmdTag(line).ToUpper().StartsWith("OK"))
            {
                throw new IMAP_ClientException(line);
            }

            return fetchItems.ToArray();
        }
Beispiel #10
0
        /// <summary>
        /// Writes a raw line to the imap client.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public string WriteLine(string data)
        {
            if (this.IsDisposed)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("You must connect first.");
            }
            if (!this.IsAuthenticated)
            {
                throw new InvalidOperationException("The command is only valid in authenticated state.");
            }

            string line = GetNextCmdTag() + " " + data;

            int countWritten = this.TcpStream.WriteLine(line);
            LogAddWrite(countWritten, line);

            SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[32000], SizeExceededAction.JunkAndThrowException);
            this.TcpStream.ReadLine(args, false);
            if (args.Error != null)
            {
                throw args.Error;
            }
            line = args.LineUtf8;
            LogAddRead(args.BytesInBuffer, line);

            return line;
        }
Beispiel #11
0
 /// <summary>
 /// This method is called after TCP client has sucessfully connected.
 /// </summary>
 protected override void OnConnected()
 {
     SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[32000], SizeExceededAction.JunkAndThrowException);
     this.TcpStream.ReadLine(args, false);
     if (args.Error != null)
     {
         throw args.Error;
     }
     string line = args.LineUtf8;
     LogAddRead(args.BytesInBuffer, line);
     line = RemoveCmdTag(line);
     if (line.ToUpper().StartsWith("OK"))
     {
         // Clear path separator, so next access will get it.
         m_PathSeparator = '\0';
     }
     else
     {
         throw new Exception("Server returned: " + line);
     }
 }
Beispiel #12
0
        /// <summary>
        /// Switches IMAP connection to SSL.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when IMAP client is not connected or is authenticated or is already secure connection.</exception>
        public void StartTLS()
        {
            /* RFC 2595 3. IMAP STARTTLS extension.

                Example:    C: a001 CAPABILITY
                            S: * CAPABILITY IMAP4rev1 STARTTLS LOGINDISABLED
                            S: a001 OK CAPABILITY completed
                            C: a002 STARTTLS
                            S: a002 OK Begin TLS negotiation now
                            <TLS negotiation, further commands are under TLS layer>
            */

            if (this.IsDisposed)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if (!this.IsConnected)
            {
                throw new InvalidOperationException("You must connect first.");
            }
            if (this.IsAuthenticated)
            {
                throw new InvalidOperationException("The STARTTLS command is only valid in non-authenticated state !");
            }
            if (this.IsSecureConnection)
            {
                throw new InvalidOperationException("Connection is already secure.");
            }

            string line = GetNextCmdTag() + " STARTTLS";
            int countWritten = this.TcpStream.WriteLine(line);
            LogAddWrite(countWritten, line);

            SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[32000], SizeExceededAction.JunkAndThrowException);
            this.TcpStream.ReadLine(args, false);
            if (args.Error != null)
            {
                throw args.Error;
            }
            line = args.LineUtf8;
            LogAddRead(args.BytesInBuffer, line);
            line = RemoveCmdTag(line);
            if (!line.ToUpper().StartsWith("OK"))
            {
                throw new IMAP_ClientException(line);
            }

            SwitchToSecure();
        }
Beispiel #13
0
 /// <summary>
 /// This method is called when TCP client has sucessfully connected.
 /// </summary>
 /// <param name="callback">Callback to be called to complete connect operation.</param>
 protected override void OnConnected(CompleteConnectCallback callback)
 {
     // Read POP3 server greeting response.
     SmartStream.ReadLineAsyncOP readGreetingOP = new SmartStream.ReadLineAsyncOP(new byte[8000],SizeExceededAction.JunkAndThrowException);
     readGreetingOP.Completed += delegate(object s,EventArgs<SmartStream.ReadLineAsyncOP> e){
         ReadServerGreetingCompleted(readGreetingOP,callback);
     };
     if(this.TcpStream.ReadLine(readGreetingOP,true)){
         ReadServerGreetingCompleted(readGreetingOP,callback);
     }
 }
Beispiel #14
0
        /// <summary>
        /// Gets files and directories in the current server directory.
        /// </summary>
        /// <param name="path">Directory or file name which listing to get. Value null means current directory will be listed.</param>
        /// <returns>Returns current working directory listing.</returns>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when FTP client is not connected or FTP data connection has active read/write operation.</exception>
        /// <exception cref="FTP_ClientException">Is raised when FTP server returns error.</exception>
        public FTP_ListItem[] GetList(string path)
        {
            if(this.IsDisposed){
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if(!this.IsConnected){
                throw new InvalidOperationException("You must connect first.");
            }
            if(m_pDataConnection.IsActive){
                throw new InvalidOperationException("There is already active read/write operation on data connection.");
            }

            List<FTP_ListItem> retVal = new List<FTP_ListItem>();

            // Set transfer mode
            SetTransferType(TransferType.Binary);

            if(m_TransferMode == FTP_TransferMode.Passive){
                Pasv();
            }
            else{
                Port();
            }

            // If FTP server supports MLSD command, use it to get directory listing.
            // MLSD is standard way to get dir listing, while LIST command isn't any strict standard.
            bool mlsdSupported = false;
            foreach(string feature in m_pExtCapabilities){
                if(feature.ToLower().StartsWith("mlsd")){
                    mlsdSupported = true;
                    break;
                }
            }

            #region MLSD

            if(mlsdSupported){
                if(string.IsNullOrEmpty(path)){
                    WriteLine("MLSD");
                }
                else{
                    WriteLine("MLSD " + path);
                }

                string[] response = ReadResponse();
                if(!response[0].StartsWith("1")){
                    throw new FTP_ClientException(response[0]);
                }

                MemoryStream ms = new MemoryStream();
                m_pDataConnection.ReadAll(ms);

                response = ReadResponse();
                if(!response[0].StartsWith("2")){
                    throw new FTP_ClientException(response[0]);
                }

                byte[] lineBuffer = new byte[8000];
                ms.Position = 0;
                SmartStream mlsdStream = new SmartStream(ms,true);
                while(true){
                    SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(lineBuffer,SizeExceededAction.JunkAndThrowException);
                    mlsdStream.ReadLine(args,false);
                    if(args.Error != null){
                        throw args.Error;
                    }
                    string line = args.LineUtf8;

                    // We reached end of stream, we readed whole list sucessfully.
                    if(line == null){
                        break;
                    }
                    else{
                        string[] parameters = line.Substring(0,line.LastIndexOf(';')).Split(';');
                        string   name       = line.Substring(line.LastIndexOf(';') + 1).Trim();

                        string   type     = "";
                        long     size     = 0;
                        DateTime modified = DateTime.MinValue;
                        foreach(string parameter in parameters){
                            string[] name_value = parameter.Split('=');
                            if(name_value[0].ToLower() == "type"){
                                type = name_value[1].ToLower();
                            }
                            else if(name_value[0].ToLower() == "size"){
                                size = Convert.ToInt32(name_value[1]);
                            }
                            else if(name_value[0].ToLower() == "modify"){
                                modified = DateTime.ParseExact(name_value[1],"yyyyMMddHHmmss",System.Globalization.DateTimeFormatInfo.InvariantInfo);
                            }
                            else{
                                // Other options won't interest us, skip them.
                            }
                        }

                        if(type == "dir"){
                            retVal.Add(new FTP_ListItem(name,0,modified,true));
                        }
                        else if(type == "file"){
                            retVal.Add(new FTP_ListItem(name,size,modified,false));
                        }
                    }
                }
            }

            #endregion

            #region LIST

            else{
                if(string.IsNullOrEmpty(path)){
                    WriteLine("LIST");
                }
                else{
                    WriteLine("LIST " + path);
                }

                string[] response = ReadResponse();
                if(!response[0].StartsWith("1")){
                    throw new FTP_ClientException(response[0]);
                }

                MemoryStream ms = new MemoryStream();
                m_pDataConnection.ReadAll(ms);

                response = ReadResponse();
                if(!response[0].StartsWith("2")){
                    throw new FTP_ClientException(response[0]);
                }

                ms.Position = 0;
                SmartStream listStream =  new SmartStream(ms,true);

                string[] winDateFormats = new string[]{"M-d-yy h:mmtt"};
                string[] unixFormats    = new string[]{"MMM d H:mm","MMM d yyyy"};

                SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[8000],SizeExceededAction.JunkAndThrowException);
                while(true){
                    listStream.ReadLine(args,false);
                    if(args.Error != null){
                        throw args.Error;
                    }
                    else if(args.BytesInBuffer == 0){
                        break;
                    }
                    string line = args.LineUtf8;

                    // Dedect listing.
                    string listingType = "unix";
                    if(line != null){
                        StringReader r = new StringReader(line);
                        DateTime modified;
                        if(DateTime.TryParseExact(r.ReadWord() + " " + r.ReadWord(),new string[]{"MM-dd-yy hh:mmtt"},System.Globalization.DateTimeFormatInfo.InvariantInfo,System.Globalization.DateTimeStyles.None,out modified)){
                            listingType = "win";
                        }
                    }

                    try{
                        // Windows listing.
                        if(listingType == "win"){
                            // MM-dd-yy hh:mm <DIR> directoryName
                            // MM-dd-yy hh:mm size  fileName

                            StringReader r = new StringReader(line);
                            // Read date
                            DateTime modified = DateTime.ParseExact(r.ReadWord() + " " + r.ReadWord(),winDateFormats,System.Globalization.DateTimeFormatInfo.InvariantInfo,System.Globalization.DateTimeStyles.None);

                            r.ReadToFirstChar();
                            // We have directory.
                            if(r.StartsWith("<dir>",false)){
                                r.ReadSpecifiedLength(5);
                                r.ReadToFirstChar();

                                retVal.Add(new FTP_ListItem(r.ReadToEnd(),0,modified,true));
                            }
                            // We have file
                            else{
                                // Read file size
                                long size = Convert.ToInt64(r.ReadWord());
                                r.ReadToFirstChar();

                                retVal.Add(new FTP_ListItem(r.ReadToEnd(),size,modified,false));
                            }
                        }
                        // Unix listing
                        else{
                            // "d"directoryAtttributes xx xx xx 0 MMM d HH:mm/yyyy directoryName
                            // fileAtttributes xx xx xx fileSize MMM d HH:mm/yyyy fileName

                            StringReader r = new StringReader(line);
                            string attributes = r.ReadWord();
                            r.ReadWord();
                            r.ReadWord();
                            r.ReadWord();
                            long size = Convert.ToInt64(r.ReadWord());
                            DateTime modified = DateTime.ParseExact(r.ReadWord() + " " + r.ReadWord() + " " + r.ReadWord(),unixFormats,System.Globalization.DateTimeFormatInfo.InvariantInfo,System.Globalization.DateTimeStyles.None);
                            r.ReadToFirstChar();
                            string name = r.ReadToEnd();
                            if(name != "." && name != ".."){
                                if(attributes.StartsWith("d")){
                                    retVal.Add(new FTP_ListItem(name,0,modified,true));
                                }
                                else{
                                    retVal.Add(new FTP_ListItem(name,size,modified,false));
                                }
                            }
                        }
                    }
                    catch{
                        // Skip unknown entries.
                    }
                }
            }

            #endregion

            return retVal.ToArray();
        }
Beispiel #15
0
        /// <summary>
        /// Reads line from TCP client.
        /// </summary>
        /// <returns></returns>
        internal string ReadLine()
        {
            SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[8000],SizeExceededAction.JunkAndThrowException);
            this.TcpClient.TcpStream.ReadLine(readLineOP,false);

            return readLineOP.LineUtf8;
        }
Beispiel #16
0
        /// <summary>
        /// Reads next continuing FETCH line and stores to fetch reader 'r'.
        /// </summary>
        /// <param name="imap">IMAP client.</param>
        /// <param name="r">String reader.</param>
        /// <param name="callback">Fetch completion callback.</param>
        /// <returns>Returns true if completed asynchronously or false if completed synchronously.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>imap</b>,<b>r</b> or <b>callback</b> is null reference.</exception>
        private bool ReadNextFetchLine(IMAP_Client imap,StringReader r,EventHandler<EventArgs<Exception>> callback)
        {
            if(imap == null){
                throw new ArgumentNullException("imap");
            }
            if(r == null){
                throw new ArgumentNullException("r");
            }
            if(callback == null){
                throw new ArgumentNullException("callback");
            }

            SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[64000],SizeExceededAction.JunkAndThrowException);
            readLineOP.Completed += delegate(object sender,EventArgs<SmartStream.ReadLineAsyncOP> e){
                try{
                    // Read line failed.
                    if(readLineOP.Error != null){
                        callback(this,new EventArgs<Exception>(readLineOP.Error));
                    }
                    else{
                        // Log.
                        imap.LogAddRead(readLineOP.BytesInBuffer,readLineOP.LineUtf8);

                        // Append fetch line to fetch reader.
                        r.AppendString(readLineOP.LineUtf8);

                        ParseDataItems(imap,r,callback);
                    }
                }
                catch(Exception x){
                    callback(this,new EventArgs<Exception>(x));
                }
                finally{
                    readLineOP.Dispose();
                }
            };

            // Read line completed synchronously.
            if(imap.TcpStream.ReadLine(readLineOP,true)){
                try{
                    // Read line failed.
                    if(readLineOP.Error != null){
                        callback(this,new EventArgs<Exception>(readLineOP.Error));

                        return true;
                    }
                    else{
                        // Log.
                        imap.LogAddRead(readLineOP.BytesInBuffer,readLineOP.LineUtf8);

                        // Append fetch line to fetch reader.
                        r.AppendString(readLineOP.LineUtf8);

                        return false;
                    }
                }
                finally{
                    readLineOP.Dispose();
                }
            }

            return true;
        }
Beispiel #17
0
        private void m_pTimerNoop_Elapsed(object sender,ElapsedEventArgs e)
        {
            try{
                /* Noop
                  Responses:
                    +OK 
                */

                lock(this.LockSynchronizer){
                    m_pClient.TcpStream.WriteLine("NOOP");

                    SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[8000],SizeExceededAction.JunkAndThrowException);
                    m_pClient.TcpStream.ReadLine(readLineOP,false);
                }
            }
            catch{
            }
        }
Beispiel #18
0
        /// <summary>
        /// Reads and logs specified line from connected host.
        /// </summary>
        /// <returns>Returns readed line.</returns>
        protected string ReadLine()
        {
            SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException);
            this.TcpStream.ReadLine(args,false);
            if(args.Error != null){
                throw args.Error;
            }
            string line = args.LineUtf8;
            if(args.BytesInBuffer > 0){
                LogAddRead(args.BytesInBuffer,line);
            }
            else{
                LogAddText("Remote host closed connection.");
            }

            return line;
        }
            /// <summary>
            /// Start operation processing.
            /// </summary>
            public void Start()
            {
                /* RFC 3501.
                    literal = "{" number "}" CRLF *CHAR8
                              ; Number represents the number of CHAR8s
                */

                // TODO: Async
                // TODO: Limit total command size. 64k ? 

                
                // If initial command line ends with literal string, read literal string and remaining command text.
                if(EndsWithLiteralString(m_InitialCmdLine)){
                    StringBuilder cmdText     = new StringBuilder();
                    int           literalSize = GetLiteralSize(m_InitialCmdLine);

                    // Add initial command line part to command text.
                    cmdText.Append(RemoveLiteralSpecifier(m_InitialCmdLine));

                    SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException);
                    while(true){
                        #region Read literal string

                        // Send "+ Continue".
                        m_pSession.WriteLine("+ Continue.");

                        // Read literal string.
                        MemoryStream msLiteral = new MemoryStream();
                        m_pSession.TcpStream.ReadFixedCount(msLiteral,literalSize);
                        
                        // Log
                        m_pSession.LogAddRead(literalSize,m_pCharset.GetString(msLiteral.ToArray()));

                        // Add to command text as quoted string.
                        cmdText.Append(TextUtils.QuoteString(m_pCharset.GetString(msLiteral.ToArray())));

                        #endregion
                        
                        #region Read continuing command text

                        // Read continuing command text.
                        m_pSession.TcpStream.ReadLine(readLineOP,false);

                        // We have error.
                        if(readLineOP.Error != null){
                            throw readLineOP.Error;
                        }
                        else{
                            string line = readLineOP.LineUtf8;

                            // Log
                            m_pSession.LogAddRead(readLineOP.BytesInBuffer,line);

                            // Add command line part to command text.
                            if(EndsWithLiteralString(line)){
                                cmdText.Append(RemoveLiteralSpecifier(line));
                            }
                            else{
                                cmdText.Append(line);
                            }

                            // No more literal string, we are done.
                            if(!EndsWithLiteralString(line)){
                                break;
                            }
                            else{
                                literalSize = GetLiteralSize(line);
                            }
                        }

                        #endregion
                    }

                    m_CmdLine = cmdText.ToString();
                }
                // We have no literal string, so initial cmd line is final.
                else{
                    m_CmdLine = m_InitialCmdLine;
                }                
            }
            /// <summary>
            /// Is called when DELE command sending has finished.
            /// </summary>
            /// <param name="ar">Asynchronous result.</param>
            private void DeleCommandSendingCompleted(IAsyncResult ar)
            {
                try{
                    m_pPop3Client.TcpStream.EndWrite(ar);

                    // Read POP3 server response.
                    SmartStream.ReadLineAsyncOP op = new SmartStream.ReadLineAsyncOP(new byte[8000],SizeExceededAction.JunkAndThrowException);
                    op.Completed += delegate(object s,EventArgs<SmartStream.ReadLineAsyncOP> e){
                        DeleReadResponseCompleted(op);
                    };
                    if(m_pPop3Client.TcpStream.ReadLine(op,true)){
                        DeleReadResponseCompleted(op);
                    }
                }
                catch(Exception x){
                    m_pException = x;
                    m_pPop3Client.LogAddException("Exception: " + x.Message,x);
                    SetState(AsyncOP_State.Completed);
                }
            }
        private bool IDLE(string cmdTag,string cmdText)
        {
            /* RFC 2177 3. IDLE Command.
                Arguments:  none

                Responses:  continuation data will be requested; the client sends
                            the continuation data "DONE" to end the command

                Result:     OK - IDLE completed after client sent "DONE"
                            NO - failure: the server will not allow the IDLE
                            command at this time
                BAD - command unknown or arguments invalid

                The IDLE command may be used with any IMAP4 server implementation
                that returns "IDLE" as one of the supported capabilities to the
                CAPABILITY command.  If the server does not advertise the IDLE
                capability, the client MUST NOT use the IDLE command and must poll
                for mailbox updates.  In particular, the client MUST continue to be
                able to accept unsolicited untagged responses to ANY command, as
                specified in the base IMAP specification.

                The IDLE command is sent from the client to the server when the
                client is ready to accept unsolicited mailbox update messages.  The
                server requests a response to the IDLE command using the continuation
                ("+") response.  The IDLE command remains active until the client
                responds to the continuation, and as long as an IDLE command is
                active, the server is now free to send untagged EXISTS, EXPUNGE, and
                other messages at any time.

                The IDLE command is terminated by the receipt of a "DONE"
                continuation from the client; such response satisfies the server's
                continuation request.  At that point, the server MAY send any
                remaining queued untagged responses and then MUST immediately send
                the tagged response to the IDLE command and prepare to process other
                commands. As in the base specification, the processing of any new
                command may cause the sending of unsolicited untagged responses,
                subject to the ambiguity limitations.  The client MUST NOT send a
                command while the server is waiting for the DONE, since the server
                will not be able to distinguish a command from a continuation.

                The server MAY consider a client inactive if it has an IDLE command
                running, and if such a server has an inactivity timeout it MAY log
                the client off implicitly at the end of its timeout period.  Because
                of that, clients using IDLE are advised to terminate the IDLE and
                re-issue it at least every 29 minutes to avoid being logged off.
                This still allows a client to receive immediate mailbox updates even
                though it need only "poll" at half hour intervals.

                Example:    C: A001 SELECT INBOX
                            S: * FLAGS (Deleted Seen)
                            S: * 3 EXISTS
                            S: * 0 RECENT
                            S: * OK [UIDVALIDITY 1]
                            S: A001 OK SELECT completed
                            C: A002 IDLE
                            S: + idling
                            ...time passes; new mail arrives...
                            S: * 4 EXISTS
                            C: DONE
                            S: A002 OK IDLE terminated
                            ...another client expunges message 2 now...
                            C: A003 FETCH 4 ALL
                            S: * 4 FETCH (...)
                            S: A003 OK FETCH completed
                            C: A004 IDLE
                            S: * 2 EXPUNGE
                            S: * 3 EXISTS
                            S: + idling
                            ...time passes; another client expunges message 3...
                            S: * 3 EXPUNGE
                            S: * 2 EXISTS
                            ...time passes; new mail arrives...
                            S: * 3 EXISTS
                            C: DONE
                            S: A004 OK IDLE terminated
                            C: A005 FETCH 3 ALL
                            S: * 3 FETCH (...)
                            S: A005 OK FETCH completed
            */

            if(!this.IsAuthenticated){
                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Authentication required."));

                return true;
            }

            m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus("+","idling"));

            TimerEx timer = new TimerEx(30000,true);
            timer.Elapsed += new System.Timers.ElapsedEventHandler(delegate(object sender,System.Timers.ElapsedEventArgs e){
                try{
                    UpdateSelectedFolderAndSendChanges();
                }
                catch{
                }
            });
            timer.Enabled = true;

            // Read client response. 
            SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException);
            readLineOP.Completed += new EventHandler<EventArgs<SmartStream.ReadLineAsyncOP>>(delegate(object sender,EventArgs<SmartStream.ReadLineAsyncOP> e){
                try{
                    if(readLineOP.Error != null){
                        LogAddText("Error: " + readLineOP.Error.Message);
                        timer.Dispose();

                        return;
                    }
                    // Remote host closed connection.
                    else if(readLineOP.BytesInBuffer == 0){
                        LogAddText("Remote host(connected client) closed IMAP connection.");
                        timer.Dispose();
                        Dispose();

                        return;
                    }

                    LogAddRead(readLineOP.BytesInBuffer,readLineOP.LineUtf8);

                    if(string.Equals(readLineOP.LineUtf8,"DONE",StringComparison.InvariantCultureIgnoreCase)){
                        timer.Dispose();

                        m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","IDLE terminated."));
                        BeginReadCmd();
                    }
                    else{
                        while(this.TcpStream.ReadLine(readLineOP,true)){
                            if(readLineOP.Error != null){
                                LogAddText("Error: " + readLineOP.Error.Message);
                                timer.Dispose();

                                return;
                            }
                            LogAddRead(readLineOP.BytesInBuffer,readLineOP.LineUtf8);

                            if(string.Equals(readLineOP.LineUtf8,"DONE",StringComparison.InvariantCultureIgnoreCase)){
                                timer.Dispose();

                                m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","IDLE terminated."));
                                BeginReadCmd();

                                break;
                            }
                        }
                    }
                }
                catch(Exception x){
                    timer.Dispose();

                    OnError(x);
                }
            });
            while(this.TcpStream.ReadLine(readLineOP,true)){
                if(readLineOP.Error != null){
                    LogAddText("Error: " + readLineOP.Error.Message);
                    timer.Dispose();

                    break;
                }

                LogAddRead(readLineOP.BytesInBuffer,readLineOP.LineUtf8);

                if(string.Equals(readLineOP.LineUtf8,"DONE",StringComparison.InvariantCultureIgnoreCase)){
                    timer.Dispose();

                    m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","IDLE terminated."));
                    BeginReadCmd();

                    break;
                }                
            }
            
            return false;            
        }
		/// <summary>
		/// Parses header fields from stream. Stream position stays where header reading ends.
		/// </summary>
		/// <param name="stream">Stream from where to parse.</param>
		public void Parse(SmartStream stream)
		{			
			/* Rfc 2822 2.2 Header Fields
				Header fields are lines composed of a field name, followed by a colon
				(":"), followed by a field body, and terminated by CRLF.  A field
				name MUST be composed of printable US-ASCII characters (i.e.,
				characters that have values between 33 and 126, inclusive), except
				colon.  A field body may be composed of any US-ASCII characters,
				except for CR and LF.  However, a field body may contain CRLF when
				used in header "folding" and  "unfolding" as described in section
				2.2.3.  All field bodies MUST conform to the syntax described in
				sections 3 and 4 of this standard. 
				
			   Rfc 2822 2.2.3 Long Header Fields
				The process of moving from this folded multiple-line representation
				of a header field to its single line representation is called
				"unfolding". Unfolding is accomplished by simply removing any CRLF
				that is immediately followed by WSP.  Each header field should be
				treated in its unfolded form for further syntactic and semantic
				evaluation.
				
				Example:
					Subject: aaaaa<CRLF>
					<TAB or SP>aaaaa<CRLF>
			*/

			m_pHeaderFields.Clear();

            SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException);
            stream.ReadLine(args,false);
            if(args.Error != null){
                throw args.Error;
            }
            string line = args.LineUtf8;

			while(line != null){
				// End of header reached
				if(line == ""){
					break;
				}

				// Store current header line and read next. We need to read 1 header line to ahead,
				// because of multiline header fields.
				string headerField = line; 
				stream.ReadLine(args,false);
                if(args.Error != null){
                    throw args.Error;
                }
                line = args.LineUtf8;

				// See if header field is multiline. See comment above.				
				while(line != null && (line.StartsWith("\t") || line.StartsWith(" "))){
					headerField += line;
					stream.ReadLine(args,false);
                    if(args.Error != null){
                        throw args.Error;
                    }
                    line = args.LineUtf8;
				}

				string[] name_value = headerField.Split(new char[]{':'},2);
				// There must be header field name and value, otherwise invalid header field
				if(name_value.Length == 2){
					Add(name_value[0] + ":",name_value[1].Trim());
				}
			}
		}
        /// <summary>
        /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
        /// </summary>
        /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source.</param>
        /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream.</param>
        /// <param name="count">The maximum number of bytes to be read from the current stream.</param>
        /// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>buffer</b> is null reference.</exception>
        /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
        /// <exception cref="NotSupportedException">Is raised when reading not supported.</exception>
        public override int Read(byte[] buffer,int offset,int count)
        {
            if(buffer == null){
                throw new ArgumentNullException("buffer");
            }
            if(offset < 0 || offset > buffer.Length){
                throw new ArgumentException("Invalid argument 'offset' value.");
            }
            if(offset + count > buffer.Length){
                throw new ArgumentException("Invalid argument 'count' value.");
            }
            if((m_AccessMode & FileAccess.Read) == 0){
                throw new NotSupportedException();
            }

            while(true){
                // Read next quoted-printable line and decode it.
                if(m_DecodedOffset >= m_DecodedCount){
                    m_DecodedOffset = 0;
                    m_DecodedCount  = 0;
                    SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.ThrowException);
                    m_pStream.ReadLine(readLineOP,false);
                    // IO error reading line.
                    if(readLineOP.Error != null){
                        throw readLineOP.Error;
                    }
                    // We reached end of stream.
                    else if(readLineOP.BytesInBuffer == 0){
                        return 0;
                    }
                    // Decode quoted-printable line.
                    else{
                        // Process bytes.
                        bool softLineBreak = false;
                        int lineLength     = readLineOP.LineBytesInBuffer;
                        for(int i=0;i<readLineOP.LineBytesInBuffer;i++){
                            byte b = readLineOP.Buffer[i];
                            // We have soft line-break.
                            if(b == '=' && i == (lineLength - 1)){
                                softLineBreak = true;
                            }
                            // We should have =XX char.
                            else if(b == '='){
                                byte b1 = readLineOP.Buffer[++i];
                                byte b2 = readLineOP.Buffer[++i];

                                m_pDecodedBuffer[m_DecodedCount++] = byte.Parse(new string(new char[]{(char)b1,(char)b2}),System.Globalization.NumberStyles.HexNumber);
                            }
                            // Normal char.
                            else{
                                m_pDecodedBuffer[m_DecodedCount++] = b;
                            }
                        }

                        if(!softLineBreak){
                            m_pDecodedBuffer[m_DecodedCount++] = (byte)'\r';
                            m_pDecodedBuffer[m_DecodedCount++] = (byte)'\n';
                        }
                    }
                }

                // We some decoded data, return it.
                if(m_DecodedOffset < m_DecodedCount){
                    int countToCopy = Math.Min(count,m_DecodedCount - m_DecodedOffset);
                    Array.Copy(m_pDecodedBuffer,m_DecodedOffset,buffer,offset,countToCopy);
                    m_DecodedOffset += countToCopy;

                    return countToCopy;
                }
            }
        }
Beispiel #24
0
		/// <summary>
		/// Parses mime entity from stream.
		/// </summary>
		/// <param name="stream">Data stream from where to read data.</param>
		/// <param name="toBoundary">Entity data is readed to specified boundary.</param>
		/// <returns>Returns false if last entity. Returns true for mulipart entity, if there are more entities.</returns>
		internal bool Parse(SmartStream stream,string toBoundary)
		{
			// Clear header fields
			m_pHeader.Clear();
			m_pHeaderFieldCache.Clear();

			// Parse header
			m_pHeader.Parse(stream);

			// Parse entity and child entities if any (Conent-Type: multipart/xxx...)
            
			// Multipart entity
			if((this.ContentType & MediaType_enum.Multipart) != 0){
				// There must be be boundary ID (rfc 1341 7.2.1  The Content-Type field for multipart entities requires one parameter,
                // "boundary", which is used to specify the encapsulation boundary.)
				string boundaryID = this.ContentType_Boundary;
				if(boundaryID == null){
					// This is invalid message, just skip this mime entity
				}
				else{
					// There is one or more mime entities

                    // Find first boundary start position
                    SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[8000],SizeExceededAction.JunkAndThrowException);
                    stream.ReadLine(args,false);
                    if(args.Error != null){
                        throw args.Error;
                    }
                    string lineString = args.LineUtf8;
				
					while(lineString != null){
						if(lineString.StartsWith("--" + boundaryID)){
							break;
						}
						
                        stream.ReadLine(args,false);
                        if(args.Error != null){
                            throw args.Error;
                        }
                        lineString = args.LineUtf8;
					}
					// This is invalid entity, boundary start not found. Skip that entity.
					if(string.IsNullOrEmpty(lineString)){
						return false;
					}
					
					// Start parsing child entities of this entity
					while(true){					
						// Parse and add child entity
						MimeEntity childEntity = new MimeEntity();					
						this.ChildEntities.Add(childEntity);
				
						// This is last entity, stop parsing
						if(childEntity.Parse(stream,boundaryID) == false){
							break;
						}
						// else{
						// There are more entities, parse them
					}
					
					// This entity is child of mulipart entity.
					// All this entity child entities are parsed,
					// we need to move stream position to next entity start.
					if(!string.IsNullOrEmpty(toBoundary)){
                        stream.ReadLine(args,false);
                        if(args.Error != null){
                            throw args.Error;
                        }
                        lineString = args.LineUtf8;

						while(lineString != null){
							if(lineString.StartsWith("--" + toBoundary)){
								break;
							}
							
							stream.ReadLine(args,false);
                            if(args.Error != null){
                                throw args.Error;
                            }
                            lineString = args.LineUtf8;
						}
						
						// Invalid boundary end, there can't be more entities 
						if(string.IsNullOrEmpty(lineString)){
							return false;
						}
					
						// See if last boundary or there is more. Last boundary ends with --
						if(lineString.EndsWith(toBoundary + "--")){
							return false; 
						}
						// else{
						// There are more entities					
						return true;
					}
				}
			}
			// Singlepart entity.
			else{
                // Boundary is specified, read data to specified boundary.
                if(!string.IsNullOrEmpty(toBoundary)){
                    MemoryStream entityData = new MemoryStream();
                    SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException);

                    // Read entity data while get boundary end tag --boundaryID-- or EOS.
                    while(true){                        
                        stream.ReadLine(readLineOP,false);
                        if(readLineOP.Error != null){
                            throw readLineOP.Error;
                        }
                        // End of stream reached. Normally we should get boundary end tag --boundaryID--, but some x mailers won't add it, so
                        // if we reach EOS, consider boundary closed.
                        if(readLineOP.BytesInBuffer == 0){
                            // Just return data what was readed.
                            m_EncodedData = entityData.ToArray();
                            return false;
                        }
                        // We readed a line.
                        else{
                            // We have boundary start/end tag or just "--" at the beginning of line.
                            if(readLineOP.LineBytesInBuffer >= 2 && readLineOP.Buffer[0] == '-' && readLineOP.Buffer[1] == '-'){
                                string lineString = readLineOP.LineUtf8;
                                // We have boundary end tag, no more boundaries.
                                if(lineString == "--" + toBoundary + "--"){
                                    m_EncodedData = entityData.ToArray();
                                    return false;
                                }
                                // We have new boundary start.
                                else if(lineString == "--" + toBoundary){
                                    m_EncodedData = entityData.ToArray();
                                    return true;
                                }
                                else{
                                    // Just skip
                                }
                            }

                            // Write readed line.
                            entityData.Write(readLineOP.Buffer,0,readLineOP.BytesInBuffer);                    
                        }
                    }
                }
				// Boundary isn't specified, read data to the stream end. 
				else{
                    MemoryStream ms = new MemoryStream();
                    stream.ReadAll(ms);
                    m_EncodedData = ms.ToArray();
				}
			}
			
			return false;
		}
Beispiel #25
0
            /// <summary>
            /// Starts operation processing.
            /// </summary>
            /// <param name="owner">Owner SMTP client.</param>
            /// <returns>Returns true if asynchronous operation in progress or false if operation completed synchronously.</returns>
            /// <exception cref="ArgumentNullException">Is raised when <b>owner</b> is null reference.</exception>
            internal bool Start(SMTP_Client owner)
            {
                if(owner == null){
                    throw new ArgumentNullException("owner");
                }

                m_pSmtpClient = owner;

                try{
                    SmartStream.ReadLineAsyncOP op = new SmartStream.ReadLineAsyncOP(new byte[8000],SizeExceededAction.JunkAndThrowException);
                    op.Completed += delegate(object s,EventArgs<SmartStream.ReadLineAsyncOP> e){   
                        try{
                            // Response reading completed.
                            if(!ReadLineCompleted(op)){
                                SetState(AsyncOP_State.Completed);                        
                                OnCompletedAsync();
                            }
                            // Continue response reading.
                            else{
                                while(owner.TcpStream.ReadLine(op,true)){
                                    // Response reading completed.
                                    if(!ReadLineCompleted(op)){
                                        SetState(AsyncOP_State.Completed);                        
                                        OnCompletedAsync();

                                        break;
                                    }
                                }
                            }
                        }
                        catch(Exception x){
                            m_pException = x;
                            SetState(AsyncOP_State.Completed);                        
                            OnCompletedAsync();
                        }
                    };
                    while(owner.TcpStream.ReadLine(op,true)){
                        // Response reading completed.
                        if(!ReadLineCompleted(op)){
                            SetState(AsyncOP_State.Completed);

                            return false;
                        }                        
                    }

                    return true;
                }
                catch(Exception x){
                    m_pException = x;
                    SetState(AsyncOP_State.Completed);

                    return false;
                }                
            }
Beispiel #26
0
            /// <summary>
            /// Is called when POP3 server CAPA response reading has completed.
            /// </summary>
            /// <param name="op">Asynchronous operation.</param>
            private void CapaReadResponseCompleted(SmartStream.ReadLineAsyncOP op)
            {
                try{
                    // Operation failed.
                    if(op.Error != null){
                        m_pException = op.Error;
                        m_pPop3Client.LogAddException("Exception: " + op.Error.Message,op.Error);
                        SetState(AsyncOP_State.Completed);
                    }
                    // Operation succeeded.
                    else{
                        // Log
                        m_pPop3Client.LogAddRead(op.BytesInBuffer,op.LineUtf8);
                                            
                        // Server returned success response.
                        if(string.Equals(op.LineUtf8.Split(new char[]{' '},2)[0],"+OK",StringComparison.InvariantCultureIgnoreCase)){
                            // Read capa-list.
                            SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[8000],SizeExceededAction.JunkAndThrowException);
                            readLineOP.Completed += delegate(object s,EventArgs<SmartStream.ReadLineAsyncOP> e){
                                try{
                                    ReadMultiLineResponseLineCompleted(readLineOP);

                                    // Read response lines while we get terminator(.).
                                    while(this.State == AsyncOP_State.Active && m_pPop3Client.TcpStream.ReadLine(readLineOP,true)){
                                        ReadMultiLineResponseLineCompleted(readLineOP);
                                    }
                                }
                                catch(Exception x){
                                    m_pException = x;
                                    m_pPop3Client.LogAddException("Exception: " + x.Message,x);
                                    SetState(AsyncOP_State.Completed);
                                }
                            };
                            // Read response lines while we get terminator(.).
                            while(this.State == AsyncOP_State.Active && m_pPop3Client.TcpStream.ReadLine(readLineOP,true)){
                                ReadMultiLineResponseLineCompleted(readLineOP);
                            }
                        }
                        // Server returned error response.
                        else{
                            m_pException = new POP3_ClientException(op.LineUtf8);
                            SetState(AsyncOP_State.Completed);
                        }
                    }
                }
                catch(Exception x){
                    m_pException = x;
                    m_pPop3Client.LogAddException("Exception: " + x.Message,x);
                    SetState(AsyncOP_State.Completed);
                }

                op.Dispose();
            }