提交 145ab625 authored 作者: Thomas Mueller's avatar Thomas Mueller

mainly javadocs

上级 9c7fd89d
......@@ -16,7 +16,6 @@ import org.h2.constraint.ConstraintReferential;
import org.h2.constraint.ConstraintUnique;
import org.h2.engine.Constants;
import org.h2.engine.Database;
import org.h2.engine.DbObject;
import org.h2.engine.Right;
import org.h2.engine.Session;
import org.h2.expression.Expression;
......@@ -75,9 +74,9 @@ public class AlterTableAddConstraint extends SchemaCommand {
super(session, schema);
}
private String generateConstraintName(DbObject obj) {
private String generateConstraintName(Table table) {
if (constraintName == null) {
constraintName = getSchema().getUniqueConstraintName(obj);
constraintName = getSchema().getUniqueConstraintName(table);
}
return constraintName;
}
......
......@@ -10,10 +10,10 @@ import java.sql.SQLException;
import org.h2.command.Prepared;
import org.h2.engine.Session;
import org.h2.expression.Expression;
import org.h2.expression.ExpressionColumn;
import org.h2.result.LocalResult;
import org.h2.table.Column;
import org.h2.util.ObjectArray;
import org.h2.value.Value;
import org.h2.value.ValueString;
......@@ -44,10 +44,11 @@ public class ExplainPlan extends Prepared {
public LocalResult query(int maxrows) throws SQLException {
// TODO rights: are rights required for explain?
ObjectArray expressions = new ObjectArray();
Column column = new Column("PLAN", Value.STRING);
ExpressionColumn expr = new ExpressionColumn(session.getDatabase(), column);
expressions.add(expr);
Expression[] expressions = new Expression[] {
expr
};
result = new LocalResult(session, expressions, 1);
if (maxrows >= 0) {
String plan = command.getPlanSQL();
......
......@@ -34,6 +34,7 @@ import org.h2.engine.Setting;
import org.h2.engine.User;
import org.h2.engine.UserAggregate;
import org.h2.engine.UserDataType;
import org.h2.expression.Expression;
import org.h2.expression.ExpressionColumn;
import org.h2.index.Cursor;
import org.h2.index.Index;
......@@ -114,9 +115,9 @@ public class ScriptCommand extends ScriptBase {
}
private LocalResult createResult() {
ObjectArray cols = new ObjectArray();
cols.add(new ExpressionColumn(session.getDatabase(), new Column("SCRIPT", Value.STRING)));
return new LocalResult(session, cols, 1);
Expression[] expressions = new Expression[] { new ExpressionColumn(session.getDatabase(), new Column("SCRIPT",
Value.STRING)) };
return new LocalResult(session, expressions, 1);
}
public LocalResult query(int maxrows) throws SQLException {
......
......@@ -1356,6 +1356,7 @@ public class Database implements DataHandler {
throw Message.getInternalError("object already exists: " + newName);
}
}
obj.checkRename();
int id = obj.getId();
removeMeta(session, id);
map.remove(obj.getName());
......
......@@ -56,6 +56,12 @@ public class User extends RightOwner {
this.passwordHash = hash;
}
/**
* Set the user name password hash. A random salt is generated as well.
* The parameter is nulled out after use.
*
* @param userPasswordHash the user name password hash
*/
public void setUserPasswordHash(byte[] userPasswordHash) {
if (userPasswordHash != null) {
salt = RandomUtils.getSecureBytes(Constants.SALT_LEN);
......
......@@ -1141,7 +1141,7 @@ public class Function extends Expression implements FunctionCall {
private byte[] getHash(String algorithm, byte[] bytes, int iterations) throws SQLException {
SHA256 hash = CipherFactory.getHash(algorithm);
for (int i = 0; i < iterations; i++) {
bytes = hash.getHash(bytes);
bytes = hash.getHash(bytes, false);
}
return bytes;
}
......
......@@ -46,6 +46,14 @@ public class LocalResult implements ResultInterface {
private boolean distinct;
private boolean closed;
/**
* Construct a local result set by reading all data from a regular result set.
*
* @param session the session
* @param rs the result set
* @param maxrows the maximum number of rows to read (0 for no limit)
* @return the local result set
*/
public static LocalResult read(Session session, ResultSet rs, int maxrows) throws SQLException {
ObjectArray cols = getExpressionColumns(session, rs);
int columnCount = cols.size();
......@@ -80,10 +88,20 @@ public class LocalResult implements ResultInterface {
return cols;
}
/**
* Construct a local result object.
*/
public LocalResult() {
// nothing to do
}
/**
* Construct a local result object.
*
* @param session the session
* @param expressions the expression array
* @param the number of visible columns
*/
public LocalResult(Session session, Expression[] expressions, int visibleColumnCount) {
this.session = session;
if (session == null) {
......@@ -97,6 +115,23 @@ public class LocalResult implements ResultInterface {
this.expressions = expressions;
}
/**
* Construct a local result object.
*
* @param session the session
* @param expressions the expression list
* @param the number of visible columns
*/
public LocalResult(Session session, ObjectArray expressionList, int visibleColumnCount) {
this(session, getList(expressionList), visibleColumnCount);
}
/**
* Create a shallow copy of the result set. The data and a temporary table (if there is any) is not copied.
*
* @param session the session
* @return the copy
*/
public LocalResult createShallowCopy(Session session) {
if (disk == null && (rows == null || rows.size() < rowCount)) {
return null;
......@@ -120,25 +155,34 @@ public class LocalResult implements ResultInterface {
return copy;
}
public LocalResult(Session session, ObjectArray expressionList, int visibleColumnCount) {
this(session, getList(expressionList), visibleColumnCount);
}
private static Expression[] getList(ObjectArray expressionList) {
Expression[] expressions = new Expression[expressionList.size()];
expressionList.toArray(expressions);
return expressions;
}
/**
* Set the sort order.
*
* @param sort the sort order
*/
public void setSortOrder(SortOrder sort) {
this.sort = sort;
}
/**
* Remove duplicate rows.
*/
public void setDistinct() {
distinct = true;
distinctRows = new ValueHashMap(session.getDatabase());
}
/**
* Remove the row from the result set if it exists.
*
* @param values the row
*/
public void removeDistinct(Value[] values) throws SQLException {
if (!distinct) {
throw Message.getInternalError();
......@@ -152,6 +196,12 @@ public class LocalResult implements ResultInterface {
}
}
/**
* Check if this result set contains the given row.
*
* @param values the row
* @return true if the row exists
*/
public boolean containsDistinct(Value[] values) throws SQLException {
if (!distinct) {
throw Message.getInternalError();
......@@ -199,6 +249,11 @@ public class LocalResult implements ResultInterface {
return rowId;
}
/**
* Add a row to this object.
*
* @param values the row to add
*/
public void addRow(Value[] values) throws SQLException {
if (distinct) {
if (distinctRows != null) {
......@@ -234,6 +289,9 @@ public class LocalResult implements ResultInterface {
return visibleColumnCount;
}
/**
* This method is called after all rows have been added.
*/
public void done() throws SQLException {
if (distinct) {
if (distinctRows != null) {
......@@ -283,6 +341,11 @@ public class LocalResult implements ResultInterface {
return rowCount;
}
/**
* Set the number of rows that this result will return at the maximum.
*
* @param limit the limit
*/
public void setLimit(int limit) {
this.limit = limit;
}
......@@ -303,6 +366,11 @@ public class LocalResult implements ResultInterface {
}
}
/**
* Check if this result set is buffered using a temporary file.
*
* @return true if it is
*/
public boolean needToClose() {
return disk != null;
}
......@@ -355,6 +423,11 @@ public class LocalResult implements ResultInterface {
return expressions[i].getScale();
}
/**
* Set the offset of the first row to return.
*
* @param offset the offset
*/
public void setOffset(int offset) {
this.offset = offset;
}
......@@ -387,6 +460,11 @@ public class LocalResult implements ResultInterface {
return "columns: " + visibleColumnCount + " rows: " + rowCount + " pos: " + rowId;
}
/**
* Check if this result set is closed.
*
* @return true if it is
*/
public boolean isClosed() {
return closed;
}
......
......@@ -25,6 +25,11 @@ public class ResultColumn {
boolean autoIncrement;
int nullable;
/**
* Read an object from the given transfer object.
*
* @param in the object from where to read the data
*/
ResultColumn(Transfer in) throws IOException {
alias = in.readString();
schemaName = in.readString();
......@@ -38,6 +43,13 @@ public class ResultColumn {
nullable = in.readInt();
}
/**
* Write a result column to the given output.
*
* @param out the object to where to write the data
* @param result the result
* @param i the column index
*/
public static void writeColumn(Transfer out, ResultInterface result, int i) throws IOException {
out.writeString(result.getAlias(i));
out.writeString(result.getSchemaName(i));
......
......@@ -17,20 +17,58 @@ import org.h2.value.Value;
*/
public interface ResultExternal {
/**
* Reset the current position of this object.
*/
void reset() throws SQLException;
/**
* Get the next row from the result.
*
* @return the next row or null
*/
Value[] next() throws SQLException;
/**
* Add a number of rows to the result.
*
* @param rows the list of rows to add
*/
void addRows(ObjectArray rows) throws SQLException;
/**
* This method is called after all rows have been added.
*/
void done() throws SQLException;
/**
* Close this object and delete the temporary file.
*/
void close();
/**
* Remove the row with the given values from this object if such a row
* exists.
*
* @param values the row
* @return the new row count
*/
int removeRow(Value[] values) throws SQLException;
/**
* Check if the given row exists in this object.
*
* @param values the row
* @return true if it exists
*/
boolean contains(Value[] values) throws SQLException;
/**
* Add a row to this object.
*
* @param values the row to add
* @return the new number of rows in this object
*/
int addRow(Value[] values) throws SQLException;
}
......@@ -38,7 +38,11 @@ public class RowList {
private boolean written;
private boolean readUncached;
/**
* Construct a new row list for this session.
*
* @param session the session
*/
public RowList(Session session) {
this.session = session;
if (SysProperties.DEFAULT_MAX_OPERATION_MEMORY > 0 && session.getDatabase().isPersistent()) {
......@@ -114,6 +118,12 @@ public class RowList {
file.write(buff.getBytes(), 0, buff.length());
}
/**
* Add a row to the list.
*
* @param r the row to add
*/
public void add(Row r) throws SQLException {
list.add(r);
memory += r.getMemorySize();
......@@ -123,6 +133,9 @@ public class RowList {
size++;
}
/**
* Remove all rows from the list.
*/
public void reset() throws SQLException {
index = 0;
if (file != null) {
......@@ -136,6 +149,11 @@ public class RowList {
}
}
/**
* Check if there are more rows in this list.
*
* @return true it there are more rows
*/
public boolean hasNext() {
return index < size;
}
......@@ -182,6 +200,11 @@ public class RowList {
return row;
}
/**
* Get the next row from the list.
*
* @return the next row
*/
public Row next() throws SQLException {
Row r;
if (file == null) {
......@@ -214,14 +237,25 @@ public class RowList {
return r;
}
/**
* Get the number of rows in this list.
*
* @return the number of rows
*/
public int size() {
return size;
}
/**
* Do not use the cache.
*/
public void invalidateCache() {
readUncached = true;
}
/**
* Close the result list and delete the temporary file.
*/
public void close() {
if (file != null) {
file.closeAndDeleteSilently();
......
......@@ -13,7 +13,6 @@ import java.util.HashSet;
import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
import org.h2.constraint.Constraint;
import org.h2.engine.Constants;
import org.h2.engine.Database;
import org.h2.engine.DbObject;
import org.h2.engine.DbObjectBase;
......@@ -43,21 +42,36 @@ public class Schema extends DbObjectBase {
private HashMap constraints = new HashMap();
private HashMap constants = new HashMap();
/*
* Set of returned unique names that are not yet stored
* (to avoid returning the same unique name twice when multiple threads
* concurrently create objects).
/**
* The set of returned unique names that are not yet stored. It is used to
* avoid returning the same unique name twice when multiple threads
* concurrently create objects.
*/
private HashSet temporaryUniqueNames = new HashSet();
/**
* Create a new schema object.
*
* @param database the database
* @param id the object id
* @param schemaName the schema name
* @param owner the owner of the schema
* @param system if this is a system schema (such a schema can not be
* dropped)
*/
public Schema(Database database, int id, String schemaName, User owner, boolean system) {
initDbObjectBase(database, id, schemaName, Trace.SCHEMA);
this.owner = owner;
this.system = system;
}
/**
* Check if this schema can be dropped. System schemas can not be dropped.
*
* @return true if it can be dropped
*/
public boolean canDrop() {
return !getName().equals(Constants.SCHEMA_INFORMATION) && !getName().equals(Constants.SCHEMA_MAIN);
return !system;
}
public String getCreateSQLForCopy(Table table, String quotedName) {
......@@ -118,6 +132,11 @@ public class Schema extends DbObjectBase {
// ok
}
/**
* Get the owner of this schema.
*
* @return the owner
*/
public User getOwner() {
return owner;
}
......@@ -141,6 +160,11 @@ public class Schema extends DbObjectBase {
}
}
/**
* Add an object to this schema.
*
* @param obj the object to add
*/
public void add(SchemaObject obj) {
if (SysProperties.CHECK && obj.getSchema() != this) {
throw Message.getInternalError("wrong schema");
......@@ -154,6 +178,12 @@ public class Schema extends DbObjectBase {
freeUniqueName(name);
}
/**
* Rename an object.
*
* @param obj the object to rename
* @param newName the new name
*/
public void rename(SchemaObject obj, String newName) throws SQLException {
int type = obj.getType();
HashMap map = getMap(type);
......@@ -165,6 +195,7 @@ public class Schema extends DbObjectBase {
throw Message.getInternalError("object already exists: " + newName);
}
}
obj.checkRename();
map.remove(obj.getName());
freeUniqueName(obj.getName());
obj.rename(newName);
......@@ -172,6 +203,15 @@ public class Schema extends DbObjectBase {
freeUniqueName(newName);
}
/**
* Try to find a table or view with this name. This method returns null if
* no object with this name exists. Local temporary tables are also
* returned.
*
* @param session the session
* @param name the object name
* @return the object or null
*/
public Table findTableOrView(Session session, String name) {
Table table = (Table) tablesAndViews.get(name);
if (table == null && session != null) {
......@@ -180,26 +220,66 @@ public class Schema extends DbObjectBase {
return table;
}
/**
* Try to find an index with this name. This method returns null if
* no object with this name exists.
*
* @param name the object name
* @return the object or null
*/
public Index findIndex(String name) {
return (Index) indexes.get(name);
}
/**
* Try to find a trigger with this name. This method returns null if
* no object with this name exists.
*
* @param name the object name
* @return the object or null
*/
public TriggerObject findTrigger(String name) {
return (TriggerObject) triggers.get(name);
}
/**
* Try to find a sequence with this name. This method returns null if
* no object with this name exists.
*
* @param name the object name
* @return the object or null
*/
public Sequence findSequence(String sequenceName) {
return (Sequence) sequences.get(sequenceName);
}
/**
* Try to find a constraint with this name. This method returns null if no
* object with this name exists.
*
* @param name the object name
* @return the object or null
*/
public Constraint findConstraint(String constraintName) {
return (Constraint) constraints.get(constraintName);
}
/**
* Try to find a user defined constant with this name. This method returns
* null if no object with this name exists.
*
* @param name the object name
* @return the object or null
*/
public Constant findConstant(String constantName) {
return (Constant) constants.get(constantName);
}
/**
* Release a unique object name.
*
* @param name the object name
*/
public void freeUniqueName(String name) {
if (name != null) {
temporaryUniqueNames.remove(name);
......@@ -229,14 +309,36 @@ public class Schema extends DbObjectBase {
return name;
}
public String getUniqueConstraintName(DbObject obj) {
return getUniqueName(obj, constraints, "CONSTRAINT_");
/**
* Create a unique constraint name.
*
* @param table the constraint table
* @return the unique name
*/
public String getUniqueConstraintName(Table table) {
return getUniqueName(table, constraints, "CONSTRAINT_");
}
public String getUniqueIndexName(DbObject obj, String prefix) {
return getUniqueName(obj, indexes, prefix);
/**
* Create a unique index name.
*
* @param table the indexed table
* @param prefix the index name prefix
* @return the unique name
*/
public String getUniqueIndexName(Table table, String prefix) {
return getUniqueName(table, indexes, prefix);
}
/**
* Get the table or view with the given name.
* Local temporary tables are also returned.
*
* @param session the session
* @param name the table or view name
* @return the table or view
* @throws SQLException if no such object exists
*/
public Table getTableOrView(Session session, String name) throws SQLException {
Table table = (Table) tablesAndViews.get(name);
if (table == null && session != null) {
......@@ -248,6 +350,13 @@ public class Schema extends DbObjectBase {
return table;
}
/**
* Get the index with the given name.
*
* @param name the index name
* @return the index
* @throws SQLException if no such object exists
*/
public Index getIndex(String name) throws SQLException {
Index index = (Index) indexes.get(name);
if (index == null) {
......@@ -256,6 +365,13 @@ public class Schema extends DbObjectBase {
return index;
}
/**
* Get the constraint with the given name.
*
* @param name the constraint name
* @return the constraint
* @throws SQLException if no such object exists
*/
public Constraint getConstraint(String name) throws SQLException {
Constraint constraint = (Constraint) constraints.get(name);
if (constraint == null) {
......@@ -264,6 +380,13 @@ public class Schema extends DbObjectBase {
return constraint;
}
/**
* Get the user defined constant with the given name.
*
* @param name the constant name
* @return the constant
* @throws SQLException if no such object exists
*/
public Constant getConstant(String constantName) throws SQLException {
Constant constant = (Constant) constants.get(constantName);
if (constant == null) {
......@@ -272,6 +395,13 @@ public class Schema extends DbObjectBase {
return constant;
}
/**
* Get the sequence with the given name.
*
* @param name the sequence name
* @return the sequence
* @throws SQLException if no such object exists
*/
public Sequence getSequence(String sequenceName) throws SQLException {
Sequence sequence = (Sequence) sequences.get(sequenceName);
if (sequence == null) {
......@@ -280,11 +410,22 @@ public class Schema extends DbObjectBase {
return sequence;
}
/**
* Get all objects of the given type.
*
* @param type the object type
* @return a (possible empty) list of all objects
*/
public ObjectArray getAll(int type) {
HashMap map = getMap(type);
return new ObjectArray(map.values());
}
/**
* Remove an object from this schema.
*
* @param obj the object to remove
*/
public void remove(SchemaObject obj) {
String objName = obj.getName();
HashMap map = getMap(obj.getType());
......@@ -296,7 +437,7 @@ public class Schema extends DbObjectBase {
}
/**
* Add a {@link TableData} to the schema.
* Add a table to the schema.
*
* @param tableName the table name
* @param id the object id
......@@ -311,7 +452,7 @@ public class Schema extends DbObjectBase {
}
/**
* Add a {@link TableLink} to the schema.
* Add a linked table to the schema.
*
* @param id the object id
* @param tableName the table name of the alias
......
......@@ -20,7 +20,12 @@ import org.h2.table.Table;
* CREATE SEQUENCE
*/
public class Sequence extends SchemaObjectBase {
/**
* The default cache size for sequences.
*/
public static final int DEFAULT_CACHE_SIZE = 32;
private long value = 1;
private long valueWithMargin;
private long increment = 1;
......@@ -83,6 +88,12 @@ public class Sequence extends SchemaObjectBase {
return buff.toString();
}
/**
* Get the next value for this sequence.
*
* @param session the session
* @return the next value
*/
public synchronized long getNext(Session session) throws SQLException {
if ((increment > 0 && value >= valueWithMargin) || (increment < 0 && value <= valueWithMargin)) {
valueWithMargin += increment * cacheSize;
......@@ -93,6 +104,11 @@ public class Sequence extends SchemaObjectBase {
return v;
}
/**
* Flush the current value, including the margin, to disk.
*
* @param session the session
*/
public synchronized void flush(Session session) throws SQLException {
Session sysSession = database.getSystemSession();
if (session == null || !database.isSysTableLocked()) {
......@@ -118,6 +134,9 @@ public class Sequence extends SchemaObjectBase {
}
}
/**
* Flush the current value to disk and close this object.
*/
public void close() throws SQLException {
valueWithMargin = value;
flush(null);
......
......@@ -28,6 +28,9 @@ import org.h2.value.Value;
*/
public class TriggerObject extends SchemaObjectBase {
/**
* The default queue size.
*/
public static final int DEFAULT_QUEUE_SIZE = 1024;
private boolean before;
......@@ -65,6 +68,14 @@ public class TriggerObject extends SchemaObjectBase {
}
}
/**
* Set the trigger class name and load the class if possible.
*
* @param session the session
* @param triggerClassName the name of the trigger class
* @param force whether exceptions (due to missing class or access rights)
* should be ignored
*/
public void setTriggerClassName(Session session, String triggerClassName, boolean force) throws SQLException {
this.triggerClassName = triggerClassName;
try {
......@@ -76,6 +87,14 @@ public class TriggerObject extends SchemaObjectBase {
}
}
/**
* Call the trigger class if required. This method does nothing if the
* trigger is not defined for the given action. This method is called before
* or after any rows have been processed, once for each statement.
*
* @param session the session
* @param beforeAction if this method is called before applying the changes
*/
public void fire(Session session, boolean beforeAction) throws SQLException {
if (rowBased || before != beforeAction) {
return;
......@@ -106,7 +125,10 @@ public class TriggerObject extends SchemaObjectBase {
}
/**
* Call the fire method of the user-defined trigger class.
* Call the fire method of the user-defined trigger class if required. This
* method does nothing if the trigger is not defined for the given action.
* This method is called before or after a row is processed, possibly many
* times for each statement.
*
* @param session the session
* @param oldRow the old row
......@@ -172,6 +194,11 @@ public class TriggerObject extends SchemaObjectBase {
}
}
/**
* Set the trigger type.
*
* @param typeMask the type
*/
public void setTypeMask(int typeMask) {
this.typeMask = typeMask;
}
......@@ -267,14 +294,29 @@ public class TriggerObject extends SchemaObjectBase {
// nothing to do
}
/**
* Get the table of this trigger.
*
* @return the table
*/
public Table getTable() {
return table;
}
/**
* Check if this is a before trigger.
*
* @return true if it is
*/
public boolean getBefore() {
return before;
}
/**
* Get the trigger class name
*
* @return the class name
*/
public String getTriggerClassName() {
return triggerClassName;
}
......
......@@ -29,6 +29,10 @@ public class AES implements BlockCipher {
private int[] encKey = new int[44];
private int[] decKey = new int[44];
AES() {
// do nothing
}
private static int rot8(int x) {
return (x >>> 8) | (x << 24);
}
......@@ -117,12 +121,12 @@ public class AES implements BlockCipher {
decKey[d++] = encKey[e++];
}
public void encrypt(byte[] buff, int off, int len) {
public void encrypt(byte[] bytes, int off, int len) {
if (SysProperties.CHECK && (len % ALIGN != 0)) {
throw Message.getInternalError("unaligned len " + len);
}
for (int i = off; i < off + len; i += 16) {
encryptBlock(buff, buff, i);
encryptBlock(bytes, bytes, i);
}
}
......
......@@ -11,8 +11,8 @@ package org.h2.security;
*/
public interface BlockCipher {
/*
* Blocks sizes are always multiples of this number
/**
* Blocks sizes are always multiples of this number.
*/
int ALIGN = 16;
......
......@@ -16,6 +16,12 @@ import org.h2.message.Message;
*/
public class CipherFactory {
/**
* Get a new block cipher object for the given algorithm.
*
* @param algorithm the algorithm
* @return a new cipher object
*/
public static BlockCipher getBlockCipher(String algorithm) throws SQLException {
if ("XTEA".equalsIgnoreCase(algorithm)) {
return new XTEA();
......@@ -26,6 +32,12 @@ public class CipherFactory {
}
}
/**
* Get a new cryptographic hash object for the given algorithm.
*
* @param algorithm the algorithm
* @return a new hash object
*/
public static SHA256 getHash(String algorithm) throws SQLException {
if ("SHA256".equalsIgnoreCase(algorithm)) {
return new SHA256();
......
......@@ -15,13 +15,35 @@ public class SHA256 {
// TODO maybe implement WHIRLPOOL
/**
* Calculate the hash code by using the given salt. The salt is appended
* after the data before the hash code is calculated. After generating the
* hash code, the data and all internal buffers are nulled out to avoid
* keeping insecure data in memory longer than required (and possibly
* swapped to disk).
*
* @param data the data to hash
* @param salt the salt to use
* @return the hash code
*/
public byte[] getHashWithSalt(byte[] data, byte[] salt) {
byte[] buff = new byte[data.length + salt.length];
System.arraycopy(data, 0, buff, 0, data.length);
System.arraycopy(salt, 0, buff, data.length, salt.length);
return getHash(buff);
return getHash(buff, true);
}
/**
* Calculate the hash of a password by prepending the user name and a '@'
* character. Both the user name and the password are encoded to a byte
* array using UTF-16. After generating the hash code, the password array
* and all internal buffers are nulled out to avoid keeping the plain text
* password in memory longer than required (and possibly swapped to disk).
*
* @param userName the user name
* @param the password
* @return the hash code
*/
public byte[] getKeyPasswordHash(String userName, char[] password) {
String user = userName + "@";
byte[] buff = new byte[2 * (user.length() + password.length)];
......@@ -37,11 +59,13 @@ public class SHA256 {
buff[n++] = (byte) (c);
}
Arrays.fill(password, (char) 0);
return getHash(buff);
return getHash(buff, true);
}
// The first 32 bits of the fractional parts of the cube roots of the first
// sixty-four prime numbers
/**
* The first 32 bits of the fractional parts of the cube roots of the first
* sixty-four prime numbers.
*/
private static final int[] K = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98,
0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,
......@@ -56,12 +80,21 @@ public class SHA256 {
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814,
0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 };
public byte[] getHash(byte[] data) {
/**
* Calculate the hash code for the given data.
*
* @param data the data to hash
* @param nullData if the data should be nulled after calculating the hash code
* @return the hash code
*/
public byte[] getHash(byte[] data, boolean nullData) {
int byteLen = data.length;
int intLen = ((byteLen + 9 + 63) / 64) * 16;
byte[] bytes = new byte[intLen * 4];
System.arraycopy(data, 0, bytes, 0, byteLen);
if (nullData) {
Arrays.fill(data, (byte) 0);
}
bytes[byteLen] = (byte) 0x80;
int[] buff = new int[intLen];
for (int i = 0, j = 0; j < intLen; i += 4, j++) {
......@@ -72,7 +105,6 @@ public class SHA256 {
int[] w = new int[64];
int[] hh = new int[] { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 };
for (int block = 0; block < intLen; block += 16) {
for (int i = 0; i < 16; i++) {
w[i] = buff[block + i];
......
......@@ -53,10 +53,10 @@ public class SecureFileStore extends FileStore {
SHA256 sha = new SHA256();
key = sha.getHashWithSalt(key, salt);
for (int i = 0; i < keyIterations; i++) {
key = sha.getHash(key);
key = sha.getHash(key, true);
}
cipher.setKey(key);
key = sha.getHash(key);
key = sha.getHash(key, true);
cipherForInitVector.setKey(key);
}
......
......@@ -34,7 +34,6 @@ import org.h2.message.Message;
import org.h2.util.ByteUtils;
import org.h2.util.FileUtils;
import org.h2.util.IOUtils;
import org.h2.util.NetUtils;
/**
* A factory to create encrypted sockets. To generate new keystore, use the
......@@ -42,26 +41,24 @@ import org.h2.util.NetUtils;
*/
public class SecureSocketFactory {
/**
* The default password to use for the .h2.keystore file
*/
public static final String KEYSTORE_PASSWORD = "h2pass";
private static final String KEYSTORE = ".h2.keystore";
private static final String KEYSTORE_KEY = "javax.net.ssl.keyStore";
private static final String KEYSTORE_PASSWORD_KEY = "javax.net.ssl.keyStorePassword";
public static final String KEYSTORE_PASSWORD = "h2pass";
private static SecureSocketFactory factory;
private static final String ANONYMOUS_CIPHER_SUITE = "SSL_DH_anon_WITH_RC4_128_MD5";
private static void setFactory(SecureSocketFactory f) {
factory = f;
}
public static SecureSocketFactory getInstance() {
if (factory == null) {
setFactory(new SecureSocketFactory());
}
return factory;
}
public Socket createSocket(InetAddress address, int port) throws IOException {
/**
* Create a secure client socket that is connected to the given address and port.
*
* @param address the address to connect to
* @param port the port
* @return the socket
*/
public static Socket createSocket(InetAddress address, int port) throws IOException {
Socket socket = null;
//## Java 1.4 begin ##
setKeystore();
......@@ -77,13 +74,19 @@ public class SecureSocketFactory {
return socket;
}
public ServerSocket createServerSocket(int port) throws IOException {
/**
* Create a secure server socket. If a bind address is specified, the socket is only bound to this address.
*
* @param port the port to listen on
* @param bindAddress the address to bind to, or null to bind to all addresses
* @return the server socket
*/
public static ServerSocket createServerSocket(int port, InetAddress bindAddress) throws IOException {
ServerSocket socket = null;
//## Java 1.4 begin ##
setKeystore();
ServerSocketFactory f = SSLServerSocketFactory.getDefault();
SSLServerSocket secureSocket;
InetAddress bindAddress = NetUtils.getBindAddress();
if (bindAddress == null) {
secureSocket = (SSLServerSocket) f.createServerSocket(port);
} else {
......@@ -113,7 +116,16 @@ public class SecureSocketFactory {
}
return bout.toByteArray();
}
//## Java 1.4 end ##
/**
* Get the keystore object using the given password.
*
* @param password the keystore password
* @return the keystore
*/
//## Java 1.4 begin ##
public static KeyStore getKeyStore(String password) throws IOException {
try {
// The following source code can be re-generated
......@@ -145,7 +157,7 @@ public class SecureSocketFactory {
}
}
private void setKeystore() throws IOException {
private static void setKeystore() throws IOException {
Properties p = System.getProperties();
if (p.getProperty(KEYSTORE_KEY) == null) {
String fileName = FileUtils.getFileInUserHome(KEYSTORE);
......@@ -176,7 +188,7 @@ public class SecureSocketFactory {
}
}
private String[] addAnonymous(String[] list) {
private static String[] addAnonymous(String[] list) {
String[] newList = new String[list.length + 1];
System.arraycopy(list, 0, newList, 1, list.length);
newList[0] = ANONYMOUS_CIPHER_SUITE;
......
......@@ -22,6 +22,10 @@ public class XTEA implements BlockCipher {
private int k0, k1, k2, k3, k4, k5, k6, k7, k8, k9, k10, k11, k12, k13, k14, k15;
private int k16, k17, k18, k19, k20, k21, k22, k23, k24, k25, k26, k27, k28, k29, k30, k31;
XTEA() {
// do nothing
}
public void setKey(byte[] b) {
int[] key = new int[4];
for (int i = 0; i < 16;) {
......@@ -57,7 +61,7 @@ public class XTEA implements BlockCipher {
}
}
public void encryptBlock(byte[] in, byte[] out, int off) {
private void encryptBlock(byte[] in, byte[] out, int off) {
int y = (in[off] << 24) | ((in[off+1] & 255) << 16) | ((in[off+2] & 255) << 8) | (in[off+3] & 255);
int z = (in[off+4] << 24) | ((in[off+5] & 255) << 16) | ((in[off+6] & 255) << 8) | (in[off+7] & 255);
y += (((z << 4) ^ (z >>> 5)) + z) ^ k0; z += (((y >>> 5) ^ (y << 4)) + y) ^ k1;
......@@ -80,7 +84,7 @@ public class XTEA implements BlockCipher {
out[off+4] = (byte) (z >> 24); out[off+5] = (byte) (z >> 16); out[off+6] = (byte) (z >> 8); out[off+7] = (byte) z;
}
public void decryptBlock(byte[] in, byte[] out, int off) {
private void decryptBlock(byte[] in, byte[] out, int off) {
int y = (in[off] << 24) | ((in[off+1] & 255) << 16) | ((in[off+2] & 255) << 8) | (in[off+3] & 255);
int z = (in[off+4] << 24) | ((in[off+5] & 255) << 16) | ((in[off+6] & 255) << 8) | (in[off+7] & 255);
z -= (((y >>> 5) ^ (y << 4)) + y) ^ k31; y -= (((z << 4) ^ (z >>> 5)) + z) ^ k30;
......
......@@ -26,6 +26,13 @@ public class NetUtils {
private static InetAddress bindAddress;
/**
* Create a loopback socket (a socket that is connected to localhost) on this port.
*
* @param port the port
* @param ssl if SSL should be used
* @return the socket
*/
public static Socket createLoopbackSocket(int port, boolean ssl) throws IOException {
InetAddress address = getBindAddress();
if (address == null) {
......@@ -34,6 +41,14 @@ public class NetUtils {
return createSocket(address.getHostAddress(), port, ssl);
}
/**
* Create a client socket that is connected to the given address and port.
*
* @param server to connect to (including an optional port)
* @param defaultPort the default port (if not specified in the server address)
* @param ssl if SSL should be used
* @return the socket
*/
public static Socket createSocket(String server, int defaultPort, boolean ssl) throws IOException {
int port = defaultPort;
// IPv6: RFC 2732 format is '[a:b:c:d:e:f:g:h]' or
......@@ -50,14 +65,29 @@ public class NetUtils {
return createSocket(address, port, ssl);
}
/**
* Create a client socket that is connected to the given address and port.
*
* @param address the address to connect to
* @param port the port
* @param ssl if SSL should be used
* @return the socket
*/
public static Socket createSocket(InetAddress address, int port, boolean ssl) throws IOException {
if (ssl) {
SecureSocketFactory f = SecureSocketFactory.getInstance();
return f.createSocket(address, port);
return SecureSocketFactory.createSocket(address, port);
}
return new Socket(address, port);
}
/**
* Create a server socket. The system property h2.bindAddress is used if
* set.
*
* @param port the port to listen on
* @param ssl if SSL should be used
* @return the server socket
*/
public static ServerSocket createServerSocket(int port, boolean ssl) throws SQLException {
try {
return createServerSocketTry(port, ssl);
......@@ -73,7 +103,7 @@ public class NetUtils {
*
* @return the bind address
*/
public static InetAddress getBindAddress() throws UnknownHostException {
private static InetAddress getBindAddress() throws UnknownHostException {
String host = SysProperties.BIND_ADDRESS;
if (host == null || host.length() == 0) {
return null;
......@@ -88,11 +118,10 @@ public class NetUtils {
private static ServerSocket createServerSocketTry(int port, boolean ssl) throws SQLException {
try {
InetAddress bindAddress = getBindAddress();
if (ssl) {
SecureSocketFactory f = SecureSocketFactory.getInstance();
return f.createServerSocket(port);
return SecureSocketFactory.createServerSocket(port, bindAddress);
}
InetAddress bindAddress = getBindAddress();
if (bindAddress == null) {
return new ServerSocket(port);
}
......@@ -105,6 +134,12 @@ public class NetUtils {
}
}
/**
* Check if a socket is connected to localhost.
*
* @param socket the socket
* @return true if it is
*/
public static boolean isLoopbackAddress(Socket socket) {
boolean result = true;
//## Java 1.4 begin ##
......@@ -113,6 +148,12 @@ public class NetUtils {
return result;
}
/**
* Close a server socket and ignore any exceptions.
*
* @param socket the socket
* @return null
*/
public static ServerSocket closeSilently(ServerSocket socket) {
if (socket != null) {
try {
......@@ -124,6 +165,11 @@ public class NetUtils {
return null;
}
/**
* Get the local host address as a string.
*
* @return the local host address
*/
public static String getLocalAddress() {
InetAddress bind = null;
try {
......
......@@ -160,12 +160,13 @@ java org.h2.test.TestAll timer
/*
http://patir.rubyforge.org/rutema/index.html
Rutema is a test execution and management tool for heterogeneous development environments written in Ruby.
jazoon
upload and test javadoc/index.html
download PostgreSQL docs
in help.csv, use complete examples for functions; add a test case
improve javadocs
......@@ -233,6 +234,9 @@ Roadmap:
sometimes a stack overflow exception was thrown instead of a IO exception.
The H2 Console could not be shut down from within the tool if the
browser supports keepAlive (most browsers do).
If the password was passed as a char array, it was kept in an internal buffer
longer than required. Theoretically the password could have been stolen
if the main memory was swapped to disk before the garbage collection was run.
*/
if (args.length > 0) {
......
--- special grammar and test cases ---------------------------------------------------------------------------------------------
alter table information_schema.help rename to information_schema.help2;
> exception
help abc;
> ID SECTION TOPIC SYNTAX TEXT EXAMPLE
> -- ------- ----- ------ ---- -------
> rows: 0
CREATE TABLE test (id int(25) NOT NULL auto_increment, name varchar NOT NULL, PRIMARY KEY (id,name));
> ok
......
......@@ -6,9 +6,9 @@
*/
package org.h2.test.unit;
import org.h2.security.AES;
import org.h2.security.BlockCipher;
import org.h2.security.CipherFactory;
import org.h2.security.SHA256;
import org.h2.security.XTEA;
import org.h2.test.TestBase;
import org.h2.util.ByteUtils;
......@@ -28,8 +28,11 @@ public class TestSecurity extends TestBase {
testOneSHA(sha);
}
private String getHashString(SHA256 sha, byte[] data) {
byte[] result = sha.getHash(data);
private String getHashString(SHA256 sha, byte[] data) throws Exception {
byte[] result = sha.getHash(data, true);
if (data.length > 0) {
check(data[0], 0);
}
return ByteUtils.convertBytesToString(result);
}
......@@ -63,21 +66,21 @@ public class TestSecurity extends TestBase {
void checkSHA256(String message, String expected) throws Exception {
SHA256 sha = new SHA256();
String hash = ByteUtils.convertBytesToString(sha.getHash(message.getBytes())).toUpperCase();
String hash = ByteUtils.convertBytesToString(sha.getHash(message.getBytes(), true)).toUpperCase();
check(expected, hash);
}
public void testXTEA() throws Exception {
byte[] test = new byte[4096];
XTEA xtea = new XTEA();
BlockCipher xtea = CipherFactory.getBlockCipher("XTEA");
xtea.setKey("abcdefghijklmnop".getBytes());
for (int i = 0; i < 10; i++) {
xtea.decryptBlock(test, test, 0);
xtea.decrypt(test, 0, test.length);
}
}
private void testAES() throws Exception {
AES test = new AES();
BlockCipher test = CipherFactory.getBlockCipher("AES");
test.setKey(ByteUtils.convertStringToBytes("000102030405060708090A0B0C0D0E0F"));
byte[] in = new byte[128];
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论