Skip to content

Commit f855f5a

Browse files
Fix URL encoding in DownloadPrivate
1 parent 2e17710 commit f855f5a

File tree

5 files changed

+42
-23
lines changed

5 files changed

+42
-23
lines changed

CloudinaryDotNet.Tests/AdminApi/RelatedAssetsTest.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Text;
34
using CloudinaryDotNet.Actions;
45
using NUnit.Framework;
@@ -65,13 +66,13 @@ public void TestAddRelatedResources()
6566
Assert.Positive(result.Success.Count);
6667
Assert.AreEqual("success", result.Success[0].Message);
6768
Assert.AreEqual("success_ids", result.Success[0].Code);
68-
Assert.AreEqual(TestIds[1], result.Success[0].Asset);
69+
Assert.AreEqual(TestIds?[1], result.Success[0].Asset);
6970
Assert.AreEqual(200, result.Success[0].Status);
7071

7172
Assert.Positive(result.Failed.Count);
7273
Assert.AreEqual("resource does not exist", result.Failed[0].Message);
7374
Assert.AreEqual("non_existing_ids", result.Failed[0].Code);
74-
Assert.AreEqual(TestIds[0], result.Failed[0].Asset);
75+
Assert.AreEqual(TestIds?[0], result.Failed[0].Asset);
7576
Assert.AreEqual(404, result.Failed[0].Status);
7677
}
7778

@@ -95,13 +96,13 @@ public void TestAddRelatedResourcesByAssetIds()
9596
Assert.Positive(result.Success.Count);
9697
Assert.AreEqual("success", result.Success[0].Message);
9798
Assert.AreEqual("success_ids", result.Success[0].Code);
98-
Assert.AreEqual(TestAssetIds[1], result.Success[0].Asset);
99+
Assert.AreEqual(TestAssetIds?[1], result.Success[0].Asset);
99100
Assert.AreEqual(200, result.Success[0].Status);
100101

101102
Assert.Positive(result.Failed.Count);
102103
Assert.AreEqual("resource does not exist", result.Failed[0].Message);
103104
Assert.AreEqual("non_existing_ids", result.Failed[0].Code);
104-
Assert.AreEqual(TestAssetIds[0], result.Failed[0].Asset);
105+
Assert.AreEqual(TestAssetIds?[0], result.Failed[0].Asset);
105106
Assert.AreEqual(404, result.Failed[0].Status);
106107
}
107108

CloudinaryDotNet.Tests/Asset/UrlBuilderTest.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -536,14 +536,15 @@ public void TestDownloadPrivate()
536536
{
537537
var cloudinary = new Cloudinary("cloudinary://a:b@test123");
538538
var expiresAt = Utils.UnixTimeNowSeconds() + 7200;
539-
const string testPublicId = "zihltjwsyczm700kqj1z";
539+
const string testPublicId = "test/ecp/ij4fg5zpgsnkezxa3bxq;";
540+
const string encodedTestPublicId = "test/ecp/ij4fg5zpgsnkezxa3bxq%3B";
540541

541542
var urlPrivateImage = cloudinary.DownloadPrivate(testPublicId, expiresAt: expiresAt);
542-
var rgImage = Regex.IsMatch(urlPrivateImage, @"https://api\.cloudinary\.com/v1_1/[^/]*/image/download\?api_key=a&expires_at=" + expiresAt + @"&public_id=zihltjwsyczm700kqj1z&signature=\w{40}&timestamp=\d{10}");
543+
var rgImage = Regex.IsMatch(urlPrivateImage, @"https://api\.cloudinary\.com/v1_1/[^/]*/image/download\?api_key=a&expires_at=" + expiresAt + "&public_id=" + encodedTestPublicId + @"&signature=\w{40}&timestamp=\d{10}");
543544
Assert.True(rgImage);
544545

545546
var urlPrivateVideo = cloudinary.DownloadPrivate(testPublicId, expiresAt: expiresAt, resourceType: "video");
546-
var rgVideo = Regex.IsMatch(urlPrivateVideo, @"https://api\.cloudinary\.com/v1_1/[^/]*/video/download\?api_key=a&expires_at=" + expiresAt + @"&public_id=zihltjwsyczm700kqj1z&signature=\w{40}&timestamp=\d{10}");
547+
var rgVideo = Regex.IsMatch(urlPrivateVideo, @"https://api\.cloudinary\.com/v1_1/[^/]*/video/download\?api_key=a&expires_at=" + expiresAt + "&public_id=" + encodedTestPublicId + @"&signature=\w{40}&timestamp=\d{10}");
547548
Assert.True(rgVideo);
548549
}
549550

