# HG changeset patch
# User František Kučera <franta-hg@frantovo.cz>
# Date 1328703323 -3600
# Node ID 3b4abb1ec5a3360017e4bc79c8f6f9e9efc8b2fd
# Parent  fdeb54809e238b7da2bfeeb299efe5b8d46f9658
Limit počtu řádků (10000) a doby provádění SQL dotazu (3 vteřiny) v pískovišti,
nedokonalá ochrana proti DoS útoku (kartézský součin, náročný dotaz).

diff -r fdeb54809e23 -r 3b4abb1ec5a3 java/sql-vyuka/src/java/cz/frantovo/sql/vyuka/dao/PiskovisteDAO.java
--- a/java/sql-vyuka/src/java/cz/frantovo/sql/vyuka/dao/PiskovisteDAO.java	Wed Feb 08 12:44:51 2012 +0100
+++ b/java/sql-vyuka/src/java/cz/frantovo/sql/vyuka/dao/PiskovisteDAO.java	Wed Feb 08 13:15:23 2012 +0100
@@ -14,118 +14,149 @@
 
 /**
  * Pro spouštění uživatelových příkazů.
+ *
  * @author fiki
  */
 public class PiskovisteDAO extends VyukaSuperDAO {
 
-    private enum VLASTNOSTI {
+	/** maximální doba trvání SQL dotazu – vteřiny */
+	private static final int LIMIT_ČASU = 3;
+	/** maximální počet řádků */
+	private static final int LIMIT_POČTU = 10000;
 
-        VYCHOZI_CESTA
-    }
-    TipyDAO tipy = new TipyDAO();
-    HistorieDAO historie = new HistorieDAO();
+	private enum VLASTNOSTI {
 
-    public VysledekSQL vykonejSQL(String sql, Uzivatel uzivatel) {
-        VysledekSQL v = new VysledekSQL();
-        if (historie.ulozPrikaz(sql, uzivatel)) {
+		VYCHOZI_CESTA,
+		LIMIT_ČASU
+	}
+	TipyDAO tipy = new TipyDAO();
+	HistorieDAO historie = new HistorieDAO();
 
-            Connection db = getSpojeni(DATABAZE.PISKOVISTE);
-            if (db == null) {
-                v.getHlasky().add(new Hlaska("Došlo k chybě spojení.", Typ.Chyba));
-            } else {
-                PreparedStatement ps = null;
-                ResultSet rs = null;
-                try {
-                    /**
-                     * Uživatelskému SQL příkazu předřadíme výchozí cestu (search_path).
-                     * Protože uživatelé si ji mohou měnit a kvůli recyklaci databázových zdrojů
-                     * by jeden uživatel mohl ovlivnit jiného.
-                     */
-                    if (getVlastnost(VLASTNOSTI.VYCHOZI_CESTA) != null) {
-                        sql = orizni(getVlastnost(VLASTNOSTI.VYCHOZI_CESTA)) + sql;
-                    }
+	public VysledekSQL vykonejSQL(String sql, Uzivatel uzivatel) {
+		VysledekSQL v = new VysledekSQL();
+		if (historie.ulozPrikaz(sql, uzivatel)) {
 
-                    long casPred = System.currentTimeMillis();
-                    ps = db.prepareStatement(sql);
-                    boolean isRS = ps.execute();
+			Connection db = getSpojeni(DATABAZE.PISKOVISTE);
+			if (db == null) {
+				v.getHlasky().add(new Hlaska("Došlo k chybě spojení.", Typ.Chyba));
+			} else {
+				PreparedStatement ps = null;
+				ResultSet rs = null;
+				try {
+					/**
+					 * Uživatelskému SQL příkazu předřadíme výchozí cestu (search_path).
+					 * Protože uživatelé si ji mohou měnit a kvůli recyklaci databázových zdrojů
+					 * by jeden uživatel mohl ovlivnit jiného.
+					 */
+					if (getVlastnost(VLASTNOSTI.VYCHOZI_CESTA) != null) {
+						sql = orizni(getVlastnost(VLASTNOSTI.VYCHOZI_CESTA)) + sql;
+					}
+					
+					/**
+					 * TODO:
+					 * použít ps.setQueryTimeout(LIMIT_ČASU);
+					 * až ho bude podporovat JDBC ovladač,
+					 * viz níže.
+					 * Uživatel ale stejně může zadat:
+					 * SET statement_timeout 0;
+					 * do svého SQL dotazu.
+					 */
+					if (getVlastnost(VLASTNOSTI.LIMIT_ČASU) != null) {
+						sql = orizni(getVlastnost(VLASTNOSTI.LIMIT_ČASU)) + sql;
+					}
 
-                    if (isRS) {
-                        rs = ps.getResultSet();
-                        v.getTabulky().add(zpracujVysledek(rs));
-                    }
+					long casPred = System.currentTimeMillis();
+					ps = db.prepareStatement(sql);
+					/**
+					 * Limit času bohužel není podporován JDBC ovladačem.
+					 * Alespoň ne v postgresql-9.1-901.jdbc4.jar
+					 * http://jdbc.postgresql.org/todo.html
+					 * 
+					 * TODO:
+					 * ps.setQueryTimeout(LIMIT_ČASU);
+					 */
+					ps.setMaxRows(LIMIT_POČTU);
+					boolean isRS = ps.execute();
 
-                    /**
-                     * Ošetříme případ, kdy uživatel zadá SQL příkaz, který nevrací výsledkovou sadu.
-                     * Typicky nastavení výchozího schématu: SET search_path = '…';
-                     * Poznámka: jeden „SET search_path TO "…"“ se obvykle předřazuje uživatelskému SQL (viz PiskovisteDAO.xml).
-                     */                    
-                    while (ps.getMoreResults() || ps.getUpdateCount() > -1) {
-                        rs = ps.getResultSet();
-                        if (rs == null) {
-                            /** Jedná se o „update count“. */
-                        } else {
-                            v.getTabulky().add(zpracujVysledek(rs));
-                        }
-                    }
-                    long dobaProvadeni = System.currentTimeMillis() - casPred;
+					if (isRS) {
+						rs = ps.getResultSet();
+						v.getTabulky().add(zpracujVysledek(rs));
+					}
 
-                    /** Varování */
-                    if (v.getHlasky().size() < 1 && v.getTabulky().size() < 1) {
-                        v.getHlasky().add(new Hlaska("SQL příkaz proběhl, ale nevrátil žádná data.", Typ.Varovani));
-                    }
+					/**
+					 * Ošetříme případ, kdy uživatel zadá SQL příkaz, který nevrací výsledkovou
+					 * sadu.
+					 * Typicky nastavení výchozího schématu: SET search_path = '…';
+					 * Poznámka: jeden „SET search_path TO "…"“ se obvykle předřazuje uživatelskému
+					 * SQL (viz PiskovisteDAO.xml).
+					 */
+					while (ps.getMoreResults() || ps.getUpdateCount() > -1) {
+						rs = ps.getResultSet();
+						if (rs == null) {
+							/** Jedná se o „update count“. */
+						} else {
+							v.getTabulky().add(zpracujVysledek(rs));
+						}
+					}
+					long dobaProvadeni = System.currentTimeMillis() - casPred;
 
-                    /** Varování */
-                    int pocitadloTabulek = 1;
-                    for (Tabulka t : v.getTabulky()) {
-                        if (t.getHodnoty().size() < 1) {
-                            v.getHlasky().add(new Hlaska("Tabulka " + pocitadloTabulek + "  je prázdná.", Typ.Varovani));
-                        }
-                        pocitadloTabulek++;
-                    }
+					/** Varování */
+					if (v.getHlasky().size() < 1 && v.getTabulky().size() < 1) {
+						v.getHlasky().add(new Hlaska("SQL příkaz proběhl, ale nevrátil žádná data.", Typ.Varovani));
+					}
 
-                    v.getHlasky().add(new Hlaska("SQL příkaz byl proveden úspěšně, během " + dobaProvadeni + " ms.", Typ.OK));
+					/** Varování */
+					int pocitadloTabulek = 1;
+					for (Tabulka t : v.getTabulky()) {
+						if (t.getHodnoty().size() < 1) {
+							v.getHlasky().add(new Hlaska("Tabulka " + pocitadloTabulek + "  je prázdná.", Typ.Varovani));
+						}
+						pocitadloTabulek++;
+					}
 
-                } catch (SQLException e) {
-                    log.log(Level.SEVERE, "SQL chyba při vykonávání uživatelského dotazu.", e);
-                    v.getHlasky().add(new Hlaska("Chybné SQL: " + e.getMessage(), Typ.Chyba));
-                } catch (Exception e) {
-                    log.log(Level.SEVERE, "Chyba při vykonávání uživatelského dotazu.", e);
-                    v.getHlasky().add(new Hlaska("Došlo k chybě dotazu.", Typ.Chyba));
-                } finally {
-                    zavri(db, ps, rs);
-                }
-            }
+					v.getHlasky().add(new Hlaska("SQL příkaz byl proveden úspěšně, během " + dobaProvadeni + " ms.", Typ.OK));
 
-            /** Tip pro uživatele */
-            String tip = tipy.getTip();
-            if (tip != null) {
-                v.getHlasky().add(new Hlaska(tip, Typ.Tip, false));
-            }
+				} catch (SQLException e) {
+					log.log(Level.SEVERE, "SQL chyba při vykonávání uživatelského dotazu.", e);
+					v.getHlasky().add(new Hlaska("Chybné SQL: " + e.getMessage(), Typ.Chyba));
+				} catch (Exception e) {
+					log.log(Level.SEVERE, "Chyba při vykonávání uživatelského dotazu.", e);
+					v.getHlasky().add(new Hlaska("Došlo k chybě dotazu.", Typ.Chyba));
+				} finally {
+					zavri(db, ps, rs);
+				}
+			}
 
-        } else {
-            v.getHlasky().add(new Hlaska("Došlo k chybě historie.", Typ.Chyba));
-        }
-        return v;
-    }
+			/** Tip pro uživatele */
+			String tip = tipy.getTip();
+			if (tip != null) {
+				v.getHlasky().add(new Hlaska(tip, Typ.Tip, false));
+			}
 
-    private Tabulka zpracujVysledek(ResultSet rs) throws SQLException {
-        Tabulka t = new Tabulka();
+		} else {
+			v.getHlasky().add(new Hlaska("Došlo k chybě historie.", Typ.Chyba));
+		}
+		return v;
+	}
 
-        int pocetSloupecku = rs.getMetaData().getColumnCount();
-        String[] zahlavi = new String[pocetSloupecku];
-        t.setZahlavi(zahlavi);
-        for (int i = 0; i < pocetSloupecku; i++) {
-            zahlavi[i] = rs.getMetaData().getColumnName(i + 1);
-        }
+	private Tabulka zpracujVysledek(ResultSet rs) throws SQLException {
+		Tabulka t = new Tabulka();
 
-        while (rs.next()) {
-            Object[] hodnoty = new Object[pocetSloupecku];
-            for (int i = 0; i < pocetSloupecku; i++) {
-                hodnoty[i] = rs.getObject(i + 1);
-            }
-            t.getHodnoty().add(hodnoty);
-        }
+		int pocetSloupecku = rs.getMetaData().getColumnCount();
+		String[] zahlavi = new String[pocetSloupecku];
+		t.setZahlavi(zahlavi);
+		for (int i = 0; i < pocetSloupecku; i++) {
+			zahlavi[i] = rs.getMetaData().getColumnName(i + 1);
+		}
 
-        return t;
-    }
+		while (rs.next()) {
+			Object[] hodnoty = new Object[pocetSloupecku];
+			for (int i = 0; i < pocetSloupecku; i++) {
+				hodnoty[i] = rs.getObject(i + 1);
+			}
+			t.getHodnoty().add(hodnoty);
+		}
+
+		return t;
+	}
 }
