001/* 002MIT License 003 004Copyright (c) 2020 FBSQL Team 005 006Permission is hereby granted, free of charge, to any person obtaining a copy 007of this software and associated documentation files (the "Software"), to deal 008in the Software without restriction, including without limitation the rights 009to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 010copies of the Software, and to permit persons to whom the Software is 011furnished to do so, subject to the following conditions: 012 013The above copyright notice and this permission notice shall be included in all 014copies or substantial portions of the Software. 015 016THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 017IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 018FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 019AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 020LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 021OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 022SOFTWARE. 023 024Home: https://fbsql.github.io 025E-Mail: fbsql.team@gmail.com 026*/ 027 028package org.fbsql.antlr4.parser; 029 030import java.nio.charset.StandardCharsets; 031import java.util.ArrayList; 032import java.util.Base64; 033import java.util.List; 034import java.util.Locale; 035import java.util.UUID; 036 037import javax.servlet.ServletConfig; 038 039import org.antlr.v4.runtime.CharStreams; 040import org.antlr.v4.runtime.CommonTokenStream; 041import org.antlr.v4.runtime.Lexer; 042import org.antlr.v4.runtime.misc.Interval; 043import org.antlr.v4.runtime.tree.ParseTree; 044import org.antlr.v4.runtime.tree.ParseTreeWalker; 045import org.fbsql.antlr4.generated.FbsqlBaseListener; 046import org.fbsql.antlr4.generated.FbsqlLexer; 047import org.fbsql.antlr4.generated.FbsqlParser; 048import org.fbsql.antlr4.generated.FbsqlParser.Connect_to_stmtContext; 049import org.fbsql.antlr4.generated.FbsqlParser.Connection_aliasContext; 050import org.fbsql.antlr4.generated.FbsqlParser.Connection_pool_size_maxContext; 051import org.fbsql.antlr4.generated.FbsqlParser.Connection_pool_size_minContext; 052import org.fbsql.antlr4.generated.FbsqlParser.Jdbc_connection_propertiesContext; 053import org.fbsql.antlr4.generated.FbsqlParser.Jdbc_driver_class_nameContext; 054import org.fbsql.antlr4.generated.FbsqlParser.Jdbc_urlContext; 055import org.fbsql.antlr4.generated.FbsqlParser.Native_sqlContext; 056import org.fbsql.antlr4.generated.FbsqlParser.PasswordContext; 057import org.fbsql.antlr4.generated.FbsqlParser.UserContext; 058import org.fbsql.servlet.CommonUtils; 059import org.fbsql.servlet.DbServlet; 060import org.fbsql.servlet.SqlParseUtils; 061import org.fbsql.servlet.StringUtils; 062 063/* 064 * ANTLR4 grammar: 065 * 066 * connect_to_stmt 067 * : CONNECT TO jdbc_url 068 * ( 069 * ( USER user ) | 070 * ( PASSWORD password ) | 071 * ( PROPERTIES jdbc_connection_properties ) | 072 * ( DRIVER jdbc_driver_class_name ) | 073 * ( LIB jar_file ( ',' jar_file )* ) | 074 * ( CONNECTION POOL MIN connection_pool_size_min ) | 075 * ( CONNECTION POOL MAX connection_pool_size_max ) | 076 * ( UNDECLARED STATEMENTS ALLOW ) | 077 * ( UNDECLARED STATEMENTS REJECT ) | 078 * ( 079 * INCOMING CONNECTIONS ( ALLOW | REJECT )+ ( IF EXISTS '(' native_sql ')' )? 080 * ) | 081 * ( AS? connection_alias ) 082 * )* 083 */ ; 084 085public class ParseStmtConnectTo { 086 public static final String NONEXPOSABLE_NAME_PREFIX = "NONEXPOSABLE_NAME:"; 087 private static final String ENCODED_PASSWORD_PREFIX = "base64:"; 088 089 private static final int DEFAULT_CONNECTION_POOL_SIZE_MIN = 1; 090 private static final int DEFAULT_CONNECTION_POOL_SIZE_MAX = 100; 091 092 /** 093 * DTO (Data Transfer Object) that holds RDBMS connection meta data 094 * Field values comes from parsing of "CONNECT TO" SQL statement 095 * 096 * @see SqlParseUtils#parseConnectStatement(String) 097 */ 098 public class StmtConnectTo { 099 100 /** 101 * Value from "CONNECT TO" clause 102 */ 103 public String jdbcUrl; 104 105 /** 106 * Value from "DRIVER" clause 107 */ 108 public String driverClassName; 109 110 /** 111 * Value from "JARS" clause 112 */ 113 public List<String> driverJars; 114 115 /** 116 * Value from "USER" clause 117 */ 118 public String user; 119 120 /** 121 * Value from "PASSWORD" clause 122 */ 123 public String password; 124 125 /** 126 * Value from "PROPERTIES" clause 127 * Java (*.properties) file contains JDBC properties 128 */ 129 public String jdbcPropertiesFile; 130 131 /** 132 * Value from "CONNECTION POOL -> MIN" clause 133 */ 134 public int connectionPoolSizeMin = DEFAULT_CONNECTION_POOL_SIZE_MIN; 135 136 /** 137 * Value from "CONNECTION POOL -> MAX" clause 138 */ 139 public int connectionPoolSizeMax = DEFAULT_CONNECTION_POOL_SIZE_MAX; 140 141 public boolean exposeUndeclaredStatements; 142 143 /** 144 * Value from "IF EXISTS" clause 145 */ 146 public String authenticationQuery; 147 148 public boolean allowConnections; 149 150 /** 151 * Value from "AS" clause 152 */ 153 public String instanceName; 154 155 @Override 156 public String toString() { 157 return "StmtConnectTo [jdbcUrl=" + jdbcUrl + ", driverClassName=" + driverClassName + ", driverJars=" + driverJars + ", user=" + user + ", password=" + password + ", jdbcPropertiesFile=" + jdbcPropertiesFile + ", connectionPoolSizeMin=" + connectionPoolSizeMin + ", connectionPoolSizeMax=" + connectionPoolSizeMax + ", exposeUndeclaredStatements=" + exposeUndeclaredStatements + ", authenticationQuery=" + authenticationQuery + ", allowConnections=" + allowConnections + ", instanceName=" + instanceName + "]"; 158 } 159 160 } 161 162 private static final String[] INCOMING_CONNECTIONS_ALLOW = new String[] { "INCOMING", "CONNECTIONS", "ALLOW" }; 163 private static final String[] INCOMING_CONNECTIONS_REJECT = new String[] { "INCOMING", "CONNECTIONS", "REJECT" }; 164 165 private static final String[] UNDECLARED_STATEMENTS_ALLOW = new String[] { "UNDECLARED", "STATEMENTS", "ALLOW" }; 166 private static final String[] UNDECLARED_STATEMENTS_REJECT = new String[] { "UNDECLARED", "STATEMENTS", "REJECT" }; 167 168 /** 169 * StmtConnectTo transfer object 170 */ 171 private StmtConnectTo st; 172 173 public ParseStmtConnectTo() { 174 st = new StmtConnectTo(); 175 st.driverJars = new ArrayList<>(); 176 } 177 178 /** 179 * CONNECT TO Statement parser 180 * 181 * E.g.: CONNECT TO ( SELECT * FROM MYTABLE ) ROLES (aamin, manager) AS myselect 182 * 183 * @param sql 184 * @return 185 */ 186 public StmtConnectTo parse(ServletConfig servletConfig, String sql) { 187 Lexer lexer = new FbsqlLexer(CharStreams.fromString(sql)); 188 FbsqlParser parser = new FbsqlParser(new CommonTokenStream(lexer)); 189 ParseTree tree = parser.connect_to_stmt(); 190 191 ParseTreeWalker.DEFAULT.walk(new FbsqlBaseListener() { 192 193 @Override 194 public void enterConnect_to_stmt(Connect_to_stmtContext ctx) { 195 String[] array = new String[ctx.children.size()]; 196 int n = 0; 197 for (ParseTree parseTree : ctx.children) 198 array[n++] = parseTree.getText().toUpperCase(Locale.ENGLISH); 199 200 if (CommonUtils.indexOf(array, UNDECLARED_STATEMENTS_ALLOW) != -1) 201 st.exposeUndeclaredStatements = true; 202 else if (CommonUtils.indexOf(array, UNDECLARED_STATEMENTS_REJECT) != -1) 203 st.exposeUndeclaredStatements = false; 204 205 if (CommonUtils.indexOf(array, INCOMING_CONNECTIONS_ALLOW) != -1) 206 st.allowConnections = true; 207 else if (CommonUtils.indexOf(array, INCOMING_CONNECTIONS_REJECT) != -1) 208 st.allowConnections = false; 209 } 210 211 @Override 212 public void enterJdbc_url(Jdbc_urlContext ctx) { 213 st.jdbcUrl = StringUtils.unquote(ctx.getText()); 214 } 215 216 @Override 217 public void enterUser(UserContext ctx) { 218 st.user = StringUtils.unquote(ctx.getText()); 219 } 220 221 @Override 222 public void enterPassword(PasswordContext ctx) { 223 st.password = StringUtils.unquote(ctx.getText()); 224 } 225 226 @Override 227 public void enterJdbc_driver_class_name(Jdbc_driver_class_nameContext ctx) { 228 st.driverClassName = StringUtils.unquote(ctx.getText()); 229 } 230 231 @Override 232 public void enterJdbc_connection_properties(Jdbc_connection_propertiesContext ctx) { 233 st.jdbcPropertiesFile = StringUtils.unquote(ctx.getText()); 234 } 235 236 @Override 237 public void enterConnection_pool_size_min(Connection_pool_size_minContext ctx) { 238 try { 239 st.connectionPoolSizeMin = Integer.parseInt(ctx.getText()); 240 } catch (NumberFormatException e) { 241 if (servletConfig != null) { 242 String s = servletConfig.getInitParameter("CONNECTION_POOL_SIZE_MIN"); 243 if (s != null && !s.trim().isEmpty()) 244 try { 245 st.connectionPoolSizeMin = Integer.parseInt(s.trim()); 246 } catch (NumberFormatException e1) { 247 e1.printStackTrace(); 248 } 249 } 250 } 251 } 252 253 @Override 254 public void enterConnection_pool_size_max(Connection_pool_size_maxContext ctx) { 255 try { 256 st.connectionPoolSizeMax = Integer.parseInt(ctx.getText()); 257 } catch (NumberFormatException e) { 258 if (servletConfig != null) { 259 String s = servletConfig.getInitParameter("CONNECTION_POOL_SIZE_MAX"); 260 if (s != null && !s.trim().isEmpty()) 261 try { 262 st.connectionPoolSizeMax = Integer.parseInt(s.trim()); 263 } catch (NumberFormatException e1) { 264 e1.printStackTrace(); 265 } 266 } 267 } 268 } 269 270 @Override 271 public void enterNative_sql(Native_sqlContext ctx) { 272 int startIndex = ctx.start.getStartIndex(); 273 int stopIndex = ctx.stop.getStopIndex(); 274 Interval interval = new Interval(startIndex, stopIndex); 275 st.authenticationQuery = ctx.start.getInputStream().getText(interval); 276 } 277 278 @Override 279 public void enterConnection_alias(Connection_aliasContext ctx) { 280 st.instanceName = StringUtils.unquote(ctx.getText()); 281 } 282 283 }, tree); 284 285 if (st.instanceName == null) 286 st.instanceName = NONEXPOSABLE_NAME_PREFIX + UUID.randomUUID().toString(); 287 288 if (st.password != null) 289 if (st.password.startsWith(ENCODED_PASSWORD_PREFIX)) { 290 st.password = st.password.substring(ENCODED_PASSWORD_PREFIX.length()); 291 st.password = new String(Base64.getDecoder().decode(st.password), StandardCharsets.UTF_8); 292 } 293 294 if (DbServlet.DEBUG) 295 System.out.println(st); 296 297 return st; 298 } 299} 300 301/* 302Please contact FBSQL Team by E-Mail fbsql.team@gmail.com 303or visit https://fbsql.github.io if you need additional 304information or have any questions. 305*/ 306 307/* EOF */