/* 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.clients.fcp; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.util.HashMap; import java.util.Map; import freenet.clients.fcp.ClientGet.ReturnType; import freenet.clients.fcp.ClientRequest.Persistence; import freenet.keys.FreenetURI; import freenet.node.Node; import freenet.node.RequestStarter; import freenet.support.LogThresholdCallback; import freenet.support.Logger; import freenet.support.Logger.LogLevel; import freenet.support.SimpleFieldSet; import freenet.support.api.Bucket; import freenet.support.api.BucketFactory; import freenet.support.io.BucketTools; /** * ClientGet message. * * Example: * * ClientGet * IgnoreDS=false // true = ignore the datastore * DSOnly=false // true = only check the datastore, don't route (~= htl 0) * URI=KSK@sample.txt * Identifier=Request Number One * Verbosity=0 // no status, just tell us when it's done * ReturnType=direct // return all at once over the FCP connection * MaxSize=100 // maximum size of returned data * MaxTempSize=1000 // maximum size of intermediary data * MaxRetries=100 // automatic retry supported as an option * PriorityClass=1 // priority class 1 = interactive * Persistence=reboot // continue until node is restarted; report progress while client is * connected, including if it reconnects after losing connection * ClientToken=hello // returned in PersistentGet, a hint to the client, so the client * doesn't need to maintain its own state * IgnoreUSKDatehints=false // true = don't use USK datehints * EndMessage */ public class ClientGetMessage extends BaseDataCarryingMessage { public final static String NAME = "ClientGet"; final boolean ignoreDS; final boolean dsOnly; final FreenetURI uri; final String identifier; final int verbosity; final ReturnType returnType; final Persistence persistence; final long maxSize; final long maxTempSize; final int maxRetries; final short priorityClass; final File diskFile; final String clientToken; final boolean global; final boolean binaryBlob; final String[] allowedMIMETypes; public boolean writeToClientCache; final String charset; final boolean filterData; final boolean realTimeFlag; final boolean ignoreUSKDatehints; private Bucket initialMetadata; private final long initialMetadataLength; private static volatile boolean logMINOR; static { Logger.registerLogThresholdCallback(new LogThresholdCallback(){ @Override public void shouldUpdate(){ logMINOR = Logger.shouldLog(LogLevel.MINOR, this); } }); } public ClientGetMessage(SimpleFieldSet fs) throws MessageInvalidException { short defaultPriority; clientToken = fs.get("ClientToken"); global = fs.getBoolean("Global", false); ignoreDS = fs.getBoolean("IgnoreDS", false); dsOnly = fs.getBoolean("DSOnly", false); identifier = fs.get("Identifier"); allowedMIMETypes = fs.getAll("AllowedMIMETypes"); filterData = fs.getBoolean("FilterData", false); charset = fs.get("Charset"); if(identifier == null) throw new MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "No Identifier", null, global); try { uri = new FreenetURI(fs.get("URI")); } catch (MalformedURLException e) { throw new MessageInvalidException(ProtocolErrorMessage.FREENET_URI_PARSE_ERROR, e.getMessage(), identifier, global); } String verbosityString = fs.get("Verbosity"); if(verbosityString == null) verbosity = 0; else { try { verbosity = Integer.parseInt(verbosityString, 10); } catch (NumberFormatException e) { throw new MessageInvalidException(ProtocolErrorMessage.ERROR_PARSING_NUMBER, "Error parsing Verbosity field: "+e.getMessage(), identifier, global); } } String returnTypeString = fs.get("ReturnType"); returnType = parseReturnTypeFCP(returnTypeString); if(returnType == ReturnType.DIRECT) { diskFile = null; // default just below FProxy defaultPriority = RequestStarter.IMMEDIATE_SPLITFILE_PRIORITY_CLASS; } else if(returnType == ReturnType.NONE) { diskFile = null; defaultPriority = RequestStarter.PREFETCH_PRIORITY_CLASS; } else if(returnType == ReturnType.DISK) { defaultPriority = RequestStarter.BULK_SPLITFILE_PRIORITY_CLASS; String filename = fs.get("Filename"); if(filename == null) throw new MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "Missing Filename", identifier, global); diskFile = new File(filename); if(diskFile.exists()) throw new MessageInvalidException(ProtocolErrorMessage.DISK_TARGET_EXISTS, null, identifier, global); try { // Check whether we can create a temp file in the target directory. File temp = File.createTempFile(diskFile.getName(), ".freenet-tmp", diskFile.getParentFile()); temp.delete(); } catch (IOException e) { throw new MessageInvalidException(ProtocolErrorMessage.COULD_NOT_CREATE_FILE, e.getMessage(), identifier, global); } } else throw new MessageInvalidException(ProtocolErrorMessage.MESSAGE_PARSE_ERROR, "Unknown return-type", identifier, global); String maxSizeString = fs.get("MaxSize"); if(maxSizeString == null) // default to unlimited maxSize = Long.MAX_VALUE; else { try { maxSize = Long.parseLong(maxSizeString, 10); if(maxSize < 0) throw new MessageInvalidException(ProtocolErrorMessage.INVALID_FIELD, "Maximum size must be positive", identifier, global); } catch (NumberFormatException e) { throw new MessageInvalidException(ProtocolErrorMessage.ERROR_PARSING_NUMBER, "Error parsing MaxSize field: "+e.getMessage(), identifier, global); } } String maxTempSizeString = fs.get("MaxTempSize"); if(maxTempSizeString == null) // default to unlimited maxTempSize = Long.MAX_VALUE; else { try { maxTempSize = Long.parseLong(maxTempSizeString, 10); if(maxTempSize < 0) throw new MessageInvalidException(ProtocolErrorMessage.INVALID_FIELD, "Maximum temp size must be positive", identifier, global); } catch (NumberFormatException e) { throw new MessageInvalidException(ProtocolErrorMessage.ERROR_PARSING_NUMBER, "Error parsing MaxSize field: "+e.getMessage(), identifier, global); } } String maxRetriesString = fs.get("MaxRetries"); if(maxRetriesString == null) // default to 0 maxRetries = 0; else { try { maxRetries = Integer.parseInt(maxRetriesString, 10); if(maxRetries < -1) throw new MessageInvalidException(ProtocolErrorMessage.INVALID_FIELD, "Max retries must be -1 or larger", identifier, global); } catch (NumberFormatException e) { throw new MessageInvalidException(ProtocolErrorMessage.ERROR_PARSING_NUMBER, "Error parsing MaxSize field: "+e.getMessage(), identifier, global); } } if(logMINOR) Logger.minor(this, "max retries="+maxRetries); String priorityString = fs.get("PriorityClass"); if(priorityString == null) { // defaults to the one just below FProxy priorityClass = defaultPriority; } else { try { priorityClass = Short.parseShort(priorityString); if(!RequestStarter.isValidPriorityClass(priorityClass)) throw new MessageInvalidException(ProtocolErrorMessage.INVALID_FIELD, "Invalid priority class "+priorityClass+" - range is "+RequestStarter.PAUSED_PRIORITY_CLASS+" to "+RequestStarter.MAXIMUM_PRIORITY_CLASS, identifier, global); } catch (NumberFormatException e) { throw new MessageInvalidException(ProtocolErrorMessage.ERROR_PARSING_NUMBER, "Error parsing PriorityClass field: "+e.getMessage(), identifier, global); } } String persistenceString = fs.get("Persistence"); persistence = Persistence.parseOrThrow(persistenceString, identifier, global); if(global && (persistence == Persistence.CONNECTION)) { throw new MessageInvalidException(ProtocolErrorMessage.NOT_SUPPORTED, "Global requests must be persistent", identifier, global); } writeToClientCache = fs.getBoolean("WriteToClientCache", persistence == Persistence.CONNECTION); binaryBlob = fs.getBoolean("BinaryBlob", false); realTimeFlag = fs.getBoolean("RealTimeFlag", false); initialMetadataLength = fs.getLong("InitialMetadata.DataLength", 0); if(initialMetadataLength < 0) throw new MessageInvalidException(ProtocolErrorMessage.INVALID_FIELD, "Invalid data length for initial metadata", identifier, global); ignoreUSKDatehints = fs.getBoolean("IgnoreUSKDatehints", false); } @Override public SimpleFieldSet getFieldSet() { SimpleFieldSet fs = new SimpleFieldSet(true); fs.put("IgnoreDS", ignoreDS); fs.putSingle("URI", uri.toString(false, false)); fs.put("FilterData", filterData); fs.putSingle("Charset", charset); fs.putSingle("Identifier", identifier); fs.put("Verbosity", verbosity); fs.putSingle("ReturnType", getReturnTypeString()); fs.put("MaxSize", maxSize); fs.put("MaxTempSize", maxTempSize); fs.put("MaxRetries", maxRetries); fs.put("BinaryBlob", binaryBlob); return fs; } private String getReturnTypeString() { return returnType.toString(); } @Override public String getName() { return NAME; } @Override public void run(FCPConnectionHandler handler, Node node) { handler.startClientGet(this); } ReturnType parseReturnTypeFCP(String string) throws MessageInvalidException { try { if(string == null) return ReturnType.DIRECT; return ReturnType.valueOf(string.toUpperCase()); } catch (IllegalArgumentException e) { throw new MessageInvalidException(ProtocolErrorMessage.INVALID_FIELD, "Unable to parse ReturnType "+string+" : "+e, identifier, global); } } @Override long dataLength() { return initialMetadataLength; } @Override public void readFrom(InputStream is, BucketFactory bf, FCPServer server) throws IOException, MessageInvalidException { if(initialMetadataLength == 0) return; Bucket data; data = bf.makeBucket(initialMetadataLength); BucketTools.copyFrom(data, is, initialMetadataLength); // No need for synchronization here. initialMetadata = data; } @Override protected void writeData(OutputStream os) throws IOException { throw new UnsupportedOperationException(); } public Bucket getInitialMetadata() { return initialMetadata; } }