From 2d97059a0696b99c018e36e701fd08cd2f91b53b Mon Sep 17 00:00:00 2001
From: xuty <xty50337@hotmail.com>
Date: Wed, 18 Aug 2021 14:14:11 +0000
Subject: [PATCH] Fixes signing error #29

---
 CHANGELOG.md               |  5 +++++
 lib/src/minio_helpers.dart | 39 ++++++++++++++++++++++++++++++++++++++
 lib/src/minio_sign.dart    |  2 +-
 test/minio_test.dart       | 11 ++++++++++-
 4 files changed, 55 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 24f18e1..5b18d25 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+# 3.0.0
+- Fixes signing error in case object name contains symbols [#29]
+
 # 2.1.0-pre
 - `getObject` now returns `MinioByteStream` with an additional `contentLength` field.
 
@@ -77,3 +80,5 @@
 ## 0.1.0
 
 - Initial version, created by Stagehand
+
+[#29]: https://github.com/xtyxtyx/minio-dart/issues/29
\ No newline at end of file
diff --git a/lib/src/minio_helpers.dart b/lib/src/minio_helpers.dart
index 0eeebe4..bebc4ff 100644
--- a/lib/src/minio_helpers.dart
+++ b/lib/src/minio_helpers.dart
@@ -1,3 +1,4 @@
+import 'package:convert/convert.dart';
 import 'package:http/http.dart';
 import 'package:mime/mime.dart' show lookupMimeType;
 import 'package:minio/src/minio_errors.dart';
@@ -231,3 +232,41 @@ void validate(Response response, {int? expect}) {
         '$expect expected, got ${response.statusCode}', null, response);
   }
 }
+
+final _a = 'a'.codeUnitAt(0);
+final _A = 'A'.codeUnitAt(0);
+final _z = 'z'.codeUnitAt(0);
+final _Z = 'Z'.codeUnitAt(0);
+final _0 = '0'.codeUnitAt(0);
+final _9 = '9'.codeUnitAt(0);
+
+final _pathIgnoredChars = {
+  '%'.codeUnitAt(0),
+  '-'.codeUnitAt(0),
+  '_'.codeUnitAt(0),
+  '.'.codeUnitAt(0),
+  '~'.codeUnitAt(0),
+  '/'.codeUnitAt(0),
+};
+
+/// encode [uri].path to HTML hex escape sequence
+String encodePath(Uri uri) {
+  final result = StringBuffer();
+  for (var char in uri.path.codeUnits) {
+    if (_A <= char && char <= _Z ||
+        _a <= char && char <= _z ||
+        _0 <= char && char <= _9) {
+      result.writeCharCode(char);
+      continue;
+    }
+
+    if (_pathIgnoredChars.contains(char)) {
+      result.writeCharCode(char);
+      continue;
+    }
+
+    result.write('%');
+    result.write(hex.encode([char]).toUpperCase());
+  }
+  return result.toString();
+}
diff --git a/lib/src/minio_sign.dart b/lib/src/minio_sign.dart
index a1fc9fe..9f62262 100644
--- a/lib/src/minio_sign.dart
+++ b/lib/src/minio_sign.dart
@@ -44,7 +44,7 @@ String getCanonicalRequest(
   List<String> signedHeaders,
   String hashedPayload,
 ) {
-  final requestResource = request.url.path;
+  final requestResource = encodePath(request.url);
   final headers = signedHeaders.map(
     (header) => '${header.toLowerCase()}:${request.headers[header]}',
   );
diff --git a/test/minio_test.dart b/test/minio_test.dart
index 02ab20b..3f00c45 100644
--- a/test/minio_test.dart
+++ b/test/minio_test.dart
@@ -362,7 +362,6 @@ void testPutObject() {
   group('putObject()', () {
     final minio = getMinioClient();
     final bucketName = DateTime.now().millisecondsSinceEpoch.toString();
-    final objectName = DateTime.now().microsecondsSinceEpoch.toString();
     final objectData = Uint8List.fromList([1, 2, 3]);
 
     setUpAll(() async {
@@ -374,6 +373,16 @@ void testPutObject() {
     });
 
     test('succeeds', () async {
+      final objectName = DateTime.now().microsecondsSinceEpoch.toString();
+      await minio.putObject(bucketName, objectName, Stream.value(objectData));
+      final stat = await minio.statObject(bucketName, objectName);
+      expect(stat.size, equals(objectData.length));
+      await minio.removeObject(bucketName, objectName);
+    });
+
+    test('works with object names with symbols', () async {
+      final objectName =
+          DateTime.now().microsecondsSinceEpoch.toString() + r'-._~,!@#$%^&*()';
       await minio.putObject(bucketName, objectName, Stream.value(objectData));
       final stat = await minio.statObject(bucketName, objectName);
       expect(stat.size, equals(objectData.length));
-- 
GitLab