Skip to content

Commit

Permalink
Support community redirection protocol. Fixes #945
Browse files Browse the repository at this point in the history
  • Loading branch information
bgrainger committed Feb 14, 2021
1 parent ff1a5fe commit 4fb8553
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 10 deletions.
52 changes: 42 additions & 10 deletions src/MySqlConnector/Utilities/Utility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -335,26 +335,58 @@ public static bool TryParseRedirectionHeader(string header, out string host, out
port = 0;
user = "";

if (!header.StartsWith("Location: mysql://", StringComparison.Ordinal))
if (!header.StartsWith("Location: mysql://", StringComparison.Ordinal) || header.Length < 22)
return false;

var hostIndex = 18;
var colonIndex = header.IndexOf(':', hostIndex);
if (colonIndex == -1)
return false;
bool isCommunityFormat;
int portIndex;
if (header[18] == '[')
{
// Community protocol:
// Location: mysql://[redirectedHostName]:redirectedPort/?user=redirectedUser&ttl=%d\n
isCommunityFormat = true;

var hostIndex = 19;
var closeSquareBracketIndex = header.IndexOf(']', hostIndex);
if (closeSquareBracketIndex == -1)
return false;

host = header.Substring(hostIndex, closeSquareBracketIndex - hostIndex);
if (header.Length <= closeSquareBracketIndex + 2)
return false;
if (header[closeSquareBracketIndex + 1] != ':')
return false;
portIndex = closeSquareBracketIndex + 2;
}
else
{
// Azure protocol:
// Location: mysql://redirectedHostName:redirectedPort/user=redirectedUser&ttl=%d (where ttl is optional)
isCommunityFormat = false;

host = header.Substring(hostIndex, colonIndex - hostIndex);
var portIndex = colonIndex + 1;
var userIndex = header.IndexOf("/user=", StringComparison.Ordinal);
var hostIndex = 18;
var colonIndex = header.IndexOf(':', hostIndex);
if (colonIndex == -1)
return false;

host = header.Substring(hostIndex, colonIndex - hostIndex);
portIndex = colonIndex + 1;
}

var userIndex = header.IndexOf(isCommunityFormat ? "/?user=" : "/user=", StringComparison.Ordinal);
if (userIndex == -1)
return false;

if (!int.TryParse(header.Substring(portIndex, userIndex - portIndex), out port) || port <= 0)
return false;

userIndex += isCommunityFormat ? 7 : 6;
var ampersandIndex = header.IndexOf('&', userIndex);
user = ampersandIndex == -1 ? header.Substring(userIndex + 6) : header.Substring(userIndex + 6, ampersandIndex - userIndex - 6);
return true;
var newlineIndex = header.IndexOf('\n', userIndex);
var terminatorIndex = ampersandIndex == -1 ? (newlineIndex == -1 ? header.Length : newlineIndex) :
(newlineIndex == -1 ? ampersandIndex : Math.Min(ampersandIndex, newlineIndex));
user = header.Substring(userIndex, terminatorIndex - userIndex);
return user.Length != 0;
}

public static TimeSpan ParseTimeSpan(ReadOnlySpan<byte> value)
Expand Down
19 changes: 19 additions & 0 deletions tests/MySqlConnector.Tests/UtilityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,17 @@ public class UtilityTests
{
[Theory]
[InlineData("Location: mysql://host.example.com:1234/user=user@host", "host.example.com", 1234, "user@host")]
[InlineData("Location: mysql://host.example.com:1234/user=user@host\n", "host.example.com", 1234, "user@host")]
[InlineData("Location: mysql://host.example.com:1234/user=user@host&ttl=60", "host.example.com", 1234, "user@host")]
[InlineData("Location: mysql://host.example.com:1234/user=user@host&ttl=60\n", "host.example.com", 1234, "user@host")]
[InlineData("Location: mysql://[host.example.com]:1234/?user=abcd", "host.example.com", 1234, "abcd")]
[InlineData("Location: mysql://[host.example.com]:1234/?user=abcd\n", "host.example.com", 1234, "abcd")]
[InlineData("Location: mysql://[host.example.com]:1234/?user=abcd&ttl=60", "host.example.com", 1234, "abcd")]
[InlineData("Location: mysql://[host.example.com]:1234/?user=abcd&ttl=60\n", "host.example.com", 1234, "abcd")]
[InlineData("Location: mysql://[2001:4860:4860::8888]:1234/?user=abcd", "2001:4860:4860::8888", 1234, "abcd")]
[InlineData("Location: mysql://[2001:4860:4860::8888]:1234/?user=abcd\n", "2001:4860:4860::8888", 1234, "abcd")]
[InlineData("Location: mysql://[2001:4860:4860::8888]:1234/?user=abcd&ttl=60", "2001:4860:4860::8888", 1234, "abcd")]
[InlineData("Location: mysql://[2001:4860:4860::8888]:1234/?user=abcd&ttl=60\n", "2001:4860:4860::8888", 1234, "abcd")]
public void ParseRedirectionHeader(string input, string expectedHost, int expectedPort, string expectedUser)
{
Assert.True(Utility.TryParseRedirectionHeader(input, out var host, out var port, out var user));
Expand All @@ -24,12 +34,21 @@ public void ParseRedirectionHeader(string input, string expectedHost, int expect
[InlineData("")]
[InlineData("Location: mysql")]
[InlineData("Location: mysql://host.example.com")]
[InlineData("Location: mysql://host.example.com:")]
[InlineData("Location: mysql://[host.example.com")]
[InlineData("Location: mysql://[host.example.com]")]
[InlineData("Location: mysql://[host.example.com]:")]
[InlineData("Location: mysql://host.example.com:123")]
[InlineData("Location: mysql://host.example.com:123/")]
[InlineData("Location: mysql://[host.example.com]:123")]
[InlineData("Location: mysql://[host.example.com]:123/")]
[InlineData("Location: mysql://host.example.com:/user=")]
[InlineData("Location: mysql://host.example.com:123/user=")]
[InlineData("Location: mysql://[host.example.com]:123/?user=")]
[InlineData("Location: mysql://host.example.com:/user=user@host")]
[InlineData("Location: mysql://host.example.com:-1/user=user@host")]
[InlineData("Location: mysql://host.example.com:0/user=user@host")]
[InlineData("Location: mysql://[host.example.com]:123/user=abcd")]
public void ParseRedirectionHeaderFails(string input)
{
Assert.False(Utility.TryParseRedirectionHeader(input, out _, out _, out _));
Expand Down

0 comments on commit 4fb8553

Please sign in to comment.