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 private final String expected; 036 037 private final String expectedKind; 038 039 private final String actual; 040 041 private final boolean retryWorthy; 042 043 /** 044 * Use in case of checksum mismatch. Creates a new exception with the specified expected, expected kind and actual 045 * checksum. The resulting exception is {@link #isRetryWorthy() retry-worthy}. The checksum match check should 046 * have already happened, this method does not check for any kind of inequality. 047 * 048 * @param expected The expected checksum as declared by the hosting repository, may be {@code null}. 049 * @param expectedKind The expected checksum kind, may be {@code null}. 050 * @param actual The actual checksum as computed from the local bytes, may be {@code null}. 051 * @since 2.0.19 052 */ 053 public static ChecksumFailureException mismatch(String expected, String expectedKind, String actual) { 054 return mismatchDetail(null, expected, expectedKind, actual); 055 } 056 057 /** 058 * Use in case of checksum mismatch. Creates a new exception with the specified expected, expected kind and actual 059 * checksum. The resulting exception is {@link #isRetryWorthy() retry-worthy}. The checksum match check should 060 * have already happened, this method does not check for any kind of inequality. 061 * 062 * @param detail The extra detail/information regarding mismatch. 063 * @param expected The expected checksum as declared by the hosting repository, may be {@code null}. 064 * @param expectedKind The expected checksum kind, may be {@code null}. 065 * @param actual The actual checksum as computed from the local bytes, may be {@code null}. 066 * @since 2.0.19 067 */ 068 public static ChecksumFailureException mismatchDetail( 069 String detail, String expected, String expectedKind, String actual) { 070 String message = "Checksum validation failed, expected '" 071 + sanitize(expected) + "'" + (expectedKind == null ? "" : " (" + sanitize(expectedKind) + ")") 072 + " but is actually '" + sanitize(actual) + "'"; 073 if (detail != null) { 074 message = message + " (" + sanitize(detail) + ")"; 075 } 076 return new ChecksumFailureException(message, null, expected, expectedKind, actual, true); 077 } 078 079 /** 080 * Replaces ISO control characters below U+0020 (except LF and TAB) and DEL (U+007F) with their visible 081 * {@code \}{@code uXXXX} escapes. The expected checksum value is remote-supplied and ends up in log output (the 082 * default "warn" checksum policy logs this exception's message as the sole integrity warning): raw terminal 083 * control characters such as ESC or CR embedded in it could erase or forge that log line. The 084 * {@link #getExpected()}, {@link #getExpectedKind()} and {@link #getActual()} accessors keep returning the 085 * raw values. 086 */ 087 private static String sanitize(String value) { 088 if (value == null) { 089 return null; 090 } 091 StringBuilder result = null; 092 for (int i = 0; i < value.length(); i++) { 093 char c = value.charAt(i); 094 if ((c < 0x20 && c != '\n' && c != '\t') || c == 0x7f) { 095 if (result == null) { 096 result = new StringBuilder(value.length() + 16); 097 result.append(value, 0, i); 098 } 099 result.append(String.format("\\u%04X", (int) c)); 100 } else if (result != null) { 101 result.append(c); 102 } 103 } 104 return result == null ? value : result.toString(); 105 } 106 107 /** 108 * Use in case of checksum not available. Optionally, one can specify which kind was not available. 109 * 110 * @param message The message. 111 * @param expectedKind The expected checksum kind, may be {@code null}. 112 * @since 2.0.19 113 */ 114 public static ChecksumFailureException noneAvailable(String message, String expectedKind) { 115 return new ChecksumFailureException(message, null, "", expectedKind == null ? "" : expectedKind, "", false); 116 } 117 118 /** 119 * Use in case of error, for example IO problem during checksum processing, calculation and alike. Ideally, one 120 * should specify cause as well. 121 * 122 * @param message The message. 123 * @param cause The cause. 124 * @since 2.0.19 125 */ 126 public static ChecksumFailureException processingFailure(String message, Throwable cause) { 127 return new ChecksumFailureException(message, cause, "", "", "", false); 128 } 129 130 /** 131 * Creates a new exception with the specified expected, expected kind and actual checksum. The resulting exception 132 * is {@link #isRetryWorthy() retry-worthy}. 133 * 134 * @param expected The expected checksum as declared by the hosting repository, may be {@code null}. 135 * @param expectedKind The expected checksum kind, may be {@code null}. 136 * @param actual The actual checksum as computed from the local bytes, may be {@code null}. 137 * @since 1.8.0 138 * @deprecated Use {@link #mismatch(String, String, String)} or other suitable helper method instead. 139 */ 140 @Deprecated 141 public ChecksumFailureException(String expected, String expectedKind, String actual) { 142 super("Checksum validation failed, expected '" 143 + sanitize(expected) + "'" + (expectedKind == null ? "" : " (" + sanitize(expectedKind) + ")") 144 + " but is actually '" + sanitize(actual) + "'"); 145 this.expected = expected; 146 this.expectedKind = expectedKind; 147 this.actual = actual; 148 this.retryWorthy = true; 149 } 150 151 /** 152 * Creates a new exception with the specified detail message. The resulting exception is not 153 * {@link #isRetryWorthy() retry-worthy}. Use this constructor ONLY in cases like "no data to work with", 154 * like missing checksums. In every other case use some other constructor. 155 * 156 * @param message The detail message, may be {@code null}. 157 * @deprecated Use {@link #noneAvailable(String, String)} or other suitable helper method instead. 158 */ 159 @Deprecated 160 public ChecksumFailureException(String message) { 161 this(message, null, "", "", "", false); 162 } 163 164 /** 165 * Creates a new exception with the specified cause. The resulting exception is not {@link #isRetryWorthy() 166 * retry-worthy}. Use this constructor in case some other error (ie IO problem) prevented checksum calculation. 167 * 168 * @param cause The exception that caused this one, may be {@code null}. 169 * @deprecated Use {@link #processingFailure(String, Throwable)} or other helper method instead. 170 */ 171 @Deprecated 172 public ChecksumFailureException(Throwable cause) { 173 this("Checksum validation failed" + getMessage(": ", cause), cause, "", "", "", false); 174 } 175 176 /** 177 * Creates a new exception with the specified detail message and cause. The resulting exception is not 178 * {@link #isRetryWorthy() retry-worthy}. Use this constructor in case some other error (ie IO problem) 179 * prevented checksum calculation. 180 * 181 * @param message The detail message, may be {@code null}. 182 * @param cause The exception that caused this one, may be {@code null}. 183 * @deprecated Use {@link #processingFailure(String, Throwable)} or other helper method instead. 184 */ 185 @Deprecated 186 public ChecksumFailureException(String message, Throwable cause) { 187 this(message, cause, "", "", "", false); 188 } 189 190 /** 191 * Creates a new exception with the specified retry flag, detail message and cause. 192 * 193 * @param retryWorthy {@code true} if the exception is retry-worthy, {@code false} otherwise. 194 * @param message The detail message, may be {@code null}. 195 * @param cause The exception that caused this one, may be {@code null}. 196 * @deprecated Do not use this constructor, it lacks information. 197 */ 198 @Deprecated 199 public ChecksumFailureException(boolean retryWorthy, String message, Throwable cause) { 200 this(message, cause, "", "", "", retryWorthy); 201 } 202 203 /** 204 * Hidden constructor, use static helper methods instead, suitable for your case. 205 */ 206 private ChecksumFailureException( 207 String message, Throwable cause, String expected, String expectedKind, String actual, boolean retryWorthy) { 208 super(message, cause); 209 this.expected = expected; 210 this.expectedKind = expectedKind; 211 this.actual = actual; 212 this.retryWorthy = retryWorthy; 213 } 214 215 /** 216 * Gets the expected checksum for the downloaded artifact/metadata. 217 * 218 * @return The expected checksum as declared by the hosting repository or {@code null} if unknown. 219 */ 220 public String getExpected() { 221 return expected; 222 } 223 224 /** 225 * Gets the expected checksum kind for the downloaded artifact/metadata. 226 * 227 * @return The expected checksum kind or {@code null} if unknown. 228 * @since 1.8.0 229 */ 230 public String getExpectedKind() { 231 return expectedKind; 232 } 233 234 /** 235 * Gets the actual checksum for the downloaded artifact/metadata. 236 * 237 * @return The actual checksum as computed from the local bytes or {@code null} if unknown. 238 */ 239 public String getActual() { 240 return actual; 241 } 242 243 /** 244 * Indicates whether the corresponding download is retry-worthy. 245 * 246 * @return {@code true} if retrying the download might solve the checksum failure, {@code false} if the checksum 247 * failure is non-recoverable. 248 */ 249 public boolean isRetryWorthy() { 250 return retryWorthy; 251 } 252}