Skip to content

Commit daa94b1

Browse files
Synced branch with and merged the origin/jdbc-detector-regex-fix branch
1 parent 291dfa6 commit daa94b1

File tree

11 files changed

+559
-46
lines changed

11 files changed

+559
-46
lines changed

pkg/detectors/jdbc/jdbc.go

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ matchLoop:
8484
}
8585

8686
if verify {
87-
j, err := newJDBC(logCtx, jdbcConn)
87+
j, err := NewJDBC(logCtx, jdbcConn)
8888
if err != nil {
8989
continue
9090
}
@@ -207,31 +207,13 @@ func tryRedactRegex(conn string) (string, bool) {
207207
return newConn, true
208208
}
209209

210-
var supportedSubprotocols = map[string]func(logContext.Context, string) (jdbc, error){
211-
"mysql": ParseMySQL,
212-
"postgresql": ParsePostgres,
213-
"sqlserver": ParseSqlServer,
210+
var supportedSubprotocols = map[string]func(logContext.Context, string) (JDBC, error){
211+
"mysql": parseMySQL,
212+
"postgresql": parsePostgres,
213+
"sqlserver": parseSqlServer,
214214
}
215215

216-
type pingResult struct {
217-
err error
218-
determinate bool
219-
}
220-
221-
// ConnectionInfo holds parsed connection information
222-
type ConnectionInfo struct {
223-
Host string // includes port if specified, e.g., "host:port"
224-
Database string
225-
User string
226-
Password string
227-
Params map[string]string
228-
}
229-
230-
type jdbc interface {
231-
ping(context.Context) pingResult
232-
}
233-
234-
func newJDBC(ctx logContext.Context, conn string) (jdbc, error) {
216+
func NewJDBC(ctx logContext.Context, conn string) (JDBC, error) {
235217
// expected format: "jdbc:{subprotocol}:{subname}"
236218
if !strings.HasPrefix(strings.ToLower(conn), "jdbc:") {
237219
return nil, errors.New("expected jdbc prefix")
@@ -243,11 +225,11 @@ func newJDBC(ctx logContext.Context, conn string) (jdbc, error) {
243225
return nil, errors.New("expected a colon separated subprotocol and subname")
244226
}
245227

246-
// get the subprotocol parser
247228
parser, ok := supportedSubprotocols[strings.ToLower(subprotocol)]
248229
if !ok {
249-
return nil, errors.New("unsupported subprotocol")
230+
return nil, fmt.Errorf("unsupported subprotocol: %s", subprotocol)
250231
}
232+
251233
return parser(ctx, subname)
252234
}
253235

pkg/detectors/jdbc/jdbc_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import (
77

88
"github.com/google/go-cmp/cmp"
99
"github.com/kylelemons/godebug/pretty"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
1013
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
1114
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
1215
)
@@ -191,3 +194,46 @@ func TestJdbc_FromDataWithIgnorePattern(t *testing.T) {
191194
})
192195
}
193196
}
197+
198+
func TestParseJDBCURL_EdgeCases(t *testing.T) {
199+
t.Run("MySQL with special characters in password", func(t *testing.T) {
200+
// Special chars: @ # $ % ^ & * ( )
201+
jdbcURL := "jdbc:mysql://user:p@ss%23word@localhost:3306/testdb"
202+
jdbc, err := NewJDBC(logContext.Background(), jdbcURL)
203+
require.NoError(t, err)
204+
205+
info := jdbc.GetConnectionInfo()
206+
assert.NoError(t, err)
207+
assert.NotNil(t, info)
208+
assert.Equal(t, "user", info.User)
209+
// URL encoding should be handled by url.Parse
210+
})
211+
212+
t.Run("PostgreSQL with empty database", func(t *testing.T) {
213+
jdbcURL := "jdbc:postgresql://user:pass@localhost:5432"
214+
jdbc, err := NewJDBC(logContext.Background(), jdbcURL)
215+
require.NoError(t, err)
216+
217+
info := jdbc.GetConnectionInfo()
218+
assert.Equal(t, "postgres", info.Database) // default
219+
})
220+
221+
t.Run("SQL Server with multiple semicolon params", func(t *testing.T) {
222+
jdbcURL := "jdbc:sqlserver://localhost:1433;database=testdb;user=sa;password=Pass123;encrypt=true;trustServerCertificate=false"
223+
jdbc, err := NewJDBC(logContext.Background(), jdbcURL)
224+
require.NoError(t, err)
225+
226+
info := jdbc.GetConnectionInfo()
227+
assert.Equal(t, "testdb", info.Database)
228+
assert.Equal(t, "sa", info.User)
229+
assert.Equal(t, "Pass123", info.Password)
230+
})
231+
232+
t.Run("MySQL missing host", func(t *testing.T) {
233+
// Missing // after prefix - will trigger error
234+
jdbcURL := "jdbc:mysql:/testdb"
235+
_, err := NewJDBC(logContext.Background(), jdbcURL)
236+
assert.Error(t, err)
237+
assert.Contains(t, err.Error(), "expected host to start with //")
238+
})
239+
}

pkg/detectors/jdbc/models.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package jdbc
2+
3+
import (
4+
"context"
5+
)
6+
7+
type DatabaseType int
8+
9+
const (
10+
Unknown DatabaseType = iota
11+
MySQL
12+
PostgreSQL
13+
SQLServer
14+
)
15+
16+
func (dt DatabaseType) String() string {
17+
switch dt {
18+
case MySQL:
19+
return "mysql"
20+
case PostgreSQL:
21+
return "postgresql"
22+
case SQLServer:
23+
return "sqlserver"
24+
default:
25+
return "unknown"
26+
}
27+
}
28+
29+
type pingResult struct {
30+
err error
31+
determinate bool
32+
}
33+
34+
// ConnectionInfo holds parsed connection information
35+
type ConnectionInfo struct {
36+
Host string // includes port if specified, e.g., "host:port"
37+
Database string
38+
User string
39+
Password string
40+
Params map[string]string
41+
}
42+
43+
type jdbcPinger interface {
44+
ping(context.Context) pingResult
45+
}
46+
47+
// public interfaces for analyzer
48+
type JDBCParser interface {
49+
GetConnectionInfo() *ConnectionInfo
50+
GetDBType() DatabaseType
51+
BuildConnectionString() string
52+
}
53+
type JDBC interface {
54+
jdbcPinger
55+
JDBCParser
56+
}

pkg/detectors/jdbc/mysql.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,26 @@ type MysqlJDBC struct {
1616
ConnectionInfo
1717
}
1818

19+
var _ JDBC = (*MysqlJDBC)(nil)
20+
1921
func (s *MysqlJDBC) ping(ctx context.Context) pingResult {
2022
return ping(ctx, "mysql", isMySQLErrorDeterminate,
21-
BuildMySQLConnectionString(s.Host, "", s.User, s.Password, s.Params))
23+
buildMySQLConnectionString(s.Host, "", s.User, s.Password, s.Params))
24+
}
25+
26+
func (s *MysqlJDBC) GetDBType() DatabaseType {
27+
return MySQL
28+
}
29+
30+
func (s *MysqlJDBC) GetConnectionInfo() *ConnectionInfo {
31+
return &s.ConnectionInfo
32+
}
33+
34+
func (s *MysqlJDBC) BuildConnectionString() string {
35+
return buildMySQLConnectionString(s.Host, s.Database, s.User, s.Password, s.Params)
2236
}
2337