diff -r fdeb54809e23 -r 3b4abb1ec5a3 java/sql-vyuka/src/java/cz/frantovo/sql/vyuka/dao/PiskovisteDAO.xml
--- a/java/sql-vyuka/src/java/cz/frantovo/sql/vyuka/dao/PiskovisteDAO.xml	Wed Feb 08 12:44:51 2012 +0100
+++ b/java/sql-vyuka/src/java/cz/frantovo/sql/vyuka/dao/PiskovisteDAO.xml	Wed Feb 08 13:15:23 2012 +0100
@@ -2,12 +2,23 @@
 <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
 <properties>    
     <!--
-    PostgreSQL proměnná „search_path“ – nastavíme ji před každým uživatelským SQL dotazem,
-    aby se uživatelé vzájemně neovlivňovali.
+		PostgreSQL proměnná „search_path“ – nastavíme ji před každým uživatelským SQL dotazem,
+		aby se uživatelé vzájemně neovlivňovali.
     -->
-    <entry key="VYCHOZI_CESTA">
+	<entry key="VYCHOZI_CESTA">
         <![CDATA[
         SET search_path TO "$user",public;
         ]]>
-    </entry>
+	</entry>
+	<!--
+		Limit (vteřiny) pro vykonání jednoho SQL příkazu.
+		TODO:
+			použít ps.setQueryTimeout(LIMIT_ČASU);
+			až ho bude podporovat JDBC ovladač.
+	-->
+	<entry key="LIMIT_ČASU">
+        <![CDATA[
+        SET statement_timeout TO 3;
+        ]]>
+	</entry>
 </properties>
\ No newline at end of file