Skip to content

Commit af94e12

Browse files
committed
Fix bugs found during code audit across api, adminapi, functional, examples
api: - Compose/copy >5GiB: send per-part x-amz-copy-source-range (was loop-invariant) - Checksum.CRC64NVME.update: bound the slicing loop by len, not p.length - messages/Filter: keep And(String,Map) tags; "exactly one" via new Utils.xor - Http BaseUrl: ELB endpoints derive the real region (was "com") - messages/Checksum.headers(): emit x-amz-checksum-<algo> - PromptObjectArgs prompt setter; ListPartsArgs.Builder extends ObjectArgs.Builder - maxKeys() null-safe; Arrays.hashCode for array-backed args - downloadObject error propagation + temp cleanup; snowball/appendObject RAF close - uploadPartsParallelly buffer return/abort; executeAsync 304 return - credential providers: AwsConfig/MinioClientConfig/MinioEnvironment NPE -> clean - GetPresignedObjectUrlArgs expiry overflow; ObjectLockConfiguration duration(); AccessControlPolicy null owner; ListenBucketNotificationArgs requires bucket; GetObjectAttributesArgs validation; LifecycleConfiguration via Utils.xor; VersioningConfiguration.excludeFolders() primitive; ReplicationConfiguration text adminapi: - updateServiceAccount newStatus nullable (no silent disable) - Status.fromString null-guard; thread signing Credentials through execute() - getBucketQuota asLong(); Crypto.encrypt exact-multiple chunk; map getters return empty map when absent functional: - PutObjectRunnable surfaces thread failures; notification tests assert; legal-hold tests fixed; stream/temp-file cleanup; TestUserAgent guards; MintLogger throws UncheckedIOException instead of blank log examples: - close getObject/Response/progress streams (try-with-resources); fix GetObjectProgressBar/SelectObjectContent object names; real file size as object size; provider isSuccessful() check; placeholder credentials Also add CLAUDE.md with build commands and architecture overview. Signed-off-by: Bala.FA <bala@minio.io>
1 parent 2424345 commit af94e12

48 files changed

Lines changed: 378 additions & 188 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/gradle.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
- name: Check limited Guava usage
2525
if: matrix.os == 'ubuntu-latest'
2626
run: |
27-
if grep --with-filename --line-number --no-messages --recursive --exclude-dir=.github "com.google.common.base.Objects" .; then
27+
if grep --with-filename --line-number --no-messages --recursive --exclude-dir=.github --exclude=CLAUDE.md "com.google.common.base.Objects" .; then
2828
echo "Error: use java.util.Objects instead of com.google.common.base.Objects"
2929
exit 1
3030
fi

