org.parosproxy.paros.db.paros.ParosTableHistory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of zap Show documentation
Show all versions of zap Show documentation
The Zed Attack Proxy (ZAP) is an easy to use integrated penetration testing tool for finding vulnerabilities in web applications. It is designed to be used by people with a wide range of security experience and as such is ideal for developers and functional testers who are new to penetration testing. ZAP provides automated scanners as well as a set of tools that allow you to find security vulnerabilities manually.
/*
*
* Paros and its related class files.
*
* Paros is an HTTP/HTTPS proxy for assessing web application security.
* Copyright (C) 2003-2006 Chinotec Technologies Company
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Clarified Artistic License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Clarified Artistic License for more details.
*
* You should have received a copy of the Clarified Artistic License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// ZAP: 2011/05/27 Ensure all PreparedStatements and ResultSets closed to prevent leaks
// ZAP: 2012/03/15 Changed to use byte[] in the request and response bodies
// instead of String.
// ZAP: 2012/04/23 Added @Override annotation to the appropriate method.
// ZAP: 2012/04/25 Changed to use the method Integer.valueOf.
// ZAP: 2012/06/11 Added method delete(List).
// ZAP: 2012/08/08 Upgrade to HSQLDB 2.x (Added updateTable() and refactored names)
// ZAP: 2013/09/26 Issue 716: ZAP flags its own HTTP responses
// ZAP: 2014/03/23 Changed to use try-with-resource statements.
// ZAP: 2014/03/23 Issue 999: History loaded in wrong order
// ZAP: 2014/03/23 Issue 1075: Change TableHistory to delete records in batches
// ZAP: 2014/03/23 Issue 1091: CoreAPI - Do not get the IDs of temporary history records
// ZAP: 2014/03/27 Issue 1072: Allow the request and response body sizes to be user-specifiable as far as possible
// ZAP: 2014/08/14 Issue 1310: Allow to set history types as temporary
// ZAP: 2014/12/11 Replaced calls to Charset.forName(String) with StandardCharsets
// ZAP: 2015/02/09 Issue 1525: Introduce a database interface layer to allow for alternative implementations
// ZAP: 2016/05/26 Delete temporary history types sequentially
// ZAP: 2016/05/27 Change to use HistoryReference to obtain the temporary types
// ZAP: 2016/08/30 Issue 2836: Change to delete temporary history types in batches to prevent out-of-memory-exception(s)
package org.parosproxy.paros.db.paros;
import java.nio.charset.StandardCharsets;
import java.sql.Array;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.ArrayUtils;
import org.apache.log4j.Logger;
import org.hsqldb.types.Types;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.db.DatabaseException;
import org.parosproxy.paros.db.DbUtils;
import org.parosproxy.paros.db.RecordHistory;
import org.parosproxy.paros.db.TableHistory;
import org.parosproxy.paros.extension.option.DatabaseParam;
import org.parosproxy.paros.model.HistoryReference;
import org.parosproxy.paros.network.HttpMalformedHeaderException;
import org.parosproxy.paros.network.HttpMessage;
import org.parosproxy.paros.network.HttpStatusCode;
public class ParosTableHistory extends ParosAbstractTable implements TableHistory {
private static final String TABLE_NAME = "HISTORY";
private static final String HISTORYID = "HISTORYID";
private static final String SESSIONID = "SESSIONID";
private static final String HISTTYPE = "HISTTYPE";
private static final String METHOD = "METHOD";
private static final String URI = "URI";
private static final String STATUSCODE = "STATUSCODE";
private static final String TIMESENTMILLIS = "TIMESENTMILLIS";
private static final String TIMEELAPSEDMILLIS = "TIMEELAPSEDMILLIS";
private static final String REQHEADER = "REQHEADER";
private static final String REQBODY = "REQBODY";
private static final String RESHEADER = "RESHEADER";
private static final String RESBODY = "RESBODY";
private static final String TAG = "TAG";
// ZAP: Added NOTE field to history table
private static final String NOTE = "NOTE";
private static final String RESPONSE_FROM_TARGET_HOST = "RESPONSEFROMTARGETHOST";
private PreparedStatement psRead = null;
private PreparedStatement psInsert = null;
private CallableStatement psGetIdLastInsert = null;
private PreparedStatement psDelete = null;
private PreparedStatement psDeleteTemp = null;
private PreparedStatement psContainsURI = null;
//private PreparedStatement psAlterTable = null;
// private PreparedStatement psUpdateTag = null;
private PreparedStatement psUpdateNote = null;
private int lastInsertedIndex;
private static boolean isExistStatusCode = false;
// ZAP: Added logger
private static final Logger log = Logger.getLogger(ParosTableHistory.class);
private boolean bodiesAsBytes;
public ParosTableHistory() {
}
//ZAP: Allow the request and response body sizes to be user-specifiable as far as possible
int configuredrequestbodysize = -1;
int configuredresponsebodysize = -1;
@Override
protected void reconnect(Connection conn) throws DatabaseException {
try {
//ZAP: Allow the request and response body sizes to be user-specifiable as far as possible
//re-load the configuration data from file, to get the configured length of the request and response bodies
//this will later be compared to the actual lengths of these fields in the database (in updateTable(Connection c))
DatabaseParam dbparams = new DatabaseParam ();
dbparams.load(Constant.getInstance().FILE_CONFIG);
this.configuredrequestbodysize = dbparams.getRequestBodySize();
this.configuredresponsebodysize = dbparams.getResponseBodySize();
bodiesAsBytes = true;
updateTable(conn);
isExistStatusCode = DbUtils.hasColumn(conn, TABLE_NAME, STATUSCODE);
psRead = conn.prepareStatement("SELECT TOP 1 * FROM HISTORY WHERE " + HISTORYID + " = ?");
// updatable recordset does not work in hsqldb jdbc impelementation!
//psWrite = mConn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
psDelete = conn.prepareStatement("DELETE FROM HISTORY WHERE " + HISTORYID + " = ?");
psDeleteTemp = conn.prepareStatement("DELETE FROM HISTORY WHERE " + HISTTYPE + " IN (?) LIMIT 1000");
psContainsURI = conn.prepareStatement("SELECT TOP 1 HISTORYID FROM HISTORY WHERE URI = ? AND METHOD = ? AND REQBODY = ? AND SESSIONID = ? AND HISTTYPE = ?");
// ZAP: Added support for the tag when creating a history record
if (isExistStatusCode) {
psInsert = conn.prepareStatement("INSERT INTO HISTORY ("
+ SESSIONID + "," + HISTTYPE + "," + TIMESENTMILLIS + "," +
TIMEELAPSEDMILLIS + "," + METHOD + "," + URI + "," + REQHEADER + "," +
REQBODY + "," + RESHEADER + "," + RESBODY + "," + TAG + ", " + STATUSCODE + "," + NOTE + ", " +
RESPONSE_FROM_TARGET_HOST
+ ") VALUES (?, ? ,?, ?, ?, ?, ?, ? ,? , ?, ?, ?, ?, ?)");
} else {
psInsert = conn.prepareStatement("INSERT INTO HISTORY ("
+ SESSIONID + "," + HISTTYPE + "," + TIMESENTMILLIS + "," +
TIMEELAPSEDMILLIS + "," + METHOD + "," + URI + "," + REQHEADER + "," +
REQBODY + "," + RESHEADER + "," + RESBODY + "," + TAG + "," + NOTE + ", " +
RESPONSE_FROM_TARGET_HOST
+ ") VALUES (?, ? ,?, ?, ?, ?, ?, ? ,? , ? , ?, ?, ?)");
}
psGetIdLastInsert = conn.prepareCall("CALL IDENTITY();");
// psUpdateTag = conn.prepareStatement("UPDATE HISTORY SET TAG = ? WHERE HISTORYID = ?");
psUpdateNote = conn.prepareStatement("UPDATE HISTORY SET NOTE = ? WHERE HISTORYID = ?");
int currentIndex = 0;
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement("SELECT TOP 1 HISTORYID FROM HISTORY ORDER BY HISTORYID DESC");
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
currentIndex = rs.getInt(1);
}
}
} finally {
if (stmt != null) {
try {
stmt.close();
} catch(SQLException e) {
if (log.isDebugEnabled()) {
log.debug(e.getMessage(), e);
}
}
}
}
lastInsertedIndex = currentIndex;
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
// ZAP: Added the method.
private void updateTable(Connection connection) throws DatabaseException {
try {
if (!DbUtils.hasColumn(connection, TABLE_NAME, TAG)) {
DbUtils.executeAndClose(connection.prepareStatement("ALTER TABLE "+TABLE_NAME+" ADD COLUMN "+TAG+" VARCHAR(32768) DEFAULT ''"));
}
// Add the NOTE column to the db if necessary
if (!DbUtils.hasColumn(connection, TABLE_NAME, NOTE)) {
DbUtils.executeAndClose(connection.prepareStatement("ALTER TABLE "+TABLE_NAME+" ADD COLUMN "+NOTE+" VARCHAR(1048576) DEFAULT ''"));
}
if (DbUtils.getColumnType(connection, TABLE_NAME, REQBODY) != Types.SQL_VARBINARY) {
bodiesAsBytes = false;
} else {
// Databases created with ZAP<1.4.0.1 used VARCHAR for the REQBODY/RESBODY
// HSQLDB 1.8.x converted from VARCHAR to bytes without problems
// (through the method ResultSet.getBytes)
// but the new version doesn't, it throws the following exception:
// incompatible data type in conversion: from SQL type VARCHAR
}
if (!DbUtils.hasColumn(connection, TABLE_NAME, RESPONSE_FROM_TARGET_HOST)) {
DbUtils.executeAndClose(connection.prepareStatement("ALTER TABLE " + TABLE_NAME + " ADD COLUMN "
+ RESPONSE_FROM_TARGET_HOST + " BOOLEAN DEFAULT FALSE"));
DbUtils.executeUpdateAndClose(connection.prepareStatement("UPDATE " + TABLE_NAME + " SET " + RESPONSE_FROM_TARGET_HOST
+ " = TRUE "));
}
int requestbodysizeindb = DbUtils.getColumnSize(connection, TABLE_NAME, REQBODY);
int responsebodysizeindb = DbUtils.getColumnSize(connection, TABLE_NAME, RESBODY);
try {
if (requestbodysizeindb != this.configuredrequestbodysize && this.configuredrequestbodysize > 0) {
if (log.isDebugEnabled()) log.debug("Extending table "+ TABLE_NAME + " request body length from "+ requestbodysizeindb + " to " + this.configuredrequestbodysize);
DbUtils.executeAndClose(connection.prepareStatement("ALTER TABLE " + TABLE_NAME + " ALTER COLUMN "
+ REQBODY + " VARBINARY("+this.configuredrequestbodysize+")"));
if (log.isDebugEnabled()) log.debug("Completed extending table "+ TABLE_NAME + " request body length from "+ requestbodysizeindb + " to " + this.configuredrequestbodysize);
}
if (responsebodysizeindb != this.configuredresponsebodysize && this.configuredresponsebodysize > 0) {
if (log.isDebugEnabled()) log.debug("Extending table "+ TABLE_NAME + " response body length from "+ responsebodysizeindb + " to " + this.configuredresponsebodysize);
DbUtils.executeAndClose(connection.prepareStatement("ALTER TABLE " + TABLE_NAME + " ALTER COLUMN "
+ RESBODY + " VARBINARY("+this.configuredresponsebodysize+")"));
if (log.isDebugEnabled()) log.debug("Completed extending table "+ TABLE_NAME + " response body length from "+ responsebodysizeindb + " to " + this.configuredresponsebodysize);
}
}
catch (SQLException e) {
log.error("An error occurred while modifying a column length on "+ TABLE_NAME);
log.error("The 'Maximum Request Body Size' value in the Database Options needs to be set to at least " + requestbodysizeindb + " to avoid this error" );
log.error("The 'Maximum Response Body Size' value in the Database Options needs to be set to at least " + responsebodysizeindb + " to avoid this error" );
log.error("The SQL Exception was:", e);
throw e;
}
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public synchronized RecordHistory read(int historyId) throws HttpMalformedHeaderException, DatabaseException {
try {
psRead.setInt(1, historyId);
psRead.execute();
RecordHistory result = null;
try (ResultSet rs = psRead.getResultSet()) {
result = build(rs);
}
return result;
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public synchronized RecordHistory write(long sessionId, int histType, HttpMessage msg) throws HttpMalformedHeaderException, DatabaseException {
try {
String reqHeader = "";
byte[] reqBody = new byte[0];
String resHeader = "";
byte[] resBody = reqBody;
String method = "";
String uri = "";
int statusCode = 0;
String note = msg.getNote();
if (!msg.getRequestHeader().isEmpty()) {
reqHeader = msg.getRequestHeader().toString();
reqBody = msg.getRequestBody().getBytes();
method = msg.getRequestHeader().getMethod();
uri = msg.getRequestHeader().getURI().toString();
}
if (!msg.getResponseHeader().isEmpty()) {
resHeader = msg.getResponseHeader().toString();
resBody = msg.getResponseBody().getBytes();
statusCode = msg.getResponseHeader().getStatusCode();
}
//return write(sessionId, histType, msg.getTimeSentMillis(), msg.getTimeElapsedMillis(), method, uri, statusCode, reqHeader, reqBody, resHeader, resBody, msg.getTag());
return write(sessionId, histType, msg.getTimeSentMillis(), msg.getTimeElapsedMillis(), method, uri, statusCode, reqHeader, reqBody, resHeader, resBody, null, note, msg.isResponseFromTargetHost());
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
private synchronized RecordHistory write(long sessionId, int histType, long timeSentMillis, int timeElapsedMillis,
String method, String uri, int statusCode,
String reqHeader, byte[] reqBody, String resHeader, byte[] resBody, String tag, String note, boolean responseFromTargetHost)
throws HttpMalformedHeaderException, SQLException, DatabaseException {
//ZAP: Allow the request and response body sizes to be user-specifiable as far as possible
if (reqBody.length > this.configuredrequestbodysize) {
throw new SQLException("The actual Request Body length "+ reqBody.length + " is greater than the configured request body length "+ this.configuredrequestbodysize);
}
if (resBody.length > this.configuredresponsebodysize) {
throw new SQLException("The actual Response Body length "+ resBody.length + " is greater than the configured response body length "+ this.configuredresponsebodysize);
}
psInsert.setLong(1, sessionId);
psInsert.setInt(2, histType);
psInsert.setLong(3, timeSentMillis);
psInsert.setInt(4, timeElapsedMillis);
psInsert.setString(5, method);
psInsert.setString(6, uri);
psInsert.setString(7, reqHeader);
if (bodiesAsBytes) {
psInsert.setBytes(8, reqBody);
} else {
psInsert.setString(8, new String(reqBody, StandardCharsets.US_ASCII));
}
psInsert.setString(9, resHeader);
if (bodiesAsBytes) {
psInsert.setBytes(10, resBody);
} else {
psInsert.setString(10, new String(resBody, StandardCharsets.US_ASCII));
}
psInsert.setString(11, tag);
// ZAP: Added the statement.
int currentIdx = 12;
if (isExistStatusCode) {
psInsert.setInt(currentIdx, statusCode);
// ZAP: Added the statement.
++currentIdx;
}
// ZAP: Added the statement.
psInsert.setString(currentIdx, note);
++currentIdx;
psInsert.setBoolean(currentIdx, responseFromTargetHost);
psInsert.executeUpdate();
/*
String sql = "INSERT INTO HISTORY ("
+ REQHEADER + "," + REQBODY + "," + RESHEADER + "," + RESBODY +
") VALUES ('"+ reqHeader + "','" + reqBody + "','" + resHeader + "','" + resBody + "'); CALL IDENTITY();";
Statement stmt = mConn.createStatement();
stmt.executeQuery(sql);
ResultSet rs = stmt.getResultSet();
*/
try (ResultSet rs = psGetIdLastInsert.executeQuery()) {
rs.next();
int id = rs.getInt(1);
lastInsertedIndex = id;
return read(id);
}
}
private RecordHistory build(ResultSet rs) throws HttpMalformedHeaderException, SQLException {
RecordHistory history = null;
try {
if (rs.next()) {
byte[] reqBody;
byte[] resBody;
if (bodiesAsBytes) {
reqBody = rs.getBytes(REQBODY);
resBody = rs.getBytes(RESBODY);
} else {
reqBody = rs.getString(REQBODY).getBytes();
resBody = rs.getString(RESBODY).getBytes();
}
history = new RecordHistory(
rs.getInt(HISTORYID),
rs.getInt(HISTTYPE),
rs.getLong(SESSIONID),
rs.getLong(TIMESENTMILLIS),
rs.getInt(TIMEELAPSEDMILLIS),
rs.getString(REQHEADER),
reqBody,
rs.getString(RESHEADER),
resBody,
rs.getString(TAG),
rs.getString(NOTE), // ZAP: Added note
rs.getBoolean(RESPONSE_FROM_TARGET_HOST)
);
}
} finally {
rs.close();
}
return history;
}
/**
* Gets all the history record IDs of the given session.
*
* @param sessionId the ID of session of the history records to be returned
* @return a {@code List} with all the history IDs of the given session, never {@code null}
* @throws DatabaseException if an error occurred while getting the history IDs
* @since 2.3.0
* @see #getHistoryIdsOfHistType(long, int...)
*/
@Override
public List getHistoryIds(long sessionId) throws DatabaseException {
return getHistoryIdsOfHistType(sessionId, null);
}
/**
* Gets all the history record IDs of the given session and with the given history types.
*
* @param sessionId the ID of session of the history records
* @param histTypes the history types of the history records that should be returned
* @return a {@code List} with all the history IDs of the given session and history types, never {@code null}
* @throws DatabaseException if an error occurred while getting the history IDs
* @since 2.3.0
* @see #getHistoryIds(long)
* @see #getHistoryIdsExceptOfHistType(long, int...)
*/
@Override
public List getHistoryIdsOfHistType(long sessionId, int... histTypes) throws DatabaseException {
try {
boolean hasHistTypes = histTypes != null && histTypes.length > 0;
int strLength = hasHistTypes ? 97 : 68;
StringBuilder strBuilder = new StringBuilder(strLength);
strBuilder.append("SELECT ").append(HISTORYID);
strBuilder.append(" FROM ").append(TABLE_NAME).append(" WHERE ").append(SESSIONID).append(" = ?");
if (hasHistTypes) {
strBuilder.append(" AND ").append(HISTTYPE).append(" IN ( UNNEST(?) )");
}
strBuilder.append(" ORDER BY ").append(HISTORYID);
try (PreparedStatement psReadSession = getConnection().prepareStatement(strBuilder.toString())) {
psReadSession.setLong(1, sessionId);
if (hasHistTypes) {
Array arrayHistTypes = getConnection().createArrayOf("INTEGER", ArrayUtils.toObject(histTypes));
psReadSession.setArray(2, arrayHistTypes);
}
try (ResultSet rs = psReadSession.executeQuery()) {
ArrayList ids = new ArrayList<>();
while (rs.next()) {
ids.add(Integer.valueOf(rs.getInt(HISTORYID)));
}
ids.trimToSize();
return ids;
}
}
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
/**
* Returns all the history record IDs of the given session except the ones with the given history types.
**
* @param sessionId the ID of session of the history records
* @param histTypes the history types of the history records that should be excluded
* @return a {@code List} with all the history IDs of the given session and history types, never {@code null}
* @throws DatabaseException if an error occurred while getting the history IDs
* @since 2.3.0
* @see #getHistoryIdsOfHistType(long, int...)
*/
@Override
public List getHistoryIdsExceptOfHistType(long sessionId, int... histTypes) throws DatabaseException {
try {
boolean hasHistTypes = histTypes != null && histTypes.length > 0;
int strLength = hasHistTypes ? 102 : 68;
StringBuilder sb = new StringBuilder(strLength);
sb.append("SELECT ").append(HISTORYID);
sb.append(" FROM ").append(TABLE_NAME).append(" WHERE ").append(SESSIONID).append(" = ?");
if (hasHistTypes) {
sb.append(" AND ").append(HISTTYPE).append(" NOT IN ( UNNEST(?) )");
}
sb.append(" ORDER BY ").append(HISTORYID);
try (PreparedStatement psReadSession = getConnection().prepareStatement(sb.toString())) {
psReadSession.setLong(1, sessionId);
if (hasHistTypes) {
Array arrayHistTypes = getConnection().createArrayOf("INTEGER", ArrayUtils.toObject(histTypes));
psReadSession.setArray(2, arrayHistTypes);
}
try (ResultSet rs = psReadSession.executeQuery()) {
ArrayList ids = new ArrayList<>();
while (rs.next()) {
ids.add(Integer.valueOf(rs.getInt(HISTORYID)));
}
ids.trimToSize();
return ids;
}
}
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
/**
* @deprecated (2.3.0) Use {@link #getHistoryIdsOfHistType(long, int...)} instead. If the thread-safety provided by the
* class {@code Vector} is really required "wrap" the returned List with
* {@link Collections#synchronizedList(List)} instead.
*/
@Deprecated
@SuppressWarnings("javadoc")
public Vector getHistoryList(long sessionId, int histType) throws DatabaseException {
return new Vector<>(getHistoryIdsOfHistType(sessionId, histType));
}
/**
* @deprecated (2.3.0) Use {@link #getHistoryIds(long)} instead. If the thread-safety provided by the class {@code Vector}
* is really required "wrap" the returned List with {@link Collections#synchronizedList(List)} instead.
*/
@Deprecated
@SuppressWarnings("javadoc")
public Vector getHistoryList(long sessionId) throws DatabaseException {
return new Vector<>(getHistoryIds(sessionId));
}
@Override
public List getHistoryList(long sessionId, int histType, String filter, boolean isRequest) throws DatabaseException {
try {
PreparedStatement psReadSearch = getConnection().prepareStatement("SELECT * FROM HISTORY WHERE " + SESSIONID + " = ? AND " + HISTTYPE + " = ? ORDER BY " + HISTORYID);
ResultSet rs = null;
Vector v = new Vector<>();
try {
Pattern pattern = Pattern.compile(filter, Pattern.MULTILINE| Pattern.CASE_INSENSITIVE);
Matcher matcher = null;
psReadSearch.setLong(1, sessionId);
psReadSearch.setInt(2, histType);
rs = psReadSearch.executeQuery();
while (rs.next()) {
if (isRequest) {
matcher = pattern.matcher(rs.getString(REQHEADER));
if (matcher.find()) {
// ZAP: Changed to use the method Integer.valueOf.
v.add(Integer.valueOf(rs.getInt(HISTORYID)));
continue;
}
matcher = pattern.matcher(rs.getString(REQBODY));
if (matcher.find()) {
// ZAP: Changed to use the method Integer.valueOf.
v.add(Integer.valueOf(rs.getInt(HISTORYID)));
continue;
}
} else {
matcher = pattern.matcher(rs.getString(RESHEADER));
if (matcher.find()) {
// ZAP: Changed to use the method Integer.valueOf.
v.add(Integer.valueOf(rs.getInt(HISTORYID)));
continue;
}
matcher = pattern.matcher(rs.getString(RESBODY));
if (matcher.find()) {
// ZAP: Changed to use the method Integer.valueOf.
v.add(Integer.valueOf(rs.getInt(HISTORYID)));
continue;
}
}
}
} finally {
if (rs != null) {
try {
rs.close();
} catch (Exception e) {
// Ignore
}
}
psReadSearch.close();
}
return v;
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public void deleteHistorySession(long sessionId) throws DatabaseException {
try {
try (Statement stmt = getConnection().createStatement()) {
stmt.executeUpdate("DELETE FROM HISTORY WHERE " + SESSIONID + " = " + sessionId);
}
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public void deleteHistoryType(long sessionId, int historyType) throws DatabaseException {
try {
try (Statement stmt = getConnection().createStatement()) {
stmt.executeUpdate("DELETE FROM HISTORY WHERE " + SESSIONID + " = " + sessionId + " AND " + HISTTYPE + " = " + historyType);
}
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public synchronized void delete(int historyId) throws DatabaseException {
try {
psDelete.setInt(1, historyId);
psDelete.executeUpdate();
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
/**
* Deletes from the database all the history records whose ID is
* in the list {@code ids}, in batches of 1000 records.
*
* @param ids
* a {@code List} containing all the IDs of the
* history records to be deleted
* @throws IllegalArgumentException if {@code ids} is null
* @throws DatabaseException
* if an error occurred while deleting the
* history records
* @since 2.0.0
* @see #delete(List, int)
*/
// ZAP: Added method.
@Override
public void delete(List ids) throws DatabaseException {
delete(ids, 1000);
}
/**
* Deletes from the database all the history records whose ID is in the list {@code ids}, in batches of given
* {@code batchSize}.
*
* @param ids a {@code List} containing all the IDs of the history records to be deleted
* @param batchSize the maximum size of records to delete in a single batch
* @throws IllegalArgumentException if {@code ids} is null
* @throws IllegalArgumentException if {@code batchSize} is not greater than zero
* @throws DatabaseException if an error occurred while deleting the history records
* @since 2.3.0
*/
@Override
public synchronized void delete(List ids, int batchSize) throws DatabaseException {
try {
if (ids == null) {
throw new IllegalArgumentException("Parameter ids must not be null.");
}
if (batchSize <= 0) {
throw new IllegalArgumentException("Parameter batchSize must be greater than zero.");
}
int count = 0;
for (Integer id : ids) {
psDelete.setInt(1, id.intValue());
psDelete.addBatch();
count++;
if (count % batchSize == 0) {
psDelete.executeBatch();
count = 0;
}
}
if (count % batchSize != 0) {
psDelete.executeBatch();
}
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
/**
* @deprecated (2.5.0) Use {@link HistoryReference#addTemporaryType(int)} instead.
* @since 2.4
* @param historyType the history type that will be set as temporary
* @see #deleteTemporary()
*/
@Deprecated
public static void setHistoryTypeAsTemporary(int historyType) {
HistoryReference.addTemporaryType(historyType);
}
/**
* @deprecated (2.5.0) Use {@link HistoryReference#removeTemporaryType(int)} instead.
* @since 2.4
* @param historyType the history type that will be marked as temporary
* @see #deleteTemporary()
*/
@Deprecated
public static void unsetHistoryTypeAsTemporary(int historyType) {
HistoryReference.removeTemporaryType(historyType);
}
/**
* Deletes all records whose history type was marked as temporary (by calling {@code setHistoryTypeTemporary(int)}).
*
* By default the only temporary history types are {@code HistoryReference#TYPE_TEMPORARY} and
* {@code HistoryReference#TYPE_SCANNER_TEMPORARY}.
*
*
* @throws DatabaseException if an error occurred while deleting the temporary history records
* @see HistoryReference#getTemporaryTypes()
*/
@Override
public void deleteTemporary() throws DatabaseException {
try {
for (Integer type : HistoryReference.getTemporaryTypes()) {
while (true) {
psDeleteTemp.setInt(1, type);
int result = psDeleteTemp.executeUpdate();
if (result == 0) {
break;
}
}
}
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public synchronized boolean containsURI(long sessionId, int historyType, String method, String uri, byte[] body) throws DatabaseException {
try {
psContainsURI.setString(1, uri);
psContainsURI.setString(2, method);
if (bodiesAsBytes) {
psContainsURI.setBytes(3, body);
} else {
psContainsURI.setString(3, new String(body));
}
psContainsURI.setLong(4, sessionId);
psContainsURI.setInt(5, historyType);
try (ResultSet rs = psContainsURI.executeQuery()) {
if (rs.next()) {
return true;
}
}
return false;
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public RecordHistory getHistoryCache(HistoryReference ref, HttpMessage reqMsg) throws DatabaseException , HttpMalformedHeaderException {
try {
// get the cache from provided reference.
// naturally, the obtained cache should be AFTER AND NEARBY to the given reference.
// - historyId up to historyId+200
// - match sessionId
// - history type can be MANUEL or hidden (hidden is used by images not explicitly stored in history)
// - match URI
PreparedStatement psReadCache = null;
if (isExistStatusCode) {
// psReadCache = getConnection().prepareStatement("SELECT TOP 1 * FROM HISTORY WHERE URI = ? AND METHOD = ? AND REQBODY = ? AND " + HISTORYID + " >= ? AND " + HISTORYID + " <= ? AND SESSIONID = ? AND (HISTTYPE = " + HistoryReference.TYPE_MANUAL + " OR HISTTYPE = " + HistoryReference.TYPE_HIDDEN + ") AND STATUSCODE != 304");
psReadCache = getConnection().prepareStatement("SELECT TOP 1 * FROM HISTORY WHERE URI = ? AND METHOD = ? AND REQBODY = ? AND " + HISTORYID + " >= ? AND " + HISTORYID + " <= ? AND SESSIONID = ? AND STATUSCODE != 304");
} else {
// psReadCache = getConnection().prepareStatement("SELECT * FROM HISTORY WHERE URI = ? AND METHOD = ? AND REQBODY = ? AND " + HISTORYID + " >= ? AND " + HISTORYID + " <= ? AND SESSIONID = ? AND (HISTTYPE = " + HistoryReference.TYPE_MANUAL + " OR HISTTYPE = " + HistoryReference.TYPE_HIDDEN + ")");
psReadCache = getConnection().prepareStatement("SELECT * FROM HISTORY WHERE URI = ? AND METHOD = ? AND REQBODY = ? AND " + HISTORYID + " >= ? AND " + HISTORYID + " <= ? AND SESSIONID = ?)");
}
psReadCache.setString(1, reqMsg.getRequestHeader().getURI().toString());
psReadCache.setString(2, reqMsg.getRequestHeader().getMethod());
if (bodiesAsBytes) {
psReadCache.setBytes(3, reqMsg.getRequestBody().getBytes());
} else {
psReadCache.setString(3, new String(reqMsg.getRequestBody().getBytes()));
}
psReadCache.setInt(4, ref.getHistoryId());
psReadCache.setInt(5, ref.getHistoryId()+200);
psReadCache.setLong(6, ref.getSessionId());
ResultSet rs = psReadCache.executeQuery();
RecordHistory rec = null;
try {
do {
rec = build(rs);
// for retrieval from cache, the message requests nature must be the same.
// and the result should NOT be NOT_MODIFIED for rendering by browser
if (rec != null && rec.getHttpMessage().equals(reqMsg) &&
rec.getHttpMessage().getResponseHeader().getStatusCode() != HttpStatusCode.NOT_MODIFIED) {
return rec;
}
} while (rec != null);
} finally {
try {
rs.close();
psReadCache.close();
} catch (Exception e) {
// ZAP: Log exceptions
log.warn(e.getMessage(), e);
}
}
// if cache not exist, probably due to NOT_MODIFIED,
// lookup from cache BEFORE the given reference
if (isExistStatusCode) {
// psReadCache = getConnection().prepareStatement("SELECT TOP 1 * FROM HISTORY WHERE URI = ? AND METHOD = ? AND REQBODY = ? AND SESSIONID = ? AND STATUSCODE != 304 AND (HISTTYPE = " + HistoryReference.TYPE_MANUAL + " OR HISTTYPE = " + HistoryReference.TYPE_HIDDEN + ")");
psReadCache = getConnection().prepareStatement("SELECT TOP 1 * FROM HISTORY WHERE URI = ? AND METHOD = ? AND REQBODY = ? AND SESSIONID = ? AND STATUSCODE != 304");
} else {
// psReadCache = getConnection().prepareStatement("SELECT * FROM HISTORY WHERE URI = ? AND METHOD = ? AND REQBODY = ? AND SESSIONID = ? AND (HISTTYPE = " + HistoryReference.TYPE_MANUAL + " OR HISTTYPE = " + HistoryReference.TYPE_HIDDEN + ")");
psReadCache = getConnection().prepareStatement("SELECT * FROM HISTORY WHERE URI = ? AND METHOD = ? AND REQBODY = ? AND SESSIONID = ?");
}
psReadCache.setString(1, reqMsg.getRequestHeader().getURI().toString());
psReadCache.setString(2, reqMsg.getRequestHeader().getMethod());
if (bodiesAsBytes) {
psReadCache.setBytes(3, reqMsg.getRequestBody().getBytes());
} else {
psReadCache.setString(3, new String(reqMsg.getRequestBody().getBytes()));
}
psReadCache.setLong(4, ref.getSessionId());
rs = psReadCache.executeQuery();
rec = null;
try {
do {
rec = build(rs);
if (rec != null && rec.getHttpMessage().equals(reqMsg) && rec.getHttpMessage().getResponseHeader().getStatusCode() != HttpStatusCode.NOT_MODIFIED) {
return rec;
}
} while (rec != null);
} finally {
try {
rs.close();
psReadCache.close();
} catch (Exception e) {
// ZAP: Log exceptions
log.warn(e.getMessage(), e);
}
}
return null;
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public synchronized void updateNote(int historyId, String note) throws DatabaseException {
try {
psUpdateNote.setString(1, note);
psUpdateNote.setInt(2, historyId);
psUpdateNote.execute();
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override
public int lastIndex () {
return lastInsertedIndex;
}
}