Skip to content

Bug fix: Escape special character in database names. #54

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -328,3 +328,6 @@ ASALocalRun/

# MFractors (Xamarin productivity tool) working folder
.mfractor/

# VsCode
.vscode
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
# 1.1.3 (2019-06-14)
# 1.1.5 (2019-12-19)

## Bug Fixes
* **Database:** Fixing special characters escaping in databases names. ([#PR54](https://github.com/matteobortolazzo/couchdb-net/pull/54))

# 1.1.4 (2019-08-19)
## Bug Fixes
* **Queries:** Fixing enums serialized as string instead of int bug. ([#PR49](https://github.com/matteobortolazzo/couchdb-net/pull/49))

# 1.1.3 (2019-06-14)

## Bug Fixes
* **Exception:** Fixing null reference exception and poor exception handling. ([#PR45](https://github.com/matteobortolazzo/couchdb-net/pull/45))
Expand Down
2 changes: 1 addition & 1 deletion LATEST_CHANGE.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
## Bug Fixes
* **Exception:** Fixing null reference exception and poor exception handling. ([#PR45](https://github.com/matteobortolazzo/couchdb-net/pull/45))
* **Database:** Fixing special characters escaping in databases names. ([#PR54](https://github.com/matteobortolazzo/couchdb-net/pull/54))
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ The produced Mango JSON:
## Getting started

* Install it from NuGet: [https://www.nuget.org/packages/CouchDB.NET](https://www.nuget.org/packages/CouchDB.NET)
* Create a client:
* Create a client, where localhost will be the IP address and 5984 is CouchDB standard tcp port:
```csharp
using(var client = new CouchClient("http://localhost")) { }
using(var client = new CouchClient("http://localhost:5984")) { }
```
* Create a document class:
```csharp
Expand Down Expand Up @@ -255,23 +255,23 @@ If authentication is needed currently there are two ways: Basic and Cookie authe
### Basic authentication

```csharp
var client = new CouchClient("http://localhost", s =>
var client = new CouchClient("http://localhost:5984", s =>
s.UseBasicAuthentication("root", "relax")
)
```

### Cookie authentication

```csharp
var client = new CouchClient("http://localhost", s => s
var client = new CouchClient("http://localhost:5984", s => s
.UseCookieAuthentication("root", "relax")
)
```

It's also possible to specify the duration of the session.

```csharp
var client = new CouchClient("http://localhost", s => s
var client = new CouchClient("http://localhost:5984", s => s
.UseCookieAuthentication("root", "relax", cookieDuration)
)
```
Expand All @@ -281,7 +281,7 @@ var client = new CouchClient("http://localhost", s => s
The second parameter of the client constructor is a function to configure CouchSettings fluently.

```csharp
var client = new CouchClient("http://localhost", s => s
var client = new CouchClient("http://localhost:5984", s => s
.UseBasicAuthentication("root", "relax")
.DisableEntitisPluralization()
....
Expand Down Expand Up @@ -360,4 +360,4 @@ Action<ClientFlurlHttpSettings> flurlConfigFunc

## Contributors

Thanks to [Ben Origas](https://github.com/borigas) for features, ideas and tests like SSL custom validation, multi queryable, async deadlock, cookie authenication and many others.
Thanks to [Ben Origas](https://github.com/borigas) for features, ideas and tests like SSL custom validation, multi queryable, async deadlock, cookie authenication and many others.
51 changes: 27 additions & 24 deletions src/CouchDB.Driver/CouchClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,15 @@ public CouchClient(string connectionString, Action<CouchSettings> couchSettingsF
#region CRUD

/// <summary>
/// Returns an instance of the CouchDB database with the given name.
/// Returns an instance of the CouchDB database with the given name.
/// If EnsureDatabaseExists is configured, it creates the database if it doesn't exists.
/// </summary>
/// <typeparam name="TSource">The type of database documents.</typeparam>
/// <param name="database">The database name.</param>
/// <returns>An instance of the CouchDB database with given name.</returns>
public CouchDatabase<TSource> GetDatabase<TSource>(string database) where TSource : CouchDocument
{
if (database == null)
{
throw new ArgumentNullException(nameof(database));
}
database = EscapeDatabaseName(database);

if (_settings.CheckDatabaseExists)
{
Expand All @@ -102,15 +99,7 @@ public CouchDatabase<TSource> GetDatabase<TSource>(string database) where TSourc
/// <returns>A task that represents the asynchronous operation. The task result contains the newly created CouchDB database.</returns>
public async Task<CouchDatabase<TSource>> CreateDatabaseAsync<TSource>(string database, int? shards = null, int? replicas = null) where TSource : CouchDocument
{
if (database == null)
{
throw new ArgumentNullException(nameof(database));
}

if (!_systemDatabases.Contains(database) && !new Regex(@"^[a-z][a-z0-9_$()+/-]*$").IsMatch(database))
{
throw new ArgumentException($"Name {database} contains invalid characters. Please visit: https://docs.couchdb.org/en/stable/api/database/common.html#put--db", nameof(database));
}
database = EscapeDatabaseName(database);

IFlurlRequest request = NewRequest()
.AppendPathSegment(database);
Expand All @@ -119,6 +108,7 @@ public async Task<CouchDatabase<TSource>> CreateDatabaseAsync<TSource>(string da
{
request = request.SetQueryParam("q", shards.Value);
}

if (replicas.HasValue)
{
request = request.SetQueryParam("n", replicas.Value);
Expand Down Expand Up @@ -146,10 +136,7 @@ public async Task<CouchDatabase<TSource>> CreateDatabaseAsync<TSource>(string da
/// <returns>A task that represents the asynchronous operation.</returns>
public async Task DeleteDatabaseAsync<TSource>(string database) where TSource : CouchDocument
{
if (database == null)
{
throw new ArgumentNullException(nameof(database));
}
database = EscapeDatabaseName(database);

OperationResult result = await NewRequest()
.AppendPathSegment(database)
Expand All @@ -158,7 +145,8 @@ public async Task DeleteDatabaseAsync<TSource>(string database) where TSource :
.SendRequestAsync()
.ConfigureAwait(false);

if (!result.Ok) {
if (!result.Ok)
{
throw new CouchException("Something went wrong during the delete.", "S");
}
}
Expand All @@ -168,7 +156,7 @@ public async Task DeleteDatabaseAsync<TSource>(string database) where TSource :
#region CRUD reflection

/// <summary>
/// Returns an instance of the CouchDB database of the given type.
/// Returns an instance of the CouchDB database of the given type.
/// If EnsureDatabaseExists is configured, it creates the database if it doesn't exists.
/// </summary>
/// <typeparam name="TSource">The type of database documents.</typeparam>
Expand All @@ -179,7 +167,7 @@ public CouchDatabase<TSource> GetDatabase<TSource>() where TSource : CouchDocume
}

/// <summary>
/// Creates a new database of the given type in the server.
/// Creates a new database of the given type in the server.
/// The name must begin with a lowercase letter and can contains only lowercase characters, digits or _, $, (, ), +, - and /.s
/// </summary>
/// <typeparam name="TSource">The type of database documents.</typeparam>
Expand Down Expand Up @@ -235,7 +223,7 @@ public CouchDatabase<TUser> GetUsersDatabase<TUser>() where TUser : CouchUser
#region Utils

/// <summary>
/// Determines whether the server is up, running, and ready to respond to requests.
/// Determines whether the server is up, running, and ready to respond to requests.
/// </summary>
/// <returns>true is the server is not in maintenance_mode; otherwise, false.</returns>
public async Task<bool> IsUpAsync()
Expand All @@ -249,7 +237,7 @@ public async Task<bool> IsUpAsync()
.ConfigureAwait(false);
return result.Status == "ok";
}
catch(CouchNotFoundException)
catch (CouchNotFoundException)
{
return false;
}
Expand Down Expand Up @@ -292,6 +280,21 @@ private IFlurlRequest NewRequest()
return _flurlClient.Request(ConnectionString);
}

private string EscapeDatabaseName(string database)
{
if (database == null)
{
throw new ArgumentNullException(nameof(database));
}

if (!_systemDatabases.Contains(database) && !new Regex(@"^[a-z][a-z0-9_$()+/-]*$").IsMatch(database))
{
throw new ArgumentException($"Name {database} contains invalid characters. Please visit: https://docs.couchdb.org/en/stable/api/database/common.html#put--db", nameof(database));
}

return Uri.EscapeDataString(database);
}

/// <summary>
/// Performs the logout and disposes the HTTP client.
/// </summary>
Expand All @@ -305,7 +308,7 @@ protected virtual void Dispose(bool disposing)
{
if (_settings.AuthenticationType == AuthenticationType.Cookie && _settings.LogOutOnDispose)
{
AsyncContext.Run(() => LogoutAsync().ConfigureAwait(false));
_ = AsyncContext.Run(() => LogoutAsync().ConfigureAwait(false));
}
_flurlClient.Dispose();
}
Expand Down
10 changes: 6 additions & 4 deletions src/CouchDB.Driver/CouchDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class CouchDatabase<TSource> where TSource : CouchDocument
private readonly IFlurlClient _flurlClient;
private readonly CouchSettings _settings;
private readonly string _connectionString;
private string _database;

/// <summary>
/// The database name.
Expand All @@ -41,9 +42,10 @@ internal CouchDatabase(IFlurlClient flurlClient, CouchSettings settings, string
_flurlClient = flurlClient ?? throw new ArgumentNullException(nameof(flurlClient));
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
Database = db ?? throw new ArgumentNullException(nameof(db));
_queryProvider = new CouchQueryProvider(flurlClient, _settings, connectionString, Database);
_database = db ?? throw new ArgumentNullException(nameof(db));
_queryProvider = new CouchQueryProvider(flurlClient, _settings, connectionString, _database);

Database = Uri.UnescapeDataString(_database);
Security = new CouchSecurity(NewRequest);
}

Expand Down Expand Up @@ -274,7 +276,7 @@ private async Task<List<TSource>> SendQueryAsync(Func<IFlurlRequest, Task<HttpRe

return findResult.Docs.ToList();
}

/// Finds all documents with given IDs.
/// </summary>
/// <param name="docIds">The collection of documents IDs.</param>
Expand Down Expand Up @@ -487,7 +489,7 @@ public override string ToString()

private IFlurlRequest NewRequest()
{
return _flurlClient.Request(_connectionString).AppendPathSegment(Database);
return _flurlClient.Request(_connectionString).AppendPathSegment(_database);
}

#endregion
Expand Down
2 changes: 1 addition & 1 deletion src/CouchDB.Driver/CouchQueryProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ private object InvokeUnsupportedMethodCallExpression(object result, MethodCallEx
MethodInfo queryableMethodInfo = methodCallExpression.Method;
Expression[] queryableMethodArguments = methodCallExpression.Arguments.ToArray();

// Since Max and Min are not map 1 to 1 from Queryable to Enumerable
// Since Max and Min are not map 1 to 1 from Queryable to Enumerable
// they need to be handled differently
MethodInfo FindEnumerableMethod()
{
Expand Down
2 changes: 1 addition & 1 deletion src/CouchDB.Driver/QueryTranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

#pragma warning disable IDE0058 // Expression value is never used
namespace CouchDB.Driver
{
{
internal partial class QueryTranslator : ExpressionVisitor
{
private readonly CouchSettings _settings;
Expand Down
36 changes: 36 additions & 0 deletions tests/CouchDB.Driver.E2ETests/Client_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,42 @@ public async Task Crud()
await client.DeleteDatabaseAsync<Rebel>().ConfigureAwait(false);
}
}

[Fact]
public async Task Crud_SpecialCharacters()
{
var databaseName = "rebel0_$()+/-";

using (var client = new CouchClient("http://localhost:5984"))
{
IEnumerable<string> dbs = await client.GetDatabasesNamesAsync().ConfigureAwait(false);
CouchDatabase<Rebel> rebels = client.GetDatabase<Rebel>(databaseName);

if (dbs.Contains(rebels.Database))
{
await client.DeleteDatabaseAsync<Rebel>(databaseName).ConfigureAwait(false);
}

rebels = await client.CreateDatabaseAsync<Rebel>(databaseName).ConfigureAwait(false);

Rebel luke = await rebels.CreateAsync(new Rebel { Name = "Luke", Age = 19 }).ConfigureAwait(false);
Assert.Equal("Luke", luke.Name);

luke.Surname = "Skywalker";
luke = await rebels.CreateOrUpdateAsync(luke).ConfigureAwait(false);
Assert.Equal("Skywalker", luke.Surname);

luke = await rebels.FindAsync(luke.Id).ConfigureAwait(false);
Assert.Equal(19, luke.Age);

await rebels.DeleteAsync(luke).ConfigureAwait(false);
luke = await rebels.FindAsync(luke.Id).ConfigureAwait(false);
Assert.Null(luke);

await client.DeleteDatabaseAsync<Rebel>(databaseName).ConfigureAwait(false);
}
}

[Fact]
public async Task Users()
{
Expand Down
Loading