/* This code is part of Freenet. It is distributed under the GNU General * Public License, version 2 (or at your option any later version). See * http://www.gnu.org/ for further details of the GPL. */ package freenet.client; import java.util.HashMap; import freenet.client.filter.DataFilterException; import freenet.keys.FreenetURI; import freenet.l10n.NodeL10n; import freenet.support.Logger; /** * Thrown when a high-level request (fetch) fails. Indicates why, whether it is worth retrying, and may give a * new URI to try, the expected size of the file, its expected MIME type, and whether these are reliable. * For most failure modes, except INTERNAL_ERROR there will be no stack trace, or it will be unhelpful or * inaccurate. */ public class FetchException extends Exception implements Cloneable { private static volatile boolean logMINOR; static { Logger.registerClass(FetchException.class); } private static final long serialVersionUID = -1106716067841151962L; /** Failure mode */ public final FetchExceptionMode mode; /** Try this URI instead. If we fetch a USK and there is a more recent version, for example, we will get * a FetchException, but it will give a new URI to try so we can update our links, bookmarks, or convert * it to an HTTP Permanent Redirect. */ public final FreenetURI newURI; /** The expected size of the data had the fetch succeeded, or -1. May not be accurate. If retrying * after TOO_BIG, you need to set the temporary and final data limits to at least this big! */ public long expectedSize; /** The expected final MIME type, or null. */ String expectedMimeType; /** If true, the expected MIME type and size are probably accurate. */ boolean finalizedSizeAndMimeType; /** Do we know the expected MIME type of the data? */ public String getExpectedMimeType() { return expectedMimeType; } /** Do we have any idea of the final size of the data? */ public boolean finalizedSize() { return finalizedSizeAndMimeType; } /** If there are many failures, usually in a splitfile fetch, tracks the number of failures of each * type. */ public final FailureCodeTracker errorCodes; /** Extra information about the failure. */ public final String extraMessage; /** Get the failure mode. */ public FetchExceptionMode getMode() { return mode; } public FetchException(FetchExceptionMode m) { super(getMessage(m)); extraMessage = null; mode = m; errorCodes = null; newURI = null; expectedSize = -1; if(mode == FetchExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "FetchException("+getMessage(mode)+ ')', this); } public FetchException(FetchExceptionMode m, long expectedSize, boolean finalizedSize, String expectedMimeType) { super(getMessage(m)); extraMessage = null; this.finalizedSizeAndMimeType = finalizedSize; mode = m; errorCodes = null; newURI = null; this.expectedSize = expectedSize; this.expectedMimeType = expectedMimeType; if(mode == FetchExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "FetchException("+getMessage(mode)+ ')', this); } public FetchException(FetchExceptionMode m, long expectedSize, boolean finalizedSize, String expectedMimeType, FreenetURI uri) { super(getMessage(m)); extraMessage = null; this.finalizedSizeAndMimeType = finalizedSize; mode = m; errorCodes = null; newURI = uri; this.expectedSize = expectedSize; this.expectedMimeType = expectedMimeType; if(mode == FetchExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "FetchException("+getMessage(mode)+ ')', this); } public FetchException(MetadataParseException e) { super(getMessage(FetchExceptionMode.INVALID_METADATA)+": "+e.getMessage()); extraMessage = e.getMessage(); mode = FetchExceptionMode.INVALID_METADATA; errorCodes = null; initCause(e); newURI = null; expectedSize = -1; if(logMINOR) Logger.minor(this, "FetchException("+getMessage(mode)+ ')', this); } public FetchException(ArchiveFailureException e) { super(getMessage(FetchExceptionMode.ARCHIVE_FAILURE)+": "+e.getMessage()); extraMessage = e.getMessage(); mode = FetchExceptionMode.ARCHIVE_FAILURE; errorCodes = null; newURI = null; initCause(e); expectedSize = -1; if(logMINOR) Logger.minor(this, "FetchException("+getMessage(mode)+ ')', this); } public FetchException(ArchiveRestartException e) { super(getMessage(FetchExceptionMode.ARCHIVE_RESTART)+": "+e.getMessage()); extraMessage = e.getMessage(); mode = FetchExceptionMode.ARCHIVE_FAILURE; errorCodes = null; initCause(e); newURI = null; expectedSize = -1; if(logMINOR) Logger.minor(this, "FetchException("+getMessage(mode)+ ')', this); } public FetchException(FetchExceptionMode mode, Throwable t) { super(getMessage(mode)+": "+t.getMessage()); extraMessage = t.getMessage(); this.mode = mode; errorCodes = null; initCause(t); newURI = null; expectedSize = -1; if(mode == FetchExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "FetchException("+getMessage(mode)+ ')', this); } public FetchException(FetchExceptionMode mode, String reason, Throwable t) { super(reason+" : "+getMessage(mode)+": "+t.getMessage()); extraMessage = t.getMessage(); this.mode = mode; errorCodes = null; initCause(t); newURI = null; expectedSize = -1; if(mode == FetchExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "FetchException("+getMessage(mode)+ ')', this); } public FetchException(FetchExceptionMode mode, long expectedSize, String reason, Throwable t, String expectedMimeType) { super(reason+" : "+getMessage(mode)+": "+t.getMessage()); extraMessage = t.getMessage(); this.mode = mode; this.expectedSize = expectedSize; this.expectedMimeType = expectedMimeType; errorCodes = null; initCause(t); newURI = null; if(mode == FetchExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "FetchException("+getMessage(mode)+ ')', this); } public FetchException(long expectedSize, DataFilterException t, String expectedMimeType) { super(getMessage(FetchExceptionMode.CONTENT_VALIDATION_FAILED)+" "+NodeL10n.getBase().getString("FetchException.unsafeContentDetails")+" "+t.getMessage()); extraMessage = t.getMessage(); this.mode = FetchExceptionMode.CONTENT_VALIDATION_FAILED; this.expectedSize = expectedSize; this.expectedMimeType = expectedMimeType; errorCodes = null; initCause(t); newURI = null; if(logMINOR) Logger.minor(this, "FetchException("+getMessage(mode)+ ')', this); } public FetchException(FetchExceptionMode mode, long expectedSize, Throwable t, String expectedMimeType) { super(getMessage(mode)+": "+t.getMessage()); extraMessage = t.getMessage(); this.mode = mode; this.expectedSize = expectedSize; this.expectedMimeType = expectedMimeType; errorCodes = null; initCause(t); newURI = null; if(mode == FetchExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "FetchException("+getMessage(mode)+ ')', this); } public FetchException(FetchExceptionMode mode, FailureCodeTracker errorCodes) { super(getMessage(mode)); if(errorCodes.isEmpty()) { Logger.error(this, "Failing with no error codes?!", new Exception("error")); } extraMessage = null; this.mode = mode; this.errorCodes = errorCodes; newURI = null; expectedSize = -1; if(mode == FetchExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "FetchException("+getMessage(mode)+ ')', this); } public FetchException(FetchExceptionMode mode, FailureCodeTracker errorCodes, String msg) { super(getMessage(mode)+": "+msg); if(errorCodes.isEmpty()) { Logger.error(this, "Failing with no error codes?!", new Exception("error")); } extraMessage = msg; this.mode = mode; this.errorCodes = errorCodes; newURI = null; expectedSize = -1; if(mode == FetchExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "FetchException("+getMessage(mode)+ ')', this); } public FetchException(FetchExceptionMode mode, String msg) { super(getMessage(mode)+": "+msg); extraMessage = msg; errorCodes = null; this.mode = mode; newURI = null; expectedSize = -1; if(mode == FetchExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "FetchException("+getMessage(mode)+ ')', this); } public FetchException(FetchExceptionMode mode, FreenetURI newURI) { super(getMessage(mode)); extraMessage = null; this.mode = mode; errorCodes = null; this.newURI = newURI; expectedSize = -1; if(mode == FetchExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "FetchException("+getMessage(mode)+ ')', this); } public FetchException(FetchExceptionMode mode, String msg, FreenetURI uri) { super(getMessage(mode)+": "+msg); extraMessage = msg; errorCodes = null; this.mode = mode; newURI = uri; expectedSize = -1; if(mode == FetchExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "FetchException("+getMessage(mode)+ ')', this); } public FetchException(FetchException e, FetchExceptionMode newMode) { super(getMessage(newMode)+(e.extraMessage != null ? ": "+e.extraMessage : "")); this.mode = newMode; this.newURI = e.newURI; this.errorCodes = e.errorCodes; this.expectedMimeType = e.expectedMimeType; this.expectedSize = e.expectedSize; this.extraMessage = e.extraMessage; this.finalizedSizeAndMimeType = e.finalizedSizeAndMimeType; if(mode == FetchExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "FetchException("+getMessage(mode)+ ')', this); } public FetchException(FetchException e, FreenetURI uri) { super(e.getMessage()); if(e.getCause() != null) initCause(e.getCause()); this.mode = e.mode; this.newURI = uri; this.errorCodes = e.errorCodes; this.expectedMimeType = e.expectedMimeType; this.expectedSize = e.expectedSize; this.extraMessage = e.extraMessage; this.finalizedSizeAndMimeType = e.finalizedSizeAndMimeType; if(mode == FetchExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "FetchException("+getMessage(mode)+ ')', this); } public FetchException(FetchException e) { super(e.getMessage()); initCause(e); this.mode = e.mode; this.newURI = e.newURI; this.errorCodes = e.errorCodes == null ? null : e.errorCodes.clone(); this.expectedMimeType = e.expectedMimeType; this.expectedSize = e.expectedSize; this.extraMessage = e.extraMessage; this.finalizedSizeAndMimeType = e.finalizedSizeAndMimeType; if(mode == FetchExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "FetchException("+getMessage(mode)+ ')', this); } protected FetchException() { // For serialization. mode = null; newURI = null; errorCodes = null; extraMessage = null; } /** Get the short name of this exception's failure. */ public String getShortMessage() { if (getCause() == null) return getShortMessage(mode); else return getCause().toString(); } /** Get the (localised) short name of this failure mode. */ public static String getShortMessage(FetchExceptionMode mode) { // FIXME change the l10n to use the names rather than codes int code = mode.code; String ret = NodeL10n.getBase().getString("FetchException.shortError."+code); if(ret == null || ret.equals("")) return "Unknown code "+mode; else return ret; } @Override public String toString() { StringBuilder sb = new StringBuilder(200); sb.append("FetchException:"); sb.append(getMessage(mode)); sb.append(':'); sb.append(newURI); sb.append(':'); sb.append(expectedSize); sb.append(':'); sb.append(expectedMimeType); sb.append(':'); sb.append(finalizedSizeAndMimeType); sb.append(':'); sb.append(errorCodes); sb.append(':'); sb.append(extraMessage); return sb.toString(); } public String toUserFriendlyString() { if(extraMessage == null) return getShortMessage(mode); else return getShortMessage(mode) + " : " + extraMessage; } /** Get the (localised) long explanation for this failure mode. */ public static String getMessage(FetchExceptionMode mode) { if(mode == null) throw new NullPointerException(); int code = mode.code; // FIXME change the l10n to use the names rather than codes String ret = NodeL10n.getBase().getString("FetchException.longError."+code); if(ret == null) return "Unknown fetch error code: "+mode; else return ret; } private static final HashMap<Integer, FetchExceptionMode> modes = new HashMap<Integer, FetchExceptionMode>(); // Modes should stay the same even if we remove some elements. public static enum FetchExceptionMode { // FIXME many of these are not used any more /** Too many levels of recursion into archives */ @Deprecated // not used TOO_DEEP_ARCHIVE_RECURSION(1), /** Don't know what to do with splitfile */ @Deprecated // not used UNKNOWN_SPLITFILE_METADATA(2), /** Don't know what to do with metadata */ UNKNOWN_METADATA(3), /** Got a MetadataParseException */ INVALID_METADATA(4), /** Got an ArchiveFailureException */ ARCHIVE_FAILURE(5), /** Failed to decode a block. But we found it i.e. it is valid on the network level. */ BLOCK_DECODE_ERROR(6), /** Too many split metadata levels */ @Deprecated // not used TOO_MANY_METADATA_LEVELS(7), /** Too many archive restarts */ TOO_MANY_ARCHIVE_RESTARTS(8), /** Too deep recursion */ // FIXME some TOO_MUCH_RECURSION may be TOO_DEEP_ARCHIVE_RECURSION TOO_MUCH_RECURSION(9), /** Tried to access an archive file but not in an archive */ NOT_IN_ARCHIVE(10), /** Too many meta strings. E.g. requesting CHK@blah,blah,blah as CHK@blah,blah,blah/filename.ext */ TOO_MANY_PATH_COMPONENTS(11), /** Failed to read from or write to a bucket; a kind of internal error */ BUCKET_ERROR(12), /** Data not found */ DATA_NOT_FOUND(13), /** Route not found */ ROUTE_NOT_FOUND(14), /** Downstream overload */ REJECTED_OVERLOAD(15), /** Too many redirects */ @Deprecated // not used TOO_MANY_REDIRECTS(16), /** An internal error occurred */ INTERNAL_ERROR(17), /** The node found the data but the transfer failed */ TRANSFER_FAILED(18), /** Splitfile error. This should be a SplitFetchException. */ SPLITFILE_ERROR(19), /** Invalid URI. */ INVALID_URI(20), /** Too big */ TOO_BIG(21), /** Metadata too big */ TOO_BIG_METADATA(22), /** Splitfile has too big segments */ TOO_MANY_BLOCKS_PER_SEGMENT(23), /** Not enough meta strings in URI given and no default document */ NOT_ENOUGH_PATH_COMPONENTS(24), /** Explicitly cancelled */ CANCELLED(25), /** Archive restart */ ARCHIVE_RESTART(26), /** There is a more recent version of the USK, ~= HTTP 301; FProxy will turn this into a 301 */ PERMANENT_REDIRECT(27), /** Not all data was found; some DNFs but some successes */ ALL_DATA_NOT_FOUND(28), /** Requestor specified a list of allowed MIME types, and the key's type wasn't in the list */ WRONG_MIME_TYPE(29), /** A node killed the request because it had recently been tried and had DNFed */ RECENTLY_FAILED(30), /** Content filtration has generally failed to produce clean data */ CONTENT_VALIDATION_FAILED(31), /** The content filter does not recognize this data type */ CONTENT_VALIDATION_UNKNOWN_MIME(32), /** The content filter knows this data type is dangerous */ CONTENT_VALIDATION_BAD_MIME(33), /** The metadata specified a hash but the data didn't match it. */ CONTENT_HASH_FAILED(34), /** FEC decode produced a block that doesn't match the data in the original splitfile. */ SPLITFILE_DECODE_ERROR(35), /** For a filtered download to disk, the MIME type is incompatible with the * extension, potentially resulting in data on disk filtered with one MIME * type but accessed by the operating system with another MIME type. This * is equivalent to it not being filtered at all i.e. potentially dangerous. */ MIME_INCOMPATIBLE_WITH_EXTENSION(36), /** Not enough disk space to start a download or the next stage of a download. */ NOT_ENOUGH_DISK_SPACE(37); public final int code; FetchExceptionMode(int code) { this.code = code; if(code < 0 || code >= UPPER_LIMIT_ERROR_CODE) throw new IllegalArgumentException(); if(modes.containsKey(code)) throw new IllegalArgumentException(); modes.put(code, this); if(code > MAX_ERROR_CODE) MAX_ERROR_CODE = code; } public static FetchExceptionMode getByCode(int code) { if(modes.get(code) == null) throw new IllegalArgumentException(); return modes.get(code); } } private static int MAX_ERROR_CODE; /** There will never be more error codes than this constant. Must not change, used for some * data structures. */ public static final int UPPER_LIMIT_ERROR_CODE = 1024; /** Is an error fatal i.e. is there no point retrying? */ public boolean isFatal() { return isFatal(mode); } /** Is an error mode fatal i.e. is there no point retrying? */ public static boolean isFatal(FetchExceptionMode mode) { switch(mode) { // Problems with the data as inserted, or the URI given. No point retrying. case ARCHIVE_FAILURE: case BLOCK_DECODE_ERROR: case TOO_MANY_PATH_COMPONENTS: case NOT_ENOUGH_PATH_COMPONENTS: case INVALID_METADATA: case NOT_IN_ARCHIVE: case TOO_DEEP_ARCHIVE_RECURSION: case TOO_MANY_ARCHIVE_RESTARTS: case TOO_MANY_METADATA_LEVELS: case TOO_MANY_REDIRECTS: case TOO_MUCH_RECURSION: case UNKNOWN_METADATA: case UNKNOWN_SPLITFILE_METADATA: case INVALID_URI: case TOO_BIG: case TOO_BIG_METADATA: case TOO_MANY_BLOCKS_PER_SEGMENT: case CONTENT_HASH_FAILED: case SPLITFILE_DECODE_ERROR: return true; // Low level errors, can be retried case DATA_NOT_FOUND: case ROUTE_NOT_FOUND: case REJECTED_OVERLOAD: case TRANSFER_FAILED: case ALL_DATA_NOT_FOUND: case RECENTLY_FAILED: // wait a bit, but fine // Not usually fatal case SPLITFILE_ERROR: return false; case BUCKET_ERROR: case INTERNAL_ERROR: case NOT_ENOUGH_DISK_SPACE: // No point retrying. return true; //The ContentFilter failed to validate the data. Retrying won't fix this. case CONTENT_VALIDATION_FAILED: case CONTENT_VALIDATION_UNKNOWN_MIME: case CONTENT_VALIDATION_BAD_MIME: case MIME_INCOMPATIBLE_WITH_EXTENSION: return true; // Wierd ones case CANCELLED: case ARCHIVE_RESTART: case PERMANENT_REDIRECT: case WRONG_MIME_TYPE: // Fatal return true; default: Logger.error(FetchException.class, "Do not know if error code is fatal: "+getMessage(mode)); return false; // assume it isn't } } public boolean isDefinitelyFatal() { return isDefinitelyFatal(mode); } public static boolean isDefinitelyFatal(FetchExceptionMode mode) { switch(mode) { // Problems with the data as inserted, or the URI given. No point retrying. case ARCHIVE_FAILURE: case BLOCK_DECODE_ERROR: case TOO_MANY_PATH_COMPONENTS: case NOT_ENOUGH_PATH_COMPONENTS: case INVALID_METADATA: case NOT_IN_ARCHIVE: case TOO_DEEP_ARCHIVE_RECURSION: case TOO_MANY_ARCHIVE_RESTARTS: case TOO_MANY_METADATA_LEVELS: case TOO_MANY_REDIRECTS: case TOO_MUCH_RECURSION: case UNKNOWN_METADATA: case UNKNOWN_SPLITFILE_METADATA: case INVALID_URI: case TOO_BIG: case TOO_BIG_METADATA: case TOO_MANY_BLOCKS_PER_SEGMENT: case CONTENT_HASH_FAILED: case SPLITFILE_DECODE_ERROR: return true; // Low level errors, can be retried case DATA_NOT_FOUND: case ROUTE_NOT_FOUND: case REJECTED_OVERLOAD: case TRANSFER_FAILED: case ALL_DATA_NOT_FOUND: case RECENTLY_FAILED: // wait a bit, but fine // Not usually fatal case SPLITFILE_ERROR: return false; case BUCKET_ERROR: case INTERNAL_ERROR: case NOT_ENOUGH_DISK_SPACE: // No point retrying. // But it's not really fatal. I.e. it's not necessarily a problem with the inserted data. return false; //The ContentFilter failed to validate the data. Retrying won't fix this. case CONTENT_VALIDATION_FAILED: case CONTENT_VALIDATION_UNKNOWN_MIME: case CONTENT_VALIDATION_BAD_MIME: case MIME_INCOMPATIBLE_WITH_EXTENSION: return true; // Wierd ones // Not necessarily a problem with the inserted data. case CANCELLED: return false; case ARCHIVE_RESTART: case PERMANENT_REDIRECT: case WRONG_MIME_TYPE: // Fatal return true; default: Logger.error(FetchException.class, "Do not know if error code is fatal: "+getMessage(mode)); return false; // assume it isn't } } /** Call to indicate the expected size and MIME type are unreliable. */ public void setNotFinalizedSize() { this.finalizedSizeAndMimeType = false; } @Override public FetchException clone() { // Cloneable shuts up findbugs but we need a deep copy. return new FetchException(this); } public boolean isDataFound() { return isDataFound(mode, errorCodes); } public static boolean isDataFound(FetchExceptionMode mode, FailureCodeTracker errorCodes) { switch(mode) { case TOO_DEEP_ARCHIVE_RECURSION: case UNKNOWN_SPLITFILE_METADATA: case TOO_MANY_REDIRECTS: case UNKNOWN_METADATA: case INVALID_METADATA: case ARCHIVE_FAILURE: case BLOCK_DECODE_ERROR: case TOO_MANY_METADATA_LEVELS: case TOO_MANY_ARCHIVE_RESTARTS: case TOO_MUCH_RECURSION: case NOT_IN_ARCHIVE: case TOO_MANY_PATH_COMPONENTS: case TOO_BIG: case TOO_BIG_METADATA: case TOO_MANY_BLOCKS_PER_SEGMENT: case NOT_ENOUGH_PATH_COMPONENTS: case ARCHIVE_RESTART: case CONTENT_VALIDATION_FAILED: case CONTENT_VALIDATION_UNKNOWN_MIME: case CONTENT_VALIDATION_BAD_MIME: case CONTENT_HASH_FAILED: case SPLITFILE_DECODE_ERROR: case NOT_ENOUGH_DISK_SPACE: return true; case SPLITFILE_ERROR: return errorCodes.isDataFound(); default: return false; } } public boolean isDNF() { switch(mode) { case DATA_NOT_FOUND: case ALL_DATA_NOT_FOUND: case RECENTLY_FAILED: return true; default: return false; } } public static boolean isErrorCode(int code) { return code >= 0 && code <= MAX_ERROR_CODE && code < UPPER_LIMIT_ERROR_CODE; } }