001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.eclipse.aether.transfer; 020 021import org.eclipse.aether.RepositoryException; 022 023/** 024 * Thrown in case of a checksum failure during an artifact/metadata download. This exception is usually thrown in 025 * following cases: 026 * <ul> 027 * <li>actual checksum <em>mismatch</em>, see {@link #mismatch(String, String, String)}</li> 028 * <li>lack of required checksums, see {@link #noneAvailable(String, String)}</li> 029 * <li>processing problem during checksum checks (ie IO problem), see {@link #processingFailure(String, Throwable)}</li> 030 * </ul> 031 * It is resolver expectation to provide most available information to caller. 032 */ 033public class ChecksumFailureException extends RepositoryException { 034 035 /** The expected checksum as declared by the hosting repository. */ 036 private final String expected; 037 038 /** The kind of the expected checksum. */ 039 private final String expectedKind; 040 041 /** The actual checksum as computed from the local bytes. */ 042 private final String actual; 043 044 /** Whether retrying the download might solve the checksum failure. */ 045 private final boolean retryWorthy; 046 047 /** 048 * Use in case of checksum mismatch. Creates a new exception with the specified expected, expected kind and actual 049 * checksum. The resulting exception is {@link #isRetryWorthy() retry-worthy}. The checksum match check should 050 * have already happened, this method does not check for any kind of inequality. 051 * 052 * @param expected The expected checksum as declared by the hosting repository, may be {@code null}. 053 * @param expectedKind The expected checksum kind, may be {@code null}. 054 * @param actual The actual checksum as computed from the local bytes, may be {@code null}. 055 * @return The new exception, never {@code null}. 056 * @since 2.0.19 057 */ 058 public static ChecksumFailureException mismatch(String expected, String expectedKind, String actual) { 059 return mismatchDetail(null, expected, expectedKind, actual); 060 } 061 062 /** 063 * Use in case of checksum mismatch. Creates a new exception with the specified expected, expected kind and actual 064 * checksum. The resulting exception is {@link #isRetryWorthy() retry-worthy}. The checksum match check should 065 * have already happened, this method does not check for any kind of inequality. 066 * 067 * @param detail The extra detail/information regarding mismatch. 068 * @param expected The expected checksum as declared by the hosting repository, may be {@code null}. 069 * @param expectedKind The expected checksum kind, may be {@code null}. 070 * @param actual The actual checksum as computed from the local bytes, may be {@code null}. 071 * @return The new exception, never {@code null}. 072 * @since 2.0.19 073 */ 074 public static ChecksumFailureException mismatchDetail( 075 String detail, String expected, String expectedKind, String actual) { 076 String message = "Checksum validation failed, expected '" 077 + sanitize(expected) + "'" + (expectedKind == null ? "" : " (" + sanitize(expectedKind) + ")") 078 + " but is actually '" + sanitize(actual) + "'"; 079 if (detail != null) { 080 message = message + " (" + sanitize(detail) + ")"; 081 } 082 return new ChecksumFailureException(message, null, expected, expectedKind, actual, true); 083 } 084 085 /** 086 * Replaces ISO control characters below U+0020 (except LF and TAB) and DEL (U+007F) with their visible 087 * {@code \}{@code uXXXX} escapes. The expected checksum value is remote-supplied and ends up in log output (the 088 * default "warn" checksum policy logs this exception's message as the sole integrity warning): raw terminal 089 * control characters such as ESC or CR embedded in it could erase or forge that log line. The 090 * {@link #getExpected()}, {@link #getExpectedKind()} and {@link #getActual()} accessors keep returning the 091 * raw values. 092 */ 093 private static String sanitize(String value) { 094 if (value == null) { 095 return null; 096 } 097 StringBuilder result = null; 098 for (int i = 0; i < value.length(); i++) { 099 char c = value.charAt(i); 100 if ((c < 0x20 && c != '\n' && c != '\t') || c == 0x7f) { 101 if (result == null) { 102 result = new StringBuilder(value.length() + 16); 103 result.append(value, 0, i); 104 } 105 result.append(String.format("\\u%04X", (int) c)); 106 } else if (result != null) { 107 result.append(c); 108 } 109 } 110 return result == null ? value : result.toString(); 111 } 112 113 /** 114 * Use in case of checksum not available. Optionally, one can specify which kind was not available. 115 * 116 * @param message The message. 117 * @param expectedKind The expected checksum kind, may be {@code null}. 118 * @return The new exception, never {@code null}. 119 * @since 2.0.19 120 */ 121 public static ChecksumFailureException noneAvailable(String message, String expectedKind) { 122 return new ChecksumFailureException(message, null, "", expectedKind == null ? "" : expectedKind, "", false); 123 } 124 125 /** 126 * Use in case of error, for example IO problem during checksum processing, calculation and alike. Ideally, one 127 * should specify cause as well. 128 * 129 * @param message The message. 130 * @param cause The cause. 131 * @return The new exception, never {@code null}. 132 * @since 2.0.19 133 */ 134 public static ChecksumFailureException processingFailure(String message, Throwable cause) { 135 return new ChecksumFailureException(message, cause, "", "", "", false); 136 } 137 138 /** 139 * Creates a new exception with the specified expected, expected kind and actual checksum. The resulting exception 140 * is {@link #isRetryWorthy() retry-worthy}. 141 * 142 * @param expected The expected checksum as declared by the hosting repository, may be {@code null}. 143 * @param expectedKind The expected checksum kind, may be {@code null}. 144 * @param actual The actual checksum as computed from the local bytes, may be {@code null}. 145 * @since 1.8.0 146 * @deprecated Use {@link #mismatch(String, String, String)} or other suitable helper method instead. 147 */ 148 @Deprecated 149 public ChecksumFailureException(String expected, String expectedKind, String actual) { 150 super("Checksum validation failed, expected '" 151 + sanitize(expected) + "'" + (expectedKind == null ? "" : " (" + sanitize(expectedKind) + ")") 152 + " but is actually '" + sanitize(actual) + "'"); 153 this.expected = expected; 154 this.expectedKind = expectedKind; 155 this.actual = actual; 156 this.retryWorthy = true; 157 } 158 159 /** 160 * Creates a new exception with the specified detail message. The resulting exception is not 161 * {@link #isRetryWorthy() retry-worthy}. Use this constructor ONLY in cases like "no data to work with", 162 * like missing checksums. In every other case use some other constructor. 163 * 164 * @param message The detail message, may be {@code null}. 165 * @deprecated Use {@link #noneAvailable(String, String)} or other suitable helper method instead. 166 */ 167 @Deprecated 168 public ChecksumFailureException(String message) { 169 this(message, null, "", "", "", false); 170 } 171 172 /** 173 * Creates a new exception with the specified cause. The resulting exception is not {@link #isRetryWorthy() 174 * retry-worthy}. Use this constructor in case some other error (ie IO problem) prevented checksum calculation. 175 * 176 * @param cause The exception that caused this one, may be {@code null}. 177 * @deprecated Use {@link #processingFailure(String, Throwable)} or other helper method instead. 178 */ 179 @Deprecated 180 public ChecksumFailureException(Throwable cause) { 181 this("Checksum validation failed" + getMessage(": ", cause), cause, "", "", "", false); 182 } 183 184 /** 185 * Creates a new exception with the specified detail message and cause. The resulting exception is not 186 * {@link #isRetryWorthy() retry-worthy}. Use this constructor in case some other error (ie IO problem) 187 * prevented checksum calculation. 188 * 189 * @param message The detail message, may be {@code null}. 190 * @param cause The exception that caused this one, may be {@code null}. 191 * @deprecated Use {@link #processingFailure(String, Throwable)} or other helper method instead. 192 */ 193 @Deprecated 194 public ChecksumFailureException(String message, Throwable cause) { 195 this(message, cause, "", "", "", false); 196 } 197 198 /** 199 * Creates a new exception with the specified retry flag, detail message and cause. 200 * 201 * @param retryWorthy {@code true} if the exception is retry-worthy, {@code false} otherwise. 202 * @param message The detail message, may be {@code null}. 203 * @param cause The exception that caused this one, may be {@code null}. 204 * @deprecated Do not use this constructor, it lacks information. 205 */ 206 @Deprecated 207 public ChecksumFailureException(boolean retryWorthy, String message, Throwable cause) { 208 this(message, cause, "", "", "", retryWorthy); 209 } 210 211 /** 212 * Hidden constructor, use static helper methods instead, suitable for your case. 213 */ 214 private ChecksumFailureException( 215 String message, Throwable cause, String expected, String expectedKind, String actual, boolean retryWorthy) { 216 super(message, cause); 217 this.expected = expected; 218 this.expectedKind = expectedKind; 219 this.actual = actual; 220 this.retryWorthy = retryWorthy; 221 } 222 223 /** 224 * Gets the expected checksum for the downloaded artifact/metadata. 225 * 226 * @return The expected checksum as declared by the hosting repository or {@code null} if unknown. 227 */ 228 public String getExpected() { 229 return expected; 230 } 231 232 /** 233 * Gets the expected checksum kind for the downloaded artifact/metadata. 234 * 235 * @return The expected checksum kind or {@code null} if unknown. 236 * @since 1.8.0 237 */ 238 public String getExpectedKind() { 239 return expectedKind; 240 } 241 242 /** 243 * Gets the actual checksum for the downloaded artifact/metadata. 244 * 245 * @return The actual checksum as computed from the local bytes or {@code null} if unknown. 246 */ 247 public String getActual() { 248 return actual; 249 } 250 251 /** 252 * Indicates whether the corresponding download is retry-worthy. 253 * 254 * @return {@code true} if retrying the download might solve the checksum failure, {@code false} if the checksum 255 * failure is non-recoverable. 256 */ 257 public boolean isRetryWorthy() { 258 return retryWorthy; 259 } 260}