Skip to content

Commit 53b69d9

Browse files
Merge pull request #54 from matteobortolazzo/dev
Bug fix: Escape special character in database names.
2 parents 969ed9c + cce5efd commit 53b69d9

File tree

10 files changed

+220
-39
lines changed

10 files changed

+220
-39
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -328,3 +328,6 @@ ASALocalRun/
328328

329329
# MFractors (Xamarin productivity tool) working folder
330330
.mfractor/
331+
332+
# VsCode
333+
.vscode

CHANGELOG.md

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
# 1.1.3 (2019-06-14)
1+
# 1.1.5 (2019-12-19)
2+
3+
## Bug Fixes
4+
* **Database:** Fixing special characters escaping in databases names. ([#PR54](https://github.com/matteobortolazzo/couchdb-net/pull/54))
5+
6+
# 1.1.4 (2019-08-19)
7+
## Bug Fixes
8+
* **Queries:** Fixing enums serialized as string instead of int bug. ([#PR49](https://github.com/matteobortolazzo/couchdb-net/pull/49))
9+
10+
# 1.1.3 (2019-06-14)
211

312
## Bug Fixes
413
* **Exception:** Fixing null reference exception and poor exception handling. ([#PR45](https://github.com/matteobortolazzo/couchdb-net/pull/45))

LATEST_CHANGE.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
## Bug Fixes
2-
* **Exception:** Fixing null reference exception and poor exception handling. ([#PR45](https://github.com/matteobortolazzo/couchdb-net/pull/45))
2+
* **Database:** Fixing special characters escaping in databases names. ([#PR54](https://github.com/matteobortolazzo/couchdb-net/pull/54))

README.md

+7-7
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,9 @@ The produced Mango JSON:
9292
## Getting started
9393

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

257257
```csharp
258-
var client = new CouchClient("http://localhost", s =>
258+
var client = new CouchClient("http://localhost:5984", s =>
259259
s.UseBasicAuthentication("root", "relax")
260260
)
261261
```
262262

263263
### Cookie authentication
264264

265265
```csharp
266-
var client = new CouchClient("http://localhost", s => s
266+
var client = new CouchClient("http://localhost:5984", s => s
267267
.UseCookieAuthentication("root", "relax")
268268
)
269269
```
270270

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

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

283283
```csharp
284-
var client = new CouchClient("http://localhost", s => s
284+
var client = new CouchClient("http://localhost:5984", s => s
285285
.UseBasicAuthentication("root", "relax")
286286
.DisableEntitisPluralization()
287287
....
@@ -360,4 +360,4 @@ Action<ClientFlurlHttpSettings> flurlConfigFunc
360360

361361
## Contributors
362362

363-
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.
363+
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.

src/CouchDB.Driver/CouchClient.cs

+27-24
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,15 @@ public CouchClient(string connectionString, Action<CouchSettings> couchSettingsF
6666
#region CRUD
6767

6868
/// <summary>
69-
/// Returns an instance of the CouchDB database with the given name.
69+
/// Returns an instance of the CouchDB database with the given name.
7070
/// If EnsureDatabaseExists is configured, it creates the database if it doesn't exists.
7171
/// </summary>
7272
/// <typeparam name="TSource">The type of database documents.</typeparam>
7373
/// <param name="database">The database name.</param>
7474
/// <returns>An instance of the CouchDB database with given name.</returns>
7575
public CouchDatabase<TSource> GetDatabase<TSource>(string database) where TSource : CouchDocument
7676
{
77-
if (database == null)
78-
{
79-
throw new ArgumentNullException(nameof(database));
80-
}
77+
database = EscapeDatabaseName(database);
8178

8279
if (_settings.CheckDatabaseExists)
8380
{
@@ -102,15 +99,7 @@ public CouchDatabase<TSource> GetDatabase<TSource>(string database) where TSourc
10299
/// <returns>A task that represents the asynchronous operation. The task result contains the newly created CouchDB database.</returns>
103100
public async Task<CouchDatabase<TSource>> CreateDatabaseAsync<TSource>(string database, int? shards = null, int? replicas = null) where TSource : CouchDocument
104101
{
105-
if (database == null)
106-
{
107-
throw new ArgumentNullException(nameof(database));
108-
}
109-
110-
if (!_systemDatabases.Contains(database) && !new Regex(@"^[a-z][a-z0-9_$()+/-]*$").IsMatch(database))
111-
{
112-
throw new ArgumentException($"Name {database} contains invalid characters. Please visit: https://docs.couchdb.org/en/stable/api/database/common.html#put--db", nameof(database));
113-
}
102+
database = EscapeDatabaseName(database);
114103

115104
IFlurlRequest request = NewRequest()
116105
.AppendPathSegment(database);
@@ -119,6 +108,7 @@ public async Task<CouchDatabase<TSource>> CreateDatabaseAsync<TSource>(string da
119108
{
120109
request = request.SetQueryParam("q", shards.Value);
121110
}
111+
122112
if (replicas.HasValue)
123113
{
124114
request = request.SetQueryParam("n", replicas.Value);
@@ -146,10 +136,7 @@ public async Task<CouchDatabase<TSource>> CreateDatabaseAsync<TSource>(string da
146136
/// <returns>A task that represents the asynchronous operation.</returns>
147137
public async Task DeleteDatabaseAsync<TSource>(string database) where TSource : CouchDocument
148138
{
149-
if (database == null)
150-
{
151-
throw new ArgumentNullException(nameof(database));
152-
}
139+
database = EscapeDatabaseName(database);
153140

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

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

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

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

237225
/// <summary>
238-
/// Determines whether the server is up, running, and ready to respond to requests.
226+
/// Determines whether the server is up, running, and ready to respond to requests.
239227
/// </summary>
240228
/// <returns>true is the server is not in maintenance_mode; otherwise, false.</returns>
241229
public async Task<bool> IsUpAsync()
@@ -249,7 +237,7 @@ public async Task<bool> IsUpAsync()
249237
.ConfigureAwait(false);
250238
return result.Status == "ok";
251239
}
252-
catch(CouchNotFoundException)
240+
catch (CouchNotFoundException)
253241
{
254242
return false;
255243
}
@@ -292,6 +280,21 @@ private IFlurlRequest NewRequest()
292280
return _flurlClient.Request(ConnectionString);
293281
}
294282

283+
private string EscapeDatabaseName(string database)
284+
{
285+
if (database == null)
286+
{
287+
throw new ArgumentNullException(nameof(database));
288+
}
289+
290+
if (!_systemDatabases.Contains(database) && !new Regex(@"^[a-z][a-z0-9_$()+/-]*$").IsMatch(database))
291+
{
292+
throw new ArgumentException($"Name {database} contains invalid characters. Please visit: https://docs.couchdb.org/en/stable/api/database/common.html#put--db", nameof(database));
293+
}
294+
295+
return Uri.EscapeDataString(database);
296+
}
297+
295298
/// <summary>
296299
/// Performs the logout and disposes the HTTP client.
297300
/// </summary>
@@ -305,7 +308,7 @@ protected virtual void Dispose(bool disposing)
305308
{
306309
if (_settings.AuthenticationType == AuthenticationType.Cookie && _settings.LogOutOnDispose)
307310
{
308-
AsyncContext.Run(() => LogoutAsync().ConfigureAwait(false));
311+
_ = AsyncContext.Run(() => LogoutAsync().ConfigureAwait(false));
309312
}
310313
_flurlClient.Dispose();
311314
}

src/CouchDB.Driver/CouchDatabase.cs

+6-4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class CouchDatabase<TSource> where TSource : CouchDocument
2525
private readonly IFlurlClient _flurlClient;
2626
private readonly CouchSettings _settings;
2727
private readonly string _connectionString;
28+
private string _database;
2829

2930
/// <summary>
3031
/// The database name.
@@ -41,9 +42,10 @@ internal CouchDatabase(IFlurlClient flurlClient, CouchSettings settings, string
4142
_flurlClient = flurlClient ?? throw new ArgumentNullException(nameof(flurlClient));
4243
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
4344
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
44-
Database = db ?? throw new ArgumentNullException(nameof(db));
45-
_queryProvider = new CouchQueryProvider(flurlClient, _settings, connectionString, Database);
45+
_database = db ?? throw new ArgumentNullException(nameof(db));
46+
_queryProvider = new CouchQueryProvider(flurlClient, _settings, connectionString, _database);
4647

48+
Database = Uri.UnescapeDataString(_database);
4749
Security = new CouchSecurity(NewRequest);
4850
}
4951

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

275277
return findResult.Docs.ToList();
276278
}
277-
279+
278280
/// Finds all documents with given IDs.
279281
/// </summary>
280282
/// <param name="docIds">The collection of documents IDs.</param>
@@ -487,7 +489,7 @@ public override string ToString()
487489

488490
private IFlurlRequest NewRequest()
489491
{
490-
return _flurlClient.Request(_connectionString).AppendPathSegment(Database);
492+
return _flurlClient.Request(_connectionString).AppendPathSegment(_database);
491493
}
492494

493495
#endregion

src/CouchDB.Driver/CouchQueryProvider.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ private object InvokeUnsupportedMethodCallExpression(object result, MethodCallEx
142142
MethodInfo queryableMethodInfo = methodCallExpression.Method;
143143
Expression[] queryableMethodArguments = methodCallExpression.Arguments.ToArray();
144144

145-
// Since Max and Min are not map 1 to 1 from Queryable to Enumerable
145+
// Since Max and Min are not map 1 to 1 from Queryable to Enumerable
146146
// they need to be handled differently
147147
MethodInfo FindEnumerableMethod()
148148
{

src/CouchDB.Driver/QueryTranslator.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
#pragma warning disable IDE0058 // Expression value is never used
88
namespace CouchDB.Driver
9-
{
9+
{
1010
internal partial class QueryTranslator : ExpressionVisitor
1111
{
1212
private readonly CouchSettings _settings;

tests/CouchDB.Driver.E2ETests/Client_Tests.cs

+36
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,42 @@ public async Task Crud()
4343
await client.DeleteDatabaseAsync<Rebel>().ConfigureAwait(false);
4444
}
4545
}
46+
47+
[Fact]
48+
public async Task Crud_SpecialCharacters()
49+
{
50+
var databaseName = "rebel0_$()+/-";
51+
52+
using (var client = new CouchClient("http://localhost:5984"))
53+
{
54+
IEnumerable<string> dbs = await client.GetDatabasesNamesAsync().ConfigureAwait(false);
55+
CouchDatabase<Rebel> rebels = client.GetDatabase<Rebel>(databaseName);
56+
57+
if (dbs.Contains(rebels.Database))
58+
{
59+
await client.DeleteDatabaseAsync<Rebel>(databaseName).ConfigureAwait(false);
60+
}
61+
62+
rebels = await client.CreateDatabaseAsync<Rebel>(databaseName).ConfigureAwait(false);
63+
64+
Rebel luke = await rebels.CreateAsync(new Rebel { Name = "Luke", Age = 19 }).ConfigureAwait(false);
65+
Assert.Equal("Luke", luke.Name);
66+
67+
luke.Surname = "Skywalker";
68+
luke = await rebels.CreateOrUpdateAsync(luke).ConfigureAwait(false);
69+
Assert.Equal("Skywalker", luke.Surname);
70+
71+
luke = await rebels.FindAsync(luke.Id).ConfigureAwait(false);
72+
Assert.Equal(19, luke.Age);
73+
74+
await rebels.DeleteAsync(luke).ConfigureAwait(false);
75+
luke = await rebels.FindAsync(luke.Id).ConfigureAwait(false);
76+
Assert.Null(luke);
77+
78+
await client.DeleteDatabaseAsync<Rebel>(databaseName).ConfigureAwait(false);
79+
}
80+
}
81+
4682
[Fact]
4783
public async Task Users()
4884
{

0 commit comments

Comments
 (0)