diff --git a/RestSharp.IntegrationTests/oAuth1Tests.cs b/RestSharp.IntegrationTests/oAuth1Tests.cs index 9387514c5..d9002c680 100644 --- a/RestSharp.IntegrationTests/oAuth1Tests.cs +++ b/RestSharp.IntegrationTests/oAuth1Tests.cs @@ -171,5 +171,20 @@ public void Properly_Encodes_Parameter_Names() Assert.Equal("name%5Bfirst%5D", sortedParams[0].Name); } + + [Fact] + public void Use_RFC_3986_Encoding_For_Auth_Signature_Base() + { + // reserved characters for 2396 and 3986 + var reserved2396Characters = new[] { ";", "/", "?", ":", "@", "&", "=", "+", "$", "," }; // http://www.ietf.org/rfc/rfc2396.txt + var additionalReserved3986Characters = new[] { "!", "*", "'", "(", ")" }; // http://www.ietf.org/rfc/rfc3986.txt + var reservedCharacterString = string.Join( string.Empty, reserved2396Characters.Union( additionalReserved3986Characters ) ); + + // act + var escapedString = OAuthTools.UrlEncodeRelaxed( reservedCharacterString ); + + // assert + Assert.Equal( "%3B%2F%3F%3A%40%26%3D%2B%24%2C%21%2A%27%28%29", escapedString ); + } } } diff --git a/RestSharp/Authenticators/OAuth/OAuthTools.cs b/RestSharp/Authenticators/OAuth/OAuthTools.cs index 27e71ab61..d8c83dd36 100644 --- a/RestSharp/Authenticators/OAuth/OAuthTools.cs +++ b/RestSharp/Authenticators/OAuth/OAuthTools.cs @@ -83,16 +83,42 @@ public static string GetTimestamp(DateTime dateTime) return timestamp.ToString(); } + /// + /// The set of characters that are unreserved in RFC 2396 but are NOT unreserved in RFC 3986. + /// + /// + private static readonly string[] UriRfc3986CharsToEscape = new[] { "!", "*", "'", "(", ")" }; + /// /// URL encodes a string based on section 5.1 of the OAuth spec. /// Namely, percent encoding with [RFC3986], avoiding unreserved characters, /// upper-casing hexadecimal characters, and UTF-8 encoding for text value pairs. /// - /// + /// The value to escape. + /// The escaped value. + /// + /// The method is supposed to take on + /// RFC 3986 behavior if certain elements are present in a .config file. Even if this + /// actually worked (which in my experiments it doesn't), we can't rely on every + /// host actually having this configuration element present. + /// /// + /// public static string UrlEncodeRelaxed(string value) { - return Uri.EscapeDataString(value); + // Start with RFC 2396 escaping by calling the .NET method to do the work. + // This MAY sometimes exhibit RFC 3986 behavior (according to the documentation). + // If it does, the escaping we do that follows it will be a no-op since the + // characters we search for to replace can't possibly exist in the string. + StringBuilder escaped = new StringBuilder(Uri.EscapeDataString(value)); + + // Upgrade the escaping to RFC 3986, if necessary. + for (int i = 0; i < UriRfc3986CharsToEscape.Length; i++) { + escaped.Replace(UriRfc3986CharsToEscape[i], Uri.HexEscape(UriRfc3986CharsToEscape[i][0])); + } + + // Return the fully-RFC3986-escaped string. + return escaped.ToString(); } ///