1616
1717package com .example .cloudsql ;
1818
19+ import edu .umd .cs .findbugs .annotations .SuppressFBWarnings ;
1920import java .io .IOException ;
2021import java .sql .Connection ;
2122import java .sql .PreparedStatement ;
2526import java .util .ArrayList ;
2627import java .util .Date ;
2728import java .util .List ;
29+ import java .util .Locale ;
2830import java .util .logging .Level ;
2931import java .util .logging .Logger ;
32+ import javax .annotation .Nullable ;
3033import javax .servlet .ServletException ;
3134import javax .servlet .annotation .WebServlet ;
3235import javax .servlet .http .HttpServlet ;
3336import javax .servlet .http .HttpServletRequest ;
3437import javax .servlet .http .HttpServletResponse ;
3538import javax .sql .DataSource ;
3639
40+ @ SuppressFBWarnings (
41+ value = {"SE_NO_SERIALVERSIONID" , "WEM_WEAK_EXCEPTION_MESSAGING" },
42+ justification = "Not needed for IndexServlet, Exception adds context" )
3743@ WebServlet (name = "Index" , value = "" )
3844public class IndexServlet extends HttpServlet {
3945
@@ -46,44 +52,48 @@ public void doGet(HttpServletRequest req, HttpServletResponse resp)
4652 // in the ContextListener when the application was started
4753 DataSource pool = (DataSource ) req .getServletContext ().getAttribute ("my-pool" );
4854
49- int tabCount ;
50- int spaceCount ;
55+ int tabCount = 0 ;
56+ int spaceCount = 0 ;
5157 List <Vote > recentVotes = new ArrayList <>();
5258 try (Connection conn = pool .getConnection ()) {
5359 // PreparedStatements are compiled by the database immediately and executed at a later date.
5460 // Most databases cache previously compiled queries, which improves efficiency.
55- PreparedStatement voteStmt = conn .prepareStatement (
56- "SELECT candidate, time_cast FROM votes ORDER BY time_cast DESC LIMIT 5" );
57- // Execute the statement
58- ResultSet voteResults = voteStmt .executeQuery ();
59- // Convert a ResultSet into Vote objects
60- while (voteResults .next ()) {
61- String candidate = voteResults .getString (1 );
62- Timestamp timeCast = voteResults .getTimestamp (2 );
63- recentVotes .add (new Vote (candidate , timeCast ));
61+ String stmt1 = "SELECT candidate, time_cast FROM votes ORDER BY time_cast DESC LIMIT 5" ;
62+ try (PreparedStatement voteStmt = conn .prepareStatement (stmt1 ); ) {
63+ // Execute the statement
64+ ResultSet voteResults = voteStmt .executeQuery ();
65+ // Convert a ResultSet into Vote objects
66+ while (voteResults .next ()) {
67+ String candidate = voteResults .getString (1 );
68+ Timestamp timeCast = voteResults .getTimestamp (2 );
69+ recentVotes .add (new Vote (candidate , timeCast ));
70+ }
6471 }
6572
6673 // PreparedStatements can also be executed multiple times with different arguments. This can
6774 // improve efficiency, and project a query from being vulnerable to an SQL injection.
68- PreparedStatement voteCountStmt = conn .prepareStatement (
69- "SELECT COUNT(vote_id) FROM votes WHERE candidate=?" );
70-
71- voteCountStmt .setString (1 , "TABS" );
72- ResultSet tabResult = voteCountStmt .executeQuery ();
73- tabResult .next (); // Move to the first result
74- tabCount = tabResult .getInt (1 );
75-
76- voteCountStmt .setString (1 , "SPACES" );
77- ResultSet spaceResult = voteCountStmt .executeQuery ();
78- spaceResult .next (); // Move to the first result
79- spaceCount = spaceResult .getInt (1 );
80-
75+ String stmt2 = "SELECT COUNT(vote_id) FROM votes WHERE candidate=?" ;
76+ try (PreparedStatement voteCountStmt = conn .prepareStatement (stmt2 ); ) {
77+ voteCountStmt .setString (1 , "TABS" );
78+ ResultSet tabResult = voteCountStmt .executeQuery ();
79+ if (tabResult .next ()) { // Move to the first result
80+ tabCount = tabResult .getInt (1 );
81+ }
82+
83+ voteCountStmt .setString (1 , "SPACES" );
84+ ResultSet spaceResult = voteCountStmt .executeQuery ();
85+ if (spaceResult .next ()) { // Move to the first result
86+ spaceCount = spaceResult .getInt (1 );
87+ }
88+ }
8189 } catch (SQLException ex ) {
8290 // If something goes wrong, the application needs to react appropriately. This might mean
8391 // getting a new connection and executing the query again, or it might mean redirecting the
8492 // user to a different page to let them know something went wrong.
85- throw new ServletException ("Unable to successfully connect to the database. Please check the "
86- + "steps in the README and try again." , ex );
93+ throw new ServletException (
94+ "Unable to successfully connect to the database. Please check the "
95+ + "steps in the README and try again." ,
96+ ex );
8797 }
8898
8999 // Add variables and render the page
@@ -93,16 +103,29 @@ public void doGet(HttpServletRequest req, HttpServletResponse resp)
93103 req .getRequestDispatcher ("/index.jsp" ).forward (req , resp );
94104 }
95105
106+ // Used to validate user input. All user provided data should be validated and sanitized before
107+ // being used something like a SQL query. Returns null if invalid.
108+ @ Nullable
109+ private String validateTeam (String input ) {
110+ if (input != null ) {
111+ input = input .toUpperCase (Locale .ENGLISH );
112+ // Must be either "TABS" or "SPACES"
113+ if (!"TABS" .equals (input ) && !"SPACES" .equals (input )) {
114+ return null ;
115+ }
116+ }
117+ return input ;
118+ }
119+
120+ @ SuppressFBWarnings (
121+ value = {"SERVLET_PARAMETER" , "XSS_SERVLET" },
122+ justification = "Input is validated and sanitized." )
96123 @ Override
97- public void doPost (HttpServletRequest req , HttpServletResponse resp )
98- throws IOException {
124+ public void doPost (HttpServletRequest req , HttpServletResponse resp ) throws IOException {
99125 // Get the team from the request and record the time of the vote.
100- String team = req .getParameter ("team" );
101- if (team != null ) {
102- team = team .toUpperCase ();
103- }
126+ String team = validateTeam (req .getParameter ("team" ));
104127 Timestamp now = new Timestamp (new Date ().getTime ());
105- if (team == null || (! team . equals ( "TABS" ) && ! team . equals ( "SPACES" )) ) {
128+ if (team == null ) {
106129 resp .setStatus (400 );
107130 resp .getWriter ().append ("Invalid team specified." );
108131 return ;
@@ -116,28 +139,29 @@ public void doPost(HttpServletRequest req, HttpServletResponse resp)
116139 try (Connection conn = pool .getConnection ()) {
117140
118141 // PreparedStatements can be more efficient and project against injections.
119- PreparedStatement voteStmt = conn .prepareStatement (
120- "INSERT INTO votes (time_cast, candidate) VALUES (?, ?);" );
121- voteStmt .setTimestamp (1 , now );
122- voteStmt .setString (2 , team );
123-
124- // Finally, execute the statement. If it fails, an error will be thrown.
125- voteStmt .execute ();
142+ String stmt = "INSERT INTO votes (time_cast, candidate) VALUES (?, ?);" ;
143+ try (PreparedStatement voteStmt = conn .prepareStatement (stmt ); ) {
144+ voteStmt .setTimestamp (1 , now );
145+ voteStmt .setString (2 , team );
126146
147+ // Finally, execute the statement. If it fails, an error will be thrown.
148+ voteStmt .execute ();
149+ }
127150 } catch (SQLException ex ) {
128151 // If something goes wrong, handle the error in this section. This might involve retrying or
129152 // adjusting parameters depending on the situation.
130153 // [START_EXCLUDE]
131154 LOGGER .log (Level .WARNING , "Error while attempting to submit vote." , ex );
132155 resp .setStatus (500 );
133- resp .getWriter ().write ("Unable to successfully cast vote! Please check the application "
134- + "logs for more details." );
156+ resp .getWriter ()
157+ .write (
158+ "Unable to successfully cast vote! Please check the application "
159+ + "logs for more details." );
135160 // [END_EXCLUDE]
136161 }
137162 // [END cloud_sql_mysql_servlet_connection]
138163
139164 resp .setStatus (200 );
140- resp .getWriter ().printf ("Vote successfully cast for '%s' at time %s!\ n " , team , now );
165+ resp .getWriter ().printf ("Vote successfully cast for '%s' at time %s!% n" , team , now );
141166 }
142-
143167}
0 commit comments