Skip to content

Commit c9b23b3

Browse files
authored
fix: implementations of FromHexString() for md5 and crc32c (#246)
1 parent 7824c15 commit c9b23b3

File tree

2 files changed

+58
-12
lines changed

2 files changed

+58
-12
lines changed

google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434
import com.google.common.io.BaseEncoding;
3535
import java.io.Serializable;
3636
import java.math.BigInteger;
37+
import java.nio.ByteBuffer;
3738
import java.util.AbstractMap;
38-
import java.util.Arrays;
3939
import java.util.Collections;
4040
import java.util.HashMap;
4141
import java.util.List;
@@ -232,6 +232,7 @@ public abstract static class Builder {
232232
*
233233
* @see <a href="https://cloud.google.com/storage/docs/hashes-etags#_JSONAPI">Hashes and ETags:
234234
* Best Practices</a>
235+
* @throws IllegalArgumentException when given an invalid hexadecimal value.
235236
*/
236237
public abstract Builder setMd5FromHexString(String md5HexString);
237238

@@ -252,6 +253,7 @@ public abstract static class Builder {
252253
*
253254
* @see <a href="https://cloud.google.com/storage/docs/hashes-etags#_JSONAPI">Hashes and ETags:
254255
* Best Practices</a>
256+
* @throws IllegalArgumentException when given an invalid hexadecimal value.
255257
*/
256258
public abstract Builder setCrc32cFromHexString(String crc32cHexString);
257259

@@ -293,7 +295,7 @@ public abstract static class Builder {
293295
}
294296

295297
static final class BuilderImpl extends Builder {
296-
298+
private final String hexDecimalValues = "0123456789abcdef";
297299
private BlobId blobId;
298300
private String generatedId;
299301
private String contentType;
@@ -442,16 +444,27 @@ public Builder setMd5(String md5) {
442444
return this;
443445
}
444446

447+
@Override
445448
public Builder setMd5FromHexString(String md5HexString) {
446449
if (md5HexString == null) {
447450
return this;
448451
}
449-
byte[] bytes = new BigInteger(md5HexString, 16).toByteArray();
450-
int leadingEmptyBytes = bytes.length - md5HexString.length() / 2;
451-
if (leadingEmptyBytes > 0) {
452-
bytes = Arrays.copyOfRange(bytes, leadingEmptyBytes, bytes.length);
452+
if (md5HexString.length() % 2 != 0) {
453+
throw new IllegalArgumentException(
454+
"each byte must be represented by 2 valid hexadecimal characters");
455+
}
456+
String md5HexStringLower = md5HexString.toLowerCase();
457+
ByteBuffer md5ByteBuffer = ByteBuffer.allocate(md5HexStringLower.length() / 2);
458+
for (int charIndex = 0; charIndex < md5HexStringLower.length(); charIndex += 2) {
459+
int higherOrderBits = this.hexDecimalValues.indexOf(md5HexStringLower.charAt(charIndex));
460+
int lowerOrderBits = this.hexDecimalValues.indexOf(md5HexStringLower.charAt(charIndex + 1));
461+
if (higherOrderBits == -1 || lowerOrderBits == -1) {
462+
throw new IllegalArgumentException(
463+
"each byte must be represented by 2 valid hexadecimal characters");
464+
}
465+
md5ByteBuffer.put((byte) (higherOrderBits << 4 | lowerOrderBits));
453466
}
454-
this.md5 = BaseEncoding.base64().encode(bytes);
467+
this.md5 = BaseEncoding.base64().encode(md5ByteBuffer.array());
455468
return this;
456469
}
457470

@@ -466,12 +479,23 @@ public Builder setCrc32cFromHexString(String crc32cHexString) {
466479
if (crc32cHexString == null) {
467480
return this;
468481
}
469-
byte[] bytes = new BigInteger(crc32cHexString, 16).toByteArray();
470-
int leadingEmptyBytes = bytes.length - crc32cHexString.length() / 2;
471-
if (leadingEmptyBytes > 0) {
472-
bytes = Arrays.copyOfRange(bytes, leadingEmptyBytes, bytes.length);
482+
if (crc32cHexString.length() % 2 != 0) {
483+
throw new IllegalArgumentException(
484+
"each byte must be represented by 2 valid hexadecimal characters");
485+
}
486+
String crc32cHexStringLower = crc32cHexString.toLowerCase();
487+
ByteBuffer crc32cByteBuffer = ByteBuffer.allocate(crc32cHexStringLower.length() / 2);
488+
for (int charIndex = 0; charIndex < crc32cHexStringLower.length(); charIndex += 2) {
489+
int higherOrderBits = this.hexDecimalValues.indexOf(crc32cHexStringLower.charAt(charIndex));
490+
int lowerOrderBits =
491+
this.hexDecimalValues.indexOf(crc32cHexStringLower.charAt(charIndex + 1));
492+
if (higherOrderBits == -1 || lowerOrderBits == -1) {
493+
throw new IllegalArgumentException(
494+
"each byte must be represented by 2 valid hexadecimal characters");
495+
}
496+
crc32cByteBuffer.put((byte) (higherOrderBits << 4 | lowerOrderBits));
473497
}
474-
this.crc32c = BaseEncoding.base64().encode(bytes);
498+
this.crc32c = BaseEncoding.base64().encode(crc32cByteBuffer.array());
475499
return this;
476500
}
477501

google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,16 @@ public class BlobInfoTest {
4949
private static final String CONTENT_LANGUAGE = "En";
5050
private static final String CRC32 = "FF00";
5151
private static final String CRC32_HEX_STRING = "145d34";
52+
private static final String CRC32_HEX_STRING_LEADING_ZEROS = "005d34";
53+
private static final String CRC32_BASE64_LEADING_ZEROS = "AF00";
5254
private static final Long DELETE_TIME = System.currentTimeMillis();
5355
private static final String ETAG = "0xFF00";
5456
private static final Long GENERATION = 1L;
5557
private static final String GENERATED_ID = "B/N:1";
5658
private static final String MD5 = "FF00";
5759
private static final String MD5_HEX_STRING = "145d34";
60+
private static final String MD5_HEX_STRING_LEADING_ZEROS = "0006a7de52b4e0b82602ce09809523ca";
61+
private static final String MD5_BASE64_LEADING_ZEROS = "AAan3lK04LgmAs4JgJUjyg==";
5862
private static final String MEDIA_LINK = "http://media/b/n";
5963
private static final Map<String, String> METADATA = ImmutableMap.of("n1", "v1", "n2", "v2");
6064
private static final Long META_GENERATION = 10L;
@@ -132,13 +136,31 @@ public void testToBuilderSetMd5FromHexString() {
132136
assertEquals(MD5, blobInfo.getMd5());
133137
}
134138

139+
@Test
140+
public void testToBuilderSetMd5FromHexStringLeadingZeros() {
141+
BlobInfo blobInfo =
142+
BlobInfo.newBuilder(BlobId.of("b2", "n2"))
143+
.setMd5FromHexString(MD5_HEX_STRING_LEADING_ZEROS)
144+
.build();
145+
assertEquals(MD5_BASE64_LEADING_ZEROS, blobInfo.getMd5());
146+
}
147+
135148
@Test
136149
public void testToBuilderSetCrc32cFromHexString() {
137150
BlobInfo blobInfo =
138151
BlobInfo.newBuilder(BlobId.of("b2", "n2")).setCrc32cFromHexString(CRC32_HEX_STRING).build();
139152
assertEquals(CRC32, blobInfo.getCrc32c());
140153
}
141154

155+
@Test
156+
public void testToBuilderSetCrc32cFromHexStringLeadingZeros() {
157+
BlobInfo blobInfo =
158+
BlobInfo.newBuilder(BlobId.of("b2", "n2"))
159+
.setCrc32cFromHexString(CRC32_HEX_STRING_LEADING_ZEROS)
160+
.build();
161+
assertEquals(CRC32_BASE64_LEADING_ZEROS, blobInfo.getCrc32c());
162+
}
163+
142164
@Test
143165
public void testToBuilderIncomplete() {
144166
BlobInfo incompleteBlobInfo = BlobInfo.newBuilder(BlobId.of("b2", "n2")).build();

0 commit comments

Comments
 (0)