adminapi/src/main/java/io/minio/admin/Crypto.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,9 @@ public static byte[] encrypt(byte[] payload, String password) throws MinioExcept
209209
boolean done = false;
210210
for (int nonceId = 1; !done; nonceId++) {
211211
int to = from + CHUNK_SIZE;
212-
if (to > payload.length) {
212+
// Use >= so a payload that is an exact multiple of CHUNK_SIZE marks its final full chunk as
213+
// the last one, rather than emitting an extra empty trailing chunk (matches madmin-go/sio).
214+
if (to >= payload.length) {
213215
additionalData = markAsLast(additionalData);
214216
to = payload.length;
215217
done = true;

adminapi/src/main/java/io/minio/admin/GetDataUsageInfoResponse.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,19 +75,25 @@ public long objectsTotalSize() {
7575
}
7676

7777
public Map<String, BucketTargetUsageInfo> objectsReplicationInfo() {
78-
return Collections.unmodifiableMap(this.objectsReplicationInfo);
78+
return this.objectsReplicationInfo == null
79+
? Collections.emptyMap()
80+
: Collections.unmodifiableMap(this.objectsReplicationInfo);
7981
}
8082

8183
public long bucketsCount() {
8284
return bucketsCount;
8385
}
8486

8587
public Map<String, BucketUsageInfo> bucketsUsageInfo() {
86-
return Collections.unmodifiableMap(this.bucketsUsageInfo);
88+
return this.bucketsUsageInfo == null
89+
? Collections.emptyMap()
90+
: Collections.unmodifiableMap(this.bucketsUsageInfo);
8791
}
8892

8993
public Map<String, Long> bucketsSizes() {
90-
return Collections.unmodifiableMap(bucketsSizes);
94+
return bucketsSizes == null
95+
? Collections.emptyMap()
96+
: Collections.unmodifiableMap(bucketsSizes);
9197
}
9298

9399
public AllTierStats tierStats() {
@@ -203,7 +209,9 @@ public long objectsCount() {
203209
}
204210

205211
public Map<String, Long> objectsSizesHistogram() {
206-
return Collections.unmodifiableMap(this.objectsSizesHistogram);
212+
return this.objectsSizesHistogram == null
213+
? Collections.emptyMap()
214+
: Collections.unmodifiableMap(this.objectsSizesHistogram);
207215
}
208216

209217
public long versionsCount() {
@@ -215,7 +223,9 @@ public long objectReplicaTotalSize() {
215223
}
216224

217225
public Map<String, BucketTargetUsageInfo> objectsReplicationInfo() {
218-
return Collections.unmodifiableMap(this.objectsReplicationInfo);
226+
return this.objectsReplicationInfo == null
227+
? Collections.emptyMap()
228+
: Collections.unmodifiableMap(this.objectsReplicationInfo);
219229
}
220230
}
221231

@@ -249,7 +259,7 @@ public static class AllTierStats {
249259
private Map<String, TierStats> tiers;
250260

251261
public Map<String, TierStats> tiers() {
252-
return Collections.unmodifiableMap(this.tiers);
262+
return this.tiers == null ? Collections.emptyMap() : Collections.unmodifiableMap(this.tiers);
253263
}
254264
}
255265
}

adminapi/src/main/java/io/minio/admin/GetServerInfoResponse.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,9 @@ public String commitID() {
289289
}
290290

291291
public Map<String, String> network() {
292-
return Collections.unmodifiableMap(this.network);
292+
return this.network == null
293+
? Collections.emptyMap()
294+
: Collections.unmodifiableMap(this.network);
293295
}
294296

295297
public List<Disk> disks() {
@@ -321,7 +323,9 @@ public GCStats gCStats() {
321323
}
322324

323325
public Map<String, String> minioEnvVars() {
324-
return Collections.unmodifiableMap(this.minioEnvVars);
326+
return this.minioEnvVars == null
327+
? Collections.emptyMap()
328+
: Collections.unmodifiableMap(this.minioEnvVars);
325329
}
326330

327331
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -613,11 +617,13 @@ public Integer totalErrorsTimeout() {
613617
}
614618

615619
public Map<String, TimedAction> lastMinute() {
616-
return Collections.unmodifiableMap(lastMinute);
620+
return lastMinute == null
621+
? Collections.emptyMap()
622+
: Collections.unmodifiableMap(lastMinute);
617623
}
618624

619625
public Map<String, String> apiCalls() {
620-
return Collections.unmodifiableMap(apiCalls);
626+
return apiCalls == null ? Collections.emptyMap() : Collections.unmodifiableMap(apiCalls);
621627
}
622628

623629
public Long totalTokens() {

adminapi/src/main/java/io/minio/admin/MinioAdminClient.java

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,12 @@ private OkHttpClient getHttpClient(PrintWriter traceStream) {
167167
}
168168

169169
private Response httpExecute(
170-
Http.Method method, Command command, Multimap<String, String> queryParamMap, byte[] body)
170+
Http.Method method,
171+
Command command,
172+
Multimap<String, String> queryParamMap,
173+
byte[] body,
174+
Credentials creds)
171175
throws IOException, MinioException {
172-
Credentials creds = getCredentials();
173-
174176
HttpUrl.Builder urlBuilder =
175177
this.baseUrl
176178
.newBuilder()
@@ -224,7 +226,21 @@ private Response execute(
224226
Http.Method method, Command command, Multimap<String, String> queryParamMap, byte[] body)
225227
throws MinioException {
226228
try {
227-
return httpExecute(method, command, queryParamMap, body);
229+
return httpExecute(method, command, queryParamMap, body, getCredentials());
230+
} catch (IOException e) {
231+
throw new MinioException(e);
232+
}
233+
}
234+
235+
private Response execute(
236+
Http.Method method,
237+
Command command,
238+
Multimap<String, String> queryParamMap,
239+
byte[] body,
240+
Credentials creds)
241+
throws MinioException {
242+
try {
243+
return httpExecute(method, command, queryParamMap, body, creds);
228244
} catch (IOException e) {
229245
throw new MinioException(e);
230246
}
@@ -258,7 +274,8 @@ public void addUser(
258274
Http.Method.PUT,
259275
Command.ADD_USER,
260276
ImmutableMultimap.of("accessKey", accessKey),
261-
Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(userInfo), creds.secretKey()))) {
277+
Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(userInfo), creds.secretKey()),
278+
creds)) {
262279
} catch (JsonProcessingException e) {
263280
throw new MinioException(e);
264281
}
@@ -292,8 +309,8 @@ public UserInfo getUserInfo(String accessKey) throws MinioException {
292309
* @throws MinioException thrown to indicate SDK exception.
293310
*/
294311
public Map<String, UserInfo> listUsers() throws MinioException {
295-
try (Response response = execute(Http.Method.GET, Command.LIST_USERS, null, null)) {
296-
Credentials creds = getCredentials();
312+
Credentials creds = getCredentials();
313+
try (Response response = execute(Http.Method.GET, Command.LIST_USERS, null, null, creds)) {
297314
byte[] jsonData = Crypto.decrypt(response.body().byteStream(), creds.secretKey());
298315
MapType mapType =
299316
OBJECT_MAPPER
@@ -459,8 +476,8 @@ public long getBucketQuota(String bucketName) throws MinioException {
459476
.stream()
460477
.filter(entry -> "quota".equals(entry.getKey()))
461478
.findFirst()
462-
.map(entry -> Long.valueOf(entry.getValue().toString()))
463-
.orElseThrow(() -> new IllegalArgumentException("found not quota"));
479+
.map(entry -> entry.getValue().asLong())
480+
.orElseThrow(() -> new IllegalArgumentException("quota not found in response"));
464481
} catch (IOException e) {
465482
throw new MinioException(e);
466483
}
@@ -676,7 +693,8 @@ public Credentials addServiceAccount(
676693
Http.Method.PUT,
677694
Command.ADD_SERVICE_ACCOUNT,
678695
null,
679-
Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(serviceAccount), creds.secretKey()))) {
696+
Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(serviceAccount), creds.secretKey()),
697+
creds)) {
680698
byte[] jsonData = Crypto.decrypt(response.body().byteStream(), creds.secretKey());
681699
return OBJECT_MAPPER.readValue(jsonData, AddServiceAccountResponse.class).credentials();
682700
} catch (JsonProcessingException e) {
@@ -691,7 +709,7 @@ public Credentials addServiceAccount(
691709
*
692710
* @param accessKey Access key.
693711
* @param newSecretKey New secret key.
694-
* @param newPolicy New policy as JSON string .
712+
* @param newPolicy New policy as JSON string.
695713
* @param newStatus New service account status.
696714
* @param newName New service account name.
697715
* @param newDescription New description.
@@ -702,7 +720,7 @@ public void updateServiceAccount(
702720
@Nonnull String accessKey,
703721
@Nullable String newSecretKey,
704722
@Nullable Map<String, Object> newPolicy,
705-
@Nullable boolean newStatus,
723+
@Nullable Boolean newStatus,
706724
@Nullable String newName,
707725
@Nullable String newDescription,
708726
@Nullable ZonedDateTime newExpiration)
@@ -724,7 +742,7 @@ public void updateServiceAccount(
724742
serviceAccount.put("newSecretKey", newSecretKey);
725743
}
726744
if (newPolicy != null && !newPolicy.isEmpty()) serviceAccount.put("newPolicy", newPolicy);
727-
serviceAccount.put("newStatus", newStatus ? "on" : "off");
745+
if (newStatus != null) serviceAccount.put("newStatus", newStatus ? "on" : "off");
728746
if (newName != null && !newName.isEmpty()) serviceAccount.put("newName", newName);
729747
if (newDescription != null && !newDescription.isEmpty()) {
730748
serviceAccount.put("newDescription", newDescription);
@@ -739,7 +757,8 @@ public void updateServiceAccount(
739757
Http.Method.POST,
740758
Command.UPDATE_SERVICE_ACCOUNT,
741759
ImmutableMultimap.of("accessKey", accessKey),
742-
Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(serviceAccount), creds.secretKey()))) {
760+
Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(serviceAccount), creds.secretKey()),
761+
creds)) {
743762
} catch (JsonProcessingException e) {
744763
throw new MinioException(e);
745764
}
@@ -777,13 +796,14 @@ public ListServiceAccountResponse listServiceAccount(@Nonnull String username)
777796
throw new IllegalArgumentException("user name must be provided");
778797
}
779798

