1 /* 2 * Zed Attack Proxy (ZAP) and its related class files. 3 * 4 * ZAP is an HTTP/HTTPS proxy for assessing web application security. 5 * 6 * Copyright 2015 The ZAP Development Team 7 * 8 * Licensed under the Apache License, Version 2.0 (the "License"); 9 * you may not use this file except in compliance with the License. 10 * You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 package org.zaproxy.zap.db.sql; 21 22 import java.nio.charset.StandardCharsets; 23 import java.sql.Connection; 24 import java.sql.PreparedStatement; 25 import java.sql.ResultSet; 26 import java.sql.SQLException; 27 import java.util.ArrayList; 28 import java.util.List; 29 import java.util.Vector; 30 import java.util.regex.Matcher; 31 import java.util.regex.Pattern; 32 import org.apache.logging.log4j.LogManager; 33 import org.apache.logging.log4j.Logger; 34 import org.parosproxy.paros.Constant; 35 import org.parosproxy.paros.db.Database; 36 import org.parosproxy.paros.db.DatabaseException; 37 import org.parosproxy.paros.db.DbUtils; 38 import org.parosproxy.paros.db.RecordHistory; 39 import org.parosproxy.paros.db.TableHistory; 40 import org.parosproxy.paros.extension.option.DatabaseParam; 41 import org.parosproxy.paros.model.HistoryReference; 42 import org.parosproxy.paros.network.HttpMalformedHeaderException; 43 import org.parosproxy.paros.network.HttpMessage; 44 import org.parosproxy.paros.network.HttpStatusCode; 45 46 public class SqlTableHistory extends SqlAbstractTable implements TableHistory { 47 48 private static final String TABLE_NAME = DbSQL.getSQL("history.table_name"); 49 50 private static final String HISTORYID = DbSQL.getSQL("history.field.historyid"); 51 private static final String SESSIONID = DbSQL.getSQL("history.field.sessionid"); 52 private static final String HISTTYPE = DbSQL.getSQL("history.field.histtype"); 53 private static final String STATUSCODE = DbSQL.getSQL("history.field.statuscode"); 54 private static final String TIMESENTMILLIS = DbSQL.getSQL("history.field.timesentmillis"); 55 private static final String TIMEELAPSEDMILLIS = DbSQL.getSQL("history.field.timeelapsedmillis"); 56 private static final String REQHEADER = DbSQL.getSQL("history.field.reqheader"); 57 private static final String REQBODY = DbSQL.getSQL("history.field.reqbody"); 58 private static final String RESHEADER = DbSQL.getSQL("history.field.resheader"); 59 private static final String RESBODY = DbSQL.getSQL("history.field.resbody"); 60 private static final String TAG = DbSQL.getSQL("history.field.tag"); 61 private static final String NOTE = DbSQL.getSQL("history.field.note"); 62 private static final String RESPONSE_FROM_TARGET_HOST = 63 DbSQL.getSQL("history.field.responsefromtargethost"); 64 65 private int lastInsertedIndex; 66 private static boolean isExistStatusCode = false; 67 68 // ZAP: Added logger 69 private static final Logger log = LogManager.getLogger(SqlTableHistory.class); 70 71 private boolean bodiesAsBytes; 72 SqlTableHistory()73 public SqlTableHistory() {} 74 75 // ZAP: Allow the request and response body sizes to be user-specifiable as far as possible 76 int configuredrequestbodysize = -1; 77 int configuredresponsebodysize = -1; 78 79 @Override reconnect(Connection conn)80 protected void reconnect(Connection conn) throws DatabaseException { 81 try { 82 // ZAP: Allow the request and response body sizes to be user-specifiable as far as 83 // possible 84 // re-load the configuration data from file, to get the configured length of the request 85 // and response bodies 86 // this will later be compared to the actual lengths of these fields in the database (in 87 // updateTable(Connection c)) 88 DatabaseParam dbparams = new DatabaseParam(); 89 dbparams.load(Constant.getInstance().FILE_CONFIG); 90 this.configuredrequestbodysize = dbparams.getRequestBodySize(); 91 this.configuredresponsebodysize = dbparams.getResponseBodySize(); 92 93 bodiesAsBytes = true; 94 95 if (DbSQL.getDbType().equals(Database.DB_TYPE_HSQLDB)) { 96 updateTable(conn); 97 } 98 99 isExistStatusCode = DbUtils.hasColumn(conn, TABLE_NAME, STATUSCODE); 100 int currentIndex = 0; 101 PreparedStatement stmt = null; 102 try { 103 stmt = conn.prepareStatement(DbSQL.getSQL("history.ps.lastindex")); 104 try (ResultSet rs = stmt.executeQuery()) { 105 if (rs.next()) { 106 currentIndex = rs.getInt(1); 107 } 108 } 109 } finally { 110 if (stmt != null) { 111 try { 112 stmt.close(); 113 } catch (SQLException e) { 114 if (log.isDebugEnabled()) { 115 log.debug(e.getMessage(), e); 116 } 117 } 118 } 119 } 120 lastInsertedIndex = currentIndex; 121 } catch (SQLException e) { 122 throw new DatabaseException(e); 123 } 124 } 125 126 // ZAP: Added the method. updateTable(Connection connection)127 private void updateTable(Connection connection) throws DatabaseException { 128 try { 129 if (!DbUtils.hasColumn(connection, TABLE_NAME, TAG)) { 130 DbUtils.execute(connection, DbSQL.getSQL("history.ps.addtag")); 131 } 132 133 // Add the NOTE column to the db if necessary 134 if (!DbUtils.hasColumn(connection, TABLE_NAME, NOTE)) { 135 DbUtils.execute(connection, DbSQL.getSQL("history.ps.addnote")); 136 } 137 138 /* TODO how to handle HSQLDB dependency?? Need to parameterize somehow.. vvvvvvvvvvvv */ 139 if (DbUtils.getColumnType(connection, TABLE_NAME, REQBODY) 140 != 61 /*Types.SQL_VARBINARY*/) { 141 bodiesAsBytes = false; 142 } else { 143 // Databases created with ZAP<1.4.0.1 used VARCHAR for the REQBODY/RESBODY 144 // HSQLDB 1.8.x converted from VARCHAR to bytes without problems 145 // (through the method ResultSet.getBytes) 146 // but the new version doesn't, it throws the following exception: 147 // incompatible data type in conversion: from SQL type VARCHAR 148 } 149 150 if (!DbUtils.hasColumn(connection, TABLE_NAME, RESPONSE_FROM_TARGET_HOST)) { 151 DbUtils.execute(connection, DbSQL.getSQL("history.ps.addrespfromtarget")); 152 DbUtils.executeUpdate(connection, DbSQL.getSQL("history.ps.setrespfromtarget")); 153 } 154 155 int requestbodysizeindb = DbUtils.getColumnSize(connection, TABLE_NAME, REQBODY); 156 int responsebodysizeindb = DbUtils.getColumnSize(connection, TABLE_NAME, RESBODY); 157 try { 158 if (requestbodysizeindb != this.configuredrequestbodysize 159 && this.configuredrequestbodysize > 0) { 160 try (PreparedStatement stmt = 161 connection.prepareStatement(DbSQL.getSQL("history.ps.changereqsize"))) { 162 stmt.setInt(1, this.configuredrequestbodysize); 163 stmt.execute(); 164 } 165 } 166 167 if (responsebodysizeindb != this.configuredresponsebodysize 168 && this.configuredresponsebodysize > 0) { 169 try (PreparedStatement stmt = 170 connection.prepareStatement( 171 DbSQL.getSQL("history.ps.changerespsize"))) { 172 stmt.setInt(1, this.configuredresponsebodysize); 173 stmt.execute(); 174 } 175 } 176 } catch (SQLException e) { 177 log.error("An error occurred while modifying a column length on " + TABLE_NAME); 178 log.error( 179 "The 'Maximum Request Body Size' value in the Database Options needs to be set to at least " 180 + requestbodysizeindb 181 + " to avoid this error"); 182 log.error( 183 "The 'Maximum Response Body Size' value in the Database Options needs to be set to at least " 184 + responsebodysizeindb 185 + " to avoid this error"); 186 log.error("The SQL Exception was:", e); 187 throw e; 188 } 189 } catch (SQLException e) { 190 throw new DatabaseException(e); 191 } 192 } 193 194 @Override read(int historyId)195 public RecordHistory read(int historyId) 196 throws HttpMalformedHeaderException, DatabaseException { 197 SqlPreparedStatementWrapper psRead = null; 198 try { 199 psRead = DbSQL.getSingleton().getPreparedStatement("history.ps.read"); 200 201 psRead.getPs().setInt(1, historyId); 202 psRead.getPs().execute(); 203 RecordHistory result = null; 204 try (ResultSet rs = psRead.getPs().getResultSet()) { 205 result = build(rs); 206 } 207 208 return result; 209 } catch (SQLException e) { 210 throw new DatabaseException(e); 211 } finally { 212 DbSQL.getSingleton().releasePreparedStatement(psRead); 213 } 214 } 215 216 @Override write(long sessionId, int histType, HttpMessage msg)217 public RecordHistory write(long sessionId, int histType, HttpMessage msg) 218 throws HttpMalformedHeaderException, DatabaseException { 219 220 String reqHeader = ""; 221 byte[] reqBody = new byte[0]; 222 String resHeader = ""; 223 byte[] resBody = reqBody; 224 String method = ""; 225 String uri = ""; 226 int statusCode = 0; 227 String note = msg.getNote(); 228 229 if (!msg.getRequestHeader().isEmpty()) { 230 reqHeader = msg.getRequestHeader().toString(); 231 reqBody = msg.getRequestBody().getBytes(); 232 method = msg.getRequestHeader().getMethod(); 233 uri = msg.getRequestHeader().getURI().toString(); 234 } 235 236 if (!msg.getResponseHeader().isEmpty()) { 237 resHeader = msg.getResponseHeader().toString(); 238 resBody = msg.getResponseBody().getBytes(); 239 statusCode = msg.getResponseHeader().getStatusCode(); 240 } 241 242 // return write(sessionId, histType, msg.getTimeSentMillis(), msg.getTimeElapsedMillis(), 243 // method, uri, statusCode, reqHeader, reqBody, resHeader, resBody, msg.getTag()); 244 return write( 245 sessionId, 246 histType, 247 msg.getTimeSentMillis(), 248 msg.getTimeElapsedMillis(), 249 method, 250 uri, 251 statusCode, 252 reqHeader, 253 reqBody, 254 resHeader, 255 resBody, 256 null, 257 note, 258 msg.isResponseFromTargetHost()); 259 } 260 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)261 private RecordHistory write( 262 long sessionId, 263 int histType, 264 long timeSentMillis, 265 int timeElapsedMillis, 266 String method, 267 String uri, 268 int statusCode, 269 String reqHeader, 270 byte[] reqBody, 271 String resHeader, 272 byte[] resBody, 273 String tag, 274 String note, 275 boolean responseFromTargetHost) 276 throws HttpMalformedHeaderException, DatabaseException { 277 278 // ZAP: Allow the request and response body sizes to be user-specifiable as far as possible 279 if (reqBody.length > this.configuredrequestbodysize) { 280 throw new DatabaseException( 281 "The actual Request Body length " 282 + reqBody.length 283 + " is greater than the configured request body length " 284 + this.configuredrequestbodysize); 285 } 286 if (resBody.length > this.configuredresponsebodysize) { 287 throw new DatabaseException( 288 "The actual Response Body length " 289 + resBody.length 290 + " is greater than the configured response body length " 291 + this.configuredresponsebodysize); 292 } 293 294 SqlPreparedStatementWrapper psInsert = null; 295 try { 296 psInsert = DbSQL.getSingleton().getPreparedStatement("history.ps.insertstd"); 297 psInsert.getPs().setLong(1, sessionId); 298 psInsert.getPs().setInt(2, histType); 299 psInsert.getPs().setLong(3, timeSentMillis); 300 psInsert.getPs().setInt(4, timeElapsedMillis); 301 psInsert.getPs().setString(5, method); 302 psInsert.getPs().setString(6, uri); 303 psInsert.getPs().setString(7, reqHeader); 304 if (bodiesAsBytes) { 305 psInsert.getPs().setBytes(8, reqBody); 306 } else { 307 psInsert.getPs().setString(8, new String(reqBody, StandardCharsets.US_ASCII)); 308 } 309 psInsert.getPs().setString(9, resHeader); 310 if (bodiesAsBytes) { 311 psInsert.getPs().setBytes(10, resBody); 312 } else { 313 psInsert.getPs().setString(10, new String(resBody, StandardCharsets.US_ASCII)); 314 } 315 psInsert.getPs().setString(11, tag); 316 317 // ZAP: Added the statement. 318 int currentIdx = 12; 319 320 if (isExistStatusCode) { 321 psInsert.getPs().setInt(currentIdx, statusCode); 322 // ZAP: Added the statement. 323 ++currentIdx; 324 } 325 326 // ZAP: Added the statement. 327 psInsert.getPs().setString(currentIdx, note); 328 ++currentIdx; 329 330 psInsert.getPs().setBoolean(currentIdx, responseFromTargetHost); 331 332 psInsert.getPs().executeUpdate(); 333 334 try (ResultSet rs = psInsert.getLastInsertedId()) { 335 rs.next(); 336 int id = rs.getInt(1); 337 lastInsertedIndex = id; 338 return read(id); 339 } 340 } catch (SQLException e) { 341 throw new DatabaseException(e); 342 } finally { 343 DbSQL.getSingleton().releasePreparedStatement(psInsert); 344 } 345 } 346 build(ResultSet rs)347 private RecordHistory build(ResultSet rs) 348 throws HttpMalformedHeaderException, DatabaseException { 349 try { 350 RecordHistory history = null; 351 try { 352 if (rs.next()) { 353 byte[] reqBody; 354 byte[] resBody; 355 356 if (bodiesAsBytes) { 357 reqBody = rs.getBytes(REQBODY); 358 resBody = rs.getBytes(RESBODY); 359 } else { 360 reqBody = rs.getString(REQBODY).getBytes(); 361 resBody = rs.getString(RESBODY).getBytes(); 362 } 363 364 history = 365 new RecordHistory( 366 rs.getInt(HISTORYID), 367 rs.getInt(HISTTYPE), 368 rs.getLong(SESSIONID), 369 rs.getLong(TIMESENTMILLIS), 370 rs.getInt(TIMEELAPSEDMILLIS), 371 rs.getString(REQHEADER), 372 reqBody, 373 rs.getString(RESHEADER), 374 resBody, 375 rs.getString(TAG), 376 rs.getString(NOTE), // ZAP: Added note 377 rs.getBoolean(RESPONSE_FROM_TARGET_HOST)); 378 } 379 } finally { 380 rs.close(); 381 } 382 return history; 383 } catch (SQLException e) { 384 throw new DatabaseException(e); 385 } 386 } 387 388 @Override getHistoryIds(long sessionId)389 public List<Integer> getHistoryIds(long sessionId) throws DatabaseException { 390 return getHistoryIdsFromPreparedStatement( 391 (wrapper) -> { 392 wrapper.getPs().setLong(1, sessionId); 393 }, 394 "history.ps.gethistoryids", 395 null); 396 } 397 getHistoryIdsFromPreparedStatement( PreparedStatementSetter preparedStatementSetter, String key, int... params)398 private List<Integer> getHistoryIdsFromPreparedStatement( 399 PreparedStatementSetter preparedStatementSetter, String key, int... params) 400 throws DatabaseException { 401 SqlPreparedStatementWrapper psGetHistoryIds = null; 402 try { 403 404 psGetHistoryIds = DbSQL.getSingleton().getPreparedStatement(key, params); 405 preparedStatementSetter.setParameter(psGetHistoryIds); 406 List<Integer> v = new ArrayList<>(); 407 try (ResultSet rs = psGetHistoryIds.getPs().executeQuery()) { 408 while (rs.next()) { 409 v.add(rs.getInt(HISTORYID)); 410 } 411 } 412 return v; 413 } catch (SQLException e) { 414 throw new DatabaseException(e); 415 } finally { 416 DbSQL.getSingleton().releasePreparedStatement(psGetHistoryIds); 417 } 418 } 419 420 @Override getHistoryIdsStartingAt(long sessionId, int startAtHistoryId)421 public List<Integer> getHistoryIdsStartingAt(long sessionId, int startAtHistoryId) 422 throws DatabaseException { 423 return getHistoryIdsFromPreparedStatement( 424 (wrapper) -> { 425 wrapper.getPs().setLong(1, sessionId); 426 wrapper.getPs().setInt(2, startAtHistoryId); 427 }, 428 "history.ps.gethistoryidsstartingat", 429 null); 430 } 431 432 @Override 433 public List<Integer> getHistoryIdsOfHistType(long sessionId, int... histTypes) 434 throws DatabaseException { 435 if (histTypes == null || histTypes.length == 0) { 436 return getHistoryIds(sessionId); 437 } 438 439 return getHistoryIdsFromPreparedStatement( 440 (wrapper) -> { 441 wrapper.getPs().setLong(1, sessionId); 442 DbSQL.setSetValues(wrapper.getPs(), 2, histTypes); 443 }, 444 "history.ps.gethistoryidsinctypes", 445 histTypes.length); 446 } 447 448 @Override 449 public List<Integer> getHistoryIdsOfHistTypeStartingAt( 450 long sessionId, int startAtHistoryId, int... histTypes) throws DatabaseException { 451 if (histTypes == null || histTypes.length == 0) { 452 return getHistoryIds(sessionId); 453 } 454 455 return getHistoryIdsFromPreparedStatement( 456 (wrapper) -> { 457 wrapper.getPs().setLong(1, sessionId); 458 wrapper.getPs().setInt(2, startAtHistoryId); 459 DbSQL.setSetValues(wrapper.getPs(), 3, histTypes); 460 }, 461 "history.ps.gethistoryidsinctypesstartingat", 462 histTypes.length); 463 } 464 465 @Override 466 public List<Integer> getHistoryIdsExceptOfHistType(long sessionId, int... histTypes) 467 throws DatabaseException { 468 if (histTypes == null || histTypes.length == 0) { 469 return getHistoryIds(sessionId); 470 } 471 472 return getHistoryIdsFromPreparedStatement( 473 (wrapper) -> { 474 wrapper.getPs().setLong(1, sessionId); 475 DbSQL.setSetValues(wrapper.getPs(), 2, histTypes); 476 }, 477 "history.ps.gethistoryidsnottypes", 478 histTypes.length); 479 } 480 481 @Override 482 public List<Integer> getHistoryIdsExceptOfHistTypeStartingAt( 483 long sessionId, int startAtHistoryId, int... histTypes) throws DatabaseException { 484 if (histTypes == null || histTypes.length == 0) { 485 return getHistoryIds(sessionId); 486 } 487 488 return getHistoryIdsFromPreparedStatement( 489 (wrapper) -> { 490 wrapper.getPs().setLong(1, sessionId); 491 wrapper.getPs().setInt(2, startAtHistoryId); 492 DbSQL.setSetValues(wrapper.getPs(), 3, histTypes); 493 }, 494 "history.ps.gethistoryidsnottypesstartingat", 495 histTypes.length); 496 } 497 498 @Override 499 public List<Integer> getHistoryList( 500 long sessionId, int histType, String filter, boolean isRequest) 501 throws DatabaseException { 502 try { // TODO 503 PreparedStatement psReadSearch = 504 getConnection() 505 .prepareStatement( 506 "SELECT * FROM HISTORY WHERE " 507 + SESSIONID 508 + " = ? AND " 509 + HISTTYPE 510 + " = ? ORDER BY " 511 + HISTORYID); 512 ResultSet rs = null; 513 Vector<Integer> v = new Vector<>(); 514 try { 515 516 Pattern pattern = 517 Pattern.compile(filter, Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); 518 Matcher matcher = null; 519 520 psReadSearch.setLong(1, sessionId); 521 psReadSearch.setInt(2, histType); 522 rs = psReadSearch.executeQuery(); 523 while (rs.next()) { 524 if (isRequest) { 525 matcher = pattern.matcher(rs.getString(REQHEADER)); 526 if (matcher.find()) { 527 // ZAP: Changed to use the method Integer.valueOf. 528 v.add(rs.getInt(HISTORYID)); 529 continue; 530 } 531 matcher = pattern.matcher(rs.getString(REQBODY)); 532 if (matcher.find()) { 533 // ZAP: Changed to use the method Integer.valueOf. 534 v.add(rs.getInt(HISTORYID)); 535 continue; 536 } 537 } else { 538 matcher = pattern.matcher(rs.getString(RESHEADER)); 539 if (matcher.find()) { 540 // ZAP: Changed to use the method Integer.valueOf. 541 v.add(rs.getInt(HISTORYID)); 542 continue; 543 } 544 matcher = pattern.matcher(rs.getString(RESBODY)); 545 if (matcher.find()) { 546 // ZAP: Changed to use the method Integer.valueOf. 547 v.add(rs.getInt(HISTORYID)); 548 continue; 549 } 550 } 551 } 552 } finally { 553 if (rs != null) { 554 try { 555 rs.close(); 556 } catch (Exception e) { 557 // Ignore 558 } 559 } 560 psReadSearch.close(); 561 } 562 563 return v; 564 } catch (SQLException e) { 565 throw new DatabaseException(e); 566 } 567 } 568 569 @Override 570 public void deleteHistorySession(long sessionId) throws DatabaseException { 571 SqlPreparedStatementWrapper psDeleteSession = null; 572 try { 573 psDeleteSession = DbSQL.getSingleton().getPreparedStatement("history.ps.deletesession"); 574 psDeleteSession.getPs().setLong(1, sessionId); 575 psDeleteSession.getPs().executeUpdate(); 576 } catch (SQLException e) { 577 throw new DatabaseException(e); 578 } finally { 579 DbSQL.getSingleton().releasePreparedStatement(psDeleteSession); 580 } 581 } 582 583 @Override 584 public void deleteHistoryType(long sessionId, int historyType) throws DatabaseException { 585 SqlPreparedStatementWrapper psDeleteType = null; 586 try { 587 psDeleteType = DbSQL.getSingleton().getPreparedStatement("history.ps.deletetype"); 588 psDeleteType.getPs().setLong(1, sessionId); 589 psDeleteType.getPs().setInt(2, historyType); 590 psDeleteType.getPs().executeUpdate(); 591 } catch (SQLException e) { 592 throw new DatabaseException(e); 593 } finally { 594 DbSQL.getSingleton().releasePreparedStatement(psDeleteType); 595 } 596 } 597 598 @Override 599 public void delete(int historyId) throws DatabaseException { 600 SqlPreparedStatementWrapper psDelete = null; 601 try { 602 psDelete = DbSQL.getSingleton().getPreparedStatement("history.ps.delete"); 603 psDelete.getPs().setInt(1, historyId); 604 psDelete.getPs().executeUpdate(); 605 } catch (SQLException e) { 606 throw new DatabaseException(e); 607 } finally { 608 DbSQL.getSingleton().releasePreparedStatement(psDelete); 609 } 610 } 611 612 // ZAP: Added method. 613 @Override 614 public void delete(List<Integer> ids) throws DatabaseException { 615 delete(ids, 1000); 616 } 617 618 @Override 619 public void delete(List<Integer> ids, int batchSize) throws DatabaseException { 620 if (ids == null) { 621 throw new IllegalArgumentException("Parameter ids must not be null."); 622 } 623 if (batchSize <= 0) { 624 throw new IllegalArgumentException("Parameter batchSize must be greater than zero."); 625 } 626 627 SqlPreparedStatementWrapper psDelete = null; 628 try { 629 psDelete = DbSQL.getSingleton().getPreparedStatement("history.ps.delete"); 630 int count = 0; 631 for (Integer id : ids) { 632 psDelete.getPs().setInt(1, id); 633 psDelete.getPs().addBatch(); 634 count++; 635 636 if (count % batchSize == 0) { 637 psDelete.getPs().executeBatch(); 638 count = 0; 639 } 640 } 641 if (count % batchSize != 0) { 642 psDelete.getPs().executeBatch(); 643 } 644 } catch (SQLException e) { 645 throw new DatabaseException(e); 646 } finally { 647 DbSQL.getSingleton().releasePreparedStatement(psDelete); 648 } 649 } 650 651 /** 652 * @deprecated (2.5.0) Use {@link HistoryReference#addTemporaryType(int)} instead. 653 * @since 2.4 654 * @param historyType the history type that will be set as temporary 655 * @see #deleteTemporary() 656 */ 657 @Deprecated 658 public static void setHistoryTypeAsTemporary(int historyType) {} 659 660 /** 661 * @deprecated (2.5.0) Use {@link HistoryReference#removeTemporaryType(int)} instead. 662 * @since 2.4 663 * @param historyType the history type that will be marked as temporary 664 * @see #deleteTemporary() 665 */ 666 @Deprecated 667 public static void unsetHistoryTypeAsTemporary(int historyType) { 668 HistoryReference.removeTemporaryType(historyType); 669 } 670 671 @Override 672 public void deleteTemporary() throws DatabaseException { 673 SqlPreparedStatementWrapper psDeleteTemp = null; 674 try { 675 psDeleteTemp = DbSQL.getSingleton().getPreparedStatement("history.ps.deletetemp"); 676 for (Integer type : HistoryReference.getTemporaryTypes()) { 677 psDeleteTemp.getPs().setInt(1, type); 678 psDeleteTemp.getPs().execute(); 679 } 680 } catch (SQLException e) { 681 throw new DatabaseException(e); 682 } finally { 683 DbSQL.getSingleton().releasePreparedStatement(psDeleteTemp); 684 } 685 } 686 687 @Override 688 public boolean containsURI( 689 long sessionId, int historyType, String method, String uri, byte[] body) 690 throws DatabaseException { 691 SqlPreparedStatementWrapper psContainsURI = null; 692 try { 693 psContainsURI = DbSQL.getSingleton().getPreparedStatement("history.ps.containsuri"); 694 psContainsURI.getPs().setString(1, uri); 695 psContainsURI.getPs().setString(2, method); 696 697 if (bodiesAsBytes) { 698 psContainsURI.getPs().setBytes(3, body); 699 } else { 700 psContainsURI.getPs().setString(3, new String(body)); 701 } 702 703 psContainsURI.getPs().setLong(4, sessionId); 704 psContainsURI.getPs().setInt(5, historyType); 705 try (ResultSet rs = psContainsURI.getPs().executeQuery()) { 706 if (rs.next()) { 707 return true; 708 } 709 } 710 return false; 711 } catch (SQLException e) { 712 throw new DatabaseException(e); 713 } finally { 714 DbSQL.getSingleton().releasePreparedStatement(psContainsURI); 715 } 716 } 717 718 @Override 719 public RecordHistory getHistoryCache(HistoryReference ref, HttpMessage reqMsg) 720 throws DatabaseException, HttpMalformedHeaderException { 721 try { 722 // get the cache from provided reference. 723 // naturally, the obtained cache should be AFTER AND NEARBY to the given reference. 724 // - historyId up to historyId+200 725 // - match sessionId 726 // - history type can be MANUEL or hidden (hidden is used by images not explicitly 727 // stored in history) 728 // - match URI 729 PreparedStatement psReadCache = null; 730 731 // TODO 732 if (isExistStatusCode) { 733 // psReadCache = getConnection().prepareStatement("SELECT TOP 1 * FROM 734 // HISTORY WHERE URI = ? AND METHOD = ? AND REQBODY = ? AND " + HISTORYID + " >= ? 735 // AND " + HISTORYID + " <= ? AND SESSIONID = ? AND (HISTTYPE = " + 736 // HistoryReference.TYPE_MANUAL + " OR HISTTYPE = " + HistoryReference.TYPE_HIDDEN + 737 // ") AND STATUSCODE != 304"); 738 psReadCache = 739 getConnection() 740 .prepareStatement( 741 "SELECT TOP 1 * FROM HISTORY WHERE URI = ? AND METHOD = ? AND REQBODY = ? AND " 742 + HISTORYID 743 + " >= ? AND " 744 + HISTORYID 745 + " <= ? AND SESSIONID = ? AND STATUSCODE != 304"); 746 747 } else { 748 // psReadCache = getConnection().prepareStatement("SELECT * FROM HISTORY 749 // WHERE URI = ? AND METHOD = ? AND REQBODY = ? AND " + HISTORYID + " >= ? AND " + 750 // HISTORYID + " <= ? AND SESSIONID = ? AND (HISTTYPE = " + 751 // HistoryReference.TYPE_MANUAL + " OR HISTTYPE = " + HistoryReference.TYPE_HIDDEN + 752 // ")"); 753 psReadCache = 754 getConnection() 755 .prepareStatement( 756 "SELECT * FROM HISTORY WHERE URI = ? AND METHOD = ? AND REQBODY = ? AND " 757 + HISTORYID 758 + " >= ? AND " 759 + HISTORYID 760 + " <= ? AND SESSIONID = ?)"); 761 } 762 psReadCache.setString(1, reqMsg.getRequestHeader().getURI().toString()); 763 psReadCache.setString(2, reqMsg.getRequestHeader().getMethod()); 764 765 if (bodiesAsBytes) { 766 psReadCache.setBytes(3, reqMsg.getRequestBody().getBytes()); 767 } else { 768 psReadCache.setString(3, new String(reqMsg.getRequestBody().getBytes())); 769 } 770 771 psReadCache.setInt(4, ref.getHistoryId()); 772 psReadCache.setInt(5, ref.getHistoryId() + 200); 773 psReadCache.setLong(6, ref.getSessionId()); 774 775 ResultSet rs = psReadCache.executeQuery(); 776 RecordHistory rec = null; 777 778 try { 779 do { 780 rec = build(rs); 781 // for retrieval from cache, the message requests nature must be the same. 782 // and the result should NOT be NOT_MODIFIED for rendering by browser 783 if (rec != null 784 && rec.getHttpMessage().equals(reqMsg) 785 && rec.getHttpMessage().getResponseHeader().getStatusCode() 786 != HttpStatusCode.NOT_MODIFIED) { 787 return rec; 788 } 789 790 } while (rec != null); 791 792 } finally { 793 try { 794 rs.close(); 795 psReadCache.close(); 796 } catch (Exception e) { 797 // ZAP: Log exceptions 798 log.warn(e.getMessage(), e); 799 } 800 } 801 802 // if cache not exist, probably due to NOT_MODIFIED, 803 // lookup from cache BEFORE the given reference 804 805 // TODO 806 if (isExistStatusCode) { 807 psReadCache = 808 getConnection() 809 .prepareStatement( 810 "SELECT TOP 1 * FROM HISTORY WHERE URI = ? AND METHOD = ? AND REQBODY = ? AND SESSIONID = ? AND STATUSCODE != 304"); 811 } else { 812 psReadCache = 813 getConnection() 814 .prepareStatement( 815 "SELECT * FROM HISTORY WHERE URI = ? AND METHOD = ? AND REQBODY = ? AND SESSIONID = ?"); 816 } 817 psReadCache.setString(1, reqMsg.getRequestHeader().getURI().toString()); 818 psReadCache.setString(2, reqMsg.getRequestHeader().getMethod()); 819 820 if (bodiesAsBytes) { 821 psReadCache.setBytes(3, reqMsg.getRequestBody().getBytes()); 822 } else { 823 psReadCache.setString(3, new String(reqMsg.getRequestBody().getBytes())); 824 } 825 826 psReadCache.setLong(4, ref.getSessionId()); 827 828 rs = psReadCache.executeQuery(); 829 rec = null; 830 831 try { 832 do { 833 rec = build(rs); 834 if (rec != null 835 && rec.getHttpMessage().equals(reqMsg) 836 && rec.getHttpMessage().getResponseHeader().getStatusCode() 837 != HttpStatusCode.NOT_MODIFIED) { 838 return rec; 839 } 840 841 } while (rec != null); 842 843 } finally { 844 try { 845 rs.close(); 846 psReadCache.close(); 847 } catch (Exception e) { 848 // ZAP: Log exceptions 849 log.warn(e.getMessage(), e); 850 } 851 } 852 853 return null; 854 } catch (SQLException e) { 855 throw new DatabaseException(e); 856 } 857 } 858 859 @Override 860 public void updateNote(int historyId, String note) throws DatabaseException { 861 SqlPreparedStatementWrapper psUpdateNote = null; 862 try { 863 psUpdateNote = DbSQL.getSingleton().getPreparedStatement("history.ps.updatenote"); 864 psUpdateNote.getPs().setString(1, note); 865 psUpdateNote.getPs().setInt(2, historyId); 866 psUpdateNote.getPs().execute(); 867 } catch (SQLException e) { 868 throw new DatabaseException(e); 869 } finally { 870 DbSQL.getSingleton().releasePreparedStatement(psUpdateNote); 871 } 872 } 873 874 @Override 875 public int lastIndex() { 876 return lastInsertedIndex; 877 } 878 879 private interface PreparedStatementSetter { 880 void setParameter(SqlPreparedStatementWrapper wrapper) throws SQLException; 881 } 882 } 883