Repository: bazelbuild/bazel
Issue: Bazel Unrecoverable Error with Capital Letters in SHA256 String
Labels: type: bug
team-ExternalDeps
untriaged

### Description of the bug:


If there is a capital letter present in the SHA256 string bazel crashes with the fatal error contained below. Some tools, specifically [Get-FileHash](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-filehash?view=powershell-7.3) on Windows return upper case SHA256 strings.

The `Illegal hexadecimal character` error message does not make it clear that the problem is that uppercase letters are not valid in a bazel SHA256 string.

The code should either be updated so that letter case doesn't matter or the error message should specifically state that upper case letters are not accepted. 

---
```
java.lang.RuntimeException: Unrecoverable error while evaluating node 'REPOSITORY_DIRECTORY:@bazel_skylib' (requested by nodes 'PACKAGE_LOOKUP:@bazel_skylib//')
        at com.google.devtools.build.skyframe.AbstractParallelEvaluator$Evaluate.run(AbstractParallelEvaluator.java:642)
        at com.google.devtools.build.lib.concurrent.AbstractQueueVisitor$WrappedRunnable.run(AbstractQueueVisitor.java:382)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at java.base/java.lang.Thread.run(Unknown Source)
Caused by: net.starlark.java.eval.Starlark$UncheckedEvalException: IllegalArgumentException thrown during Starlark evaluation (LOADING)
        at <starlark>.download_and_extract(<builtin>:0)
        at <starlark>._http_archive_impl(/private/var/tmp/_bazel_abeattie/6d30cdea4ede9832a87c4794c274b995/external/bazel_tools/tools/build_defs/repo/http.bzl:132)
Caused by: java.lang.IllegalArgumentException: Illegal hexadecimal character: D
        at com.google.common.hash.HashCode.decode(HashCode.java:360)
        at com.google.common.hash.HashCode.fromString(HashCode.java:346)
        at com.google.devtools.build.lib.bazel.repository.downloader.Checksum.fromString(Checksum.java:47)
        at com.google.devtools.build.lib.bazel.repository.starlark.StarlarkBaseExternalContext.validateChecksum(StarlarkBaseExternalContext.java:302)
        at com.google.devtools.build.lib.bazel.repository.starlark.StarlarkBaseExternalContext.downloadAndExtract(StarlarkBaseExternalContext.java:650)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.base/java.lang.reflect.Method.invoke(Unknown Source)
        at net.starlark.java.eval.MethodDescriptor.call(MethodDescriptor.java:162)
        at net.starlark.java.eval.BuiltinFunction.fastcall(BuiltinFunction.java:77)
        at net.starlark.java.eval.Starlark.fastcall(Starlark.java:638)
        at net.starlark.java.eval.Eval.evalCall(Eval.java:682)
        at net.starlark.java.eval.Eval.eval(Eval.java:497)
        at net.starlark.java.eval.Eval.execAssignment(Eval.java:109)
        at net.starlark.java.eval.Eval.exec(Eval.java:268)
        at net.starlark.java.eval.Eval.execStatements(Eval.java:82)
        at net.starlark.java.eval.Eval.execFunctionBody(Eval.java:66)
        at net.starlark.java.eval.StarlarkFunction.fastcall(StarlarkFunction.java:173)
        at net.starlark.java.eval.Starlark.fastcall(Starlark.java:638)
        at net.starlark.java.eval.Starlark.call(Starlark.java:604)
        at com.google.devtools.build.lib.bazel.repository.starlark.StarlarkRepositoryFunction.fetch(StarlarkRepositoryFunction.java:220)
        at com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction.fetchRepository(RepositoryDelegatorFunction.java:413)
        at com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction.compute(RepositoryDelegatorFunction.java:344)
        at com.google.devtools.build.skyframe.AbstractParallelEvaluator$Evaluate.run(AbstractParallelEvaluator.java:571)
        at com.google.devtools.build.lib.concurrent.AbstractQueueVisitor$WrappedRunnable.run(AbstractQueueVisitor.java:382)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at java.base/java.lang.Thread.run(Unknown Source)
```

### What's the simplest, easiest way to reproduce this bug? Please provide a minimal example if possible.


Fails:
```
http_archive(
    name = "bazel_skylib",
    sha256 = "74D544D96F4A5BB630D465CA8BBCFE231E3594E5AAE57E1EDBF17A6EB3CA2506",
    urls = [
        "https://storage.googleapis.com/mirror.tensorflow.org/github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
        "https://github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
    ],
)
```
Succeeds:
```
http_archive(
    name = "bazel_skylib",
    sha256 = "74d544d96f4a5bb630d465ca8bbcfe231e3594e5aae57e1edbf17a6eb3ca2506",
    urls = [
        "https://storage.googleapis.com/mirror.tensorflow.org/github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
        "https://github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
    ],
)
```

### Which operating system are you running Bazel on?


Windows & MacOS

### What is the output of `bazel info release`?


6.1.1

### If `bazel info release` returns `development version` or `(@non-git)`, tell us how you built Bazel.


_No response_

### What's the output of `git remote get-url origin; git rev-parse master; git rev-parse HEAD` ?


_No response_

### Have you found anything relevant by searching the web?

_No response_

### Any other information, logs, or outputs that you want to share?

_No response_

Issue comments:

Pull request review comments:

Patch that resolved the issue:
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/Checksum.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/Checksum.java
index a63e1b4f4a5bf9..09b3aeea884ac0 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/Checksum.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/Checksum.java
@@ -14,6 +14,7 @@
 
 package com.google.devtools.build.lib.bazel.repository.downloader;
 
+import com.google.common.base.Ascii;
 import com.google.common.hash.HashCode;
 import com.google.devtools.build.lib.bazel.repository.cache.RepositoryCache.KeyType;
 import java.util.Base64;
@@ -44,7 +45,7 @@ public static Checksum fromString(KeyType keyType, String hash) throws InvalidCh
     if (!keyType.isValid(hash)) {
       throw new InvalidChecksumException(keyType, hash);
     }
-    return new Checksum(keyType, HashCode.fromString(hash));
+    return new Checksum(keyType, HashCode.fromString(Ascii.toLowerCase(hash)));
   }
 
   /** Constructs a new Checksum from a hash in Subresource Integrity format. */


Test patch:
diff --git a/src/test/shell/bazel/external_integration_test.sh b/src/test/shell/bazel/external_integration_test.sh
index 7216fd1412760a..a6092e9110164b 100755
--- a/src/test/shell/bazel/external_integration_test.sh
+++ b/src/test/shell/bazel/external_integration_test.sh
@@ -221,6 +221,22 @@ EOF
   assert_contains "test content" "${base_external_path}/test_dir/test_file"
 }
 
+function test_http_archive_upper_case_sha() {
+  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+http_archive(
+    name = 'test_zstd_repo',
+    url = 'file://$(rlocation io_bazel/src/test/shell/bazel/testdata/zstd_test_archive.tar.zst)',
+    sha256 = '12B0116F2A3C804859438E102A8A1D5F494C108D1B026DA9F6CA55FB5107C7E9',
+    build_file_content = 'filegroup(name="x", srcs=glob(["*"]))',
+)
+EOF
+  bazel build @test_zstd_repo//...
+
+  base_external_path=bazel-out/../external/test_zstd_repo
+  assert_contains "test content" "${base_external_path}/test_dir/test_file"
+}
+
 function test_http_archive_no_server() {
   cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
 load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")