diff --git a/src/SIPSorcery/SIPSorcery.csproj b/src/SIPSorcery/SIPSorcery.csproj index c45885c8a..b74d46e25 100644 --- a/src/SIPSorcery/SIPSorcery.csproj +++ b/src/SIPSorcery/SIPSorcery.csproj @@ -22,6 +22,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -35,13 +39,17 @@ + + + + netstandard2.0;netstandard2.1;netcoreapp3.1;net462;net5.0;net6.0;net8.0;net9.0;net10.0 - latest + 14.0 true $(NoWarn);SYSLIB0050 True @@ -66,11 +74,12 @@ https://github.com/sipsorcery-org/sipsorcery git master + true SIP WebRTC VoIP RTP SDP STUN ICE SIPSorcery -v10.0.8: Bug fixes. -v10.0.7: Network address change fix for Unity. --v10.0.6: Bug fixes. --v10.0.5: Stable release. Bug fixes. +-v10.0.6: Bug fixes. +-v10.0.5: Stable release. Bug fixes. -v10.0.4-pre: New SRTP and DTLS implementation (huge thanks to @jimm98y). -v10.0.3: Removed null SRTP ciphers. -v10.0.2: Removed use of master key index for SRTP. @@ -94,7 +103,7 @@ -v8.0.0: RTP header extension improvements (thanks to @ChristopheI). Major version to 8 to reflect highest .net runtime supported. en 10.0.8 - 10.0.8 + 10.0.8 10.0.8 diff --git a/src/SIPSorcery/app/Media/Sources/AudioExtrasSource.cs b/src/SIPSorcery/app/Media/Sources/AudioExtrasSource.cs index aa7f51dcf..f07533aa4 100644 --- a/src/SIPSorcery/app/Media/Sources/AudioExtrasSource.cs +++ b/src/SIPSorcery/app/Media/Sources/AudioExtrasSource.cs @@ -159,8 +159,7 @@ public int AudioSamplePeriodMilliseconds { if (value < AUDIO_SAMPLE_PERIOD_MILLISECONDS_MIN || value > AUDIO_SAMPLE_PERIOD_MILLISECONDS_MAX) { - throw new ApplicationException("Invalid value for the audio sample period. Must be between " + - $"{AUDIO_SAMPLE_PERIOD_MILLISECONDS_MIN} and {AUDIO_SAMPLE_PERIOD_MILLISECONDS_MAX}ms."); + throw new ApplicationException($"Invalid value for the audio sample period. Must be between {AUDIO_SAMPLE_PERIOD_MILLISECONDS_MIN} and {AUDIO_SAMPLE_PERIOD_MILLISECONDS_MAX}ms."); } else { diff --git a/src/SIPSorcery/app/SIPPacketMangler.cs b/src/SIPSorcery/app/SIPPacketMangler.cs index 8e2ddf1ba..80e6c57b0 100644 --- a/src/SIPSorcery/app/SIPPacketMangler.cs +++ b/src/SIPSorcery/app/SIPPacketMangler.cs @@ -55,7 +55,7 @@ public static string MangleSDP(string sdpBody, string publicIPAddress, out bool && pubaddr.AddressFamily == AddressFamily.InterNetworkV6 && addr.AddressFamily == AddressFamily.InterNetworkV6) { - string mangledSDP = Regex.Replace(sdpBody, @"c=IN IP6 (?([:a-fA-F0-9]+))", "c=IN IP6" + publicIPAddress, RegexOptions.Singleline); + string mangledSDP = Regex.Replace(sdpBody, @"c=IN IP6 (?([:a-fA-F0-9]+))", $"c=IN IP6{publicIPAddress}", RegexOptions.Singleline); wasMangled = true; return mangledSDP; @@ -65,7 +65,7 @@ public static string MangleSDP(string sdpBody, string publicIPAddress, out bool && addr.AddressFamily == AddressFamily.InterNetwork) { //logger.LogDebug("MangleSDP replacing private " + sdpAddress + " with " + publicIPAddress + "."); - string mangledSDP = Regex.Replace(sdpBody, @"c=IN IP4 (?(\d+\.){3}\d+)", "c=IN IP4 " + publicIPAddress, RegexOptions.Singleline); + string mangledSDP = Regex.Replace(sdpBody, @"c=IN IP4 (?(\d+\.){3}\d+)", $"c=IN IP4 {publicIPAddress}", RegexOptions.Singleline); wasMangled = true; return mangledSDP; diff --git a/src/SIPSorcery/app/SIPUserAgents/SIPCallDescriptor.cs b/src/SIPSorcery/app/SIPUserAgents/SIPCallDescriptor.cs index b10540f13..d610db82a 100644 --- a/src/SIPSorcery/app/SIPUserAgents/SIPCallDescriptor.cs +++ b/src/SIPSorcery/app/SIPUserAgents/SIPCallDescriptor.cs @@ -160,7 +160,7 @@ public class SIPCallDescriptor public SIPCallDescriptor(ISIPAccount toSIPAccount, string uri, string fromHeader, string contentType, string content) { ToSIPAccount = toSIPAccount; - Uri = uri ?? toSIPAccount.SIPUsername + "@" + toSIPAccount.SIPDomain; + Uri = uri ?? $"{toSIPAccount.SIPUsername}@{toSIPAccount.SIPDomain}"; From = fromHeader; ContentType = contentType; Content = content; @@ -291,14 +291,14 @@ public void ParseCallOptions(string options) options = options.Trim('[', ']'); // Parse delay time option. - Match delayCallMatch = Regex.Match(options, DELAY_CALL_OPTION_KEY + @"=(?\d+)"); + Match delayCallMatch = Regex.Match(options, $@"{DELAY_CALL_OPTION_KEY}=(?\d+)"); if (delayCallMatch.Success) { int.TryParse(delayCallMatch.Result("${delaytime}"), out DelaySeconds); } // Parse redirect mode option. - Match redirectModeMatch = Regex.Match(options, REDIRECT_MODE_OPTION_KEY + @"=(?\w)"); + Match redirectModeMatch = Regex.Match(options, $@"{REDIRECT_MODE_OPTION_KEY}=(?\w)"); if (redirectModeMatch.Success) { string redirectMode = redirectModeMatch.Result("${redirectmode}"); @@ -321,42 +321,42 @@ public void ParseCallOptions(string options) } // Parse call duration limit option. - Match callDurationMatch = Regex.Match(options, CALL_DURATION_OPTION_KEY + @"=(?\d+)"); + Match callDurationMatch = Regex.Match(options, $@"{CALL_DURATION_OPTION_KEY}=(?\d+)"); if (callDurationMatch.Success) { int.TryParse(callDurationMatch.Result("${callduration}"), out CallDurationLimit); } // Parse the mangle option. - Match mangleMatch = Regex.Match(options, MANGLE_MODE_OPTION_KEY + @"=(?\w+)"); + Match mangleMatch = Regex.Match(options, $@"{MANGLE_MODE_OPTION_KEY}=(?\w+)"); if (mangleMatch.Success) { bool.TryParse(mangleMatch.Result("${mangle}"), out MangleResponseSDP); } // Parse the From header display name option. - Match fromDisplayNameMatch = Regex.Match(options, FROM_DISPLAY_NAME_KEY + @"=(?.+?)(,|$)"); + Match fromDisplayNameMatch = Regex.Match(options, $@"{FROM_DISPLAY_NAME_KEY}=(?.+?)(,|$)"); if (fromDisplayNameMatch.Success) { FromDisplayName = fromDisplayNameMatch.Result("${displayname}").Trim(); } // Parse the From header URI username option. - Match fromUsernameNameMatch = Regex.Match(options, FROM_USERNAME_KEY + @"=(?.+?)(,|$)"); + Match fromUsernameNameMatch = Regex.Match(options, $@"{FROM_USERNAME_KEY}=(?.+?)(,|$)"); if (fromUsernameNameMatch.Success) { FromURIUsername = fromUsernameNameMatch.Result("${username}").Trim(); } // Parse the From header URI host option. - Match fromURIHostMatch = Regex.Match(options, FROM_HOST_KEY + @"=(?.+?)(,|$)"); + Match fromURIHostMatch = Regex.Match(options, $@"{FROM_HOST_KEY}=(?.+?)(,|$)"); if (fromURIHostMatch.Success) { FromURIHost = fromURIHostMatch.Result("${host}").Trim(); } // Parse the Transfer behaviour option. - Match transferMatch = Regex.Match(options, TRANSFER_MODE_OPTION_KEY + @"=(?.+?)(,|$)"); + Match transferMatch = Regex.Match(options, $@"{TRANSFER_MODE_OPTION_KEY}=(?.+?)(,|$)"); if (transferMatch.Success) { string transferMode = transferMatch.Result("${transfermode}"); @@ -387,28 +387,28 @@ public void ParseCallOptions(string options) } // Parse the request caller details option. - Match callerDetailsMatch = Regex.Match(options, REQUEST_CALLER_DETAILS + @"=(?\w+)"); + Match callerDetailsMatch = Regex.Match(options, $@"{REQUEST_CALLER_DETAILS}=(?\w+)"); if (callerDetailsMatch.Success) { bool.TryParse(callerDetailsMatch.Result("${callerdetails}"), out RequestCallerDetails); } // Parse the accountcode. - Match accountCodeMatch = Regex.Match(options, ACCOUNT_CODE_KEY + @"=(?\w+)"); + Match accountCodeMatch = Regex.Match(options, $@"{ACCOUNT_CODE_KEY}=(?\w+)"); if (accountCodeMatch.Success) { AccountCode = accountCodeMatch.Result("${accountCode}"); } // Parse the rate code. - Match rateCodeMatch = Regex.Match(options, RATE_CODE_KEY + @"=(?\w+)"); + Match rateCodeMatch = Regex.Match(options, $@"{RATE_CODE_KEY}=(?\w+)"); if (rateCodeMatch.Success) { RateCode = rateCodeMatch.Result("${rateCode}"); } // Parse the delayed reinvite option. - Match delayedReinviteMatch = Regex.Match(options, DELAYED_REINVITE_KEY + @"=(?\d+)"); + Match delayedReinviteMatch = Regex.Match(options, $@"{DELAYED_REINVITE_KEY}=(?\d+)"); if (delayedReinviteMatch.Success) { int.TryParse(delayedReinviteMatch.Result("${delayedReinvite}"), out ReinviteDelay); @@ -457,7 +457,14 @@ public static List ParseCustomHeaders(string customHeaders) //string headerName = customHeader.Substring(0, colonIndex).Trim(); //string headerValue = (customHeader.Length > colonIndex) ? customHeader.Substring(colonIndex + 1).Trim() : String.Empty; - if (Regex.Match(customHeader.Trim(), "^(Via|From|Contact|CSeq|Call-ID|Max-Forwards|Content-Length)$", RegexOptions.IgnoreCase).Success) + var trimmedCustomHeader = customHeader.AsSpan().Trim(); + if (trimmedCustomHeader.Equals(SIPHeaders.SIP_HEADER_VIA, StringComparison.OrdinalIgnoreCase) || + trimmedCustomHeader.Equals(SIPHeaders.SIP_HEADER_FROM, StringComparison.OrdinalIgnoreCase) || + trimmedCustomHeader.Equals(SIPHeaders.SIP_HEADER_CONTACT, StringComparison.OrdinalIgnoreCase) || + trimmedCustomHeader.Equals(SIPHeaders.SIP_HEADER_CSEQ, StringComparison.OrdinalIgnoreCase) || + trimmedCustomHeader.Equals(SIPHeaders.SIP_HEADER_CALLID, StringComparison.OrdinalIgnoreCase) || + trimmedCustomHeader.Equals(SIPHeaders.SIP_HEADER_MAXFORWARDS, StringComparison.OrdinalIgnoreCase) || + trimmedCustomHeader.Equals(SIPHeaders.SIP_HEADER_CONTENTLENGTH, StringComparison.OrdinalIgnoreCase)) { logger.LogWarning("ParseCustomHeaders skipping custom header due to an non-permitted string in header name, {CustomHeader}.", customHeader); continue; diff --git a/src/SIPSorcery/app/SIPUserAgents/SIPClientUserAgent.cs b/src/SIPSorcery/app/SIPUserAgents/SIPClientUserAgent.cs index f03060926..ffda6e131 100644 --- a/src/SIPSorcery/app/SIPUserAgents/SIPClientUserAgent.cs +++ b/src/SIPSorcery/app/SIPUserAgents/SIPClientUserAgent.cs @@ -286,7 +286,7 @@ public void Cancel(string reason = null) // If auth header is included inside INVITE request, we re-include them inside CANCEL request if (m_serverTransaction.TransactionRequest.Header.HasAuthenticationHeader) { - string username = (m_sipCallDescriptor.AuthUsername == null || m_sipCallDescriptor.AuthUsername.Trim().Length <= 0 ? m_sipCallDescriptor.Username : m_sipCallDescriptor.AuthUsername); + var username = string.IsNullOrWhiteSpace(m_sipCallDescriptor.AuthUsername) ? m_sipCallDescriptor.Username : m_sipCallDescriptor.AuthUsername; SIPAuthorisationDigest authDigest = m_serverTransaction.TransactionRequest.Header.AuthenticationHeaders.First().SIPDigest; authDigest.SetCredentials(username, m_sipCallDescriptor.Password, m_sipCallDescriptor.Uri, SIPMethodsEnum.CANCEL.ToString()); @@ -332,7 +332,7 @@ public void Hangup(string reason = null) //SIPRequest byeRequest = GetByeRequest(m_serverTransaction.TransactionFinalResponse, m_sipDialogue.RemoteTarget); SIPRequest byeRequest = m_sipDialogue.GetInDialogRequest(SIPMethodsEnum.BYE); byeRequest.SetSendFromHints(m_serverTransaction.TransactionRequest.LocalSIPEndPoint); - + if (!string.IsNullOrEmpty(reason)) { // The REASON header gets pre-appended automatically in the SIPHeader class as "Reason: " when ToString() is called on the SIP Header class. @@ -427,7 +427,7 @@ private Task ServerFinalResponseReceived(SIPEndPoint localSIPEndPoi m_serverAuthAttempts = 1; // Resend INVITE with credentials. - string username = (m_sipCallDescriptor.AuthUsername != null && m_sipCallDescriptor.AuthUsername.Trim().Length > 0) ? m_sipCallDescriptor.AuthUsername : m_sipCallDescriptor.Username; + var username = !string.IsNullOrWhiteSpace(m_sipCallDescriptor.AuthUsername) ? m_sipCallDescriptor.AuthUsername : m_sipCallDescriptor.Username; var authRequest = m_serverTransaction.TransactionRequest.DuplicateAndAuthenticate(sipResponse.Header.AuthenticationHeaders, username, m_sipCallDescriptor.Password); @@ -529,7 +529,7 @@ private Task ByeServerFinalResponseReceived(SIPEndPoint localSIPEnd if (sipResponse.Status == SIPResponseStatusCodesEnum.ProxyAuthenticationRequired || sipResponse.Status == SIPResponseStatusCodesEnum.Unauthorised) { - string username = (m_sipCallDescriptor.AuthUsername == null || m_sipCallDescriptor.AuthUsername.Trim().Length <= 0 ? m_sipCallDescriptor.Username : m_sipCallDescriptor.AuthUsername); + var username = string.IsNullOrWhiteSpace(m_sipCallDescriptor.AuthUsername) ? m_sipCallDescriptor.Username : m_sipCallDescriptor.AuthUsername; var authRequest = transaction.TransactionRequest.DuplicateAndAuthenticate(sipResponse.Header.AuthenticationHeaders, username, m_sipCallDescriptor.Password); @@ -560,8 +560,9 @@ private SIPRequest GetInviteRequest(SIPCallDescriptor sipCallDescriptor, string inviteHeader.CSeqMethod = SIPMethodsEnum.INVITE; inviteHeader.UserAgent = SIPConstants.SipUserAgentVersionString; inviteHeader.Routes = routeSet; - inviteHeader.Supported = SIPExtensionHeaders.REPLACES + ", " + SIPExtensionHeaders.NO_REFER_SUB - + ((PrackSupported == true) ? ", " + SIPExtensionHeaders.PRACK : ""); + inviteHeader.Supported = PrackSupported == true + ? $"{SIPExtensionHeaders.REPLACES}, {SIPExtensionHeaders.NO_REFER_SUB}, {SIPExtensionHeaders.PRACK}" + : $"{SIPExtensionHeaders.REPLACES}, {SIPExtensionHeaders.NO_REFER_SUB}"; inviteRequest.Header = inviteHeader; @@ -587,13 +588,17 @@ private SIPRequest GetInviteRequest(SIPCallDescriptor sipCallDescriptor, string { continue; } - else if (customHeader.Trim().StartsWith(SIPHeaders.SIP_HEADER_USERAGENT)) + + var customHeaderSpan = customHeader.AsSpan().Trim(); + if (customHeaderSpan.StartsWith(SIPHeaders.SIP_HEADER_USERAGENT, StringComparison.Ordinal)) { - inviteRequest.Header.UserAgent = customHeader.Substring(customHeader.IndexOf(":") + 1).Trim(); + inviteRequest.Header.UserAgent = customHeader.Substring(customHeader.IndexOf(':') + 1).Trim(); } - else if (customHeader.Trim().StartsWith(SIPHeaders.SIP_HEADER_TO + ":")) + else if (customHeaderSpan.StartsWith(SIPHeaders.SIP_HEADER_TO, StringComparison.Ordinal) && + customHeaderSpan.Length > SIPHeaders.SIP_HEADER_TO.Length && + customHeaderSpan[SIPHeaders.SIP_HEADER_TO.Length] == ':') { - var customToHeader = SIPUserField.ParseSIPUserField(customHeader.Substring(customHeader.IndexOf(":") + 1).Trim()); + var customToHeader = SIPUserField.ParseSIPUserField(customHeader.Substring(customHeader.IndexOf(':') + 1).Trim()); if (customToHeader != null) { inviteRequest.Header.To.ToUserField = customToHeader; diff --git a/src/SIPSorcery/app/SIPUserAgents/SIPNonInviteClientUserAgent.cs b/src/SIPSorcery/app/SIPUserAgents/SIPNonInviteClientUserAgent.cs index 4626fceac..ca8551896 100644 --- a/src/SIPSorcery/app/SIPUserAgents/SIPNonInviteClientUserAgent.cs +++ b/src/SIPSorcery/app/SIPUserAgents/SIPNonInviteClientUserAgent.cs @@ -159,7 +159,7 @@ private SIPRequest GetRequest(SIPMethodsEnum method) { continue; } - else if (customHeader.Trim().StartsWith(SIPHeaders.SIP_HEADER_USERAGENT)) + else if (customHeader.AsSpan().Trim().StartsWith(SIPHeaders.SIP_HEADER_USERAGENT, StringComparison.Ordinal)) { request.Header.UserAgent = customHeader.Substring(customHeader.IndexOf(":") + 1).Trim(); } diff --git a/src/SIPSorcery/app/SIPUserAgents/SIPNotifierClient.cs b/src/SIPSorcery/app/SIPUserAgents/SIPNotifierClient.cs index fd856d26d..7661689d6 100644 --- a/src/SIPSorcery/app/SIPUserAgents/SIPNotifierClient.cs +++ b/src/SIPSorcery/app/SIPUserAgents/SIPNotifierClient.cs @@ -285,7 +285,7 @@ public void Subscribe(SIPURI subscribeURI, long expiry, SIPEventPackagesEnum sip catch (Exception excp) { logger.LogError(excp, "Exception SIPNotifierClient Subscribe. {ErrorMessage}", excp.Message); - SubscriptionFailed?.Invoke(m_resourceURI, SIPResponseStatusCodesEnum.InternalServerError, "Exception Subscribing. " + excp.Message); + SubscriptionFailed?.Invoke(m_resourceURI, SIPResponseStatusCodesEnum.InternalServerError, $"Exception Subscribing. {excp.Message}"); m_waitForSubscribeResponse.Set(); } } @@ -311,21 +311,21 @@ private Task SubscribeTransactionFinalResponseReceived(SIPEndPoint else if (sipResponse.Status == SIPResponseStatusCodesEnum.Forbidden) { // The subscription is never going to succeed so cancel it. - SubscriptionFailed?.Invoke(m_resourceURI, sipResponse.Status, "A Forbidden response was received on a subscribe attempt to " + m_resourceURI.ToString() + " for user " + m_authUsername + "."); + SubscriptionFailed?.Invoke(m_resourceURI, sipResponse.Status, $"A Forbidden response was received on a subscribe attempt to {m_resourceURI.ToString()} for user {m_authUsername}."); m_exit = true; m_waitForSubscribeResponse.Set(); } else if (sipResponse.Status == SIPResponseStatusCodesEnum.BadEvent) { // The subscription is never going to succeed so cancel it. - SubscriptionFailed?.Invoke(m_resourceURI, sipResponse.Status, "A BadEvent response was received on a subscribe attempt to " + m_resourceURI.ToString() + " for event package " + m_sipEventPackage.ToString() + "."); + SubscriptionFailed?.Invoke(m_resourceURI, sipResponse.Status, $"A BadEvent response was received on a subscribe attempt to {m_resourceURI.ToString()} for event package {m_sipEventPackage.ToString()}."); m_exit = true; m_waitForSubscribeResponse.Set(); } else if (sipResponse.Status == SIPResponseStatusCodesEnum.CallLegTransactionDoesNotExist) { // The notifier server does not have a record for the existing subscription. - SubscriptionFailed?.Invoke(m_resourceURI, sipResponse.Status, "Subscribe failed with response " + sipResponse.StatusCode + " " + sipResponse.ReasonPhrase + "."); + SubscriptionFailed?.Invoke(m_resourceURI, sipResponse.Status, $"Subscribe failed with response {sipResponse.StatusCode} {sipResponse.ReasonPhrase}."); m_waitForSubscribeResponse.Set(); } else if (sipResponse.Status == SIPResponseStatusCodesEnum.ProxyAuthenticationRequired || sipResponse.Status == SIPResponseStatusCodesEnum.Unauthorised) @@ -388,7 +388,7 @@ private Task SubscribeTransactionFinalResponseReceived(SIPEndPoint } else { - SubscriptionFailed?.Invoke(m_resourceURI, sipResponse.Status, "Subscribe failed with response " + sipResponse.StatusCode + " " + sipResponse.ReasonPhrase + "."); + SubscriptionFailed?.Invoke(m_resourceURI, sipResponse.Status, $"Subscribe failed with response {sipResponse.StatusCode} {sipResponse.ReasonPhrase}."); m_waitForSubscribeResponse.Set(); } @@ -397,7 +397,7 @@ private Task SubscribeTransactionFinalResponseReceived(SIPEndPoint catch (Exception excp) { logger.LogError(excp, "Exception SubscribeTransactionFinalResponseReceived. {ErrorMessage}", excp.Message); - SubscriptionFailed?.Invoke(m_resourceURI, SIPResponseStatusCodesEnum.InternalServerError, "Exception processing subscribe response. " + excp.Message); + SubscriptionFailed?.Invoke(m_resourceURI, SIPResponseStatusCodesEnum.InternalServerError, $"Exception processing subscribe response. {excp.Message}"); m_waitForSubscribeResponse.Set(); return Task.FromResult(SocketError.Fault); diff --git a/src/SIPSorcery/app/SIPUserAgents/SIPRegistrationUserAgent.cs b/src/SIPSorcery/app/SIPUserAgents/SIPRegistrationUserAgent.cs index 1bd89e716..469861dca 100644 --- a/src/SIPSorcery/app/SIPUserAgents/SIPRegistrationUserAgent.cs +++ b/src/SIPSorcery/app/SIPUserAgents/SIPRegistrationUserAgent.cs @@ -246,7 +246,7 @@ private void DoRegistration(object state) if (!m_exit && RegistrationTemporaryFailure != null) { - RegistrationTemporaryFailure(m_sipAccountAOR, null, "Registration to " + m_registrarHost + " for " + m_sipAccountAOR.ToString() + " timed out."); + RegistrationTemporaryFailure(m_sipAccountAOR, null, $"Registration to {m_registrarHost} for {m_sipAccountAOR.ToString()} timed out."); } } @@ -414,7 +414,7 @@ private void SendInitialRegister() catch (Exception excp) { logger.LogError(excp, "Exception SendInitialRegister to {RegistrarHost}.", m_registrarHost); - RegistrationFailed?.Invoke(m_sipAccountAOR, null, "Exception SendInitialRegister to " + m_registrarHost + ". " + excp.Message); + RegistrationFailed?.Invoke(m_sipAccountAOR, null, $"Exception SendInitialRegister to {m_registrarHost}. {excp.Message}"); } } @@ -471,7 +471,7 @@ private void ServerResponseReceived(SIPEndPoint localSIPEndPoint, SIPEndPoint re { logger.LogWarning("SIPRegistrationAgent could not resolve {RegistrarHost}.", m_registrarHost); - RegistrationFailed?.Invoke(m_sipAccountAOR, sipResponse, "Could not resolve " + m_registrarHost + "."); + RegistrationFailed?.Invoke(m_sipAccountAOR, sipResponse, $"Could not resolve {m_registrarHost}."); } else { @@ -522,7 +522,7 @@ private void ServerResponseReceived(SIPEndPoint localSIPEndPoint, SIPEndPoint re logger.LogWarning("Registration unequivocal failure with {Status} for {SIPAccountAOR}. No further registration attempts will be made: {Exit}.", sipResponse.Status, m_sipAccountAOR, m_exit); string reasonPhrase = (sipResponse.ReasonPhrase.IsNullOrBlank()) ? sipResponse.Status.ToString() : sipResponse.ReasonPhrase; - RegistrationFailed?.Invoke(m_sipAccountAOR, sipResponse, "Registration failed with " + (int)sipResponse.Status + " " + reasonPhrase + "."); + RegistrationFailed?.Invoke(m_sipAccountAOR, sipResponse, $"Registration failed with {(int)sipResponse.Status} {reasonPhrase}."); m_waitForRegistrationMRE.Set(); } @@ -585,7 +585,7 @@ private void AuthResponseReceived(SIPEndPoint localSIPEndPoint, SIPEndPoint remo logger.LogWarning("Registration unequivocal failure with {Status} for {SipAccountAOR}{Action}.", sipResponse.Status, m_sipAccountAOR, (m_exit ? " ,no further registration attempts will be made" : "")); string reasonPhrase = (sipResponse.ReasonPhrase.IsNullOrBlank()) ? sipResponse.Status.ToString() : sipResponse.ReasonPhrase; - RegistrationFailed?.Invoke(m_sipAccountAOR, sipResponse, "Registration failed with " + (int)sipResponse.Status + " " + reasonPhrase + "."); + RegistrationFailed?.Invoke(m_sipAccountAOR, sipResponse, $"Registration failed with {(int)sipResponse.Status} {reasonPhrase}."); m_waitForRegistrationMRE.Set(); } @@ -596,7 +596,7 @@ private void AuthResponseReceived(SIPEndPoint localSIPEndPoint, SIPEndPoint remo logger.LogWarning("Registration unequivocal failure with {Status} for {SipAccountAOR}{Action}.", sipResponse.Status, m_sipAccountAOR, (m_exit ? " ,no further registration attempts will be made" : "")); string reasonPhrase = (sipResponse.ReasonPhrase.IsNullOrBlank()) ? sipResponse.Status.ToString() : sipResponse.ReasonPhrase; - RegistrationFailed?.Invoke(m_sipAccountAOR, sipResponse, "Registration failed with " + (int)sipResponse.Status + " " + reasonPhrase + "."); + RegistrationFailed?.Invoke(m_sipAccountAOR, sipResponse, $"Registration failed with {(int)sipResponse.Status} {reasonPhrase}."); m_waitForRegistrationMRE.Set(); } @@ -604,7 +604,7 @@ private void AuthResponseReceived(SIPEndPoint localSIPEndPoint, SIPEndPoint remo { logger.LogWarning("Registration failed with {Status} for {SipAccountAOR}.", sipResponse.Status, m_sipAccountAOR); m_isRegistered = false; - RegistrationTemporaryFailure?.Invoke(m_sipAccountAOR, sipResponse, "Registration failed with " + sipResponse.Status + "."); + RegistrationTemporaryFailure?.Invoke(m_sipAccountAOR, sipResponse, $"Registration failed with {sipResponse.Status}."); m_waitForRegistrationMRE.Set(); } } diff --git a/src/SIPSorcery/app/SIPUserAgents/SIPServerUserAgent.cs b/src/SIPSorcery/app/SIPUserAgents/SIPServerUserAgent.cs index 4c2715f46..be8b72252 100644 --- a/src/SIPSorcery/app/SIPUserAgents/SIPServerUserAgent.cs +++ b/src/SIPSorcery/app/SIPUserAgents/SIPServerUserAgent.cs @@ -357,7 +357,7 @@ public void Reject(SIPResponseStatusCodesEnum failureStatus, string reasonPhrase { //UASStateChanged?.Invoke(this, failureStatus, reasonPhrase); - string failureReason = (!reasonPhrase.IsNullOrBlank()) ? " and " + reasonPhrase : null; + string failureReason = (!reasonPhrase.IsNullOrBlank()) ? $" and {reasonPhrase}" : null; logger.LogWarning("UAS call failed with a response status of {FailureStatus}{FailureReason}.", (int)failureStatus, failureReason); SIPResponse failureResponse = SIPResponse.GetResponse(m_uasTransaction.TransactionRequest, failureStatus, reasonPhrase); diff --git a/src/SIPSorcery/app/SIPUserAgents/SIPUserAgent.cs b/src/SIPSorcery/app/SIPUserAgents/SIPUserAgent.cs index 71d640398..22c9aa8b2 100644 --- a/src/SIPSorcery/app/SIPUserAgents/SIPUserAgent.cs +++ b/src/SIPSorcery/app/SIPUserAgents/SIPUserAgent.cs @@ -1400,7 +1400,7 @@ private void SendReInviteRequest(SDP sdp) reinviteRequest.Header.UserAgent = SIPConstants.SipUserAgentVersionString; reinviteRequest.Header.ContentType = m_sdpContentType; reinviteRequest.Body = sdp.ToString(); - reinviteRequest.Header.Supported = SIPExtensionHeaders.REPLACES + ", " + SIPExtensionHeaders.NO_REFER_SUB + ", " + SIPExtensionHeaders.PRACK; + reinviteRequest.Header.Supported = $"{SIPExtensionHeaders.REPLACES}, {SIPExtensionHeaders.NO_REFER_SUB}, {SIPExtensionHeaders.PRACK}"; if (m_uac != null) { diff --git a/src/SIPSorcery/core/SIP/SIPAuthorisationDigest.cs b/src/SIPSorcery/core/SIP/SIPAuthorisationDigest.cs index d16022b27..5b7a3fb05 100644 --- a/src/SIPSorcery/core/SIP/SIPAuthorisationDigest.cs +++ b/src/SIPSorcery/core/SIP/SIPAuthorisationDigest.cs @@ -16,8 +16,8 @@ using System; using System.Security.Cryptography; using System.Text; -using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; +using Polyfills; using SIPSorcery.Sys; namespace SIPSorcery.SIP @@ -85,69 +85,72 @@ public static SIPAuthorisationDigest ParseAuthorisationDigest(SIPAuthorisationHe { SIPAuthorisationDigest authRequest = new SIPAuthorisationDigest(authorisationType); - string noDigestHeader = Regex.Replace(authorisationRequest, $@"^\s*{METHOD}\s*", "", RegexOptions.IgnoreCase); - string[] headerFields = noDigestHeader.Split(','); + ArgumentNullException.ThrowIfNull(authorisationRequest); - if (headerFields != null && headerFields.Length > 0) + var headerFields = authorisationRequest.AsSpan().TrimStart(); + if (headerFields.StartsWith(METHOD, StringComparison.OrdinalIgnoreCase)) { - foreach (string headerField in headerFields) + headerFields = headerFields.Slice(METHOD.Length).TrimStart(); + } + + Span headerKeyValueRange = stackalloc Range[2]; + foreach (var headerFieldRange in headerFields.Split(',')) + { + var headerField = headerFields[headerFieldRange]; + + if (headerField.Split(headerKeyValueRange, '=') == 2) { - int equalsIndex = headerField.IndexOf('='); + var headerName = headerField[headerKeyValueRange[0]].Trim().ToString(); + var headerValue = headerField[headerKeyValueRange[1]].ToString().Trim(m_headerFieldRemoveChars); - if (equalsIndex != -1 && equalsIndex < headerField.Length) + if (string.Equals(headerName, AuthHeaders.AUTH_REALM_KEY, StringComparison.OrdinalIgnoreCase)) + { + authRequest.Realm = headerValue; + } + else if (string.Equals(headerName, AuthHeaders.AUTH_NONCE_KEY, StringComparison.OrdinalIgnoreCase)) + { + authRequest.Nonce = headerValue; + } + else if (string.Equals(headerName, AuthHeaders.AUTH_USERNAME_KEY, StringComparison.OrdinalIgnoreCase)) + { + authRequest.Username = headerValue; + } + else if (string.Equals(headerName, AuthHeaders.AUTH_RESPONSE_KEY, StringComparison.OrdinalIgnoreCase)) + { + authRequest.Response = headerValue; + } + else if (string.Equals(headerName, AuthHeaders.AUTH_URI_KEY, StringComparison.OrdinalIgnoreCase)) + { + authRequest.URI = headerValue; + } + else if (string.Equals(headerName, AuthHeaders.AUTH_CNONCE_KEY, StringComparison.OrdinalIgnoreCase)) + { + authRequest.Cnonce = headerValue; + } + else if (string.Equals(headerName, AuthHeaders.AUTH_NONCECOUNT_KEY, StringComparison.OrdinalIgnoreCase)) + { + Int32.TryParse(headerValue, out authRequest.NonceCount); + } + else if (string.Equals(headerName, AuthHeaders.AUTH_QOP_KEY, StringComparison.OrdinalIgnoreCase)) + { + authRequest.Qop = headerValue.ToLower(); + } + else if (string.Equals(headerName, AuthHeaders.AUTH_OPAQUE_KEY, StringComparison.OrdinalIgnoreCase)) + { + authRequest.Opaque = headerValue; + } + else if (string.Equals(headerName, AuthHeaders.AUTH_ALGORITHM_KEY, StringComparison.OrdinalIgnoreCase)) { - string headerName = headerField.Substring(0, equalsIndex).Trim(); - string headerValue = headerField.Substring(equalsIndex + 1).Trim(m_headerFieldRemoveChars); + //authRequest.Algorithhm = headerValue; - if (Regex.Match(headerName, "^" + AuthHeaders.AUTH_REALM_KEY + "$", RegexOptions.IgnoreCase).Success) - { - authRequest.Realm = headerValue; - } - else if (Regex.Match(headerName, "^" + AuthHeaders.AUTH_NONCE_KEY + "$", RegexOptions.IgnoreCase).Success) - { - authRequest.Nonce = headerValue; - } - else if (Regex.Match(headerName, "^" + AuthHeaders.AUTH_USERNAME_KEY + "$", RegexOptions.IgnoreCase).Success) - { - authRequest.Username = headerValue; - } - else if (Regex.Match(headerName, "^" + AuthHeaders.AUTH_RESPONSE_KEY + "$", RegexOptions.IgnoreCase).Success) - { - authRequest.Response = headerValue; - } - else if (Regex.Match(headerName, "^" + AuthHeaders.AUTH_URI_KEY + "$", RegexOptions.IgnoreCase).Success) - { - authRequest.URI = headerValue; - } - else if (Regex.Match(headerName, "^" + AuthHeaders.AUTH_CNONCE_KEY + "$", RegexOptions.IgnoreCase).Success) + if (Enum.TryParse(headerValue.Replace("-", ""), true, out var alg)) { - authRequest.Cnonce = headerValue; + authRequest.DigestAlgorithm = alg; } - else if (Regex.Match(headerName, "^" + AuthHeaders.AUTH_NONCECOUNT_KEY + "$", RegexOptions.IgnoreCase).Success) + else { - Int32.TryParse(headerValue, out authRequest.NonceCount); - } - else if (Regex.Match(headerName, "^" + AuthHeaders.AUTH_QOP_KEY + "$", RegexOptions.IgnoreCase).Success) - { - authRequest.Qop = headerValue.ToLower(); - } - else if (Regex.Match(headerName, "^" + AuthHeaders.AUTH_OPAQUE_KEY + "$", RegexOptions.IgnoreCase).Success) - { - authRequest.Opaque = headerValue; - } - else if (Regex.Match(headerName, "^" + AuthHeaders.AUTH_ALGORITHM_KEY + "$", RegexOptions.IgnoreCase).Success) - { - //authRequest.Algorithhm = headerValue; - - if (Enum.TryParse(headerValue.Replace("-",""), true, out var alg)) - { - authRequest.DigestAlgorithm = alg; - } - else - { - logger.LogWarning("SIPAuthorisationDigest did not recognised digest algorithm value of {DigestAlgorithms}, defaulting to {DigestAlgorithmsEnumMD5}.", headerValue, DigestAlgorithmsEnum.MD5); - authRequest.DigestAlgorithm = DigestAlgorithmsEnum.MD5; - } + logger.LogWarning("SIPAuthorisationDigest did not recognised digest algorithm value of {DigestAlgorithms}, defaulting to {DigestAlgorithmsEnumMD5}.", headerValue, DigestAlgorithmsEnum.MD5); + authRequest.DigestAlgorithm = DigestAlgorithmsEnum.MD5; } } } @@ -211,7 +214,7 @@ public string GetDigest() NonceCount = (NonceCount != 0) ? NonceCount : NONCE_DEFAULT_COUNT; nonceCountStr = GetPaddedNonceCount(NonceCount); - if (Cnonce == null || Cnonce.Trim().Length == 0) + if (string.IsNullOrWhiteSpace(Cnonce)) { Cnonce = Crypto.GetRandomInt().ToString(); } @@ -256,22 +259,84 @@ public string GetDigest() public override string ToString() { - string authHeader = AuthHeaders.AUTH_DIGEST_KEY + " "; - - authHeader += (Username != null && Username.Trim().Length != 0) ? AuthHeaders.AUTH_USERNAME_KEY + "=\"" + Username + "\"" : null; - authHeader += (authHeader.IndexOf('=') != -1) ? "," + AuthHeaders.AUTH_REALM_KEY + "=\"" + Realm + "\"" : AuthHeaders.AUTH_REALM_KEY + "=\"" + Realm + "\""; - authHeader += (Nonce != null) ? "," + AuthHeaders.AUTH_NONCE_KEY + "=\"" + Nonce + "\"" : null; - authHeader += (URI != null && URI.Trim().Length != 0) ? "," + AuthHeaders.AUTH_URI_KEY + "=\"" + URI + "\"" : null; - authHeader += (Response != null && Response.Length != 0) ? "," + AuthHeaders.AUTH_RESPONSE_KEY + "=\"" + Response + "\"" : null; - authHeader += (Cnonce != null) ? "," + AuthHeaders.AUTH_CNONCE_KEY + "=\"" + Cnonce + "\"" : null; - authHeader += (NonceCount != 0) ? "," + AuthHeaders.AUTH_NONCECOUNT_KEY + "=" + GetPaddedNonceCount(NonceCount) : null; - authHeader += (Qop != null) ? "," + AuthHeaders.AUTH_QOP_KEY + "=" + Qop : null; - authHeader += (Opaque != null) ? "," + AuthHeaders.AUTH_OPAQUE_KEY + "=\"" + Opaque + "\"" : null; + var authHeader = new StringBuilder(); + var hasParameter = false; + + void AppendSeparator() + { + if (hasParameter) + { + authHeader.Append(','); + } + else + { + hasParameter = true; + } + } + + void AppendQuotedParameter(string key, string value) + { + AppendSeparator(); + authHeader.Append(key).Append("=\"").Append(value).Append('"'); + } + + void AppendParameter(string key, string value) + { + AppendSeparator(); + authHeader.Append(key).Append('=').Append(value); + } + + authHeader.Append(AuthHeaders.AUTH_DIGEST_KEY).Append(' '); + + if (!string.IsNullOrWhiteSpace(Username)) + { + AppendQuotedParameter(AuthHeaders.AUTH_USERNAME_KEY, Username); + } + + AppendQuotedParameter(AuthHeaders.AUTH_REALM_KEY, Realm); + + if (Nonce != null) + { + AppendQuotedParameter(AuthHeaders.AUTH_NONCE_KEY, Nonce); + } + + if (!string.IsNullOrWhiteSpace(URI)) + { + AppendQuotedParameter(AuthHeaders.AUTH_URI_KEY, URI); + } + + if (Response != null && Response.Length != 0) + { + AppendQuotedParameter(AuthHeaders.AUTH_RESPONSE_KEY, Response); + } + + if (Cnonce != null) + { + AppendQuotedParameter(AuthHeaders.AUTH_CNONCE_KEY, Cnonce); + } + + if (NonceCount != 0) + { + AppendParameter(AuthHeaders.AUTH_NONCECOUNT_KEY, GetPaddedNonceCount(NonceCount)); + } + + if (Qop != null) + { + AppendParameter(AuthHeaders.AUTH_QOP_KEY, Qop); + } + + if (Opaque != null) + { + AppendQuotedParameter(AuthHeaders.AUTH_OPAQUE_KEY, Opaque); + } string algorithmID = (DigestAlgorithm == DigestAlgorithmsEnum.SHA256) ? SHA256_ALGORITHM_ID : DigestAlgorithm.ToString(); - authHeader += (Response != null) ? "," + AuthHeaders.AUTH_ALGORITHM_KEY + "=" + algorithmID : null; + if (Response != null) + { + AppendParameter(AuthHeaders.AUTH_ALGORITHM_KEY, algorithmID); + } - return authHeader; + return authHeader.ToString(); } public SIPAuthorisationDigest CopyOf() @@ -294,7 +359,7 @@ public void IncrementNonceCount() private string GetPaddedNonceCount(int count) { - return "00000000".Substring(0, 8 - NonceCount.ToString().Length) + count; + return $"{"00000000".Substring(0, 8 - NonceCount.ToString().Length)}{count}"; } } @@ -309,8 +374,7 @@ public static string DigestCalcHA1( string password, DigestAlgorithmsEnum hashAlg = DigestAlgorithmsEnum.MD5) { - string a1 = String.Format("{0}:{1}:{2}", username, realm, password); - return GetHashHex(hashAlg, a1); + return GetHashHex(hashAlg, $"{username}:{realm}:{password}"); } /// @@ -321,9 +385,7 @@ public static string DigestCalcHA2( string uri, DigestAlgorithmsEnum hashAlg = DigestAlgorithmsEnum.MD5) { - string A2 = String.Format("{0}:{1}", method, uri); - - return GetHashHex(hashAlg, A2); + return GetHashHex(hashAlg, $"{method}:{uri}"); } public static string DigestCalcResponse( @@ -352,28 +414,13 @@ public static string DigestCalcResponse( string method, DigestAlgorithmsEnum hashAlg = DigestAlgorithmsEnum.MD5) { - string HA2 = DigestCalcHA2(method, uri, hashAlg); - - string unhashedDigest = null; + var HA2 = DigestCalcHA2(method, uri, hashAlg); if (nonceCount != null && cnonce != null && qop != null) { - unhashedDigest = String.Format("{0}:{1}:{2}:{3}:{4}:{5}", - ha1, - nonce, - nonceCount, - cnonce, - qop, - HA2); - } - else - { - unhashedDigest = String.Format("{0}:{1}:{2}", - ha1, - nonce, - HA2); + return GetHashHex(hashAlg, $"{ha1}:{nonce}:{nonceCount}:{cnonce}:{qop}:{HA2}"); } - return GetHashHex(hashAlg, unhashedDigest); + return GetHashHex(hashAlg, $"{ha1}:{nonce}:{HA2}"); } public static string GetHashHex(DigestAlgorithmsEnum hashAlg, string val) diff --git a/src/SIPSorcery/core/SIP/SIPConstants.cs b/src/SIPSorcery/core/SIP/SIPConstants.cs index 0258b5151..79c3bb696 100644 --- a/src/SIPSorcery/core/SIP/SIPConstants.cs +++ b/src/SIPSorcery/core/SIP/SIPConstants.cs @@ -17,6 +17,7 @@ using System.Collections.Generic; using System.Reflection; using System.Text; +using Polyfills; using SIPSorcery.Sys; // ReSharper disable InconsistentNaming @@ -37,10 +38,9 @@ public static class SIPConstants /// The maximum size supported for an incoming SIP message. /// /// - /// From https://tools.ietf.org/html/rfc3261#section-18.1.1: - /// However, implementations MUST be able to handle messages up to the maximum - /// datagram packet size.For UDP, this size is 65,535 bytes, including - /// IP and UDP headers. + /// From https://tools.ietf.org/html/rfc3261#section-18.1.1: However, implementations MUST be able to handle + /// messages up to the maximum datagram packet size.For UDP, this size is 65,535 bytes, including IP and UDP + /// headers. /// public const int SIP_MAXIMUM_RECEIVE_LENGTH = 65535; @@ -84,7 +84,7 @@ public static string SipUserAgentVersionString public static Encoding DEFAULT_ENCODING = Encoding.UTF8; /// - /// Gets the default SIP port for the protocol. + /// Gets the default SIP port for the protocol. /// /// The transport layer protocol to get the port for. /// The default port to use. @@ -118,26 +118,26 @@ public enum SIPMessageTypesEnum public static class SIPTimings { /// - /// Value of the SIP defined timer T1 in milliseconds and is the time for the first retransmit. - /// Should not need to be adjusted in normal circumstances. + /// Value of the SIP defined timer T1 in milliseconds and is the time for the first retransmit. Should not need + /// to be adjusted in normal circumstances. /// public static int T1 = 500; /// - /// Value of the SIP defined timer T2 in milliseconds and is the maximum time between retransmits. - /// Should not need to be adjusted in normal circumstances. + /// Value of the SIP defined timer T2 in milliseconds and is the maximum time between retransmits. Should not + /// need to be adjusted in normal circumstances. /// public static int T2 = 4000; /// - /// Value of the SIP defined timer T6 in milliseconds and is the period after which a transaction - /// has timed out. Should not need to be adjusted in normal circumstances. + /// Value of the SIP defined timer T6 in milliseconds and is the period after which a transaction has timed out. + /// Should not need to be adjusted in normal circumstances. /// public static int T6 = 64 * T1; /// - /// The number of milliseconds a transaction can stay in the proceeding state - /// (i.e. an INVITE will ring for) before the call is given up and timed out. + /// The number of milliseconds a transaction can stay in the proceeding state (i.e. an INVITE will ring for) + /// before the call is given up and timed out. /// public static int MAX_RING_TIME = 180000; } @@ -160,12 +160,11 @@ public static SIPSchemesEnum GetSchemeType(string schemeType) public static bool IsAllowedScheme(string schemeType) { return Enum.TryParse(schemeType, true, out _); - } - } + } + } /// - /// A list of the transport layer protocols that are supported (the network layers - /// supported are IPv4 and IPv6). + /// A list of the transport layer protocols that are supported (the network layers supported are IPv4 and IPv6). /// public enum SIPProtocolsEnum { @@ -173,8 +172,8 @@ public enum SIPProtocolsEnum /// User Datagram Protocol. /// udp = 1, - /// . - /// Transmission Control Protocol + /// + /// . Transmission Control Protocol /// tcp = 2, /// @@ -206,11 +205,11 @@ public static SIPProtocolsEnum GetProtocolTypeFromId(int protocolTypeId) public static bool IsAllowedProtocol(string protocol) { return Enum.TryParse(protocol, true, out _); - } + } /// - /// Returns true for connectionless transport protocols, such as UDP, and false for - /// connection oriented protocols. + /// Returns true for connectionless transport protocols, such as UDP, and false for connection oriented + /// protocols. /// /// The protocol to check. /// True if the protocol is connectionless. @@ -495,22 +494,11 @@ public static class SIPMIMETypes /// /// For SIP URI user portion the reserved characters below need to be escaped. - /// /// - /// + /// /// - /// - /// For SIP URI parameters different characters are unreserved (just to make life difficult). - /// - /// + /// For SIP URI parameters different characters are unreserved (just to make life difficult). + /// /// /// public static class SIPEscape @@ -582,9 +570,9 @@ public static string SIPURIParameterUnescape(string escapedString) } } - /// + /// /// List of SIP extensions to RFC3262. - /// + /// public enum SIPExtensions { None = 0, @@ -596,8 +584,8 @@ public enum SIPExtensions } /// - /// Constants that can be placed in the SIP Supported or Required headers to indicate support or mandate for - /// a particular SIP extension. + /// Constants that can be placed in the SIP Supported or Required headers to indicate support or mandate for a + /// particular SIP extension. /// public static class SIPExtensionHeaders { @@ -608,49 +596,77 @@ public static class SIPExtensionHeaders public const string MULTIPLE_REFER = "multiple-refer"; /// - /// Parses a string containing a list of SIP extensions into a list of extensions that this library - /// understands. + /// Parses a string containing a list of SIP extensions into a list of extensions that this library understands. /// /// The string containing the list of extensions to parse. /// A comma separated list of the unsupported extensions. - /// A list of extensions that were understood and a boolean indicating whether any unknown extensions were present. + /// + /// A list of extensions that were understood and a boolean indicating whether any unknown extensions were + /// present. + /// public static List ParseSIPExtensions(string extensionList, out string unknownExtensions) { List knownExtensions = new List(); unknownExtensions = null; - if (String.IsNullOrEmpty(extensionList) == false) + if (!string.IsNullOrEmpty(extensionList)) { - string[] extensions = extensionList.Trim().Split(','); + var firstUnknown = ReadOnlySpan.Empty; + var unknownSb = default(StringBuilder); + var extensions = extensionList.AsSpan().Trim(); - foreach (string extension in extensions) + foreach (var extensionRange in extensions.Split(',')) { - if (String.IsNullOrEmpty(extension) == false) + var extension = extensions[extensionRange]; + + var trimmedExtension = extension.Trim(); + switch (trimmedExtension) { - string trimmedExtension = extension.Trim().ToLower(); - switch (trimmedExtension) - { - case PRACK: - knownExtensions.Add(SIPExtensions.Prack); - break; - case NO_REFER_SUB: - knownExtensions.Add(SIPExtensions.NoReferSub); - break; - case REPLACES: - knownExtensions.Add(SIPExtensions.Replaces); - break; - case SIPREC: - knownExtensions.Add(SIPExtensions.SipRec); - break; - case MULTIPLE_REFER: - knownExtensions.Add(SIPExtensions.MultipleRefer); + case var x when PRACK.Equals(x, StringComparison.OrdinalIgnoreCase): + knownExtensions.Add(SIPExtensions.Prack); + break; + case var x when NO_REFER_SUB.Equals(x, StringComparison.OrdinalIgnoreCase): + knownExtensions.Add(SIPExtensions.NoReferSub); + break; + case var x when REPLACES.Equals(x, StringComparison.OrdinalIgnoreCase): + knownExtensions.Add(SIPExtensions.Replaces); + break; + case var x when SIPREC.Equals(x, StringComparison.OrdinalIgnoreCase): + knownExtensions.Add(SIPExtensions.SipRec); + break; + case var x when MULTIPLE_REFER.Equals(x, StringComparison.OrdinalIgnoreCase): + knownExtensions.Add(SIPExtensions.MultipleRefer); + break; + default: + if (trimmedExtension.IsEmpty) + { break; - default: - unknownExtensions += (unknownExtensions != null) ? $",{extension.Trim()}" : extension.Trim(); - break; - } + } + + if (unknownSb is not null) + { + unknownSb.Append(',').Append(trimmedExtension); + } + else if (firstUnknown.IsEmpty) + { + firstUnknown = trimmedExtension; + } + else + { + unknownSb = new StringBuilder().Append(firstUnknown).Append(',').Append(trimmedExtension); + } + break; } } + + if (unknownSb is not null) + { + unknownExtensions = unknownSb.ToString(); + } + else if (!firstUnknown.IsEmpty) + { + unknownExtensions = firstUnknown.ToString(); + } } return knownExtensions; diff --git a/src/SIPSorcery/core/SIP/SIPDialogue.cs b/src/SIPSorcery/core/SIP/SIPDialogue.cs index 5b2c2c768..03fe079d1 100644 --- a/src/SIPSorcery/core/SIP/SIPDialogue.cs +++ b/src/SIPSorcery/core/SIP/SIPDialogue.cs @@ -86,14 +86,14 @@ public string DialogueName string dialogueName = "L(??)"; if (LocalUserField != null && !LocalUserField.URI.User.IsNullOrBlank()) { - dialogueName = "L(" + LocalUserField.URI.ToString() + ")"; + dialogueName = $"L({LocalUserField.URI.ToString()})"; } dialogueName += "-"; if (RemoteUserField != null && !RemoteUserField.URI.User.IsNullOrBlank()) { - dialogueName += "R(" + RemoteUserField.URI.ToString() + ")"; + dialogueName += $"R({RemoteUserField.URI.ToString()})"; } else { diff --git a/src/SIPSorcery/core/SIP/SIPEndPoint.cs b/src/SIPSorcery/core/SIP/SIPEndPoint.cs index 6460d26aa..efd9ae5af 100644 --- a/src/SIPSorcery/core/SIP/SIPEndPoint.cs +++ b/src/SIPSorcery/core/SIP/SIPEndPoint.cs @@ -137,11 +137,11 @@ public static SIPEndPoint ParseSIPEndPoint(string sipEndPointStr) return null; } - if (sipEndPointStr.ToLower().StartsWith("udp:") || - sipEndPointStr.ToLower().StartsWith("tcp:") || - sipEndPointStr.ToLower().StartsWith("tls:") || - sipEndPointStr.ToLower().StartsWith("ws:") || - sipEndPointStr.ToLower().StartsWith("wss:")) + if (sipEndPointStr.StartsWith("udp:", StringComparison.OrdinalIgnoreCase) || + sipEndPointStr.StartsWith("tcp:", StringComparison.OrdinalIgnoreCase) || + sipEndPointStr.StartsWith("tls:", StringComparison.OrdinalIgnoreCase) || + sipEndPointStr.StartsWith("ws:", StringComparison.OrdinalIgnoreCase) || + sipEndPointStr.StartsWith("wss:", StringComparison.OrdinalIgnoreCase)) { return ParseSerialisedSIPEndPoint(sipEndPointStr); } @@ -219,7 +219,7 @@ public override string ToString() { if (Address == null) { - return Protocol + ":empty"; + return $"{Protocol}:empty"; } else { diff --git a/src/SIPSorcery/core/SIP/SIPHeader.cs b/src/SIPSorcery/core/SIP/SIPHeader.cs index f1b976356..52aaa924a 100644 --- a/src/SIPSorcery/core/SIP/SIPHeader.cs +++ b/src/SIPSorcery/core/SIP/SIPHeader.cs @@ -19,8 +19,8 @@ using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Text; -using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; +using Polyfills; using SIPSorcery.Sys; namespace SIPSorcery.SIP @@ -128,7 +128,7 @@ public string ContactAddress // This the address placed into the Via { if (ipEndPoint.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6) { - return "[" + ipEndPoint.Address.ToString() + "]"; + return $"[{ipEndPoint.Address}]"; } else { @@ -143,7 +143,7 @@ public string ContactAddress // This the address placed into the Via } else if (Port != 0) { - return Host + ":" + Port; + return $"{Host}:{Port}"; } else { @@ -179,7 +179,7 @@ public string ReceivedFromAddress // This is the socket the request was re } else { - return Host + ":" + ReceivedFromPort; + return $"{Host}:{ReceivedFromPort}"; } } else if (Port != 0) @@ -191,7 +191,7 @@ public string ReceivedFromAddress // This is the socket the request was re } else { - return Host + ":" + Port; + return $"{Host}:{Port}"; } } else @@ -247,7 +247,7 @@ public static SIPViaHeader[] ParseSIPViaHeader(string viaHeaderStr) foreach (string viaHeaderStrItem in viaHeaders) { - if (viaHeaderStrItem == null || viaHeaderStrItem.Trim().Length == 0) + if (string.IsNullOrWhiteSpace(viaHeaderStrItem)) { throw new SIPValidationException(SIPValidationFieldsEnum.ViaHeader, "No Contact address."); } @@ -263,11 +263,13 @@ public static SIPViaHeader[] ParseSIPViaHeader(string viaHeaderStr) } else { - string versionAndTransport = header.Substring(0, firstSpacePosn); - viaHeader.Version = versionAndTransport.Substring(0, versionAndTransport.LastIndexOf('/')); - viaHeader.Transport = SIPProtocolsType.GetProtocolType(versionAndTransport.Substring(versionAndTransport.LastIndexOf('/') + 1)); + var headerSpan = header.AsSpan(); + var versionAndTransport = headerSpan.Slice(0, firstSpacePosn); + var transportDelimiterIndex = versionAndTransport.LastIndexOf('/'); + viaHeader.Version = versionAndTransport.Slice(0, transportDelimiterIndex).ToString(); + viaHeader.Transport = SIPProtocolsType.GetProtocolType(versionAndTransport.Slice(transportDelimiterIndex + 1).ToString()); - string nextField = header.Substring(firstSpacePosn, header.Length - firstSpacePosn).Trim(); + var nextField = headerSpan.Slice(firstSpacePosn).Trim().ToString(); int delimIndex = nextField.IndexOf(';'); string contactAddress = null; @@ -275,7 +277,7 @@ public static SIPViaHeader[] ParseSIPViaHeader(string viaHeaderStr) // Some user agents include branch but have the semi-colon missing, that's easy to cope with by replacing "branch" with ";branch". if (delimIndex == -1 && nextField.Contains(m_branchKey)) { - nextField = nextField.Replace(m_branchKey, ";" + m_branchKey); + nextField = nextField.Replace(m_branchKey, $";{m_branchKey}"); delimIndex = nextField.IndexOf(';'); } @@ -288,11 +290,11 @@ public static SIPViaHeader[] ParseSIPViaHeader(string viaHeaderStr) } else { - contactAddress = nextField.Substring(0, delimIndex).Trim(); + contactAddress = nextField.AsSpan(0, delimIndex).Trim().ToString(); viaHeader.ViaParameters = new SIPParameters(nextField.Substring(delimIndex, nextField.Length - delimIndex), m_paramDelimChar); } - if (contactAddress == null || contactAddress.Trim().Length == 0) + if (string.IsNullOrWhiteSpace(contactAddress)) { // Check that the branch parameter is present, without it the Via header is illegal. //if (!viaHeader.ViaParameters.Has(m_branchKey)) @@ -353,12 +355,9 @@ public static SIPViaHeader[] ParseSIPViaHeader(string viaHeaderStr) } } - public new string ToString() + public override string ToString() { - string sipViaHeader = SIPHeaders.SIP_HEADER_VIA + ": " + this.Version + "/" + this.Transport.ToString().ToUpper() + " " + ContactAddress; - sipViaHeader += (ViaParameters != null && ViaParameters.Count > 0) ? ViaParameters.ToString() : null; - - return sipViaHeader; + return $"{SIPHeaders.SIP_HEADER_VIA}: {this.Version}/{this.Transport.ToString().ToUpper()} {ContactAddress}{((ViaParameters != null && ViaParameters.Count > 0) ? ViaParameters.ToString() : null)}"; } } @@ -408,7 +407,7 @@ public string FromTag get { return FromParameters.Get(PARAMETER_TAG); } set { - if (value != null && value.Trim().Length > 0) + if (!string.IsNullOrWhiteSpace(value)) { FromParameters.Set(PARAMETER_TAG, value); } @@ -477,7 +476,7 @@ public override string ToString() public string FriendlyDescription() { string caller = FromURI.ToAOR(); - caller = (!string.IsNullOrEmpty(FromName)) ? FromName + " " + caller : caller; + caller = (!string.IsNullOrEmpty(FromName)) ? $"{FromName} {caller}" : caller; return caller; } } @@ -516,7 +515,7 @@ public string ToTag get { return ToParameters.Get(PARAMETER_TAG); } set { - if (value != null && value.Trim().Length > 0) + if (!string.IsNullOrWhiteSpace(value)) { ToParameters.Set(PARAMETER_TAG, value); } @@ -680,7 +679,7 @@ public static List ParseContactHeader(string contactHeaderStr) { try { - if (contactHeaderStr == null || contactHeaderStr.Trim().Length == 0) + if (string.IsNullOrWhiteSpace(contactHeaderStr)) { return null; } @@ -728,7 +727,7 @@ public static List ParseContactHeader(string contactHeaderStr) } catch (Exception excp) { - throw new SIPValidationException(SIPValidationFieldsEnum.ContactHeader, "Contact header invalid, parse failed. " + excp.Message); + throw new SIPValidationException(SIPValidationFieldsEnum.ContactHeader, $"Contact header invalid, parse failed. {excp.Message}"); } } @@ -869,7 +868,7 @@ public static SIPAuthenticationHeader ParseSIPAuthenticationHeader(SIPAuthorisat } catch { - throw new ApplicationException("Error parsing SIP authentication header request, " + headerValue); + throw new ApplicationException($"Error parsing SIP authentication header request, {headerValue}"); } } @@ -879,23 +878,23 @@ private static string BuildAuthorisationHeaderName(SIPAuthorisationHeadersEnum a string authHeader = null; if (authorisationHeaderType == SIPAuthorisationHeadersEnum.Authorize) { - authHeader = SIPHeaders.SIP_HEADER_AUTHORIZATION + ": "; + authHeader = $"{SIPHeaders.SIP_HEADER_AUTHORIZATION}: "; } else if (authorisationHeaderType == SIPAuthorisationHeadersEnum.ProxyAuthenticate) { - authHeader = SIPHeaders.SIP_HEADER_PROXYAUTHENTICATION + ": "; + authHeader = $"{SIPHeaders.SIP_HEADER_PROXYAUTHENTICATION}: "; } else if (authorisationHeaderType == SIPAuthorisationHeadersEnum.ProxyAuthorization) { - authHeader = SIPHeaders.SIP_HEADER_PROXYAUTHORIZATION + ": "; + authHeader = $"{SIPHeaders.SIP_HEADER_PROXYAUTHORIZATION}: "; } else if (authorisationHeaderType == SIPAuthorisationHeadersEnum.WWWAuthenticate) { - authHeader = SIPHeaders.SIP_HEADER_WWWAUTHENTICATE + ": "; + authHeader = $"{SIPHeaders.SIP_HEADER_WWWAUTHENTICATE}: "; } else { - authHeader = SIPHeaders.SIP_HEADER_AUTHORIZATION + ": "; + authHeader = $"{SIPHeaders.SIP_HEADER_AUTHORIZATION}: "; } return authHeader; @@ -1127,7 +1126,7 @@ public void PushRoute(string host) public void PushRoute(IPEndPoint socket, SIPSchemesEnum scheme, SIPProtocolsEnum protcol) { - m_sipRoutes.Insert(0, new SIPRoute(scheme + ":" + socket.ToString(), true)); + m_sipRoutes.Insert(0, new SIPRoute($"{scheme}:{socket.ToString()}", true)); } public void AddBottomRoute(SIPRoute route) @@ -1153,7 +1152,8 @@ public void RemoveBottomRoute() if (m_sipRoutes.Count > 0) { m_sipRoutes.RemoveAt(m_sipRoutes.Count - 1); - }; + } + ; } public SIPRouteSet Reversed() @@ -1191,19 +1191,25 @@ public void ReplaceRoute(string origSocket, string replacementSocket) } } - public new string ToString() + public override string ToString() { - string routeStr = null; - if (m_sipRoutes != null && m_sipRoutes.Count > 0) { + var routeStr = new StringBuilder(); for (int routeIndex = 0; routeIndex < m_sipRoutes.Count; routeIndex++) { - routeStr += (routeStr != null) ? "," + m_sipRoutes[routeIndex].ToString() : m_sipRoutes[routeIndex].ToString(); + if (routeIndex > 0) + { + routeStr.Append(','); + } + + routeStr.Append(m_sipRoutes[routeIndex].ToString()); } + + return routeStr.ToString(); } - return routeStr; + return null; } } @@ -1305,19 +1311,20 @@ public void PushViaHeader(SIPViaHeader viaHeader) m_viaHeaders.Insert(0, viaHeader); } - public new string ToString() + public override string ToString() { - string viaStr = null; - if (m_viaHeaders != null && m_viaHeaders.Count > 0) { + var viaStr = new StringBuilder(); for (int viaIndex = 0; viaIndex < m_viaHeaders.Count; viaIndex++) { - viaStr += (m_viaHeaders[viaIndex]).ToString() + m_CRLF; + viaStr.Append(m_viaHeaders[viaIndex].ToString()).Append(m_CRLF); } + + return viaStr.ToString(); } - return viaStr; + return null; } } @@ -1411,7 +1418,7 @@ public static List ParseHeader(string headerStr) } catch { - throw new SIPValidationException(SIPValidationFieldsEnum.Unknown, "One of the SIP SIPMultiUriHeaders was invalid, header: " + headerStr); + throw new SIPValidationException(SIPValidationFieldsEnum.Unknown, $"One of the SIP SIPMultiUriHeaders was invalid, header: {headerStr}"); } } @@ -1428,7 +1435,7 @@ public override string ToString() public string FriendlyDescription() { string caller = URI.ToAOR(); - caller = (!string.IsNullOrEmpty(Name)) ? Name + " " + caller : caller; + caller = (!string.IsNullOrEmpty(Name)) ? $"{Name} {caller}" : caller; return caller; } } @@ -1573,7 +1580,7 @@ private void Initialise(List contact, SIPFromHeader from, SIPT throw new ApplicationException("The To header cannot be empty when creating a new SIP header."); } - if (callId == null || callId.Trim().Length == 0) + if (string.IsNullOrWhiteSpace(callId)) { throw new ApplicationException("The CallId header cannot be empty when creating a new SIP header."); } @@ -1595,13 +1602,71 @@ private void Initialise(List contact, SIPFromHeader from, SIPT public static string[] SplitHeaders(string message) { + static string NormalizeFoldedHeaderLines(string headerBlock) + { + var normalised = default(StringBuilder); + var segmentStart = 0; + var position = 0; + + while (position < headerBlock.Length) + { + if (position + 2 < headerBlock.Length && + headerBlock[position] == '\r' && + headerBlock[position + 1] == '\n' && + char.IsWhiteSpace(headerBlock[position + 2])) + { + normalised ??= new StringBuilder(headerBlock.Length); + normalised.Append(headerBlock, segmentStart, position - segmentStart); + normalised.Append(' '); + + position += 2; + while (position < headerBlock.Length && char.IsWhiteSpace(headerBlock[position])) + { + position++; + } + + segmentStart = position; + continue; + } + + if (position + 1 < headerBlock.Length && + headerBlock[position] == '\r' && + headerBlock[position + 1] == ' ') + { + normalised ??= new StringBuilder(headerBlock.Length); + normalised.Append(headerBlock, segmentStart, position - segmentStart); + normalised.Append(m_CRLF); + + position += 2; + segmentStart = position; + continue; + } + + position++; + } + + if (normalised is null) + { + return headerBlock; + } + + normalised.Append(headerBlock, segmentStart, headerBlock.Length - segmentStart); + return normalised.ToString(); + } + // SIP headers can be extended across lines if the first character of the next line is at least on whitespace character. - message = Regex.Replace(message, m_CRLF + @"\s+", " ", RegexOptions.Singleline); + // Some user agents couldn't get the \r\n bit right; normalise those at the same time. + message = NormalizeFoldedHeaderLines(message); + + var headers = new List(); + var messageSpan = message.AsSpan(); - // Some user agents couldn't get the \r\n bit right. - message = Regex.Replace(message, "\r ", m_CRLF, RegexOptions.Singleline); + foreach (var headerRange in messageSpan.Split(m_CRLF.AsSpan())) + { + headers.Add(messageSpan[headerRange].ToString()); + } - return Regex.Split(message, m_CRLF); + return headers.ToArray(); } public static SIPHeader ParseSIPHeaders(string[] headersCollection) @@ -1626,15 +1691,15 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) string headerValue = null; // If the first character of a line is whitespace it's a continuation of the previous line. - if (headerLine.StartsWith(" ")) + if (headerLine.StartsWith(" ", StringComparison.Ordinal)) { headerName = lastHeader; headerValue = headerLine.Trim(); } else { - headerLine = headerLine.Trim(); - int delimiterIndex = headerLine.IndexOf(SIPConstants.HEADER_DELIMITER_CHAR); + var headerLineSpan = headerLine.AsSpan().Trim(); + var delimiterIndex = headerLineSpan.IndexOf(SIPConstants.HEADER_DELIMITER_CHAR); if (delimiterIndex == -1) { @@ -1642,17 +1707,44 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) continue; } - headerName = headerLine.Substring(0, delimiterIndex).Trim(); - headerValue = headerLine.Substring(delimiterIndex + 1).Trim(); + headerLine = headerLineSpan.ToString(); + headerName = headerLineSpan.Slice(0, delimiterIndex).Trim().ToString(); + headerValue = headerLineSpan.Slice(delimiterIndex + 1).Trim().ToString(); } try { - string headerNameLower = headerName.ToLower(); + static bool TryGetSpaceSeparatedToken(ReadOnlySpan value, int tokenIndex, out ReadOnlySpan token) + { + token = value; + + for (var i = 0; i < tokenIndex; i++) + { + var separatorIndex = token.IndexOf(' '); + if (separatorIndex == -1) + { + token = default; + return false; + } + + token = token.Slice(separatorIndex + 1); + } + + var nextSeparatorIndex = token.IndexOf(' '); + if (nextSeparatorIndex != -1) + { + token = token.Slice(0, nextSeparatorIndex); + } + + return true; + } + + bool IsHeaderName(string knownHeaderName) => + string.Equals(headerName, knownHeaderName, StringComparison.OrdinalIgnoreCase); #region Via - if (headerNameLower == SIPHeaders.SIP_COMPACTHEADER_VIA || - headerNameLower == SIPHeaders.SIP_HEADER_VIA.ToLower()) + if (IsHeaderName(SIPHeaders.SIP_COMPACTHEADER_VIA) || + IsHeaderName(SIPHeaders.SIP_HEADER_VIA)) { //sipHeader.RawVia += headerValue; @@ -1668,32 +1760,32 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) } #endregion #region CallId - else if (headerNameLower == SIPHeaders.SIP_COMPACTHEADER_CALLID || - headerNameLower == SIPHeaders.SIP_HEADER_CALLID.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_COMPACTHEADER_CALLID) || + IsHeaderName(SIPHeaders.SIP_HEADER_CALLID)) { sipHeader.CallId = headerValue; } #endregion #region CSeq - else if (headerNameLower == SIPHeaders.SIP_HEADER_CSEQ.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_CSEQ)) { //sipHeader.RawCSeq += headerValue; - string[] cseqFields = headerValue.Split(' '); - if (cseqFields == null || cseqFields.Length == 0) + var cseqFields = headerValue.AsSpan(); + if (!TryGetSpaceSeparatedToken(cseqFields, 0, out var cseqNumber)) { logger.LogWarning("The " + SIPHeaders.SIP_HEADER_CSEQ + " was empty."); } else { - if (!Int32.TryParse(cseqFields[0], out sipHeader.CSeq)) + if (!int.TryParse(cseqNumber, out sipHeader.CSeq)) { logger.LogWarning(SIPHeaders.SIP_HEADER_CSEQ + " did not contain a valid integer, {HeaderLine}.", headerLine); } - if (cseqFields != null && cseqFields.Length > 1) + if (TryGetSpaceSeparatedToken(cseqFields, 1, out var cseqMethod)) { - sipHeader.CSeqMethod = SIPMethods.GetMethod(cseqFields[1]); + sipHeader.CSeqMethod = SIPMethods.GetMethod(cseqMethod.ToString()); } else { @@ -1703,7 +1795,7 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) } #endregion #region Expires - else if (headerNameLower == SIPHeaders.SIP_HEADER_EXPIRES.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_EXPIRES)) { //sipHeader.RawExpires += headerValue; @@ -1714,7 +1806,7 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) } #endregion #region Min-Expires - else if (headerNameLower == SIPHeaders.SIP_HEADER_MINEXPIRES.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_MINEXPIRES)) { if (!Int64.TryParse(headerValue, out sipHeader.MinExpires)) { @@ -1723,8 +1815,8 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) } #endregion #region Contact - else if (headerNameLower == SIPHeaders.SIP_COMPACTHEADER_CONTACT || - headerNameLower == SIPHeaders.SIP_HEADER_CONTACT.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_COMPACTHEADER_CONTACT) || + IsHeaderName(SIPHeaders.SIP_HEADER_CONTACT)) { List contacts = SIPContactHeader.ParseContactHeader(headerValue); if (contacts != null && contacts.Count > 0) @@ -1734,56 +1826,56 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) } #endregion #region From - else if (headerNameLower == SIPHeaders.SIP_COMPACTHEADER_FROM || - headerNameLower == SIPHeaders.SIP_HEADER_FROM.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_COMPACTHEADER_FROM) || + IsHeaderName(SIPHeaders.SIP_HEADER_FROM)) { //sipHeader.RawFrom = headerValue; sipHeader.From = SIPFromHeader.ParseFromHeader(headerValue); } #endregion #region To - else if (headerNameLower == SIPHeaders.SIP_COMPACTHEADER_TO || - headerNameLower == SIPHeaders.SIP_HEADER_TO.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_COMPACTHEADER_TO) || + IsHeaderName(SIPHeaders.SIP_HEADER_TO)) { //sipHeader.RawTo = headerValue; sipHeader.To = SIPToHeader.ParseToHeader(headerValue); } #endregion #region WWWAuthenticate - else if (headerNameLower == SIPHeaders.SIP_HEADER_WWWAUTHENTICATE.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_WWWAUTHENTICATE)) { //sipHeader.RawAuthentication = headerValue; sipHeader.AuthenticationHeaders.Add(SIPAuthenticationHeader.ParseSIPAuthenticationHeader(SIPAuthorisationHeadersEnum.WWWAuthenticate, headerValue)); } #endregion #region Authorization - else if (headerNameLower == SIPHeaders.SIP_HEADER_AUTHORIZATION.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_AUTHORIZATION)) { //sipHeader.RawAuthentication = headerValue; sipHeader.AuthenticationHeaders.Add(SIPAuthenticationHeader.ParseSIPAuthenticationHeader(SIPAuthorisationHeadersEnum.Authorize, headerValue)); } #endregion #region ProxyAuthentication - else if (headerNameLower == SIPHeaders.SIP_HEADER_PROXYAUTHENTICATION.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_PROXYAUTHENTICATION)) { //sipHeader.RawAuthentication = headerValue; sipHeader.AuthenticationHeaders.Add(SIPAuthenticationHeader.ParseSIPAuthenticationHeader(SIPAuthorisationHeadersEnum.ProxyAuthenticate, headerValue)); } #endregion #region ProxyAuthorization - else if (headerNameLower == SIPHeaders.SIP_HEADER_PROXYAUTHORIZATION.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_PROXYAUTHORIZATION)) { sipHeader.AuthenticationHeaders.Add(SIPAuthenticationHeader.ParseSIPAuthenticationHeader(SIPAuthorisationHeadersEnum.ProxyAuthorization, headerValue)); } #endregion #region UserAgent - else if (headerNameLower == SIPHeaders.SIP_HEADER_USERAGENT.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_USERAGENT)) { sipHeader.UserAgent = headerValue; } #endregion #region MaxForwards - else if (headerNameLower == SIPHeaders.SIP_HEADER_MAXFORWARDS.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_MAXFORWARDS)) { if (!Int32.TryParse(headerValue, out sipHeader.MaxForwards)) { @@ -1792,8 +1884,8 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) } #endregion #region ContentLength - else if (headerNameLower == SIPHeaders.SIP_COMPACTHEADER_CONTENTLENGTH || - headerNameLower == SIPHeaders.SIP_HEADER_CONTENTLENGTH.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_COMPACTHEADER_CONTENTLENGTH) || + IsHeaderName(SIPHeaders.SIP_HEADER_CONTENTLENGTH)) { if (!Int32.TryParse(headerValue, out sipHeader.ContentLength)) { @@ -1802,20 +1894,20 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) } #endregion #region ContentType - else if (headerNameLower == SIPHeaders.SIP_COMPACTHEADER_CONTENTTYPE || - headerNameLower == SIPHeaders.SIP_HEADER_CONTENTTYPE.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_COMPACTHEADER_CONTENTTYPE) || + IsHeaderName(SIPHeaders.SIP_HEADER_CONTENTTYPE)) { sipHeader.ContentType = headerValue; } #endregion #region Accept - else if (headerNameLower == SIPHeaders.SIP_HEADER_ACCEPT.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_ACCEPT)) { sipHeader.Accept = headerValue; } #endregion #region Route - else if (headerNameLower == SIPHeaders.SIP_HEADER_ROUTE.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_ROUTE)) { SIPRouteSet routeSet = SIPRouteSet.ParseSIPRouteSet(headerValue); if (routeSet != null) @@ -1828,7 +1920,7 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) } #endregion #region RecordRoute - else if (headerNameLower == SIPHeaders.SIP_HEADER_RECORDROUTE.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_RECORDROUTE)) { SIPRouteSet recordRouteSet = SIPRouteSet.ParseSIPRouteSet(headerValue); if (recordRouteSet != null) @@ -1841,37 +1933,37 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) } #endregion #region Allow-Events - else if (headerNameLower == SIPHeaders.SIP_HEADER_ALLOW_EVENTS || headerNameLower == SIPHeaders.SIP_COMPACTHEADER_ALLOWEVENTS) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_ALLOW_EVENTS) || IsHeaderName(SIPHeaders.SIP_COMPACTHEADER_ALLOWEVENTS)) { sipHeader.AllowEvents = headerValue; } #endregion #region Event - else if (headerNameLower == SIPHeaders.SIP_HEADER_EVENT.ToLower() || headerNameLower == SIPHeaders.SIP_COMPACTHEADER_EVENT) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_EVENT) || IsHeaderName(SIPHeaders.SIP_COMPACTHEADER_EVENT)) { sipHeader.Event = headerValue; } #endregion #region SubscriptionState. - else if (headerNameLower == SIPHeaders.SIP_HEADER_SUBSCRIPTION_STATE.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_SUBSCRIPTION_STATE)) { sipHeader.SubscriptionState = headerValue; } #endregion #region Timestamp. - else if (headerNameLower == SIPHeaders.SIP_HEADER_TIMESTAMP.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_TIMESTAMP)) { sipHeader.Timestamp = headerValue; } #endregion #region Date. - else if (headerNameLower == SIPHeaders.SIP_HEADER_DATE.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_DATE)) { sipHeader.Date = headerValue; } #endregion #region Refer-Sub. - else if (headerNameLower == SIPHeaders.SIP_HEADER_REFERSUB.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_REFERSUB)) { if (sipHeader.ReferSub == null) { @@ -1884,8 +1976,8 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) } #endregion #region Refer-To. - else if (headerNameLower == SIPHeaders.SIP_HEADER_REFERTO.ToLower() || - headerNameLower == SIPHeaders.SIP_COMPACTHEADER_REFERTO) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_REFERTO) || + IsHeaderName(SIPHeaders.SIP_COMPACTHEADER_REFERTO)) { if (sipHeader.ReferTo == null) { @@ -1898,19 +1990,19 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) } #endregion #region Referred-By. - else if (headerNameLower == SIPHeaders.SIP_HEADER_REFERREDBY.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_REFERREDBY)) { sipHeader.ReferredBy = headerValue; } #endregion #region Replaces. - else if (headerNameLower == SIPHeaders.SIP_HEADER_REPLACES.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_REPLACES)) { sipHeader.Replaces = headerValue; } #endregion #region Require. - else if (headerNameLower == SIPHeaders.SIP_HEADER_REQUIRE.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_REQUIRE)) { sipHeader.Require = headerValue; @@ -1921,32 +2013,32 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) } #endregion #region Reason. - else if (headerNameLower == SIPHeaders.SIP_HEADER_REASON.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_REASON)) { sipHeader.Reason = headerValue; } #endregion #region Proxy-ReceivedFrom. - else if (headerNameLower == SIPHeaders.SIP_HEADER_PROXY_RECEIVEDFROM.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_PROXY_RECEIVEDFROM)) { sipHeader.ProxyReceivedFrom = headerValue; } #endregion #region Proxy-ReceivedOn. - else if (headerNameLower == SIPHeaders.SIP_HEADER_PROXY_RECEIVEDON.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_PROXY_RECEIVEDON)) { sipHeader.ProxyReceivedOn = headerValue; } #endregion #region Proxy-SendFrom. - else if (headerNameLower == SIPHeaders.SIP_HEADER_PROXY_SENDFROM.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_PROXY_SENDFROM)) { sipHeader.ProxySendFrom = headerValue; } #endregion #region Supported - else if (headerNameLower == SIPHeaders.SIP_COMPACTHEADER_SUPPORTED || - headerNameLower == SIPHeaders.SIP_HEADER_SUPPORTED.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_COMPACTHEADER_SUPPORTED) || + IsHeaderName(SIPHeaders.SIP_HEADER_SUPPORTED)) { sipHeader.Supported = headerValue; @@ -1957,149 +2049,149 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) } #endregion #region Authentication-Info - else if (headerNameLower == SIPHeaders.SIP_HEADER_AUTHENTICATIONINFO.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_AUTHENTICATIONINFO)) { sipHeader.AuthenticationInfo = headerValue; } #endregion #region Accept-Encoding - else if (headerNameLower == SIPHeaders.SIP_HEADER_ACCEPTENCODING.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_ACCEPTENCODING)) { sipHeader.AcceptEncoding = headerValue; } #endregion #region Accept-Language - else if (headerNameLower == SIPHeaders.SIP_HEADER_ACCEPTLANGUAGE.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_ACCEPTLANGUAGE)) { sipHeader.AcceptLanguage = headerValue; } #endregion #region Alert-Info - else if (headerNameLower == SIPHeaders.SIP_HEADER_ALERTINFO.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_ALERTINFO)) { sipHeader.AlertInfo = headerValue; } #endregion #region Allow - else if (headerNameLower == SIPHeaders.SIP_HEADER_ALLOW.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_ALLOW)) { sipHeader.Allow = headerValue; } #endregion #region Call-Info - else if (headerNameLower == SIPHeaders.SIP_HEADER_CALLINFO.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_CALLINFO)) { sipHeader.CallInfo = headerValue; } #endregion #region Content-Disposition - else if (headerNameLower == SIPHeaders.SIP_HEADER_CONTENT_DISPOSITION.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_CONTENT_DISPOSITION)) { sipHeader.ContentDisposition = headerValue; } #endregion #region Content-Encoding - else if (headerNameLower == SIPHeaders.SIP_HEADER_CONTENT_ENCODING.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_CONTENT_ENCODING)) { sipHeader.ContentEncoding = headerValue; } #endregion #region Content-Language - else if (headerNameLower == SIPHeaders.SIP_HEADER_CONTENT_LANGUAGE.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_CONTENT_LANGUAGE)) { sipHeader.ContentLanguage = headerValue; } #endregion #region Error-Info - else if (headerNameLower == SIPHeaders.SIP_HEADER_ERROR_INFO.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_ERROR_INFO)) { sipHeader.ErrorInfo = headerValue; } #endregion #region In-Reply-To - else if (headerNameLower == SIPHeaders.SIP_HEADER_IN_REPLY_TO.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_IN_REPLY_TO)) { sipHeader.InReplyTo = headerValue; } #endregion #region MIME-Version - else if (headerNameLower == SIPHeaders.SIP_HEADER_MIME_VERSION.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_MIME_VERSION)) { sipHeader.MIMEVersion = headerValue; } #endregion #region Organization - else if (headerNameLower == SIPHeaders.SIP_HEADER_ORGANIZATION.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_ORGANIZATION)) { sipHeader.Organization = headerValue; } #endregion #region Priority - else if (headerNameLower == SIPHeaders.SIP_HEADER_PRIORITY.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_PRIORITY)) { sipHeader.Priority = headerValue; } #endregion #region Proxy-Require - else if (headerNameLower == SIPHeaders.SIP_HEADER_PROXY_REQUIRE.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_PROXY_REQUIRE)) { sipHeader.ProxyRequire = headerValue; } #endregion #region Reply-To - else if (headerNameLower == SIPHeaders.SIP_HEADER_REPLY_TO.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_REPLY_TO)) { sipHeader.ReplyTo = headerValue; } #endregion #region Retry-After - else if (headerNameLower == SIPHeaders.SIP_HEADER_RETRY_AFTER.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_RETRY_AFTER)) { sipHeader.RetryAfter = headerValue; } #endregion #region Subject - else if (headerNameLower == SIPHeaders.SIP_HEADER_SUBJECT.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_SUBJECT)) { sipHeader.Subject = headerValue; } #endregion #region Unsupported - else if (headerNameLower == SIPHeaders.SIP_HEADER_UNSUPPORTED.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_UNSUPPORTED)) { sipHeader.Unsupported = headerValue; } #endregion #region Warning - else if (headerNameLower == SIPHeaders.SIP_HEADER_WARNING.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_WARNING)) { sipHeader.Warning = headerValue; } #endregion #region ETag - else if (headerNameLower == SIPHeaders.SIP_HEADER_ETAG.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_ETAG)) { sipHeader.ETag = headerValue; } #endregion #region RAck - else if (headerNameLower == SIPHeaders.SIP_HEADER_RELIABLE_ACK.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_RELIABLE_ACK)) { - string[] rackFields = headerValue.Split(' '); - if (rackFields?.Length == 0) + var rackFields = headerValue.AsSpan(); + if (!TryGetSpaceSeparatedToken(rackFields, 0, out var rackRSeq)) { logger.LogWarning("The " + SIPHeaders.SIP_HEADER_RELIABLE_ACK + " was empty."); } else { - if (!Int32.TryParse(rackFields[0], out sipHeader.RAckRSeq)) + if (!int.TryParse(rackRSeq, out sipHeader.RAckRSeq)) { logger.LogWarning(SIPHeaders.SIP_HEADER_RELIABLE_ACK + " did not contain a valid integer for the RSeq being acknowledged, {HeaderLine}", headerLine); } - if (rackFields?.Length > 1) + if (TryGetSpaceSeparatedToken(rackFields, 1, out var rackCSeq)) { - if (!Int32.TryParse(rackFields[1], out sipHeader.RAckCSeq)) + if (!int.TryParse(rackCSeq, out sipHeader.RAckCSeq)) { logger.LogWarning(SIPHeaders.SIP_HEADER_RELIABLE_ACK + " did not contain a valid integer for the CSeq being acknowledged, {HeaderLine}", headerLine); } @@ -2109,9 +2201,9 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) logger.LogWarning("There was no " + SIPHeaders.SIP_HEADER_RELIABLE_ACK + " method, {HeaderLine}", headerLine); } - if (rackFields?.Length > 2) + if (TryGetSpaceSeparatedToken(rackFields, 2, out var rackCSeqMethod)) { - sipHeader.RAckCSeqMethod = SIPMethods.GetMethod(rackFields[2]); + sipHeader.RAckCSeqMethod = SIPMethods.GetMethod(rackCSeqMethod.ToString()); } else { @@ -2121,7 +2213,7 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) } #endregion #region RSeq - else if (headerNameLower == SIPHeaders.SIP_HEADER_RELIABLE_SEQ.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_RELIABLE_SEQ)) { if (!Int32.TryParse(headerValue, out sipHeader.RSeq)) { @@ -2130,25 +2222,25 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) } #endregion #region Server - else if (headerNameLower == SIPHeaders.SIP_HEADER_SERVER.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_SERVER)) { sipHeader.Server = headerValue; } #endregion #region P-Asserted-Indentity - else if (headerNameLower == SIPHeaders.SIP_HEADER_PASSERTED_IDENTITY.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_PASSERTED_IDENTITY)) { sipHeader.PassertedIdentity.AddRange(SIPUriHeader.ParseHeader(headerValue)); } #endregion #region History-Info - else if (headerNameLower == SIPHeaders.SIP_HEADER_HISTORY_INFO.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_HISTORY_INFO)) { sipHeader.HistoryInfo.AddRange(SIPUriHeader.ParseHeader(headerValue)); } #endregion #region Diversion - else if (headerNameLower == SIPHeaders.SIP_HEADER_DIVERSION.ToLower()) + else if (IsHeaderName(SIPHeaders.SIP_HEADER_DIVERSION)) { sipHeader.Diversion.AddRange(SIPUriHeader.ParseHeader(headerValue)); } @@ -2199,35 +2291,49 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) /// Puts the SIP headers together into a string ready for transmission. /// /// String representing the SIP headers. - public new string ToString() + public override string ToString() { try { - StringBuilder headersBuilder = new StringBuilder(); + var headersBuilder = new StringBuilder(); + + void AppendHeader(string headerName, T headerValue) => + headersBuilder.Append($"{headerName}: {headerValue}{m_CRLF}"); headersBuilder.Append(Vias.ToString()); - string cseqField = null; - if (this.CSeq >= 0) + if (To != null) { - cseqField = (this.CSeqMethod != SIPMethodsEnum.NONE) ? this.CSeq + " " + this.CSeqMethod.ToString() : this.CSeq.ToString(); + AppendHeader(SIPHeaders.SIP_HEADER_TO, this.To); } - headersBuilder.Append((To != null) ? SIPHeaders.SIP_HEADER_TO + ": " + this.To.ToString() + m_CRLF : null); - headersBuilder.Append((From != null) ? SIPHeaders.SIP_HEADER_FROM + ": " + this.From.ToString() + m_CRLF : null); - headersBuilder.Append((CallId != null) ? SIPHeaders.SIP_HEADER_CALLID + ": " + this.CallId + m_CRLF : null); - headersBuilder.Append((CSeq >= 0) ? SIPHeaders.SIP_HEADER_CSEQ + ": " + cseqField + m_CRLF : null); + if (From != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_FROM, this.From); + } + + if (CallId != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_CALLID, this.CallId); + } + + if (CSeq >= 0) + { + AppendHeader( + SIPHeaders.SIP_HEADER_CSEQ, + this.CSeqMethod != SIPMethodsEnum.NONE ? $"{this.CSeq} {this.CSeqMethod}" : this.CSeq.ToString()); + } #region Appending Contact header. if (Contact != null && Contact.Count == 1) { - headersBuilder.Append(SIPHeaders.SIP_HEADER_CONTACT + ": " + Contact[0].ToString() + m_CRLF); + AppendHeader(SIPHeaders.SIP_HEADER_CONTACT, Contact[0]); } else if (Contact != null && Contact.Count > 1) { StringBuilder contactsBuilder = new StringBuilder(); - contactsBuilder.Append(SIPHeaders.SIP_HEADER_CONTACT + ": "); + contactsBuilder.Append($"{SIPHeaders.SIP_HEADER_CONTACT}: "); bool firstContact = true; foreach (SIPContactHeader contactHeader in Contact) @@ -2238,29 +2344,76 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) } else { - contactsBuilder.Append("," + contactHeader.ToString()); + contactsBuilder.Append($",{contactHeader}"); } firstContact = false; } - headersBuilder.Append(contactsBuilder.ToString() + m_CRLF); + headersBuilder.Append(contactsBuilder).Append(m_CRLF); } #endregion - headersBuilder.Append((MaxForwards >= 0) ? SIPHeaders.SIP_HEADER_MAXFORWARDS + ": " + this.MaxForwards + m_CRLF : null); - headersBuilder.Append((Routes != null && Routes.Length > 0) ? SIPHeaders.SIP_HEADER_ROUTE + ": " + Routes.ToString() + m_CRLF : null); - headersBuilder.Append((RecordRoutes != null && RecordRoutes.Length > 0) ? SIPHeaders.SIP_HEADER_RECORDROUTE + ": " + RecordRoutes.ToString() + m_CRLF : null); - headersBuilder.Append((UserAgent != null && UserAgent.Trim().Length != 0) ? SIPHeaders.SIP_HEADER_USERAGENT + ": " + this.UserAgent + m_CRLF : null); - headersBuilder.Append((Expires != -1) ? SIPHeaders.SIP_HEADER_EXPIRES + ": " + this.Expires + m_CRLF : null); - headersBuilder.Append((MinExpires != -1) ? SIPHeaders.SIP_HEADER_MINEXPIRES + ": " + this.MinExpires + m_CRLF : null); - headersBuilder.Append((Accept != null) ? SIPHeaders.SIP_HEADER_ACCEPT + ": " + this.Accept + m_CRLF : null); - headersBuilder.Append((AcceptEncoding != null) ? SIPHeaders.SIP_HEADER_ACCEPTENCODING + ": " + this.AcceptEncoding + m_CRLF : null); - headersBuilder.Append((AcceptLanguage != null) ? SIPHeaders.SIP_HEADER_ACCEPTLANGUAGE + ": " + this.AcceptLanguage + m_CRLF : null); - headersBuilder.Append((Allow != null) ? SIPHeaders.SIP_HEADER_ALLOW + ": " + this.Allow + m_CRLF : null); - headersBuilder.Append((AlertInfo != null) ? SIPHeaders.SIP_HEADER_ALERTINFO + ": " + this.AlertInfo + m_CRLF : null); - headersBuilder.Append((AuthenticationInfo != null) ? SIPHeaders.SIP_HEADER_AUTHENTICATIONINFO + ": " + this.AuthenticationInfo + m_CRLF : null); + if (MaxForwards >= 0) + { + AppendHeader(SIPHeaders.SIP_HEADER_MAXFORWARDS, this.MaxForwards); + } + + if (Routes != null && Routes.Length > 0) + { + AppendHeader(SIPHeaders.SIP_HEADER_ROUTE, Routes); + } + + if (RecordRoutes != null && RecordRoutes.Length > 0) + { + AppendHeader(SIPHeaders.SIP_HEADER_RECORDROUTE, RecordRoutes); + } + + if (!string.IsNullOrWhiteSpace(UserAgent)) + { + AppendHeader(SIPHeaders.SIP_HEADER_USERAGENT, this.UserAgent); + } + + if (Expires != -1) + { + AppendHeader(SIPHeaders.SIP_HEADER_EXPIRES, this.Expires); + } + + if (MinExpires != -1) + { + AppendHeader(SIPHeaders.SIP_HEADER_MINEXPIRES, this.MinExpires); + } + + if (Accept != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_ACCEPT, this.Accept); + } + + if (AcceptEncoding != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_ACCEPTENCODING, this.AcceptEncoding); + } + + if (AcceptLanguage != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_ACCEPTLANGUAGE, this.AcceptLanguage); + } + + if (Allow != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_ALLOW, this.Allow); + } + + if (AlertInfo != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_ALERTINFO, this.AlertInfo); + } + + if (AuthenticationInfo != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_AUTHENTICATIONINFO, this.AuthenticationInfo); + } if (AuthenticationHeaders.Count > 0) { @@ -2269,72 +2422,202 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection) var value = authHeader.ToString(); if (value != null) { - headersBuilder.Append(authHeader.ToString() + m_CRLF); + headersBuilder.Append(value).Append(m_CRLF); } } } - headersBuilder.Append((CallInfo != null) ? SIPHeaders.SIP_HEADER_CALLINFO + ": " + this.CallInfo + m_CRLF : null); - headersBuilder.Append((ContentDisposition != null) ? SIPHeaders.SIP_HEADER_CONTENT_DISPOSITION + ": " + this.ContentDisposition + m_CRLF : null); - headersBuilder.Append((ContentEncoding != null) ? SIPHeaders.SIP_HEADER_CONTENT_ENCODING + ": " + this.ContentEncoding + m_CRLF : null); - headersBuilder.Append((ContentLanguage != null) ? SIPHeaders.SIP_HEADER_CONTENT_LANGUAGE + ": " + this.ContentLanguage + m_CRLF : null); - headersBuilder.Append((Date != null) ? SIPHeaders.SIP_HEADER_DATE + ": " + Date + m_CRLF : null); - headersBuilder.Append((ErrorInfo != null) ? SIPHeaders.SIP_HEADER_ERROR_INFO + ": " + this.ErrorInfo + m_CRLF : null); - headersBuilder.Append((InReplyTo != null) ? SIPHeaders.SIP_HEADER_IN_REPLY_TO + ": " + this.InReplyTo + m_CRLF : null); - headersBuilder.Append((Organization != null) ? SIPHeaders.SIP_HEADER_ORGANIZATION + ": " + this.Organization + m_CRLF : null); - headersBuilder.Append((Priority != null) ? SIPHeaders.SIP_HEADER_PRIORITY + ": " + Priority + m_CRLF : null); - headersBuilder.Append((ProxyRequire != null) ? SIPHeaders.SIP_HEADER_PROXY_REQUIRE + ": " + this.ProxyRequire + m_CRLF : null); - headersBuilder.Append((ReplyTo != null) ? SIPHeaders.SIP_HEADER_REPLY_TO + ": " + this.ReplyTo + m_CRLF : null); - headersBuilder.Append((Require != null) ? SIPHeaders.SIP_HEADER_REQUIRE + ": " + Require + m_CRLF : null); - headersBuilder.Append((RetryAfter != null) ? SIPHeaders.SIP_HEADER_RETRY_AFTER + ": " + this.RetryAfter + m_CRLF : null); - headersBuilder.Append((Server != null && Server.Trim().Length != 0) ? SIPHeaders.SIP_HEADER_SERVER + ": " + this.Server + m_CRLF : null); - headersBuilder.Append((Subject != null) ? SIPHeaders.SIP_HEADER_SUBJECT + ": " + Subject + m_CRLF : null); - headersBuilder.Append((Supported != null) ? SIPHeaders.SIP_HEADER_SUPPORTED + ": " + Supported + m_CRLF : null); - headersBuilder.Append((Timestamp != null) ? SIPHeaders.SIP_HEADER_TIMESTAMP + ": " + Timestamp + m_CRLF : null); - headersBuilder.Append((Unsupported != null) ? SIPHeaders.SIP_HEADER_UNSUPPORTED + ": " + Unsupported + m_CRLF : null); - headersBuilder.Append((Warning != null) ? SIPHeaders.SIP_HEADER_WARNING + ": " + Warning + m_CRLF : null); - headersBuilder.Append((ETag != null) ? SIPHeaders.SIP_HEADER_ETAG + ": " + ETag + m_CRLF : null); - headersBuilder.Append(SIPHeaders.SIP_HEADER_CONTENTLENGTH + ": " + this.ContentLength + m_CRLF); - if (this.ContentType != null && this.ContentType.Trim().Length > 0) - { - headersBuilder.Append(SIPHeaders.SIP_HEADER_CONTENTTYPE + ": " + this.ContentType + m_CRLF); + if (CallInfo != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_CALLINFO, this.CallInfo); + } + + if (ContentDisposition != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_CONTENT_DISPOSITION, this.ContentDisposition); + } + + if (ContentEncoding != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_CONTENT_ENCODING, this.ContentEncoding); + } + + if (ContentLanguage != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_CONTENT_LANGUAGE, this.ContentLanguage); + } + + if (Date != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_DATE, Date); + } + + if (ErrorInfo != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_ERROR_INFO, this.ErrorInfo); + } + + if (InReplyTo != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_IN_REPLY_TO, this.InReplyTo); + } + + if (Organization != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_ORGANIZATION, this.Organization); + } + + if (Priority != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_PRIORITY, Priority); + } + + if (ProxyRequire != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_PROXY_REQUIRE, this.ProxyRequire); + } + + if (ReplyTo != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_REPLY_TO, this.ReplyTo); + } + + if (Require != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_REQUIRE, Require); + } + + if (RetryAfter != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_RETRY_AFTER, this.RetryAfter); + } + + if (!string.IsNullOrWhiteSpace(Server)) + { + AppendHeader(SIPHeaders.SIP_HEADER_SERVER, this.Server); + } + + if (Subject != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_SUBJECT, Subject); + } + + if (Supported != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_SUPPORTED, Supported); + } + + if (Timestamp != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_TIMESTAMP, Timestamp); + } + + if (Unsupported != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_UNSUPPORTED, Unsupported); + } + + if (Warning != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_WARNING, Warning); + } + + if (ETag != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_ETAG, ETag); + } + + AppendHeader(SIPHeaders.SIP_HEADER_CONTENTLENGTH, this.ContentLength); + if (!string.IsNullOrWhiteSpace(this.ContentType)) + { + AppendHeader(SIPHeaders.SIP_HEADER_CONTENTTYPE, this.ContentType); } // Non-core SIP headers. - headersBuilder.Append((AllowEvents != null) ? SIPHeaders.SIP_HEADER_ALLOW_EVENTS + ": " + AllowEvents + m_CRLF : null); - headersBuilder.Append((Event != null) ? SIPHeaders.SIP_HEADER_EVENT + ": " + Event + m_CRLF : null); - headersBuilder.Append((SubscriptionState != null) ? SIPHeaders.SIP_HEADER_SUBSCRIPTION_STATE + ": " + SubscriptionState + m_CRLF : null); - headersBuilder.Append((ReferSub != null) ? SIPHeaders.SIP_HEADER_REFERSUB + ": " + ReferSub + m_CRLF : null); - headersBuilder.Append((ReferTo != null) ? SIPHeaders.SIP_HEADER_REFERTO + ": " + ReferTo + m_CRLF : null); - headersBuilder.Append((ReferredBy != null) ? SIPHeaders.SIP_HEADER_REFERREDBY + ": " + ReferredBy + m_CRLF : null); - headersBuilder.Append((Replaces != null) ? SIPHeaders.SIP_HEADER_REPLACES + ": " + Replaces + m_CRLF : null); - headersBuilder.Append((Reason != null) ? SIPHeaders.SIP_HEADER_REASON + ": " + Reason + m_CRLF : null); - headersBuilder.Append((RSeq != -1) ? SIPHeaders.SIP_HEADER_RELIABLE_SEQ + ": " + RSeq + m_CRLF : null); - headersBuilder.Append((RAckRSeq != -1) ? SIPHeaders.SIP_HEADER_RELIABLE_ACK + ": " + RAckRSeq + " " + RAckCSeq + " " + RAckCSeqMethod + m_CRLF : null); + if (AllowEvents != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_ALLOW_EVENTS, AllowEvents); + } + + if (Event != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_EVENT, Event); + } + + if (SubscriptionState != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_SUBSCRIPTION_STATE, SubscriptionState); + } + + if (ReferSub != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_REFERSUB, ReferSub); + } + + if (ReferTo != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_REFERTO, ReferTo); + } + + if (ReferredBy != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_REFERREDBY, ReferredBy); + } + + if (Replaces != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_REPLACES, Replaces); + } + + if (Reason != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_REASON, Reason); + } + + if (RSeq != -1) + { + AppendHeader(SIPHeaders.SIP_HEADER_RELIABLE_SEQ, RSeq); + } + + if (RAckRSeq != -1) + { + AppendHeader(SIPHeaders.SIP_HEADER_RELIABLE_ACK, $"{RAckRSeq} {RAckCSeq} {RAckCSeqMethod}"); + } foreach (var PAI in PassertedIdentity) { - headersBuilder.Append(SIPHeaders.SIP_HEADER_PASSERTED_IDENTITY + ": " + PAI + m_CRLF); + AppendHeader(SIPHeaders.SIP_HEADER_PASSERTED_IDENTITY, PAI); } foreach (var HistInfo in HistoryInfo) { - headersBuilder.Append(SIPHeaders.SIP_HEADER_HISTORY_INFO + ": " + HistInfo + m_CRLF); + AppendHeader(SIPHeaders.SIP_HEADER_HISTORY_INFO, HistInfo); } foreach (var DiversionHeader in Diversion) { - headersBuilder.Append(SIPHeaders.SIP_HEADER_DIVERSION + ": " + DiversionHeader + m_CRLF); + AppendHeader(SIPHeaders.SIP_HEADER_DIVERSION, DiversionHeader); } // Custom SIP headers. - headersBuilder.Append((ProxyReceivedFrom != null) ? SIPHeaders.SIP_HEADER_PROXY_RECEIVEDFROM + ": " + ProxyReceivedFrom + m_CRLF : null); - headersBuilder.Append((ProxyReceivedOn != null) ? SIPHeaders.SIP_HEADER_PROXY_RECEIVEDON + ": " + ProxyReceivedOn + m_CRLF : null); - headersBuilder.Append((ProxySendFrom != null) ? SIPHeaders.SIP_HEADER_PROXY_SENDFROM + ": " + ProxySendFrom + m_CRLF : null); + if (ProxyReceivedFrom != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_PROXY_RECEIVEDFROM, ProxyReceivedFrom); + } + + if (ProxyReceivedOn != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_PROXY_RECEIVEDON, ProxyReceivedOn); + } + + if (ProxySendFrom != null) + { + AppendHeader(SIPHeaders.SIP_HEADER_PROXY_SENDFROM, ProxySendFrom); + } // Unknown SIP headers foreach (string unknownHeader in UnknownHeaders) { - headersBuilder.Append(unknownHeader + m_CRLF); + headersBuilder.Append(unknownHeader).Append(m_CRLF); } return headersBuilder.ToString(); @@ -2356,7 +2639,7 @@ private void Validate() public void SetDateHeader() { - Date = DateTime.UtcNow.ToString("ddd, dd MMM yyyy HH:mm:ss ") + "GMT"; + Date = $"{DateTime.UtcNow.ToString("ddd, dd MMM yyyy HH:mm:ss ")}GMT"; } public void SetDateHeader(bool useLocalTime, string timeFormat) @@ -2395,11 +2678,12 @@ public string GetUnknownHeaderValue(string unknownHeaderName) continue; } - string headerName = trimmedHeader.Substring(0, delimiterIndex).Trim(); + var trimmedHeaderSpan = trimmedHeader.AsSpan(); + var headerName = trimmedHeaderSpan.Slice(0, delimiterIndex).Trim().ToString(); - if (headerName.ToLower() == unknownHeaderName.ToLower()) + if (string.Equals(headerName, unknownHeaderName, StringComparison.OrdinalIgnoreCase)) { - return trimmedHeader.Substring(delimiterIndex + 1).Trim(); + return trimmedHeaderSpan.Slice(delimiterIndex + 1).Trim().ToString(); } } diff --git a/src/SIPSorcery/core/SIP/SIPParameters.cs b/src/SIPSorcery/core/SIP/SIPParameters.cs index 5496f1cac..6924686b4 100644 --- a/src/SIPSorcery/core/SIP/SIPParameters.cs +++ b/src/SIPSorcery/core/SIP/SIPParameters.cs @@ -19,7 +19,9 @@ using System.Linq; using System.Net; using System.Runtime.Serialization; +using System.Text; using Microsoft.Extensions.Logging; +using Polyfills; using SIPSorcery.Sys; namespace SIPSorcery.SIP @@ -85,14 +87,125 @@ private void Initialise(string sipString, char delimiter) { TagDelimiter = delimiter; - string[] keyValuePairs = GetKeyValuePairsFromQuoted(sipString, delimiter); + void AddKeyValuePair(ReadOnlySpan keyValuePair, Span keyValueRange) + { + if (keyValuePair.Trim().Length > 0) + { + if (keyValuePair.Split(keyValueRange, TAG_NAME_VALUE_SEPERATOR) == 2) + { + var keyName = keyValuePair[keyValueRange[0]].Trim().ToString(); - if (keyValuePairs != null && keyValuePairs.Length > 0) + // If this is not the parameter that is being removed put it back on. + if (!m_dictionary.ContainsKey(keyName)) + { + m_dictionary.TryAdd(keyName, keyValuePair[keyValueRange[1]].Trim().ToString()); + } + } + else + { + // Keys with no values are valid in SIP so they get added to the collection with a null value. + var keyName = keyValuePair.ToString(); + if (!m_dictionary.ContainsKey(keyName)) + { + m_dictionary.TryAdd(keyName, null); + } + } + } + } + + static int IndexOfDelimiterOrQuote(ReadOnlySpan value, int startIndex, char delimiter) { - foreach (string keyValuePair in keyValuePairs) + var remaining = value.Slice(startIndex); + var delimiterIndex = remaining.IndexOf(delimiter); + var quoteIndex = remaining.IndexOf(QUOTE); + + if (delimiterIndex == -1) + { + return quoteIndex == -1 ? -1 : startIndex + quoteIndex; + } + + if (quoteIndex == -1) { - AddKeyValuePair(keyValuePair, m_dictionary); + return startIndex + delimiterIndex; } + + return startIndex + Math.Min(delimiterIndex, quoteIndex); + } + + var sipParameters = sipString.AsSpan(); + if (sipParameters.Trim().Length == 0) + { + return; + } + + Span keyValueRange = stackalloc Range[2]; + if (sipParameters.IndexOf(delimiter) == -1) + { + AddKeyValuePair(sipParameters, keyValueRange); + return; + } + + var startParameterPosn = 0; + var inParameterPosn = 0; + var inQuotedStr = false; + + while (inParameterPosn != -1 && inParameterPosn < sipParameters.Length) + { + inParameterPosn = IndexOfDelimiterOrQuote(sipParameters, inParameterPosn, delimiter); + + // Determine if the delimiter position represents the end of the parameter or is in a quoted string. + if (inParameterPosn != -1) + { + if (inParameterPosn <= startParameterPosn && sipParameters[inParameterPosn] == delimiter) + { + // Initial or doubled up Parameter delimiter character, ignore and move on. + inQuotedStr = false; + inParameterPosn++; + startParameterPosn = inParameterPosn; + } + else if (sipParameters[inParameterPosn] == QUOTE) + { + if (inQuotedStr && inParameterPosn > 0 && sipParameters[inParameterPosn - 1] != BACK_SLASH) + { + // If in a quoted string and this quote has not been escaped close the quoted string. + inQuotedStr = false; + } + else if (inQuotedStr && inParameterPosn > 0 && sipParameters[inParameterPosn - 1] == BACK_SLASH) + { + // Do nothing, quote has been escaped in a quoted string. + } + else if (!inQuotedStr) + { + // Start quoted string. + inQuotedStr = true; + } + + inParameterPosn++; + } + else + { + if (!inQuotedStr) + { + // Parameter delimiter found and not in quoted string therefore this is a parameter separator. + AddKeyValuePair(sipParameters.Slice(startParameterPosn, inParameterPosn - startParameterPosn), keyValueRange); + + inParameterPosn++; + startParameterPosn = inParameterPosn; + } + else + { + // Do nothing, separator character is within a quoted string. + inParameterPosn++; + } + } + } + } + + // Add the last parameter. + if (startParameterPosn < sipParameters.Length) + { + // Parameter delimiter found and not in quoted string therefore this is a parameter separator. + AddKeyValuePair(sipParameters.Slice(startParameterPosn), keyValueRange); } } @@ -102,7 +215,7 @@ public static string[] GetKeyValuePairsFromQuoted(string quotedString, char deli { List keyValuePairList = new List(); - if (quotedString == null || quotedString.Trim().Length == 0) + if (string.IsNullOrWhiteSpace(quotedString)) { return null; } @@ -112,13 +225,32 @@ public static string[] GetKeyValuePairsFromQuoted(string quotedString, char deli } else { + static int IndexOfDelimiterOrQuote(ReadOnlySpan value, int startIndex, char delimiter) + { + var remaining = value.Slice(startIndex); + var delimiterIndex = remaining.IndexOf(delimiter); + var quoteIndex = remaining.IndexOf(QUOTE); + + if (delimiterIndex == -1) + { + return quoteIndex == -1 ? -1 : startIndex + quoteIndex; + } + + if (quoteIndex == -1) + { + return startIndex + delimiterIndex; + } + + return startIndex + Math.Min(delimiterIndex, quoteIndex); + } + int startParameterPosn = 0; int inParameterPosn = 0; bool inQuotedStr = false; while (inParameterPosn != -1 && inParameterPosn < quotedString.Length) { - inParameterPosn = quotedString.IndexOfAny(new char[] { delimiter, QUOTE }, inParameterPosn); + inParameterPosn = IndexOfDelimiterOrQuote(quotedString.AsSpan(), inParameterPosn, delimiter); // Determine if the delimiter position represents the end of the parameter or is in a quoted string. if (inParameterPosn != -1) @@ -187,32 +319,6 @@ public static string[] GetKeyValuePairsFromQuoted(string quotedString, char deli } } - private void AddKeyValuePair(string keyValuePair, ConcurrentDictionary dictionary) - { - if (keyValuePair != null && keyValuePair.Trim().Length > 0) - { - int seperatorPosn = keyValuePair.IndexOf(TAG_NAME_VALUE_SEPERATOR); - if (seperatorPosn != -1) - { - string keyName = keyValuePair.Substring(0, seperatorPosn).Trim(); - - // If this is not the parameter that is being removed put it back on. - if (!dictionary.ContainsKey(keyName)) - { - dictionary.TryAdd(keyName, keyValuePair.Substring(seperatorPosn + 1).Trim()); - } - } - else - { - // Keys with no values are valid in SIP so they get added to the collection with a null value. - if (!dictionary.ContainsKey(keyValuePair)) - { - dictionary.TryAdd(keyValuePair, null); - } - } - } - } - public void Set(string name, string value) { if (m_dictionary.ContainsKey(name)) @@ -290,24 +396,23 @@ public string[] GetKeys() public override string ToString() { - string paramStr = null; + var paramStr = default(StringBuilder); if (m_dictionary != null) { foreach (KeyValuePair param in m_dictionary) { - if (param.Value != null && param.Value.Trim().Length > 0) - { - paramStr += TagDelimiter + param.Key + TAG_NAME_VALUE_SEPERATOR + SIPEscape.SIPURIParameterEscape(param.Value); - } - else + paramStr ??= new StringBuilder(); + paramStr.Append(TagDelimiter).Append(param.Key); + + if (!string.IsNullOrWhiteSpace(param.Value)) { - paramStr += TagDelimiter + param.Key; + paramStr.Append(TAG_NAME_VALUE_SEPERATOR).Append(SIPEscape.SIPURIParameterEscape(param.Value)); } } } - return paramStr; + return paramStr?.ToString(); } public override int GetHashCode() diff --git a/src/SIPSorcery/core/SIP/SIPRequest.cs b/src/SIPSorcery/core/SIP/SIPRequest.cs index 2c63bbaeb..16d3663f6 100644 --- a/src/SIPSorcery/core/SIP/SIPRequest.cs +++ b/src/SIPSorcery/core/SIP/SIPRequest.cs @@ -47,11 +47,11 @@ public string StatusLine get { string methodStr = (Method != SIPMethodsEnum.UNKNOWN) ? Method.ToString() : UnknownMethod; - return methodStr + " " + URI.ToString() + " " + SIPVersion; + return $"{methodStr} {URI.ToString()} {SIPVersion}"; } } - private SIPRequest(Encoding sipEncoding, Encoding sipBodyEncoding) : this(SIPMethodsEnum.NONE, SIPURI.None,sipEncoding,sipBodyEncoding) + private SIPRequest(Encoding sipEncoding, Encoding sipBodyEncoding) : this(SIPMethodsEnum.NONE, SIPURI.None, sipEncoding, sipBodyEncoding) { } @@ -59,18 +59,18 @@ private SIPRequest() { } - public SIPRequest(SIPMethodsEnum method, string uri):this(method, SIPURI.ParseSIPURI(uri)) + public SIPRequest(SIPMethodsEnum method, string uri) : this(method, SIPURI.ParseSIPURI(uri)) { } - public SIPRequest(SIPMethodsEnum method, SIPURI uri) + public SIPRequest(SIPMethodsEnum method, SIPURI uri) { Method = method; URI = uri; SIPVersion = m_sipFullVersion; } - public SIPRequest(SIPMethodsEnum method, SIPURI uri,Encoding sipEncoding,Encoding sipBodyEncoding):base(sipEncoding,sipBodyEncoding) + public SIPRequest(SIPMethodsEnum method, SIPURI uri, Encoding sipEncoding, Encoding sipBodyEncoding) : base(sipEncoding, sipBodyEncoding) { Method = method; URI = uri; @@ -80,19 +80,17 @@ public SIPRequest(SIPMethodsEnum method, SIPURI uri,Encoding sipEncoding,Encodin public static SIPRequest ParseSIPRequest(SIPMessageBuffer sipMessage) => ParseSIPRequest(sipMessage, SIPConstants.DEFAULT_ENCODING, SIPConstants.DEFAULT_ENCODING); - public static SIPRequest ParseSIPRequest(SIPMessageBuffer sipMessage,Encoding sipEncoding,Encoding sipBodyEncoding) + public static SIPRequest ParseSIPRequest(SIPMessageBuffer sipMessage, Encoding sipEncoding, Encoding sipBodyEncoding) { try { - SIPRequest sipRequest = new SIPRequest(sipEncoding,sipBodyEncoding); + SIPRequest sipRequest = new SIPRequest(sipEncoding, sipBodyEncoding); sipRequest.LocalSIPEndPoint = sipMessage.LocalSIPEndPoint; sipRequest.RemoteSIPEndPoint = sipMessage.RemoteSIPEndPoint; - string statusLine = sipMessage.FirstLine; - - int firstSpacePosn = statusLine.IndexOf(" "); - - string method = statusLine.Substring(0, firstSpacePosn).Trim(); + var statusLine = sipMessage.FirstLine.AsSpan(); + var firstSpacePosn = statusLine.IndexOf(' '); + var method = statusLine.Slice(0, firstSpacePosn).Trim().ToString(); sipRequest.Method = SIPMethods.GetMethod(method); if (sipRequest.Method == SIPMethodsEnum.UNKNOWN) { @@ -100,15 +98,15 @@ public static SIPRequest ParseSIPRequest(SIPMessageBuffer sipMessage,Encoding si logger.LogWarning("Unknown SIP method received {UnknownMethod}.", sipRequest.UnknownMethod); } - statusLine = statusLine.Substring(firstSpacePosn).Trim(); - int secondSpacePosn = statusLine.IndexOf(" "); + statusLine = statusLine.Slice(firstSpacePosn).Trim(); + var secondSpacePosn = statusLine.IndexOf(' '); if (secondSpacePosn != -1) { - string uriStr = statusLine.Substring(0, secondSpacePosn); + var uriStr = statusLine.Slice(0, secondSpacePosn).ToString(); sipRequest.URI = SIPURI.ParseSIPURI(uriStr); - sipRequest.SIPVersion = statusLine.Substring(secondSpacePosn, statusLine.Length - secondSpacePosn).Trim(); + sipRequest.SIPVersion = statusLine.Slice(secondSpacePosn).Trim().ToString(); sipRequest.Header = SIPHeader.ParseSIPHeaders(sipMessage.SIPHeaders); sipRequest.BodyBuffer = sipMessage.Body; @@ -133,12 +131,12 @@ public static SIPRequest ParseSIPRequest(SIPMessageBuffer sipMessage,Encoding si public static SIPRequest ParseSIPRequest(string sipMessageStr) => ParseSIPRequest(sipMessageStr, SIPConstants.DEFAULT_ENCODING, SIPConstants.DEFAULT_ENCODING); - public static SIPRequest ParseSIPRequest(string sipMessageStr,Encoding sipEncoding,Encoding sipBodyEncoding) + public static SIPRequest ParseSIPRequest(string sipMessageStr, Encoding sipEncoding, Encoding sipBodyEncoding) { try { - SIPMessageBuffer sipMessageBuffer = SIPMessageBuffer.ParseSIPMessage(sipMessageStr, sipEncoding,sipBodyEncoding, null, null); - return SIPRequest.ParseSIPRequest(sipMessageBuffer,sipEncoding,sipBodyEncoding); + SIPMessageBuffer sipMessageBuffer = SIPMessageBuffer.ParseSIPMessage(sipMessageStr, sipEncoding, sipBodyEncoding, null, null); + return SIPRequest.ParseSIPRequest(sipMessageBuffer, sipEncoding, sipBodyEncoding); } catch (SIPValidationException) { @@ -370,7 +368,7 @@ public SIPRequest DuplicateAndAuthenticate(List authent var md5AuthHeader = SIPAuthChallenge.GetAuthenticationHeader(authenticationChallenges, this.URI, this.Method, username, password); dupRequest.Header.AuthenticationHeaders.Add(md5AuthHeader); } - + return dupRequest; } } diff --git a/src/SIPSorcery/core/SIP/SIPResponse.cs b/src/SIPSorcery/core/SIP/SIPResponse.cs index cc60a0af1..14be4b894 100644 --- a/src/SIPSorcery/core/SIP/SIPResponse.cs +++ b/src/SIPSorcery/core/SIP/SIPResponse.cs @@ -65,14 +65,14 @@ public bool IsSuccessStatusCode /// public string ShortDescription { - get { return Header?.CSeqMethod + " " + StatusCode + " " + ReasonPhrase; } + get { return $"{Header?.CSeqMethod} {StatusCode} {ReasonPhrase}"; } } private SIPResponse( Encoding sipEncoding, - Encoding sipBodyEncoding) : this(SIPResponseStatusCodesEnum.None, string.Empty,sipEncoding,sipBodyEncoding) + Encoding sipBodyEncoding) : this(SIPResponseStatusCodesEnum.None, string.Empty, sipEncoding, sipBodyEncoding) { } - private SIPResponse():this(SIPResponseStatusCodesEnum.None,string.Empty) + private SIPResponse() : this(SIPResponseStatusCodesEnum.None, string.Empty) { } public SIPResponse( @@ -97,7 +97,7 @@ public SIPResponse( SIPResponseStatusCodesEnum responseStatus, string reasonPhrase, Encoding sipEncoding, - Encoding sipBodyEncoding):base(sipEncoding,sipBodyEncoding) + Encoding sipBodyEncoding) : base(sipEncoding, sipBodyEncoding) { SIPVersion = m_sipFullVersion; StatusCode = (int)responseStatus; @@ -116,22 +116,21 @@ public static SIPResponse ParseSIPResponse(SIPMessageBuffer sipMessageBuffer) => /// /// /// A new SIP response object. - public static SIPResponse ParseSIPResponse(SIPMessageBuffer sipMessageBuffer,Encoding sipEncoding,Encoding sipBodyEncoding) + public static SIPResponse ParseSIPResponse(SIPMessageBuffer sipMessageBuffer, Encoding sipEncoding, Encoding sipBodyEncoding) { try { - SIPResponse sipResponse = new SIPResponse(sipEncoding,sipBodyEncoding); + SIPResponse sipResponse = new SIPResponse(sipEncoding, sipBodyEncoding); sipResponse.LocalSIPEndPoint = sipMessageBuffer.LocalSIPEndPoint; sipResponse.RemoteSIPEndPoint = sipMessageBuffer.RemoteSIPEndPoint; - string statusLine = sipMessageBuffer.FirstLine; + var statusLine = sipMessageBuffer.FirstLine.AsSpan(); + var firstSpacePosn = statusLine.IndexOf(' '); - int firstSpacePosn = statusLine.IndexOf(" "); - - sipResponse.SIPVersion = statusLine.Substring(0, firstSpacePosn).Trim(); - statusLine = statusLine.Substring(firstSpacePosn).Trim(); - sipResponse.StatusCode = Convert.ToInt32(statusLine.Substring(0, 3)); + sipResponse.SIPVersion = statusLine.Slice(0, firstSpacePosn).Trim().ToString(); + statusLine = statusLine.Slice(firstSpacePosn).Trim(); + sipResponse.StatusCode = Convert.ToInt32(statusLine.Slice(0, 3).ToString()); sipResponse.Status = SIPResponseStatusCodes.GetStatusTypeForCode(sipResponse.StatusCode); - sipResponse.ReasonPhrase = statusLine.Substring(3).Trim(); + sipResponse.ReasonPhrase = statusLine.Slice(3).Trim().ToString(); sipResponse.Header = SIPHeader.ParseSIPHeaders(sipMessageBuffer.SIPHeaders); sipResponse.BodyBuffer = sipMessageBuffer.Body; @@ -144,7 +143,7 @@ public static SIPResponse ParseSIPResponse(SIPMessageBuffer sipMessageBuffer,Enc } catch (Exception excp) { - logger.LogError(excp, "Exception ParseSIPResponse: {SipMessage}. {ErrorMessage}", sipMessageBuffer.RawMessage, excp.Message); + logger.LogError(excp, "Exception ParseSIPResponse: {SipMessage}. {ErrorMessage}", sipMessageBuffer.RawMessage, excp.Message); throw new SIPValidationException(SIPValidationFieldsEnum.Response, "Error parsing SIP Response"); } } @@ -159,12 +158,12 @@ public static SIPResponse ParseSIPResponse(string sipMessageStr) => /// /// /// A new SIP response object. - public static SIPResponse ParseSIPResponse(string sipMessageStr,Encoding sipEncoding, Encoding sipBodyEncoding) + public static SIPResponse ParseSIPResponse(string sipMessageStr, Encoding sipEncoding, Encoding sipBodyEncoding) { try { SIPMessageBuffer sipMessage = SIPMessageBuffer.ParseSIPMessage(sipMessageStr, sipEncoding, sipBodyEncoding, null, null); - return SIPResponse.ParseSIPResponse(sipMessage, sipEncoding,sipBodyEncoding); + return SIPResponse.ParseSIPResponse(sipMessage, sipEncoding, sipBodyEncoding); } catch (SIPValidationException) { @@ -183,11 +182,10 @@ public static SIPResponse ParseSIPResponse(string sipMessageStr,Encoding sipEnco /// A string representation of the SIP response. public override string ToString() { - string reasonPhrase = (!ReasonPhrase.IsNullOrBlank()) ? " " + ReasonPhrase : null; + string reasonPhrase = (!ReasonPhrase.IsNullOrBlank()) ? $" {ReasonPhrase}" : null; - string message = - SIPVersion + " " + StatusCode + reasonPhrase + m_CRLF + - this.Header.ToString(); + var message = + $"{SIPVersion} {StatusCode}{reasonPhrase}{m_CRLF}{Header}"; if (Body != null) { @@ -334,8 +332,8 @@ public static SIPResponse GetResponse(SIPEndPoint localSIPEndPoint, SIPEndPoint public byte[] GetBytes() { - string reasonPhrase = (!ReasonPhrase.IsNullOrBlank()) ? " " + ReasonPhrase : null; - string firstLine = SIPVersion + " " + StatusCode + reasonPhrase + m_CRLF; + string reasonPhrase = (!ReasonPhrase.IsNullOrBlank()) ? $" {ReasonPhrase}" : null; + string firstLine = $"{SIPVersion} {StatusCode}{reasonPhrase}{m_CRLF}"; return base.GetBytes(firstLine); } } diff --git a/src/SIPSorcery/core/SIP/SIPTransport.cs b/src/SIPSorcery/core/SIP/SIPTransport.cs index c2d6ce2bf..a49e1b931 100644 --- a/src/SIPSorcery/core/SIP/SIPTransport.cs +++ b/src/SIPSorcery/core/SIP/SIPTransport.cs @@ -827,7 +827,7 @@ private SIPHeader AdjustHeadersForEndPoint(SIPEndPoint sendFromSIPEndPoint, SIPH if (IPAddress.TryParse(ContactHost, out _)) { // If the custom host is an IP address include the port number that's being used for the send. - copy.Contact.Single().ContactURI.Host = ContactHost + ":" + sendFromEndPoint.Port.ToString(); + copy.Contact.Single().ContactURI.Host = $"{ContactHost}:{sendFromEndPoint.Port.ToString()}"; } else { @@ -920,8 +920,8 @@ private Task SIPMessageReceived( // Treat all messages that don't match STUN requests as SIP. if (buffer.Length > SIPConstants.SIP_MAXIMUM_RECEIVE_LENGTH) { - string rawErrorMessage = m_sipEncoding.GetString(buffer, 0, 1024) + "\r\n..truncated"; - SIPBadRequestInTraceEvent?.Invoke(localEndPoint, remoteEndPoint, "SIP message too large, " + buffer.Length + " bytes, maximum allowed is " + SIPConstants.SIP_MAXIMUM_RECEIVE_LENGTH + " bytes.", SIPValidationFieldsEnum.Request, rawErrorMessage); + string rawErrorMessage = $"{m_sipEncoding.GetString(buffer, 0, 1024)}\r\n..truncated"; + SIPBadRequestInTraceEvent?.Invoke(localEndPoint, remoteEndPoint, $"SIP message too large, {buffer.Length} bytes, maximum allowed is {SIPConstants.SIP_MAXIMUM_RECEIVE_LENGTH} bytes.", SIPValidationFieldsEnum.Request, rawErrorMessage); SIPResponse tooLargeResponse = SIPResponse.GetResponse(localEndPoint, remoteEndPoint, SIPResponseStatusCodesEnum.MessageTooLarge, null); return SendResponseAsync(tooLargeResponse); } @@ -1012,7 +1012,7 @@ private Task SIPMessageReceived( } else { - SIPBadRequestInTraceEvent?.Invoke(localEndPoint, remoteEndPoint, "ACK received on " + requestTransaction.TransactionState + " transaction, ignoring.", SIPValidationFieldsEnum.Request, null); + SIPBadRequestInTraceEvent?.Invoke(localEndPoint, remoteEndPoint, $"ACK received on {requestTransaction.TransactionState} transaction, ignoring.", SIPValidationFieldsEnum.Request, null); } } else if (sipRequest.Method == SIPMethodsEnum.PRACK) @@ -1107,7 +1107,7 @@ private Task SIPMessageReceived( catch (Exception excp) { logger.LogError(excp, "Exception SIPMessageReceived. {ErrorMessage}", excp.Message); - SIPBadRequestInTraceEvent?.Invoke(localEndPoint, remoteEndPoint, "Exception SIPTransport. " + excp.Message, SIPValidationFieldsEnum.Unknown, rawSIPMessage); + SIPBadRequestInTraceEvent?.Invoke(localEndPoint, remoteEndPoint, $"Exception SIPTransport. {excp.Message}", SIPValidationFieldsEnum.Unknown, rawSIPMessage); return Task.FromResult(SocketError.Fault); } } diff --git a/src/SIPSorcery/core/SIP/SIPURI.cs b/src/SIPSorcery/core/SIP/SIPURI.cs index 2558eae58..6406c8b7f 100644 --- a/src/SIPSorcery/core/SIP/SIPURI.cs +++ b/src/SIPSorcery/core/SIP/SIPURI.cs @@ -124,8 +124,8 @@ public string CanonicalAddress { get { - string canonicalAddress = Scheme + ":"; - canonicalAddress += !String.IsNullOrEmpty(User) ? User + "@" : null; + string canonicalAddress = $"{Scheme}:"; + canonicalAddress += !String.IsNullOrEmpty(User) ? $"{User}@" : null; // First expression is for IPv6 addresses with a port and tel: URIs. // Second expression is for IPv4 addresses and hostnames with a port. @@ -137,7 +137,7 @@ public string CanonicalAddress } else { - canonicalAddress += Host + ":" + SIPConstants.GetDefaultPort(Protocol); + canonicalAddress += $"{Host}:{SIPConstants.GetDefaultPort(Protocol)}"; } return canonicalAddress; @@ -182,7 +182,7 @@ public string MAddrOrHost { return MAddrOrHostAddress; } - return MAddrOrHostAddress + ":" + this.HostPort; + return $"{MAddrOrHostAddress}:{this.HostPort}"; } } @@ -294,7 +294,7 @@ public void ParseUserParameters() { // in case there is a ';' we check wheter there is a '=' in the first part, // if so we assume there is nothing but params - if (User.Substring(0, UserParamsPosn).IndexOf(TAG_NAME_VALUE_SEPERATOR) != -1) + if (User.AsSpan(0, UserParamsPosn).IndexOf(TAG_NAME_VALUE_SEPERATOR) != -1) { UserParameters = new SIPParameters(User, PARAM_TAG_DELIMITER); UserWithoutParameters = ""; @@ -358,7 +358,7 @@ public static SIPURI ParseSIPURI(string uri) } catch { - throw new SIPValidationException(SIPValidationFieldsEnum.URI, SIPResponseStatusCodesEnum.UnsupportedURIScheme, "SIP scheme " + uri.Substring(0, colonPosn) + " was not understood"); + throw new SIPValidationException(SIPValidationFieldsEnum.URI, SIPResponseStatusCodesEnum.UnsupportedURIScheme, $"SIP scheme {uri.Substring(0, colonPosn)} was not understood"); } string uriHostPortion = uri.Substring(colonPosn + 1); @@ -448,15 +448,15 @@ public static SIPURI ParseSIPURI(string uri) public static SIPURI ParseSIPURIRelaxed(string partialURI) { - if (partialURI == null || partialURI.Trim().Length == 0) + if (string.IsNullOrWhiteSpace(partialURI)) { return null; } else { - string regexSchemePattern = "^(" + SIPSchemesEnum.sip + "|" + SIPSchemesEnum.sips + "):"; + string regexSchemePattern = $"^({SIPSchemesEnum.sip}|{SIPSchemesEnum.sips}):"; - if (Regex.Match(partialURI, regexSchemePattern + @"\S+").Success) + if (Regex.Match(partialURI, $@"{regexSchemePattern}\S+").Success) { // The partial uri is already valid. return SIPURI.ParseSIPURI(partialURI); @@ -577,7 +577,7 @@ public SIPEndPoint ToSIPEndPoint() private void ParseParamsAndHeaders(string paramsAndHeaders) { - if (paramsAndHeaders != null && paramsAndHeaders.Trim().Length > 0) + if (!string.IsNullOrWhiteSpace(paramsAndHeaders)) { int headerDelimPosn = paramsAndHeaders.IndexOf(HEADER_START_DELIMITER); diff --git a/src/SIPSorcery/core/SIP/SIPUserField.cs b/src/SIPSorcery/core/SIP/SIPUserField.cs index 6e606aa4d..1335c8596 100644 --- a/src/SIPSorcery/core/SIP/SIPUserField.cs +++ b/src/SIPSorcery/core/SIP/SIPUserField.cs @@ -103,7 +103,7 @@ public static SIPUserField ParseSIPUserField(string userFieldStr) if (paramDelimPosn != -1) { - string paramStr = trimUserField.Substring(paramDelimPosn + 1).Trim(); + var paramStr = trimUserField.AsSpan(paramDelimPosn + 1).Trim().ToString(); userField.Parameters = new SIPParameters(paramStr, PARAM_TAG_DELIMITER); uriStr = trimUserField.Substring(0, paramDelimPosn); } @@ -114,7 +114,7 @@ public static SIPUserField ParseSIPUserField(string userFieldStr) { if (position > 0) { - userField.Name = trimUserField.Substring(0, position).Trim().Trim('"'); + userField.Name = trimUserField.AsSpan(0, position).Trim().ToString().Trim('"'); trimUserField = trimUserField.Substring(position, trimUserField.Length - position); } @@ -124,7 +124,7 @@ public static SIPUserField ParseSIPUserField(string userFieldStr) { addrSpecLen = position - 1; - string paramStr = trimUserField.Substring(position + 1).Trim(); + var paramStr = trimUserField.AsSpan(position + 1).Trim().ToString(); userField.Parameters = new SIPParameters(paramStr, PARAM_TAG_DELIMITER); string addrSpec = trimUserField.Substring(1, addrSpecLen); @@ -133,7 +133,7 @@ public static SIPUserField ParseSIPUserField(string userFieldStr) } else { - throw new SIPValidationException(SIPValidationFieldsEnum.ContactHeader, "A SIPUserField was missing the right quote, " + userFieldStr + "."); + throw new SIPValidationException(SIPValidationFieldsEnum.ContactHeader, $"A SIPUserField was missing the right quote, {userFieldStr}."); } } @@ -157,10 +157,10 @@ public override string ToString() userFieldStr = Name + " "; }*/ - userFieldStr = "\"" + Name + "\" "; + userFieldStr = $"\"{Name}\" "; } - userFieldStr += "<" + URI.ToString() + ">" + Parameters.ToString(); + userFieldStr += $"<{URI.ToString()}>{Parameters.ToString()}"; return userFieldStr; } @@ -179,10 +179,10 @@ public string ToParameterlessString() if (Name != null) { - userFieldStr = "\"" + Name + "\" "; + userFieldStr = $"\"{Name}\" "; } - userFieldStr += "<" + URI.ToParameterlessString() + ">"; + userFieldStr += $"<{URI.ToParameterlessString()}>"; return userFieldStr; } diff --git a/src/SIPSorcery/sys/TypeExtensions.cs b/src/SIPSorcery/sys/TypeExtensions.cs index 69d24596c..51935f203 100644 --- a/src/SIPSorcery/sys/TypeExtensions.cs +++ b/src/SIPSorcery/sys/TypeExtensions.cs @@ -206,12 +206,6 @@ public static byte[] ParseHexStr(string hexStr) return buffer.ToArray(); } - public static void Deconstruct(this KeyValuePair tuple, out T1 key, out T2 value) - { - key = tuple.Key; - value = tuple.Value; - } - public static bool IsPrivate(this IPAddress address) { return IPSocket.IsPrivateAddress(address.ToString()); diff --git a/test/integration/SIPSorcery.IntegrationTests.csproj b/test/integration/SIPSorcery.IntegrationTests.csproj index 07159f7f1..f28b36958 100755 --- a/test/integration/SIPSorcery.IntegrationTests.csproj +++ b/test/integration/SIPSorcery.IntegrationTests.csproj @@ -20,10 +20,6 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/test/unit/SIPSorcery.UnitTests.csproj b/test/unit/SIPSorcery.UnitTests.csproj index 484c45b6e..07bed7bae 100755 --- a/test/unit/SIPSorcery.UnitTests.csproj +++ b/test/unit/SIPSorcery.UnitTests.csproj @@ -14,10 +14,6 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/test/unit/core/SIP/SIPAuthorisationDigestUnitTest.cs b/test/unit/core/SIP/SIPAuthorisationDigestUnitTest.cs index 39ebfcded..c60fc1261 100644 --- a/test/unit/core/SIP/SIPAuthorisationDigestUnitTest.cs +++ b/test/unit/core/SIP/SIPAuthorisationDigestUnitTest.cs @@ -433,7 +433,7 @@ public void SIPRequestAuthRoundTripWithSHA256DIgest() authResult = SIPRequestAuthenticator.AuthenticateSIPRequest(SIPEndPoint.Empty, SIPEndPoint.Empty, authReq, account); Assert.True(authResult.Authenticated); - } + } /// /// A base64 nonce that ends with '=' padding must be preserved verbatim. The header is split