24-
func BuildMySQLConnectionString(host, database, user, password string, params map[string]string) string {
38+
func buildMySQLConnectionString(host, database, user, password string, params map[string]string) string {
2539
conn := host + "/" + database
2640
userPass := user
2741
if password != "" {
@@ -56,7 +70,7 @@ func isMySQLErrorDeterminate(err error) bool {
5670
return false
5771
}
5872

59-
func ParseMySQL(ctx logContext.Context, subname string) (jdbc, error) {
73+
func parseMySQL(ctx logContext.Context, subname string) (JDBC, error) {
6074
// expected form: [subprotocol:]//[user:password@]HOST[/DB][?key=val[&key=val]]
6175
if !strings.HasPrefix(subname, "//") {
6276
return nil, errors.New("expected host to start with //")
@@ -86,7 +100,7 @@ func ParseMySQL(ctx logContext.Context, subname string) (jdbc, error) {
86100
}, nil
87101
}
88102

89-
func parseMySQLURI(ctx logContext.Context, subname string) (jdbc, error) {
103+
func parseMySQLURI(ctx logContext.Context, subname string) (JDBC, error) {
90104

91105
// for standard URI format, which is all i've seen for JDBC
92106
u, err := url.Parse(subname)

pkg/detectors/jdbc/mysql_integration_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func TestMySQL(t *testing.T) {
9191
}
9292
for _, tt := range tests {
9393
t.Run(tt.input, func(t *testing.T) {
94-
j, err := ParseMySQL(logContext.Background(), tt.input)
94+
j, err := parseMySQL(logContext.Background(), tt.input)
9595

9696
if err != nil {
9797
got := result{ParseErr: true}

0 commit comments

Comments
 (0)