CloudinaryDotNet/Cloudinary.UploadApi.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,7 @@ private string GetDownloadUrl(UrlBuilder builder, IDictionary<string, object> pa
10691069
{
10701070
m_api.FinalizeUploadParameters(parameters);
10711071
builder.SetParameters(parameters);
1072-
return builder.ToString();
1072+
return builder.ToEncodedString();
10731073
}
10741074

10751075
/// <summary>

CloudinaryDotNet/UrlBuilder.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{
33
using System;
44
using System.Collections.Generic;
5+
using System.Linq;
56

67
/// <summary>
78
/// Provides a custom constructor for uniform resource identifiers (URIs) and modifies URIs
@@ -173,6 +174,17 @@ public void SetParameters(IDictionary<string, object> @params)
173174
return Uri.AbsoluteUri;
174175
}
175176

177+
/// <summary>
178+
/// Returns a string that represents the current Url with encoded parameter values.
179+
/// </summary>
180+
/// <returns>A string that represents the URL with encoded parameter values.</returns>
181+
public string ToEncodedString()
182+
{
183+
BuildQueryString(true);
184+
185+
return Uri.AbsoluteUri;
186+
}
187+
176188
private void PopulateQueryString()
177189
{
178190
string query = Query;
@@ -200,7 +212,7 @@ private void PopulateQueryString()
200212
}
201213
}
202214

203-
private void BuildQueryString()
215+
private void BuildQueryString(bool encodeValue = false)
204216
{
205217
if (queryString == null)
206218
{
@@ -215,19 +227,7 @@ private void BuildQueryString()
215227
return;
216228
}
217229

218-
string[] keys = new string[count];
219-
string[] values = new string[count];
220-
string[] pairs = new string[count];
221-
222-
queryString.Keys.CopyTo(keys, 0);
223-
queryString.Values.CopyTo(values, 0);
224-
225-
for (int i = 0; i < count; i++)
226-
{
227-
pairs[i] = string.Concat(keys[i], "=", values[i]);
228-
}
229-
230-
Query = string.Join("&", pairs);
230+
Query = string.Join("&", queryString.Select(kvp => $"{kvp.Key}={(encodeValue ? Utils.SmartEscape(kvp.Value) : kvp.Value)}"));
231231
}
232232
}
233233
}

CloudinaryDotNet/Utils.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,23 @@ internal static string EncodeUrlSafe(byte[] bytes)
102102
return Convert.ToBase64String(bytes).Replace('+', '-').Replace('/', '_');
103103
}
104104

105+
/// <summary>
106+
/// Escapes characters in the input string based on the specified unsafe pattern.
107+
/// </summary>
108+
/// <param name="input">The input string to be escaped.</param>
109+
/// <param name="unsafePattern">The regular expression pattern for identifying unsafe characters (default: "([^a-zA-Z0-9_.\\-/:]+)").</param>
110+
/// <returns>The input string with unsafe characters replaced by their percent-encoded representation.</returns>
111+
internal static string SmartEscape(string input, string unsafePattern = "([^a-zA-Z0-9_.\\-/:]+)")
112+
{
113+
var unsafeRegex = new Regex(unsafePattern);
114+
return unsafeRegex.Replace(input, m =>
115+
{
116+
var bytes = Encoding.UTF8.GetBytes(m.Value);
117+
var hex = BitConverter.ToString(bytes).Replace("-", "%");
118+
return "%" + hex;
119+
});
120+
}
121+
105122
/// <summary>
106123
/// Computes the hash value for the specified string, using default hashing algorithm.
107124
/// </summary>

0 commit comments

Comments
 (0)