799+
Credentials creds = getCredentials();
780800
try (Response response =
781801
execute(
782802
Http.Method.GET,
783803
Command.LIST_SERVICE_ACCOUNTS,
784804
ImmutableMultimap.of("user", username),
785-
null)) {
786-
Credentials creds = getCredentials();
805+
null,
806+
creds)) {
787807
byte[] jsonData = Crypto.decrypt(response.body().byteStream(), creds.secretKey());
788808
return OBJECT_MAPPER.readValue(jsonData, ListServiceAccountResponse.class);
789809
} catch (IOException e) {
@@ -804,13 +824,14 @@ public GetServiceAccountInfoResponse getServiceAccountInfo(@Nonnull String acces
804824
if (accessKey == null || accessKey.isEmpty()) {
805825
throw new IllegalArgumentException("access key must be provided");
806826
}
827+
Credentials creds = getCredentials();
807828
try (Response response =
808829
execute(
809830
Http.Method.GET,
810831
Command.INFO_SERVICE_ACCOUNT,
811832
ImmutableMultimap.of("accessKey", accessKey),
812-
null)) {
813-
Credentials creds = getCredentials();
833+
null,
834+
creds)) {
814835
byte[] jsonData = Crypto.decrypt(response.body().byteStream(), creds.secretKey());
815836
return OBJECT_MAPPER.readValue(jsonData, GetServiceAccountInfoResponse.class);
816837
} catch (IOException e) {
@@ -824,7 +845,7 @@ private PolicyAssociationResponse attachDetachPolicy(
824845
@Nullable String user,
825846
@Nullable String group)
826847
throws MinioException {
827-
if (!(user != null ^ group != null)) {
848+
if (!Utils.xor(user, group)) {
828849
throw new IllegalArgumentException("either user or group must be provided");
829850
}
830851

@@ -842,7 +863,8 @@ private PolicyAssociationResponse attachDetachPolicy(
842863
Http.Method.POST,
843864
command,
844865
null,
845-
Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(map), creds.secretKey()))) {
866+
Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(map), creds.secretKey()),
867+
creds)) {
846868
return OBJECT_MAPPER.readValue(
847869
Crypto.decrypt(response.body().byteStream(), creds.secretKey()),
848870
PolicyAssociationResponse.class);

adminapi/src/main/java/io/minio/admin/Status.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public static Status fromString(String statusString) {
4545
return DISABLED;
4646
}
4747

48-
if (statusString.isEmpty()) {
48+
if (statusString == null || statusString.isEmpty()) {
4949
return null;
5050
}
5151

api/src/main/java/io/minio/AppendObjectArgs.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ public boolean equals(Object o) {
135135

136136
@Override
137137
public int hashCode() {
138-
return Objects.hash(super.hashCode(), filename, stream, data, length, chunkSize);
138+
return Objects.hash(
139+
super.hashCode(), filename, stream, Arrays.hashCode(data), length, chunkSize);
139140
}
140141
}

api/src/main/java/io/minio/BaseS3Client.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -406,12 +406,13 @@ private void onResponse(final Response response) throws IOException {
406406
if (!s3request.method().equals(Http.Method.HEAD)
407407
&& (contentType == null
408408
|| !Arrays.asList(contentType.split(";")).contains("application/xml"))) {
409-
if (response.code() == 304 && response.body().contentLength() == 0) {
409+
if (response.code() == 304 && errorXml.isEmpty()) {
410410
completableFuture.completeExceptionally(
411411
new ServerException(
412412
"server failed with HTTP status code " + response.code(),
413413
response.code(),
414414
traceBuilder.toString()));
415+
return;
415416
}
416417

417418
completableFuture.completeExceptionally(
@@ -475,8 +476,8 @@ private void onResponse(final Response response) throws IOException {
475476
break;
476477
case 409:
477478
if (s3request.bucket() != null) {
478-
code = NO_SUCH_BUCKET;
479-
message = NO_SUCH_BUCKET_MESSAGE;
479+
code = "Conflict";
480+
message = "Bucket not empty";
480481
} else {
481482
code = "ResourceConflict";
482483
message = "Request resource conflicts";

api/src/main/java/io/minio/Checksum.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,12 +343,13 @@ public CRC64NVME() {}
343343

344344
@Override
345345
public void update(byte[] p, int off, int len) {
346+
int limit = off + len;
346347
java.nio.ByteBuffer byteBuffer = java.nio.ByteBuffer.wrap(p, off, len);
347348
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
348349
int offset = byteBuffer.position();
349350

350351
crc = ~crc;
351-
while (p.length >= 64 && (p.length - offset) > 8) {
352+
while (len >= 64 && (limit - offset) > 8) {
352353
long value = byteBuffer.getLong();
353354
crc ^= value;
354355
crc =
@@ -363,7 +364,7 @@ public void update(byte[] p, int off, int len) {
363364
offset = byteBuffer.position();
364365
}
365366

366-
for (; offset < len; offset++) {
367+
for (; offset < limit; offset++) {
367368
crc = CRC64_TABLE[(int) ((crc ^ (long) p[offset]) & 0xFF)] ^ (crc >>> 8);
368369
}
369370

api/src/main/java/io/minio/CompleteMultipartUploadArgs.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ public boolean equals(Object o) {
132132

133133
@Override
134134
public int hashCode() {
135-
return Objects.hash(super.hashCode(), uploadId, parts, ssec, delayMs, maxRetries);
135+
return Objects.hash(
136+
super.hashCode(), uploadId, Arrays.hashCode(parts), ssec, delayMs, maxRetries);
136137
}
137138
}

0 commit comments

Comments
 (0)