Author: toad
Date: 2006-03-13 20:25:10 +0000 (Mon, 13 Mar 2006)
New Revision: 8240
Modified:
trunk/freenet/src/freenet/client/Metadata.java
trunk/freenet/src/freenet/keys/ClientSSK.java
trunk/freenet/src/freenet/keys/FreenetURI.java
trunk/freenet/src/freenet/node/Version.java
trunk/freenet/src/freenet/node/fcp/ClientPut.java
trunk/freenet/src/freenet/node/fcp/ClientPutMessage.java
trunk/freenet/src/freenet/node/fcp/GetFailedMessage.java
trunk/freenet/src/freenet/node/fcp/PersistentPut.java
trunk/freenet/src/freenet/support/SimpleReadOnlyArrayBucket.java
Log:
530:
ClientPut
UploadFrom=redirect
TargetURI=<target URI>
...
Related fixes: redirects to CHKs with meta-strings should work. SSKs maybe.
Modified: trunk/freenet/src/freenet/client/Metadata.java
===================================================================
--- trunk/freenet/src/freenet/client/Metadata.java 2006-03-13 17:08:42 UTC
(rev 8239)
+++ trunk/freenet/src/freenet/client/Metadata.java 2006-03-13 20:25:10 UTC
(rev 8240)
@@ -523,6 +523,8 @@
noMIME = true;
}
simpleRedirectKey = uri;
+ if(!(uri.getKeyType().equals("CHK") &&
(uri.getAllMetaStrings() == null || uri.getAllMetaStrings().length == 0)))
+ fullKeys = true;
} else
throw new IllegalArgumentException();
}
@@ -610,6 +612,7 @@
} else throw new IllegalArgumentException("Full keys
must be enabled to write non-CHKs");
}
}
+
/** Is a manifest? */
public boolean isSimpleManifest() {
return documentType == SIMPLE_MANIFEST;
Modified: trunk/freenet/src/freenet/keys/ClientSSK.java
===================================================================
--- trunk/freenet/src/freenet/keys/ClientSSK.java 2006-03-13 17:08:42 UTC
(rev 8239)
+++ trunk/freenet/src/freenet/keys/ClientSSK.java 2006-03-13 20:25:10 UTC
(rev 8240)
@@ -25,6 +25,7 @@
public final byte[] ehDocname;
static final int CRYPTO_KEY_LENGTH = 32;
+ public static final int EXTRA_LENGTH = 5;
public ClientSSK(String docName, byte[] pubKeyHash, byte[] extras,
DSAPublicKey pubKey, byte[] cryptoKey) throws MalformedURLException {
this.docName = docName;
@@ -83,7 +84,7 @@
}
protected static final byte[] getExtraBytes() {
- // 3 bytes.
+ // 5 bytes.
byte[] extra = new byte[5];
short cryptoAlgorithm = NodeSSK.ALGO_AES_PCFB_256_SHA256;
Modified: trunk/freenet/src/freenet/keys/FreenetURI.java
===================================================================
--- trunk/freenet/src/freenet/keys/FreenetURI.java 2006-03-13 17:08:42 UTC
(rev 8239)
+++ trunk/freenet/src/freenet/keys/FreenetURI.java 2006-03-13 20:25:10 UTC
(rev 8240)
@@ -13,6 +13,7 @@
import freenet.support.Base64;
import freenet.support.HexUtil;
import freenet.support.IllegalBase64Exception;
+import freenet.support.Logger;
/**
* Note that the metadata pairs below are not presently supported. They are
supported
@@ -384,9 +385,10 @@
static final byte SSK = 2;
public static FreenetURI readFullBinaryKeyWithLength(DataInputStream
dis) throws IOException {
- int len = dis.readShort() & 0xffff;
+ int len = dis.readShort();
byte[] buf = new byte[len];
dis.readFully(buf);
+ Logger.minor(FreenetURI.class, "Read "+len+" bytes for key");
return fromFullBinaryKey(buf);
}
@@ -397,7 +399,6 @@
}
public static FreenetURI readFullBinaryKey(DataInputStream dis) throws
IOException {
- int x = 0;
byte type = dis.readByte();
String keyType;
if(type == CHK)
@@ -416,13 +417,14 @@
int extraLen;
if(type == CHK)
extraLen = ClientCHK.EXTRA_LENGTH;
- else
- throw new UnsupportedOperationException("SSKs not
implemented yet!");
- //extraLen = ClientSSK.EXTRA_LENGTH;
+ else //if(type == SSK)
+ extraLen = ClientSSK.EXTRA_LENGTH;
byte[] extra = new byte[extraLen];
dis.readFully(extra);
- String docName = dis.readUTF();
- int count = dis.readByte() & 0xff;
+ String docName = null;
+ if(type != CHK)
+ docName = dis.readUTF();
+ int count = dis.readInt();
String[] metaStrings = new String[count];
for(int i=0;i<metaStrings.length;i++) metaStrings[i] =
dis.readUTF();
return new FreenetURI(keyType, docName, metaStrings,
routingKey, cryptoKey, extra);
@@ -441,9 +443,10 @@
writeFullBinaryKey(ndos);
ndos.close();
byte[] data = baos.toByteArray();
- if(data.length > 0xffff)
+ if(data.length > Short.MAX_VALUE)
throw new MalformedURLException("Full key too long:
"+data.length+" - "+this);
dos.writeShort((short)data.length);
+ Logger.minor(this, "Written "+data.length+" bytes");
dos.write(data);
}
@@ -463,14 +466,17 @@
throw new MalformedURLException("Cannot write key of
type "+keyType+" - do not know how");
if(routingKey.length != 32)
throw new MalformedURLException("Routing key must be of
length 32");
+ dos.write(routingKey);
if(cryptoKey.length != 32)
throw new MalformedURLException("Crypto key must be of
length 32");
+ dos.write(cryptoKey);
if(keyType.equals("CHK") && extra.length !=
ClientCHK.EXTRA_LENGTH)
throw new MalformedURLException("Wrong number of extra
bytes for CHK");
if(keyType.equals("SSK"))
throw new UnsupportedOperationException("SSK support
not yet implemented");
dos.write(extra);
- dos.writeUTF(docName);
+ if(!keyType.equals("CHK"))
+ dos.writeUTF(docName);
if(metaStr != null) {
dos.writeInt(metaStr.length);
for(int i=0;i<metaStr.length;i++)
Modified: trunk/freenet/src/freenet/node/Version.java
===================================================================
--- trunk/freenet/src/freenet/node/Version.java 2006-03-13 17:08:42 UTC (rev
8239)
+++ trunk/freenet/src/freenet/node/Version.java 2006-03-13 20:25:10 UTC (rev
8240)
@@ -20,7 +20,7 @@
public static final String protocolVersion = "1.0";
/** The build number of the current revision */
- private static final int buildNumber = 529;
+ private static final int buildNumber = 530;
/** Oldest build of Fred we will talk to */
private static final int lastGoodBuild = 507;
Modified: trunk/freenet/src/freenet/node/fcp/ClientPut.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/ClientPut.java 2006-03-13 17:08:42 UTC
(rev 8239)
+++ trunk/freenet/src/freenet/node/fcp/ClientPut.java 2006-03-13 20:25:10 UTC
(rev 8240)
@@ -2,41 +2,64 @@
import java.io.File;
import java.io.IOException;
+import java.io.OutputStream;
import freenet.client.ClientMetadata;
import freenet.client.InsertBlock;
import freenet.client.InserterException;
+import freenet.client.Metadata;
import freenet.client.async.ClientPutter;
+import freenet.keys.FreenetURI;
import freenet.support.Bucket;
import freenet.support.Fields;
import freenet.support.HexUtil;
+import freenet.support.Logger;
import freenet.support.PaddedEphemerallyEncryptedBucket;
import freenet.support.SimpleFieldSet;
+import freenet.support.SimpleReadOnlyArrayBucket;
import freenet.support.io.FileBucket;
public class ClientPut extends ClientPutBase {
final ClientPutter inserter;
- final InsertBlock block;
- /** Was this from disk? Purely for PersistentPut */
- private final boolean fromDisk;
+ private final short uploadFrom;
/** Original filename if from disk, otherwise null. Purely for
PersistentPut. */
private final File origFilename;
+ /** If uploadFrom==UPLOAD_FROM_REDIRECT, this is the target URI */
+ private final FreenetURI targetURI;
+ private final Bucket data;
+ private final ClientMetadata clientMetadata;
public ClientPut(FCPConnectionHandler handler, ClientPutMessage
message) throws IdentifierCollisionException {
super(message.uri, message.identifier, message.verbosity,
handler,
message.priorityClass, message.persistenceType,
message.clientToken, message.global,
message.getCHKOnly, message.dontCompress,
message.maxRetries);
- this.fromDisk = message.fromDisk;
+ this.uploadFrom = message.uploadFromType;
this.origFilename = message.origFilename;
// Now go through the fields one at a time
String mimeType = message.contentType;
clientToken = message.clientToken;
- block = new InsertBlock(message.bucket, new
ClientMetadata(mimeType), uri);
if(persistenceType != PERSIST_CONNECTION)
client.register(this);
- inserter = new ClientPutter(this, message.bucket, uri, new
ClientMetadata(mimeType),
- ctx, client.node.chkPutScheduler,
client.node.sskPutScheduler, priorityClass, getCHKOnly, false, client);
+ Bucket data = message.bucket;
+ ClientMetadata cm = new ClientMetadata(mimeType);
+ boolean isMetadata = false;
+ Logger.minor(this, "data = "+data+", uploadFrom =
"+ClientPutMessage.uploadFromString(uploadFrom));
+ if(uploadFrom == ClientPutMessage.UPLOAD_FROM_REDIRECT) {
+ this.targetURI = message.redirectTarget;
+ Metadata m = new Metadata(Metadata.SIMPLE_REDIRECT,
targetURI, cm);
+ cm = null;
+ byte[] d = m.writeToByteArray();
+ data = new SimpleReadOnlyArrayBucket(d);
+ isMetadata = true;
+ } else
+ targetURI = null;
+ this.data = data;
+ this.clientMetadata = cm;
+ Logger.minor(this, "data = "+data+", uploadFrom =
"+ClientPutMessage.uploadFromString(uploadFrom));
+ inserter = new ClientPutter(this, data, uri, cm,
+ ctx, client.node.chkPutScheduler,
client.node.sskPutScheduler, priorityClass,
+ getCHKOnly, isMetadata, client);
if(persistenceType != PERSIST_CONNECTION && handler != null)
sendPendingMessages(handler.outputHandler, true);
}
@@ -51,12 +74,35 @@
public ClientPut(SimpleFieldSet fs, FCPClient client2) throws
PersistenceParseException, IOException {
super(fs, client2);
String mimeType = fs.get("Metadata.ContentType");
- fromDisk = Fields.stringToBool(fs.get("FromDisk"), false);
- Bucket data;
- if(fromDisk) {
+
+ String from = fs.get("UploadFrom");
+
+ if(from.equals("direct")) {
+ uploadFrom = ClientPutMessage.UPLOAD_FROM_DIRECT;
+ } else if(from.equals("disk")) {
+ uploadFrom = ClientPutMessage.UPLOAD_FROM_DISK;
+ } else if(from.equals("redirect")) {
+ uploadFrom = ClientPutMessage.UPLOAD_FROM_REDIRECT;
+ } else {
+ // FIXME remove this back compatibility hack in a few
builds' time
+ String s = fs.get("FromDisk");
+ if(s.equalsIgnoreCase("true"))
+ uploadFrom = ClientPutMessage.UPLOAD_FROM_DISK;
+ else if(s.equalsIgnoreCase("false"))
+ uploadFrom =
ClientPutMessage.UPLOAD_FROM_DIRECT;
+ else
+ throw new PersistenceParseException("Unknown
UploadFrom: "+from);
+ }
+
+ ClientMetadata cm = new ClientMetadata(mimeType);
+
+ boolean isMetadata = false;
+
+ if(uploadFrom == ClientPutMessage.UPLOAD_FROM_DISK) {
origFilename = new File(fs.get("Filename"));
data = new FileBucket(origFilename, true, false, false,
false);
- } else {
+ targetURI = null;
+ } else if(uploadFrom == ClientPutMessage.UPLOAD_FROM_DIRECT) {
origFilename = null;
if(!succeeded) {
byte[] key =
HexUtil.hexToBytes(fs.get("TempBucket.DecryptKey"));
@@ -66,9 +112,22 @@
if(data.size() != sz)
throw new
PersistenceParseException("Size of bucket is wrong: "+data.size()+" should be
"+sz);
} else data = null;
+ targetURI = null;
+ } else if(uploadFrom == ClientPutMessage.UPLOAD_FROM_REDIRECT) {
+ String target = fs.get("TargetURI");
+ targetURI = new FreenetURI(target);
+ Metadata m = new Metadata(Metadata.SIMPLE_REDIRECT,
targetURI, cm);
+ cm = null;
+ byte[] d = m.writeToByteArray();
+ data = new SimpleReadOnlyArrayBucket(d);
+ origFilename = null;
+ isMetadata = true;
+ } else {
+ throw new PersistenceParseException("shouldn't happen");
}
- block = new InsertBlock(data, new ClientMetadata(mimeType),
uri);
- inserter = new ClientPutter(this, data, uri, new
ClientMetadata(mimeType), ctx, client.node.chkPutScheduler,
client.node.sskPutScheduler, priorityClass, getCHKOnly, false, client);
+ this.clientMetadata = cm;
+ inserter = new ClientPutter(this, data, uri, cm, ctx,
client.node.chkPutScheduler,
+ client.node.sskPutScheduler, priorityClass,
getCHKOnly, isMetadata, client);
if(!finished)
start();
}
@@ -82,22 +141,24 @@
}
protected void freeData() {
- block.getData().free();
+ data.free();
}
public synchronized SimpleFieldSet getFieldSet() {
SimpleFieldSet fs = super.getFieldSet();
- fs.put("Metadata.ContentType",
block.clientMetadata.getMIMEType());
+ fs.put("Metadata.ContentType", clientMetadata.getMIMEType());
fs.put("GetCHKOnly", Boolean.toString(getCHKOnly));
- fs.put("FromDisk", Boolean.toString(fromDisk));
- if(fromDisk) {
+ fs.put("UploadFrom",
ClientPutMessage.uploadFromString(uploadFrom));
+ if(uploadFrom == ClientPutMessage.UPLOAD_FROM_DISK) {
fs.put("Filename", origFilename.getPath());
- } else {
+ } else if(uploadFrom == ClientPutMessage.UPLOAD_FROM_DIRECT) {
// the bucket is a persistent encrypted temp bucket
- PaddedEphemerallyEncryptedBucket bucket =
(PaddedEphemerallyEncryptedBucket) block.getData();
+ PaddedEphemerallyEncryptedBucket bucket =
(PaddedEphemerallyEncryptedBucket) data;
fs.put("TempBucket.DecryptKey",
HexUtil.bytesToHex(bucket.getKey()));
fs.put("TempBucket.Filename",
((FileBucket)(bucket.getUnderlying())).getName());
fs.put("TempBucket.Size", Long.toString(bucket.size()));
+ } else if(uploadFrom == ClientPutMessage.UPLOAD_FROM_REDIRECT) {
+ fs.put("TargetURI", targetURI.toString());
}
return fs;
}
@@ -107,8 +168,8 @@
}
protected FCPMessage persistentTagMessage() {
- return new PersistentPut(identifier, uri, verbosity,
priorityClass, fromDisk, persistenceType, origFilename,
- block.clientMetadata.getMIMEType(),
client.isGlobalQueue);
+ return new PersistentPut(identifier, uri, verbosity,
priorityClass, uploadFrom, targetURI,
+ persistenceType, origFilename,
clientMetadata.getMIMEType(), client.isGlobalQueue);
}
protected String getTypeName() {
Modified: trunk/freenet/src/freenet/node/fcp/ClientPutMessage.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/ClientPutMessage.java 2006-03-13
17:08:42 UTC (rev 8239)
+++ trunk/freenet/src/freenet/node/fcp/ClientPutMessage.java 2006-03-13
20:25:10 UTC (rev 8240)
@@ -45,12 +45,17 @@
final boolean getCHKOnly;
final short priorityClass;
final short persistenceType;
- final boolean fromDisk;
+ final short uploadFromType;
final boolean dontCompress;
final String clientToken;
final File origFilename;
final boolean global;
+ final FreenetURI redirectTarget;
+ static final short UPLOAD_FROM_DIRECT = 0;
+ static final short UPLOAD_FROM_DISK = 1;
+ static final short UPLOAD_FROM_REDIRECT = 2;
+
public ClientPutMessage(SimpleFieldSet fs) throws
MessageInvalidException {
identifier = fs.get("Identifier");
if(identifier == null)
@@ -101,8 +106,20 @@
}
}
String uploadFrom = fs.get("UploadFrom");
- if(uploadFrom != null && uploadFrom.equalsIgnoreCase("disk")) {
- fromDisk = true;
+ if(uploadFrom == null || uploadFrom.equalsIgnoreCase("direct"))
{
+ uploadFromType = UPLOAD_FROM_DIRECT;
+ String dataLengthString = fs.get("DataLength");
+ if(dataLengthString == null)
+ throw new
MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "Need DataLength on
a ClientPut", identifier);
+ try {
+ dataLength = Long.parseLong(dataLengthString,
10);
+ } catch (NumberFormatException e) {
+ throw new
MessageInvalidException(ProtocolErrorMessage.ERROR_PARSING_NUMBER, "Error
parsing DataLength field: "+e.getMessage(), identifier);
+ }
+ this.origFilename = null;
+ redirectTarget = null;
+ } else if(uploadFrom.equalsIgnoreCase("disk")) {
+ uploadFromType = UPLOAD_FROM_DISK;
String filename = fs.get("Filename");
if(filename == null)
throw new
MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "Missing field
Filename", identifier);
@@ -113,18 +130,22 @@
FileBucket fileBucket = new FileBucket(f, true, false,
false, false);
this.bucket = fileBucket;
this.origFilename = f;
- } else {
- fromDisk = false;
- String dataLengthString = fs.get("DataLength");
- if(dataLengthString == null)
- throw new
MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "Need DataLength on
a ClientPut", identifier);
+ redirectTarget = null;
+ } else if(uploadFrom.equalsIgnoreCase("redirect")) {
+ uploadFromType = UPLOAD_FROM_REDIRECT;
+ String target = fs.get("TargetURI");
+ if(target == null)
+ throw new
MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "TargetURI missing
but UploadFrom=redirect", identifier);
try {
- dataLength = Long.parseLong(dataLengthString,
10);
- } catch (NumberFormatException e) {
- throw new
MessageInvalidException(ProtocolErrorMessage.ERROR_PARSING_NUMBER, "Error
parsing DataLength field: "+e.getMessage(), identifier);
+ redirectTarget = new FreenetURI(target);
+ } catch (MalformedURLException e) {
+ throw new
MessageInvalidException(ProtocolErrorMessage.INVALID_FIELD, "Invalid TargetURI:
"+e, identifier);
}
- this.origFilename = null;
- }
+ dataLength = 0;
+ origFilename = null;
+ bucket = null;
+ } else
+ throw new
MessageInvalidException(ProtocolErrorMessage.INVALID_FIELD, "UploadFrom invalid
or unrecognized: "+uploadFrom, identifier);
dontCompress = Fields.stringToBool(fs.get("DontCompress"),
false);
String persistenceString = fs.get("Persistence");
if(persistenceString == null ||
persistenceString.equalsIgnoreCase("connection")) {
@@ -151,12 +172,16 @@
sfs.put("MaxRetries", Integer.toString(maxRetries));
sfs.put("Metadata.ContentType", contentType);
sfs.put("ClientToken", clientToken);
- if(fromDisk) {
+ if(uploadFromType == UPLOAD_FROM_DIRECT) {
+ sfs.put("UploadFrom", "direct");
+ sfs.put("DataLength", Long.toString(dataLength));
+ } else if(uploadFromType == UPLOAD_FROM_DISK) {
sfs.put("UploadFrom", "disk");
sfs.put("Filename", origFilename.getAbsolutePath());
- } else {
- sfs.put("UploadFrom", "direct");
sfs.put("DataLength", Long.toString(dataLength));
+ } else if(uploadFromType == UPLOAD_FROM_REDIRECT) {
+ sfs.put("UploadFrom", "redirect");
+ sfs.put("TargetURI", redirectTarget.toString());
}
sfs.put("GetCHKOnly", Boolean.toString(getCHKOnly));
sfs.put("PriorityClass", Short.toString(priorityClass));
@@ -176,7 +201,6 @@
}
long dataLength() {
- if(fromDisk) return 0;
return dataLength;
}
@@ -191,5 +215,18 @@
return super.createBucket(bf, length, server);
}
}
+
+ public static String uploadFromString(short uploadFrom) {
+ switch(uploadFrom) {
+ case UPLOAD_FROM_DIRECT:
+ return "direct";
+ case UPLOAD_FROM_DISK:
+ return "disk";
+ case UPLOAD_FROM_REDIRECT:
+ return "redirect";
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
}
\ No newline at end of file
Modified: trunk/freenet/src/freenet/node/fcp/GetFailedMessage.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/GetFailedMessage.java 2006-03-13
17:08:42 UTC (rev 8239)
+++ trunk/freenet/src/freenet/node/fcp/GetFailedMessage.java 2006-03-13
20:25:10 UTC (rev 8240)
@@ -7,6 +7,7 @@
import freenet.client.InserterException;
import freenet.node.Node;
import freenet.support.Fields;
+import freenet.support.Logger;
import freenet.support.SimpleFieldSet;
public class GetFailedMessage extends FCPMessage {
@@ -20,6 +21,7 @@
final String identifier;
public GetFailedMessage(FetchException e, String identifier) {
+ Logger.minor(this, "Creating get failed from "+e+" for
"+identifier, e);
this.tracker = e.errorCodes;
this.code = e.mode;
this.codeDescription = FetchException.getMessage(code);
Modified: trunk/freenet/src/freenet/node/fcp/PersistentPut.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/PersistentPut.java 2006-03-13
17:08:42 UTC (rev 8239)
+++ trunk/freenet/src/freenet/node/fcp/PersistentPut.java 2006-03-13
20:25:10 UTC (rev 8240)
@@ -14,20 +14,23 @@
final FreenetURI uri;
final int verbosity;
final short priorityClass;
- final boolean fromDisk;
+ final short uploadFrom;
final short persistenceType;
final File origFilename;
final String mimeType;
final boolean global;
+ final FreenetURI targetURI;
public PersistentPut(String identifier, FreenetURI uri, int verbosity,
- short priorityClass, boolean fromDisk, short
persistenceType,
- File origFilename, String mimeType, boolean global) {
+ short priorityClass, short uploadFrom, FreenetURI
targetURI,
+ short persistenceType, File origFilename, String
mimeType,
+ boolean global) {
this.identifier = identifier;
this.uri = uri;
this.verbosity = verbosity;
this.priorityClass = priorityClass;
- this.fromDisk = fromDisk;
+ this.uploadFrom = uploadFrom;
+ this.targetURI = targetURI;
this.persistenceType = persistenceType;
this.origFilename = origFilename;
this.mimeType = mimeType;
@@ -40,10 +43,12 @@
fs.put("URI", uri.toString(false));
fs.put("Verbosity", Integer.toString(verbosity));
fs.put("PriorityClass", Short.toString(priorityClass));
- fs.put("UploadFrom", (fromDisk ? "disk" : "direct"));
+ fs.put("UploadFrom",
ClientPutMessage.uploadFromString(uploadFrom));
fs.put("Persistence",
ClientRequest.persistenceTypeString(persistenceType));
if(origFilename != null)
fs.put("Filename", origFilename.getAbsolutePath());
+ if(targetURI != null)
+ fs.put("TargetURI", targetURI.toString());
if(mimeType != null)
fs.put("Metadata.ContentType", mimeType);
fs.put("PriorityClass", Short.toString(priorityClass));
Modified: trunk/freenet/src/freenet/support/SimpleReadOnlyArrayBucket.java
===================================================================
--- trunk/freenet/src/freenet/support/SimpleReadOnlyArrayBucket.java
2006-03-13 17:08:42 UTC (rev 8239)
+++ trunk/freenet/src/freenet/support/SimpleReadOnlyArrayBucket.java
2006-03-13 20:25:10 UTC (rev 8240)
@@ -23,6 +23,10 @@
this.length = length;
}
+ public SimpleReadOnlyArrayBucket(byte[] buf) {
+ this(buf, 0, buf.length);
+ }
+
public OutputStream getOutputStream() throws IOException {
throw new IOException("Read only");
}